]> git.basschouten.com Git - openhab-addons.git/commitdiff
[Daikinmadoka] New channels and fixes (#9368)
authorBenjamin Lafois <benjamin.lafois@gmail.com>
Wed, 6 Jan 2021 20:50:55 +0000 (21:50 +0100)
committerGitHub <noreply@github.com>
Wed, 6 Jan 2021 20:50:55 +0000 (21:50 +0100)
* added new channels and extra fixes

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
* wip

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
* added multiple channels

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
* fixes after PR comments

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
* added support for AUTO fan mode

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
* Fixes units

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
* Fix PR

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
* PR fixes

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
* PR fixes

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
* Fixed copyright 2020->2021

Signed-off-by: Benjamin Lafois <benjamin.lafois@gmail.com>
30 files changed:
bundles/org.openhab.binding.bluetooth.daikinmadoka/README.md
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/DaikinMadokaBindingConstants.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/handler/DaikinMadokaHandler.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/BRC1HUartProcessor.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/MadokaMessage.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/MadokaProperties.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/MadokaSettings.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/MadokaValue.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/BRC1HCommand.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/DisableCleanFilterIndicatorCommand.java [new file with mode: 0644]
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/EnterPrivilegedModeCommand.java [new file with mode: 0644]
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetCleanFilterIndicatorCommand.java [new file with mode: 0644]
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetEyeBrightnessCommand.java [new file with mode: 0644]
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetFanspeedCommand.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetIndoorOutoorTemperatures.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetOperationHoursCommand.java [new file with mode: 0644]
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetOperationmodeCommand.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetPowerstateCommand.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetSetpointCommand.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetVersionCommand.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/ResetCleanFilterTimerCommand.java [new file with mode: 0644]
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/ResponseListener.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/SetEyeBrightnessCommand.java [new file with mode: 0644]
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/SetFanspeedCommand.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/SetOperationmodeCommand.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/SetPowerstateCommand.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/SetSetpointCommand.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/resources/OH-INF/thing/daikinmadoka.xml
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/test/java/org/openhab/binding/bluetooth/daikinmadoka/internal/MadokaMessageTest.java
bundles/org.openhab.binding.bluetooth.daikinmadoka/src/test/java/org/openhab/binding/bluetooth/daikinmadoka/internal/UartProcessorTest.java [new file with mode: 0644]

index 29efb6f8c91a7e358d5afb3ad42c0bc6b1f271af..1f2e2461b7863321c3fccecb53fb04baf090dbde 100644 (file)
@@ -49,10 +49,16 @@ _Note that it is planned to generate some part of this based on the XML files wi
 | commCtrlVersion  | String | R | Communication Controller Firmware Version
 | remoteCtrlVersion  | String | R | Remote Controller Firmware Version
 | operationMode  | String | R/W | The operation mode of the AC unit. Currently supported values: HEAT, COOL.  
-| fanSpeed  | Number | R/W | This is a "virtual channel" : its value is calculated depending on current operation mode. It is the channel to be used to change the fan speed, whatever the current mode is. Fan speed are from 1 to 5. On BRC1H, the device supports 3 speeds: LOW (1), MEDIUM (2-4), MAX (5).
+| fanSpeed  | Number | R/W | This is a "virtual channel" : its value is calculated depending on current operation mode. It is the channel to be used to change the fan speed, whatever the current mode is. Fan speed are from 1 to 5. On BRC1H, the device supports 3 speeds: LOW (1), MEDIUM (2-4), MAX (5). Some BRC1H also support an AUTO (0) mode - but not all of them support it (depending on internal unit).
 | setpoint  | Number:Temperature | R/W | This is a "virtual channel" : its value is calculated depending on current operation mode. It is the channel to be used to change the setpoint, whatever the current mode is.
 | homekitCurrentHeatingCoolingMode  | String | R | This channel is a "virtual channel" to be used with the HomeKit add-on to implement Thermostat thing. Values supported are the HomeKit addon ones: Off, CoolOn, HeatOn, Auto.
 | homekitTargetHeatingCoolingMode  | String | R/W | This channel is a "virtual channel" to be used with the HomeKit add-on to implement Thermostat thing. Values supported are the HomeKit addon ones: Off, CoolOn, HeatOn, Auto.
+| homebridgeMode | String | R/W | This channel is a "virtual channel" to be used with external HomeBridge. Values are: Off, Heating, Cooling, Auto.
+| eyeBrightness | Dimmer | R/W | This channel allows to manipulate the Blue "Eye" indicator Brightness. Values are between 0 and 100.
+| indoorPowerHours | Number:Time | R | This channel indicates the number of hours the indoor unit has been powered (operating or not).
+| indoorOperationHours | Number:Time | R | This channel indicates the number of hours the indoor unit has been operating.
+| indoorFanHours | Number:Time | R | This channel indicates the number of hours the fan has been blowing.
+| cleanFilterIndicator | Switch | R/W | This channel indicates if the filter needs cleaning. The indicator can be reset by writing "OFF" to the channel.
 
 ## Full Example
 
index bb8a863a9a156b56c8ced79eebe66a4fd14664db..4fc9b180b5728f727cb1594dca6ac5737322d11d 100644 (file)
@@ -30,6 +30,8 @@ public class DaikinMadokaBindingConstants {
     private DaikinMadokaBindingConstants() {
     }
 
+    public static final int WRITE_CHARACTERISTIC_MAX_RETRIES = 3;
+
     public static final ThingTypeUID THING_TYPE_BRC1H = new ThingTypeUID(BluetoothBindingConstants.BINDING_ID, "brc1h");
 
     public static final String CHANNEL_ID_ONOFF_STATUS = "onOffStatus";
@@ -45,6 +47,13 @@ public class DaikinMadokaBindingConstants {
     public static final String CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE = "homekitTargetHeatingCoolingMode";
     public static final String CHANNEL_ID_HOMEBRIDGE_MODE = "homebridgeMode";
 
+    public static final String CHANNEL_ID_EYE_BRIGHTNESS = "eyeBrightness";
+    public static final String CHANNEL_ID_INDOOR_OPERATION_HOURS = "indoorOperationHours";
+    public static final String CHANNEL_ID_INDOOR_POWER_HOURS = "indoorPowerHours";
+    public static final String CHANNEL_ID_INDOOR_FAN_HOURS = "indoorFanHours";
+
+    public static final String CHANNEL_ID_CLEAN_FILTER_INDICATOR = "cleanFilterIndicator";
+
     /**
      * BLUETOOTH UUID (service + chars)
      */
index 506f117db6e6aac08b3154330e58a26a3234127e..e8169113b7beef79cc78d6364be500a82f30a8c3 100644 (file)
 package org.openhab.binding.bluetooth.daikinmadoka.handler;
 
 import java.util.Arrays;
+import java.util.Random;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
+import javax.measure.quantity.Temperature;
+import javax.measure.quantity.Time;
+
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -35,13 +39,20 @@ import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaPropertie
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaProperties.OperationMode;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaSettings;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.BRC1HCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.DisableCleanFilterIndicatorCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.EnterPrivilegedModeCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetCleanFilterIndicatorCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetEyeBrightnessCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetFanspeedCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetIndoorOutoorTemperatures;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetOperationHoursCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetOperationmodeCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetPowerstateCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetSetpointCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetVersionCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.ResetCleanFilterTimerCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.ResponseListener;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetEyeBrightnessCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetFanspeedCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetOperationmodeCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetPowerstateCommand;
@@ -49,6 +60,7 @@ import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetSet
 import org.openhab.core.common.NamedThreadFactory;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.thing.ChannelUID;
@@ -121,7 +133,31 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
             submitCommand(new GetPowerstateCommand()); // always keep the "GetPowerState" aftern the "GetOperationMode"
             submitCommand(new GetSetpointCommand());
             submitCommand(new GetFanspeedCommand());
-        }, 10, c.refreshInterval, TimeUnit.SECONDS);
+            submitCommand(new GetCleanFilterIndicatorCommand());
+
+            try {
+                // As it is a complex operation - it has been extracted to a method.
+                retrieveOperationHours();
+            } catch (InterruptedException e) {
+                // The thread wants to exit!
+                return;
+            }
+
+            submitCommand(new GetEyeBrightnessCommand());
+        }, new Random().nextInt(30), c.refreshInterval, TimeUnit.SECONDS); // We introduce a random start time, it
+                                                                           // avoids when having multiple devices to
+                                                                           // have the commands sent simultaneously.
+    }
+
+    private void retrieveOperationHours() throws InterruptedException {
+        // This one is special - and MUST be ran twice, after being in priv mode
+        // run it once an hour is sufficient... TODO
+        submitCommand(new EnterPrivilegedModeCommand());
+        submitCommand(new GetOperationHoursCommand());
+        // a 1second+ delay is necessary
+        Thread.sleep(1500);
+
+        submitCommand(new GetOperationHoursCommand());
     }
 
     @Override
@@ -179,15 +215,29 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
         }
 
         switch (channelUID.getId()) {
+            case DaikinMadokaBindingConstants.CHANNEL_ID_CLEAN_FILTER_INDICATOR:
+                OnOffType cleanFilterOrder = (OnOffType) command;
+                if (cleanFilterOrder == OnOffType.OFF) {
+                    resetCleanFilterIndicator();
+                }
+                break;
             case DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT:
                 try {
-                    QuantityType<?> setpoint = (QuantityType<?>) command;
-                    DecimalType dt = new DecimalType(setpoint.intValue());
-                    submitCommand(new SetSetpointCommand(dt, dt));
+                    QuantityType<Temperature> setpoint = (QuantityType<Temperature>) command;
+                    submitCommand(new SetSetpointCommand(setpoint, setpoint));
                 } catch (Exception e) {
                     logger.warn("Data received is not a valid temperature.", e);
                 }
                 break;
+            case DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS:
+                try {
+                    logger.debug("Set eye brightness with value {}, {}", command.getClass().getName(), command);
+                    PercentType p = (PercentType) command;
+                    submitCommand(new SetEyeBrightnessCommand(p));
+                } catch (Exception e) {
+                    logger.warn("Data received is not a valid Eye Brightness status", e);
+                }
+                break;
             case DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS:
                 try {
                     OnOffType oot = (OnOffType) command;
@@ -290,8 +340,21 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
         }
     }
 
+    /**
+     * 2 actions need to be done: disable the notification AND reset the filter timer
+     */
+    private void resetCleanFilterIndicator() {
+        logger.debug("[{}] resetCleanFilterIndicator()", super.thing.getUID().getId());
+        submitCommand(new DisableCleanFilterIndicatorCommand());
+        submitCommand(new ResetCleanFilterTimerCommand());
+    }
+
     @Override
     public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("[{}] onCharacteristicUpdate({})", super.thing.getUID().getId(),
+                    HexUtils.bytesToHex(characteristic.getByteValue()));
+        }
         super.onCharacteristicUpdate(characteristic);
 
         // Check that arguments are valid.
@@ -359,14 +422,27 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
                 device.enableNotifications(charNotif);
             }
 
-            charWrite.setValue(command.getRequest());
-            command.setState(BRC1HCommand.State.ENQUEUED);
-            device.writeCharacteristic(charWrite);
+            // Commands can be composed of multiple chunks
+            for (byte[] chunk : command.getRequest()) {
+                charWrite.setValue(chunk);
+                command.setState(BRC1HCommand.State.ENQUEUED);
+                for (int i = 0; i < DaikinMadokaBindingConstants.WRITE_CHARACTERISTIC_MAX_RETRIES; i++) {
+                    if (device.writeCharacteristic(charWrite)) {
+                        command.setState(BRC1HCommand.State.SENT);
+                        synchronized (command) {
+                            command.wait(100);
+                        }
+                        break;
+                    }
+                    Thread.sleep(100);
+                }
+            }
 
-            if (this.config != null) {
+            if (command.getState() == BRC1HCommand.State.SENT && this.config != null) {
                 if (!command.awaitStateChange(this.config.commandTimeout, TimeUnit.MILLISECONDS,
                         BRC1HCommand.State.SUCCEEDED, BRC1HCommand.State.FAILED)) {
-                    logger.debug("Command {} to device {} timed out", command, device.getAddress());
+                    logger.debug("[{}] Command {} to device {} timed out", super.thing.getUID().getId(), command,
+                            device.getAddress());
                     command.setState(BRC1HCommand.State.FAILED);
                 }
             }
@@ -392,8 +468,13 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
         BRC1HCommand command = currentCommand;
 
         if (command != null) {
-            if (!Arrays.equals(request, command.getRequest())) {
-                logger.debug("Write completed for unknown command");
+            // last chunk:
+            byte[] lastChunk = command.getRequest()[command.getRequest().length - 1];
+            if (!Arrays.equals(request, lastChunk)) {
+                logger.debug("Write completed for a chunk, but not a complete command.");
+                synchronized (command) {
+                    command.notify();
+                }
                 return;
             }
             switch (status) {
@@ -506,7 +587,7 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
             return;
         }
 
-        DecimalType sp;
+        QuantityType<Temperature> sp;
 
         switch (operationMode) {
             case AUTO:
@@ -535,7 +616,7 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
 
         this.madokaSettings.setSetpoint(sp);
 
-        DecimalType dt = this.madokaSettings.getSetpoint();
+        QuantityType<Temperature> dt = this.madokaSettings.getSetpoint();
         if (dt != null) {
             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
         }
@@ -635,13 +716,13 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
 
     @Override
     public void receivedResponse(GetIndoorOutoorTemperatures command) {
-        DecimalType newIndoorTemp = command.getIndoorTemperature();
+        QuantityType<Temperature> newIndoorTemp = command.getIndoorTemperature();
         if (newIndoorTemp != null) {
             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_TEMPERATURE, newIndoorTemp);
             this.madokaSettings.setIndoorTemperature(newIndoorTemp);
         }
 
-        DecimalType newOutdoorTemp = command.getOutdoorTemperature();
+        QuantityType<Temperature> newOutdoorTemp = command.getOutdoorTemperature();
         if (newOutdoorTemp == null) {
             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OUTDOOR_TEMPERATURE, UnDefType.UNDEF);
         } else {
@@ -650,6 +731,23 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
         }
     }
 
+    @Override
+    public void receivedResponse(GetEyeBrightnessCommand command) {
+        PercentType eyeBrightnessTemp = command.getEyeBrightness();
+        if (eyeBrightnessTemp != null) {
+            this.madokaSettings.setEyeBrightness(eyeBrightnessTemp);
+            updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS, eyeBrightnessTemp);
+            logger.debug("Notified {} channel with value {}", DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS,
+                    eyeBrightnessTemp);
+        }
+    }
+
+    @Override
+    public void receivedResponse(SetEyeBrightnessCommand command) {
+        updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS, command.getEyeBrightness());
+        madokaSettings.setEyeBrightness(command.getEyeBrightness());
+    }
+
     @Override
     public void receivedResponse(SetPowerstateCommand command) {
         updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS, command.getPowerState());
@@ -690,6 +788,36 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
         }
     }
 
+    @Override
+    public void receivedResponse(GetOperationHoursCommand command) {
+        logger.debug("receivedResponse(GetOperationHoursCommand command)");
+
+        QuantityType<Time> indoorPowerHours = command.getIndoorPowerHours();
+        QuantityType<Time> indoorOperationHours = command.getIndoorOperationHours();
+        QuantityType<Time> indoorFanHours = command.getIndoorFanHours();
+
+        if (indoorPowerHours != null) {
+            this.madokaSettings.setIndoorPowerHours(indoorPowerHours);
+            updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_POWER_HOURS, indoorPowerHours);
+            logger.debug("Notified {} channel with value {}",
+                    DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_POWER_HOURS, indoorPowerHours);
+        }
+
+        if (indoorOperationHours != null) {
+            this.madokaSettings.setIndoorOperationHours(indoorOperationHours);
+            updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_OPERATION_HOURS, indoorOperationHours);
+            logger.debug("Notified {} channel with value {}",
+                    DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_OPERATION_HOURS, indoorOperationHours);
+        }
+
+        if (indoorFanHours != null) {
+            this.madokaSettings.setIndoorFanHours(indoorFanHours);
+            updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_FAN_HOURS, indoorFanHours);
+            logger.debug("Notified {} channel with value {}", DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_FAN_HOURS,
+                    indoorFanHours);
+        }
+    }
+
     @Override
     public void receivedResponse(SetSetpointCommand command) {
         // The update depends on the mode - so if not set - skip
@@ -713,12 +841,22 @@ public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements Re
                 return;
         }
 
-        DecimalType dt = madokaSettings.getSetpoint();
+        QuantityType<Temperature> dt = madokaSettings.getSetpoint();
         if (dt != null) {
             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
         }
     }
 
+    @Override
+    public void receivedResponse(GetCleanFilterIndicatorCommand command) {
+        Boolean indicatorStatus = command.getCleanFilterIndicator();
+        if (indicatorStatus != null) {
+            this.madokaSettings.setCleanFilterIndicator(indicatorStatus);
+            updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_CLEAN_FILTER_INDICATOR,
+                    indicatorStatus == true ? OnOffType.ON : OnOffType.OFF);
+        }
+    }
+
     /**
      * Received response to "SetOperationmodeCommand" command
      */
index f1807024083eebf5ea4b11043a8bdd0af834e7ea..24e1bd7fb12bdddca44177ff40e2e68213619d2c 100644 (file)
@@ -15,9 +15,13 @@ package org.openhab.binding.bluetooth.daikinmadoka.internal;
 import java.io.ByteArrayOutputStream;
 import java.util.Comparator;
 import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.ResponseListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * As the protocol emutes an UART communication over BLE (characteristics write/notify), this class takes care of BLE
@@ -28,6 +32,8 @@ import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.Respon
 @NonNullByDefault
 public class BRC1HUartProcessor {
 
+    private final Logger logger = LoggerFactory.getLogger(BRC1HUartProcessor.class);
+
     /**
      * Maximum number of bytes per message chunk, including headers
      */
@@ -42,6 +48,8 @@ public class BRC1HUartProcessor {
 
     private ResponseListener responseListener;
 
+    private final Lock stateLock = new ReentrantLock();
+
     public BRC1HUartProcessor(ResponseListener responseListener) {
         this.responseListener = responseListener;
     }
@@ -78,21 +86,31 @@ public class BRC1HUartProcessor {
     }
 
     public void chunkReceived(byte[] byteValue) {
-        this.uartMessages.add(byteValue);
-        if (isMessageComplete()) {
-
-            // Beyond this point, full message received
-            ByteArrayOutputStream bos = new ByteArrayOutputStream();
-
-            for (byte[] msg : uartMessages) {
-                if (msg.length > 1) {
-                    bos.write(msg, 1, msg.length - 1);
+        byte[] fullReceivedMessage = null;
+        stateLock.lock();
+        try {
+            this.uartMessages.add(byteValue);
+            if (isMessageComplete()) {
+                logger.debug("Complete message received!");
+
+                // Beyond this point, full message received
+                ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+                for (byte[] msg : uartMessages) {
+                    if (msg.length > 1) {
+                        bos.write(msg, 1, msg.length - 1);
+                    }
                 }
-            }
 
-            this.uartMessages.clear();
+                this.uartMessages.clear();
+                fullReceivedMessage = bos.toByteArray();
+            }
+        } finally {
+            stateLock.unlock();
+        }
 
-            this.responseListener.receivedResponse(bos.toByteArray());
+        if (fullReceivedMessage != null) {
+            this.responseListener.receivedResponse(fullReceivedMessage);
         }
     }
 
index 260e0bea9641786139ad062a9dbd101c8642eebd..5e929180d9383847d91262962708d420552fcbd2 100644 (file)
@@ -12,6 +12,7 @@
  */
 package org.openhab.binding.bluetooth.daikinmadoka.internal.model;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
@@ -45,13 +46,15 @@ public class MadokaMessage {
         values = new HashMap<>();
     }
 
-    public static byte[] createRequest(BRC1HCommand command, MadokaValue... parameters) {
+    public static byte[][] createRequest(BRC1HCommand command, MadokaValue... parameters) {
         try {
             ByteArrayOutputStream output = new ByteArrayOutputStream();
             DataOutputStream request = new DataOutputStream(output);
 
-            // Message Length - Computed in the end
-            request.writeByte(0);
+            // Chunk ID
+            // request.writeByte(0);
+
+            // Message Length - Computed in the end - left at 0 for now
             request.writeByte(0);
 
             // Command ID, coded on 3 bytes
@@ -70,10 +73,25 @@ public class MadokaMessage {
             }
 
             // Finally, compute array size
-            byte[] ret = output.toByteArray();
-            ret[1] = (byte) (ret.length - 1);
+            byte[] payload = output.toByteArray();
+            payload[0] = (byte) (payload.length);
+
+            // Now, split in chunks
+            byte[][] chunks = new byte[(int) Math.ceil(payload.length / 19.)][0];
+
+            ByteArrayInputStream left = new ByteArrayInputStream(payload);
+            int chunkId = 0;
+            while (left.available() > 0) {
+                ByteArrayOutputStream bos = new ByteArrayOutputStream();
+                DataOutputStream chunk = new DataOutputStream(bos);
+                chunk.writeByte(chunkId);
+                chunk.write(left.readNBytes(19));
+
+                chunk.flush();
+                chunks[chunkId++] = bos.toByteArray();
+            }
 
-            return ret;
+            return chunks;
         } catch (IOException e) {
             logger.info("Error while building request", e);
             throw new RuntimeException(e);
@@ -105,7 +123,13 @@ public class MadokaMessage {
 
             mv = new MadokaValue();
             mv.setId(msg[i]);
-            mv.setSize(Byte.toUnsignedInt(msg[i + 1]));
+
+            if (Byte.toUnsignedInt(msg[i + 1]) == 0xff) {
+                // Specific case - msg length 0xFF. See GetOperationHousCommand
+                mv.setSize(0);
+            } else {
+                mv.setSize(Byte.toUnsignedInt(msg[i + 1]));
+            }
 
             if ((i + 1 + mv.getSize()) >= msg.length) {
                 throw new MadokaParsingException("Truncated message detected while parsing response value content");
index c749a6a41e79f57246fb0f62ffdf8e792686674d..67133f67d284723d86ed194708f7f080815a2bc2 100644 (file)
@@ -26,7 +26,8 @@ public class MadokaProperties {
     public enum FanSpeed {
         MAX(5),
         MEDIUM(3),
-        LOW(1);
+        LOW(1),
+        AUTO(0);
 
         private int v;
 
@@ -39,8 +40,10 @@ public class MadokaProperties {
                 return MAX;
             } else if (v >= 2 && v <= 4) {
                 return MEDIUM;
-            } else {
+            } else if (v == 1) {
                 return LOW;
+            } else {
+                return AUTO;
             }
         }
 
index 72b8cbb82ee8e5914cda9065e3a2fc739caabf63..5e800e8e1f7e6c83b60911941be24e2c44b5dba8 100644 (file)
  */
 package org.openhab.binding.bluetooth.daikinmadoka.internal.model;
 
+import javax.measure.quantity.Temperature;
+import javax.measure.quantity.Time;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaProperties.FanSpeed;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaProperties.OperationMode;
-import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.QuantityType;
 
 /**
  * This class contains the current state of the controllerw
@@ -30,10 +34,10 @@ public class MadokaSettings {
 
     private @Nullable OnOffType onOffState;
 
-    private @Nullable DecimalType setpoint;
+    private @Nullable QuantityType<Temperature> setpoint;
 
-    private @Nullable DecimalType indoorTemperature;
-    private @Nullable DecimalType outdoorTemperature;
+    private @Nullable QuantityType<Temperature> indoorTemperature;
+    private @Nullable QuantityType<Temperature> outdoorTemperature;
 
     private @Nullable FanSpeed fanspeed;
 
@@ -45,6 +49,21 @@ public class MadokaSettings {
     private @Nullable String communicationControllerVersion;
     private @Nullable String remoteControllerVersion;
 
+    private @Nullable PercentType eyeBrightness;
+    private @Nullable QuantityType<Time> indoorPowerHours;
+    private @Nullable QuantityType<Time> indoorOperationHours;
+    private @Nullable QuantityType<Time> indoorFanHours;
+
+    private @Nullable Boolean cleanFilterIndicator;
+
+    public @Nullable Boolean getCleanFilterIndicator() {
+        return cleanFilterIndicator;
+    }
+
+    public void setCleanFilterIndicator(Boolean cleanFilterIndicator) {
+        this.cleanFilterIndicator = cleanFilterIndicator;
+    }
+
     public @Nullable OnOffType getOnOffState() {
         return onOffState;
     }
@@ -53,27 +72,27 @@ public class MadokaSettings {
         this.onOffState = onOffState;
     }
 
-    public @Nullable DecimalType getSetpoint() {
+    public @Nullable QuantityType<Temperature> getSetpoint() {
         return setpoint;
     }
 
-    public void setSetpoint(DecimalType setpoint) {
+    public void setSetpoint(QuantityType<Temperature> setpoint) {
         this.setpoint = setpoint;
     }
 
-    public @Nullable DecimalType getIndoorTemperature() {
+    public @Nullable QuantityType<Temperature> getIndoorTemperature() {
         return indoorTemperature;
     }
 
-    public void setIndoorTemperature(DecimalType indoorTemperature) {
+    public void setIndoorTemperature(QuantityType<Temperature> indoorTemperature) {
         this.indoorTemperature = indoorTemperature;
     }
 
-    public @Nullable DecimalType getOutdoorTemperature() {
+    public @Nullable QuantityType<Temperature> getOutdoorTemperature() {
         return outdoorTemperature;
     }
 
-    public void setOutdoorTemperature(DecimalType outdoorTemperature) {
+    public void setOutdoorTemperature(QuantityType<Temperature> outdoorTemperature) {
         this.outdoorTemperature = outdoorTemperature;
     }
 
@@ -124,4 +143,36 @@ public class MadokaSettings {
     public void setRemoteControllerVersion(String remoteControllerVersion) {
         this.remoteControllerVersion = remoteControllerVersion;
     }
+
+    public @Nullable PercentType getEyeBrightness() {
+        return eyeBrightness;
+    }
+
+    public void setEyeBrightness(PercentType eyeBrightness) {
+        this.eyeBrightness = eyeBrightness;
+    }
+
+    public @Nullable QuantityType<Time> getIndoorPowerHours() {
+        return indoorPowerHours;
+    }
+
+    public void setIndoorPowerHours(QuantityType<Time> indoorPowerHours) {
+        this.indoorPowerHours = indoorPowerHours;
+    }
+
+    public @Nullable QuantityType<Time> getIndoorOperationHours() {
+        return indoorOperationHours;
+    }
+
+    public void setIndoorOperationHours(QuantityType<Time> indoorOperationHours) {
+        this.indoorOperationHours = indoorOperationHours;
+    }
+
+    public @Nullable QuantityType<Time> getIndoorFanHours() {
+        return indoorFanHours;
+    }
+
+    public void setIndoorFanHours(QuantityType<Time> indoorFanHours) {
+        this.indoorFanHours = indoorFanHours;
+    }
 }
index a545758123a2d5c6ec95a8c27f0dc53e2d2f230b..988e02a34700d211edd431319e8dc69be2c9ddc3 100644 (file)
@@ -13,6 +13,7 @@
 package org.openhab.binding.bluetooth.daikinmadoka.internal.model;
 
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -62,20 +63,35 @@ public class MadokaValue {
         this.rawValue = rawValue;
     }
 
+    /**
+     * For backward compatibility
+     *
+     * @return
+     */
     public long getComputedValue() {
+        return getComputedValue(ByteOrder.BIG_ENDIAN);
+    }
+
+    public long getComputedValue(ByteOrder e) {
         byte[] v = rawValue;
         if (v != null) {
+            ByteBuffer bb;
             switch (size) {
                 case 1:
                     return v[0];
                 case 2:
-                    return ByteBuffer.wrap(v, 0, 2).getShort();
+                    bb = ByteBuffer.wrap(v, 0, 2);
+                    bb.order(e);
+                    return bb.getShort();
                 case 4:
-                    return ByteBuffer.wrap(v, 0, 4).getInt();
+                    bb = ByteBuffer.wrap(v, 0, 4);
+                    bb.order(e);
+                    return bb.getInt();
                 default:
                     // unsupported
                     break;
             }
+
         }
         return 0;
     }
index 8b849ebc94f9b758d19ad4b52a3c42cf0b7aa450..b244659ce330b4272771cb211be2b71168ea6453 100644 (file)
@@ -53,7 +53,7 @@ public abstract class BRC1HCommand {
      *
      * @return
      */
-    public abstract byte[] getRequest();
+    public abstract byte[][] getRequest();
 
     /**
      * This is the command number, in the protocol
diff --git a/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/DisableCleanFilterIndicatorCommand.java b/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/DisableCleanFilterIndicatorCommand.java
new file mode 100644 (file)
index 0000000..4bffc8d
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Command used to disable the Clean Filter Indicator notification
+ *
+ * @author Benjamin Lafois - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class DisableCleanFilterIndicatorCommand extends BRC1HCommand {
+
+    private final Logger logger = LoggerFactory.getLogger(DisableCleanFilterIndicatorCommand.class);
+
+    @Override
+    public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
+            throws MadokaParsingException {
+        setState(State.SUCCEEDED);
+    }
+
+    @Override
+    public byte[][] getRequest() {
+        MadokaValue mv = new MadokaValue(0x51, 1, new byte[] { (byte) 0x01 });
+        return MadokaMessage.createRequest(this, mv);
+    }
+
+    @Override
+    public int getCommandId() {
+        return 16928;
+    }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/EnterPrivilegedModeCommand.java b/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/EnterPrivilegedModeCommand.java
new file mode 100644 (file)
index 0000000..4df5624
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaValue;
+import org.openhab.core.util.HexUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This command enable privileged commands on remote device
+ *
+ * @author Benjamin Lafois - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class EnterPrivilegedModeCommand extends BRC1HCommand {
+
+    private final Logger logger = LoggerFactory.getLogger(EnterPrivilegedModeCommand.class);
+
+    @Override
+    public byte[][] getRequest() {
+        MadokaValue privilegedMode = new MadokaValue(0xfe, 1, new byte[] { (byte) 0x01 });
+        return MadokaMessage.createRequest(this, privilegedMode);
+    }
+
+    @Override
+    public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm) {
+        byte[] msg = mm.getRawMessage();
+        if (logger.isDebugEnabled() && msg != null) {
+            logger.debug("Got response for {} : {}", this.getClass().getSimpleName(), HexUtils.bytesToHex(msg));
+        }
+
+        setState(State.SUCCEEDED);
+    }
+
+    @Override
+    public int getCommandId() {
+        return 16658;
+    }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetCleanFilterIndicatorCommand.java b/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetCleanFilterIndicatorCommand.java
new file mode 100644 (file)
index 0000000..e48d0a7
--- /dev/null
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Command used to get the Clean Filter Indicator status
+ *
+ * @author Benjamin Lafois - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class GetCleanFilterIndicatorCommand extends BRC1HCommand {
+
+    private final Logger logger = LoggerFactory.getLogger(GetCleanFilterIndicatorCommand.class);
+
+    private @Nullable Boolean cleanFilterIndicator;
+
+    @Override
+    public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
+            throws MadokaParsingException {
+
+        byte[] valueCleanFilterIndicator = mm.getValues().get(0x62).getRawValue();
+        if (valueCleanFilterIndicator == null || valueCleanFilterIndicator.length != 1) {
+            setState(State.FAILED);
+            throw new MadokaParsingException("Incorrect clean filter indicator value");
+        }
+
+        if ((valueCleanFilterIndicator[0] & 0x01) == 0x01) {
+            this.cleanFilterIndicator = true;
+        } else {
+            this.cleanFilterIndicator = false;
+        }
+
+        setState(State.SUCCEEDED);
+        executor.execute(() -> listener.receivedResponse(this));
+    }
+
+    public @Nullable Boolean getCleanFilterIndicator() {
+        return cleanFilterIndicator;
+    }
+
+    @Override
+    public byte[][] getRequest() {
+        return MadokaMessage.createRequest(this);
+    }
+
+    @Override
+    public int getCommandId() {
+        return 256;
+    }
+}
diff --git a/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetEyeBrightnessCommand.java b/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetEyeBrightnessCommand.java
new file mode 100644 (file)
index 0000000..241adf1
--- /dev/null
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaValue;
+import org.openhab.core.library.types.PercentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Command used to get the blue Eye brightness level
+ *
+ * @author Benjamin Lafois - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class GetEyeBrightnessCommand extends BRC1HCommand {
+
+    private final Logger logger = LoggerFactory.getLogger(GetEyeBrightnessCommand.class);
+
+    private @Nullable PercentType eyeBrightness;
+
+    @Override
+    public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
+            throws MadokaParsingException {
+        byte[] bEyeBrightness = mm.getValues().get(0x33).getRawValue();
+
+        if (bEyeBrightness == null || bEyeBrightness == null) {
+            setState(State.FAILED);
+            throw new MadokaParsingException("Incorrect eye brightness value");
+        }
+
+        Integer iEyeBrightness = Integer.valueOf(bEyeBrightness[0]);
+
+        if (iEyeBrightness != null) {
+            // The values accepted by the device are from 0 to 19 - integers so conversion needed for Dimmer channel
+            eyeBrightness = new PercentType((int) Math.round(iEyeBrightness / 0.19));
+        }
+
+        logger.debug("Eye Brightness: {}", eyeBrightness);
+
+        setState(State.SUCCEEDED);
+        executor.execute(() -> listener.receivedResponse(this));
+    }
+
+    @Override
+    public byte[][] getRequest() {
+        // We can call the function without parameters - but it will return all the display parameters, which makes a 3
+        // chunks return message. As such, specifying requested value 0x33 (eyeBrightness)
+        MadokaValue mv = new MadokaValue(0x33, 1, new byte[] { (byte) 0x00 });
+        return MadokaMessage.createRequest(this, mv);
+    }
+
+    @Override
+    public int getCommandId() {
+        return 770;
+    }
+
+    public @Nullable PercentType getEyeBrightness() {
+        return eyeBrightness;
+    }
+}
index 7b1eccd7e4091018514bd052c83774b0cba86d28..2732783e4d683ba8859c6120daa0589b82b2b298 100644 (file)
@@ -37,7 +37,7 @@ public class GetFanspeedCommand extends BRC1HCommand {
     private @Nullable FanSpeed heatingFanSpeed;
 
     @Override
-    public byte[] getRequest() {
+    public byte[][] getRequest() {
         return MadokaMessage.createRequest(this);
     }
 
index c389b7122f58a14070461e28ba04256a4e830299..2f3870c410785607ee25128201ac31eff4da805c 100644 (file)
@@ -14,11 +14,14 @@ package org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands;
 
 import java.util.concurrent.Executor;
 
+import javax.measure.quantity.Temperature;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
-import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -33,11 +36,11 @@ public class GetIndoorOutoorTemperatures extends BRC1HCommand {
 
     private final Logger logger = LoggerFactory.getLogger(GetIndoorOutoorTemperatures.class);
 
-    private @Nullable DecimalType indoorTemperature;
-    private @Nullable DecimalType outdoorTemperature;
+    private @Nullable QuantityType<Temperature> indoorTemperature;
+    private @Nullable QuantityType<Temperature> outdoorTemperature;
 
     @Override
-    public byte[] getRequest() {
+    public byte[][] getRequest() {
         return MadokaMessage.createRequest(this);
     }
 
@@ -64,11 +67,11 @@ public class GetIndoorOutoorTemperatures extends BRC1HCommand {
         }
 
         if (iIndoorTemperature != null) {
-            indoorTemperature = new DecimalType(iIndoorTemperature);
+            indoorTemperature = new QuantityType<Temperature>(iIndoorTemperature, SIUnits.CELSIUS);
         }
 
         if (iOutdoorTemperature != null) {
-            outdoorTemperature = new DecimalType(iOutdoorTemperature);
+            outdoorTemperature = new QuantityType<Temperature>(iOutdoorTemperature, SIUnits.CELSIUS);
         }
 
         logger.debug("Indoor Temp: {}", indoorTemperature);
@@ -78,11 +81,11 @@ public class GetIndoorOutoorTemperatures extends BRC1HCommand {
         executor.execute(() -> listener.receivedResponse(this));
     }
 
-    public @Nullable DecimalType getIndoorTemperature() {
+    public @Nullable QuantityType<Temperature> getIndoorTemperature() {
         return indoorTemperature;
     }
 
-    public @Nullable DecimalType getOutdoorTemperature() {
+    public @Nullable QuantityType<Temperature> getOutdoorTemperature() {
         return outdoorTemperature;
     }
 
diff --git a/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetOperationHoursCommand.java b/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/GetOperationHoursCommand.java
new file mode 100644 (file)
index 0000000..600253b
--- /dev/null
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands;
+
+import java.nio.ByteOrder;
+import java.util.concurrent.Executor;
+
+import javax.measure.quantity.Time;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaValue;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.util.HexUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This command returns the operating hours of internal unit
+ *
+ * @author Benjamin Lafois - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class GetOperationHoursCommand extends BRC1HCommand {
+
+    private final Logger logger = LoggerFactory.getLogger(GetOperationHoursCommand.class);
+
+    private @Nullable QuantityType<Time> indoorOperationHours;
+    private @Nullable QuantityType<Time> indoorFanHours;
+    private @Nullable QuantityType<Time> indoorPowerHours;
+
+    @Override
+    public byte[][] getRequest() {
+        MadokaValue specificUnitNumber = new MadokaValue(0x02, 1, new byte[] { (byte) 0x00 });
+        MadokaValue p40 = new MadokaValue(0x40, 0, new byte[] {});
+        MadokaValue p41 = new MadokaValue(0x41, 0, new byte[] {});
+        MadokaValue p42 = new MadokaValue(0x42, 0, new byte[] {});
+        MadokaValue p43 = new MadokaValue(0x43, 0, new byte[] {});
+        MadokaValue p44 = new MadokaValue(0x44, 0, new byte[] {});
+        MadokaValue p45 = new MadokaValue(0x45, 0, new byte[] {});
+        MadokaValue p46 = new MadokaValue(0x46, 0, new byte[] {});
+        MadokaValue p47 = new MadokaValue(0x47, 0, new byte[] {});
+        MadokaValue p48 = new MadokaValue(0x48, 0, new byte[] {});
+
+        return MadokaMessage.createRequest(this, specificUnitNumber, p40, p41, p42, p43, p44, p45, p46, p47, p48);
+    }
+
+    @Override
+    public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
+            throws MadokaParsingException {
+        try {
+
+            byte[] msg = mm.getRawMessage();
+            if (logger.isDebugEnabled() && msg != null) {
+                logger.debug("Got response for {} : {}", this.getClass().getSimpleName(), HexUtils.bytesToHex(msg));
+            }
+
+            // The specific GetOperationHours requires 2 consecutive runs for some reason.
+            // If value size is 0, then it will be for the next query!
+            if (mm.getValues().get(0x40).getSize() == 0) {
+                setState(State.SUCCEEDED);
+                return;
+            }
+
+            Integer iIndoorOperationHours = (int) (mm.getValues().get(0x40).getComputedValue(ByteOrder.LITTLE_ENDIAN));
+            Integer iIndoorFanHours = (int) (mm.getValues().get(0x41).getComputedValue(ByteOrder.LITTLE_ENDIAN));
+            Integer iIndoorPowerHours = (int) (mm.getValues().get(0x42).getComputedValue(ByteOrder.LITTLE_ENDIAN));
+
+            this.indoorOperationHours = new QuantityType<Time>(iIndoorOperationHours, Units.HOUR);
+            this.indoorFanHours = new QuantityType<Time>(iIndoorFanHours, Units.HOUR);
+            this.indoorPowerHours = new QuantityType<Time>(iIndoorPowerHours, Units.HOUR);
+
+            logger.debug("indoorOperationHours: {}", indoorOperationHours);
+            logger.debug("indoorFanHours: {}", indoorFanHours);
+            logger.debug("indoorPowerHours: {}", indoorPowerHours);
+
+            setState(State.SUCCEEDED);
+            executor.execute(() -> listener.receivedResponse(this));
+        } catch (Exception e) {
+            setState(State.FAILED);
+            throw new MadokaParsingException(e);
+        }
+    }
+
+    @Override
+    public int getCommandId() {
+        return 274;
+    }
+
+    public @Nullable QuantityType<Time> getIndoorOperationHours() {
+        return indoorOperationHours;
+    }
+
+    public @Nullable QuantityType<Time> getIndoorFanHours() {
+        return indoorFanHours;
+    }
+
+    public @Nullable QuantityType<Time> getIndoorPowerHours() {
+        return indoorPowerHours;
+    }
+}
index df3ec267e7207f20bf8f99f45f1d40ab4e408838..0296bb8c7f109937b2656f03a1b7391ee6af4aaf 100644 (file)
@@ -36,7 +36,7 @@ public class GetOperationmodeCommand extends BRC1HCommand {
     private @Nullable OperationMode operationMode;
 
     @Override
-    public byte[] getRequest() {
+    public byte[][] getRequest() {
         return MadokaMessage.createRequest(this);
     }
 
index 04884b8cbf1043b4b0c51b658c2c38678d0390a6..7731d2d976746276ab4f82e468b78291822aecde 100644 (file)
@@ -35,7 +35,7 @@ public class GetPowerstateCommand extends BRC1HCommand {
     private @Nullable Boolean powerState;
 
     @Override
-    public byte[] getRequest() {
+    public byte[][] getRequest() {
         return MadokaMessage.createRequest(this);
     }
 
index 424f554e45a79a20a9c60bca4e13acd8236ab3a9..419c5e48d471997e9ad0ab8e8ede154fede8ed06 100644 (file)
@@ -14,11 +14,14 @@ package org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands;
 
 import java.util.concurrent.Executor;
 
+import javax.measure.quantity.Temperature;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
-import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -33,11 +36,11 @@ public class GetSetpointCommand extends BRC1HCommand {
 
     private final Logger logger = LoggerFactory.getLogger(GetSetpointCommand.class);
 
-    private @Nullable DecimalType heatingSetpoint;
-    private @Nullable DecimalType coolingSetpoint;
+    private @Nullable QuantityType<Temperature> heatingSetpoint;
+    private @Nullable QuantityType<Temperature> coolingSetpoint;
 
     @Override
-    public byte[] getRequest() {
+    public byte[][] getRequest() {
         return MadokaMessage.createRequest(this);
     }
 
@@ -48,8 +51,8 @@ public class GetSetpointCommand extends BRC1HCommand {
             Integer iHeatingSetpoint = (int) (mm.getValues().get(0x21).getComputedValue() / 128.);
             Integer iCoolingSetpoint = (int) (mm.getValues().get(0x20).getComputedValue() / 128.);
 
-            this.heatingSetpoint = new DecimalType(iHeatingSetpoint);
-            this.coolingSetpoint = new DecimalType(iCoolingSetpoint);
+            this.heatingSetpoint = new QuantityType<Temperature>(iHeatingSetpoint, SIUnits.CELSIUS);
+            this.coolingSetpoint = new QuantityType<Temperature>(iCoolingSetpoint, SIUnits.CELSIUS);
 
             logger.debug("heatingSetpoint: {}", heatingSetpoint);
             logger.debug("coolingSetpoint: {}", coolingSetpoint);
@@ -67,11 +70,11 @@ public class GetSetpointCommand extends BRC1HCommand {
         return 64;
     }
 
-    public @Nullable DecimalType getHeatingSetpoint() {
+    public @Nullable QuantityType<Temperature> getHeatingSetpoint() {
         return heatingSetpoint;
     }
 
-    public @Nullable DecimalType getCoolingSetpoint() {
+    public @Nullable QuantityType<Temperature> getCoolingSetpoint() {
         return coolingSetpoint;
     }
 }
index a6c0f0015b15224ad47c1cb38990a513f685c967..de09c148bf68f65cf41166862d6a993bb0ce25fe 100644 (file)
@@ -32,7 +32,7 @@ public class GetVersionCommand extends BRC1HCommand {
     private @Nullable String communicationControllerVersion;
 
     @Override
-    public byte[] getRequest() {
+    public byte[][] getRequest() {
         return MadokaMessage.createRequest(this);
     }
 
diff --git a/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/ResetCleanFilterTimerCommand.java b/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/ResetCleanFilterTimerCommand.java
new file mode 100644 (file)
index 0000000..4d8a846
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Command used to reset the Clean Filter Indicator timer
+ *
+ * @author Benjamin Lafois - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ResetCleanFilterTimerCommand extends BRC1HCommand {
+
+    private final Logger logger = LoggerFactory.getLogger(ResetCleanFilterTimerCommand.class);
+
+    @Override
+    public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
+            throws MadokaParsingException {
+        setState(State.SUCCEEDED);
+    }
+
+    @Override
+    public byte[][] getRequest() {
+        MadokaValue mv = new MadokaValue(0xFE, 1, new byte[] { (byte) 0x01 });
+        return MadokaMessage.createRequest(this, mv);
+    }
+
+    @Override
+    public int getCommandId() {
+        return 16928;
+    }
+}
index 6cb3df29d00e413e11b79899bb6aeaf5626eb7c1..a8ab86a814bac652cbc36e477a388c18ee1751f4 100644 (file)
@@ -44,4 +44,12 @@ public interface ResponseListener {
     public void receivedResponse(SetOperationmodeCommand command);
 
     public void receivedResponse(SetFanspeedCommand command);
+
+    public void receivedResponse(GetOperationHoursCommand command);
+
+    public void receivedResponse(GetEyeBrightnessCommand command);
+
+    public void receivedResponse(SetEyeBrightnessCommand command);
+
+    public void receivedResponse(GetCleanFilterIndicatorCommand command);
 }
diff --git a/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/SetEyeBrightnessCommand.java b/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/main/java/org/openhab/binding/bluetooth/daikinmadoka/internal/model/commands/SetEyeBrightnessCommand.java
new file mode 100644 (file)
index 0000000..2b9103f
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaValue;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.util.HexUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Command used to set the Blue Eye Brightness
+ *
+ * @author Benjamin Lafois - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class SetEyeBrightnessCommand extends BRC1HCommand {
+
+    private final Logger logger = LoggerFactory.getLogger(SetEyeBrightnessCommand.class);
+
+    private PercentType eyeBrightness;
+
+    public SetEyeBrightnessCommand(PercentType eyeBrightness) {
+        this.eyeBrightness = eyeBrightness;
+    }
+
+    @Override
+    public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
+            throws MadokaParsingException {
+        byte[] msg = mm.getRawMessage();
+        if (logger.isDebugEnabled() && msg != null) {
+            logger.debug("Got response for {} : {}", this.getClass().getSimpleName(), HexUtils.bytesToHex(msg));
+        }
+
+        setState(State.SUCCEEDED);
+        executor.execute(() -> listener.receivedResponse(this));
+    }
+
+    @Override
+    public byte[][] getRequest() {
+        // The values accepted by the device are from 0 to 19 - integers
+        byte val = (byte) Math.round(eyeBrightness.intValue() * 0.19);
+
+        MadokaValue mv = new MadokaValue(0x33, 1, new byte[] { val });
+        return MadokaMessage.createRequest(this, mv);
+    }
+
+    @Override
+    public int getCommandId() {
+        return 17154;
+    }
+
+    public PercentType getEyeBrightness() {
+        return eyeBrightness;
+    }
+
+    /**
+     *
+     * @param eyeBrightness a percentage - between 0 and 100
+     */
+    public void setEyeBrightness(PercentType eyeBrightness) {
+        this.eyeBrightness = eyeBrightness;
+    }
+}
index 6f7936d59e4029f1bfa5f58e6d4f336f143a979b..59cce2ae3fcd14367f012ea64d90314bf350b97d 100644 (file)
@@ -42,7 +42,7 @@ public class SetFanspeedCommand extends BRC1HCommand {
     }
 
     @Override
-    public byte[] getRequest() {
+    public byte[][] getRequest() {
         MadokaValue paramCoolingFanSpeed = new MadokaValue(0x20, 1, new byte[] { (byte) coolingFanSpeed.value() });
         MadokaValue paramHeatingFanSpeed = new MadokaValue(0x21, 1, new byte[] { (byte) heatingFanSpeed.value() });
 
index a0571e99c66abf704d9637e13ae3c26487610213..131b5707843acddc29e016c22cfdd64afaf3c5dd 100644 (file)
@@ -40,7 +40,7 @@ public class SetOperationmodeCommand extends BRC1HCommand {
     }
 
     @Override
-    public byte[] getRequest() {
+    public byte[][] getRequest() {
         MadokaValue mv = new MadokaValue(0x20, 1, new byte[] { (byte) this.operationMode.value() });
         return MadokaMessage.createRequest(this, mv);
     }
index 21f712a7ad36d389b9a2b0632c41b535b6b11253..1765dee669ea74b2337cdf38d04a70a3b8f08176 100644 (file)
@@ -40,7 +40,7 @@ public class SetPowerstateCommand extends BRC1HCommand {
     }
 
     @Override
-    public byte[] getRequest() {
+    public byte[][] getRequest() {
         MadokaValue mv = new MadokaValue(0x20, 1,
                 new byte[] { (byte) (this.powerState == OnOffType.ON ? 0x01 : 0x00) });
 
index 299b2f656b0576919a9602e84d2fc875d0f952d5..147a6d5175229ef5dbde79901d8a47521073fd7d 100644 (file)
@@ -15,10 +15,12 @@ package org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands;
 import java.nio.ByteBuffer;
 import java.util.concurrent.Executor;
 
+import javax.measure.quantity.Temperature;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaValue;
-import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.util.HexUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -34,16 +36,16 @@ public class SetSetpointCommand extends BRC1HCommand {
 
     private final Logger logger = LoggerFactory.getLogger(SetSetpointCommand.class);
 
-    private DecimalType coolingSetpoint;
-    private DecimalType heatingSetpoint;
+    private QuantityType<Temperature> coolingSetpoint;
+    private QuantityType<Temperature> heatingSetpoint;
 
-    public SetSetpointCommand(DecimalType coolingSetpoint, DecimalType heatingSetpoint) {
+    public SetSetpointCommand(QuantityType<Temperature> coolingSetpoint, QuantityType<Temperature> heatingSetpoint) {
         this.coolingSetpoint = coolingSetpoint;
         this.heatingSetpoint = heatingSetpoint;
     }
 
     @Override
-    public byte[] getRequest() {
+    public byte[][] getRequest() {
         byte[] heatingSetpointBytes = ByteBuffer.allocate(2).putShort((short) (128. * heatingSetpoint.shortValue()))
                 .array();
         byte[] coolingSetpointBytes = ByteBuffer.allocate(2).putShort((short) (128. * coolingSetpoint.shortValue()))
@@ -72,11 +74,11 @@ public class SetSetpointCommand extends BRC1HCommand {
         return 16448;
     }
 
-    public DecimalType getCoolingSetpoint() {
+    public QuantityType<Temperature> getCoolingSetpoint() {
         return coolingSetpoint;
     }
 
-    public DecimalType getHeatingSetpoint() {
+    public QuantityType<Temperature> getHeatingSetpoint() {
         return heatingSetpoint;
     }
 }
index ed0be1e882e0ac22672cf63abe7502a6cabe0b69..2bb375f4de447fa16c0c3e540efb7b2afc06f7f8 100644 (file)
                        <channel id="homekitCurrentHeatingCoolingMode" typeId="brc1h_homekitCurrentHeatingCoolingMode"/>
                        <channel id="homekitTargetHeatingCoolingMode" typeId="brc1h_homekitTargetHeatingCoolingMode"/>
                        <channel id="homebridgeMode" typeId="brc1h_homebridgeMode"/>
+                       <channel id="eyeBrightness" typeId="brc1h_eyeBrightness"/>
+                       <channel id="indoorPowerHours" typeId="brc1h_indoorPowerHours"/>
+                       <channel id="indoorOperationHours" typeId="brc1h_indoorOperationHours"/>
+                       <channel id="indoorFanHours" typeId="brc1h_indoorFanHours"/>
+                       <channel id="cleanFilterIndicator" typeId="brc1h_cleanFilter"/>
                </channels>
 
                <config-description>
                <label>Unit Power Status</label>
        </channel-type>
 
+       <channel-type id="brc1h_cleanFilter">
+               <item-type>Switch</item-type>
+               <label>Clean Filter Indicator</label>
+       </channel-type>
+
        <channel-type id="brc1h_indoorTemperature">
                <item-type>Number:Temperature</item-type>
                <label>Indoor Temperature</label>
                </command>
        </channel-type>
 
+       <channel-type id="brc1h_eyeBrightness">
+               <item-type>Dimmer</item-type>
+               <label>Eye Illumination Brightness</label>
+               <state min="0" max="100" step="1" readOnly="false"/>
+       </channel-type>
+
+       <channel-type id="brc1h_indoorPowerHours">
+               <item-type>Number:Time</item-type>
+               <label>Number of hours system has been powered up</label>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="brc1h_indoorOperationHours">
+               <item-type>Number:Time</item-type>
+               <label>Number of hours system has been operating</label>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="brc1h_indoorFanHours">
+               <item-type>Number:Time</item-type>
+               <label>Number of hours fan has been operating</label>
+               <state readOnly="true"/>
+       </channel-type>
+
 </thing:thing-descriptions>
index 742d85dfdbaa4bae6bc148e8048fd5a3f997e878..0ee0f245f647ce2785315ec931a4aaeaa98477a3 100644 (file)
@@ -14,10 +14,13 @@ package org.openhab.binding.bluetooth.daikinmadoka.internal;
 
 import static org.junit.jupiter.api.Assertions.*;
 
+import java.nio.ByteOrder;
+
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaValue;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetIndoorOutoorTemperatures;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetOperationHoursCommand;
 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetPowerstateCommand;
 import org.openhab.core.library.types.OnOffType;
 
@@ -30,16 +33,43 @@ public class MadokaMessageTest {
 
     @Test
     public void testMessageBuildTemperature() {
-        byte[] resp = MadokaMessage.createRequest(new GetIndoorOutoorTemperatures());
-        assertArrayEquals(resp, new byte[] { 0x00, 0x06, 0x00, 0x01, 0x10, 0x00, 0x00 });
+        byte[][] resp = new GetIndoorOutoorTemperatures().getRequest();
+        assertArrayEquals(resp[0], new byte[] { 0x00, 0x06, 0x00, 0x01, 0x10, 0x00, 0x00 });
     }
 
     @Test
     public void testMessageBuildSetPower() {
         boolean powered = true;
         MadokaValue mv = new MadokaValue(0x20, 1, new byte[] { 1 });
-        byte[] resp = MadokaMessage.createRequest(new SetPowerstateCommand(OnOffType.ON), mv);
+        byte[][] resp = MadokaMessage.createRequest(new SetPowerstateCommand(OnOffType.ON), mv);
         assertArrayEquals(
-                new byte[] { 0x00, 0x07, 0x00, 0x40, 0x20, 0x20, 0x01, (byte) (powered == true ? 0x01 : 0x00) }, resp);
+                new byte[] { 0x00, 0x07, 0x00, 0x40, 0x20, 0x20, 0x01, (byte) (powered == true ? 0x01 : 0x00) },
+                resp[0]);
+    }
+
+    @Test
+    public void testMessageBuildSize() {
+        byte[][] resp = MadokaMessage.createRequest(new GetIndoorOutoorTemperatures());
+        assertEquals(1, resp.length);
+    }
+
+    @Test
+    public void testOperationHoursCommand() {
+        byte[][] resp = new GetOperationHoursCommand().getRequest();
+        assertEquals(2, resp.length);
+        assertArrayEquals(new byte[] { 0x00, 0x19, 0x00, 0x01, 0x12, 0x02, 0x01, 0x00, 0x40, 0x00, 0x41, 0x00, 0x42,
+                0x00, 0x43, 0x00, 0x44, 0x00, 0x45, 0x00 }, resp[0]);
+        assertArrayEquals(new byte[] { 0x01, 0x46, 0x00, 0x47, 0x00, 0x48, 0x00 }, resp[1]);
+    }
+
+    @Test
+    public void testParseOperationHours() {
+        String s = "390001120201004004DC0B00004104F40300004204642300004304000000004404000000004504000000004604000000004704000000004800";
+
+        MadokaValue mv = new MadokaValue(0, 4, new byte[] { (byte) 0xF4, 0x03, 0x00, 0x00 });
+        // MadokaValue mv = new MadokaValue(0, 4, new byte[] { 0x00, 0x00, 0x03, (byte) 0xF4 });
+
+        Long v = mv.getComputedValue(ByteOrder.LITTLE_ENDIAN);
+        assertEquals(1012, v);
     }
 }
diff --git a/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/test/java/org/openhab/binding/bluetooth/daikinmadoka/internal/UartProcessorTest.java b/bundles/org.openhab.binding.bluetooth.daikinmadoka/src/test/java/org/openhab/binding/bluetooth/daikinmadoka/internal/UartProcessorTest.java
new file mode 100644 (file)
index 0000000..a02d2cb
--- /dev/null
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.daikinmadoka.internal;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetCleanFilterIndicatorCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetEyeBrightnessCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetFanspeedCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetIndoorOutoorTemperatures;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetOperationHoursCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetOperationmodeCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetPowerstateCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetSetpointCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetVersionCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.ResponseListener;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetEyeBrightnessCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetFanspeedCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetOperationmodeCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetPowerstateCommand;
+import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetSetpointCommand;
+
+/**
+ *
+ * @author blafois
+ *
+ */
+public class UartProcessorTest implements ResponseListener {
+
+    private boolean completed = false;
+
+    @Test
+    public void testUartProcessor() {
+        BRC1HUartProcessor processor = new BRC1HUartProcessor(this);
+
+        processor.chunkReceived(
+                new byte[] { 0x01, 0x1F, 0x01, 0x03, 0x20, 0x01, 0x04, 0x21, 0x01, 0x01, 0x30, 0x01, 0x00 });
+
+        processor.chunkReceived(new byte[] { 0x00, 0x1F, 0x00, 0x00, 0x30, 0x10, 0x01, 0x00, 0x13, 0x01, 0x1F, 0x15,
+                0x01, 0x10, 0x16, 0x01, 0x12, 0x17, 0x01, 0x20 });
+
+        assertTrue(completed);
+
+        this.completed = false;
+
+        processor.chunkReceived(new byte[] { 0x01, 0x01, 0x00, 0x31, 0x01, 0x01, 0x32, 0x01, 0x00, 0x40, 0x01, 0x00,
+                (byte) 0xA0, 0x01, 0x10, (byte) 0xA1, 0x01, 0x10, (byte) 0xA2, 0x02 });
+        assertFalse(completed);
+        processor.chunkReceived(new byte[] { 0x00, 0x49, 0x00, 0x00, 0x40, 0x12, 0x01, 0x1C, 0x15, 0x01, (byte) 0xF0,
+                0x20, 0x02, 0x0A, (byte) 0x80, 0x21, 0x02, 0x0A, (byte) 0x80, 0x30 });
+        assertFalse(completed);
+        processor.chunkReceived(new byte[] { 0x02, 0x08, 0x00, (byte) 0xA3, 0x02, 0x08, 0x00, (byte) 0xA4, 0x01, 0x11,
+                (byte) 0xA5, 0x01, 0x11, (byte) 0xB0, 0x01, 0x20, (byte) 0xB1, 0x01, 0x20, (byte) 0xB2 });
+        assertFalse(completed);
+        processor.chunkReceived(new byte[] { 0x03, 0x02, 0x10, 0x00, (byte) 0xB3, 0x02, 0x10, 0x00, (byte) 0xB4, 0x01,
+                0x17, (byte) 0xB5, 0x01, 0x17, (byte) 0xFE, 0x01, 0x02 });
+        assertTrue(completed);
+    }
+
+    @Override
+    public void receivedResponse(byte @NonNull [] bytes) {
+        this.completed = true;
+    }
+
+    @Override
+    public void receivedResponse(@NonNull GetVersionCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull GetFanspeedCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull GetOperationmodeCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull GetPowerstateCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull GetSetpointCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull GetIndoorOutoorTemperatures command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull SetPowerstateCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull SetSetpointCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull SetOperationmodeCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull SetFanspeedCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull GetOperationHoursCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull GetEyeBrightnessCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull GetCleanFilterIndicatorCommand command) {
+    }
+
+    @Override
+    public void receivedResponse(@NonNull SetEyeBrightnessCommand command) {
+    }
+}