| 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
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";
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)
*/
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;
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;
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;
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
}
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;
}
}
+ /**
+ * 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.
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);
}
}
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) {
return;
}
- DecimalType sp;
+ QuantityType<Temperature> sp;
switch (operationMode) {
case AUTO:
this.madokaSettings.setSetpoint(sp);
- DecimalType dt = this.madokaSettings.getSetpoint();
+ QuantityType<Temperature> dt = this.madokaSettings.getSetpoint();
if (dt != null) {
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
}
@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 {
}
}
+ @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());
}
}
+ @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
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
*/
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
@NonNullByDefault
public class BRC1HUartProcessor {
+ private final Logger logger = LoggerFactory.getLogger(BRC1HUartProcessor.class);
+
/**
* Maximum number of bytes per message chunk, including headers
*/
private ResponseListener responseListener;
+ private final Lock stateLock = new ReentrantLock();
+
public BRC1HUartProcessor(ResponseListener responseListener) {
this.responseListener = responseListener;
}
}
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);
}
}
*/
package org.openhab.binding.bluetooth.daikinmadoka.internal.model;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
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
}
// 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);
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");
public enum FanSpeed {
MAX(5),
MEDIUM(3),
- LOW(1);
+ LOW(1),
+ AUTO(0);
private int v;
return MAX;
} else if (v >= 2 && v <= 4) {
return MEDIUM;
- } else {
+ } else if (v == 1) {
return LOW;
+ } else {
+ return AUTO;
}
}
*/
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
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;
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;
}
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;
}
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;
+ }
}
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;
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;
}
*
* @return
*/
- public abstract byte[] getRequest();
+ public abstract byte[][] getRequest();
/**
* This is the command number, in the protocol
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
private @Nullable FanSpeed heatingFanSpeed;
@Override
- public byte[] getRequest() {
+ public byte[][] getRequest() {
return MadokaMessage.createRequest(this);
}
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;
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);
}
}
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);
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;
}
--- /dev/null
+/**
+ * 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;
+ }
+}
private @Nullable OperationMode operationMode;
@Override
- public byte[] getRequest() {
+ public byte[][] getRequest() {
return MadokaMessage.createRequest(this);
}
private @Nullable Boolean powerState;
@Override
- public byte[] getRequest() {
+ public byte[][] getRequest() {
return MadokaMessage.createRequest(this);
}
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;
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);
}
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);
return 64;
}
- public @Nullable DecimalType getHeatingSetpoint() {
+ public @Nullable QuantityType<Temperature> getHeatingSetpoint() {
return heatingSetpoint;
}
- public @Nullable DecimalType getCoolingSetpoint() {
+ public @Nullable QuantityType<Temperature> getCoolingSetpoint() {
return coolingSetpoint;
}
}
private @Nullable String communicationControllerVersion;
@Override
- public byte[] getRequest() {
+ public byte[][] getRequest() {
return MadokaMessage.createRequest(this);
}
--- /dev/null
+/**
+ * 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;
+ }
+}
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);
}
--- /dev/null
+/**
+ * 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;
+ }
+}
}
@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() });
}
@Override
- public byte[] getRequest() {
+ public byte[][] getRequest() {
MadokaValue mv = new MadokaValue(0x20, 1, new byte[] { (byte) this.operationMode.value() });
return MadokaMessage.createRequest(this, mv);
}
}
@Override
- public byte[] getRequest() {
+ public byte[][] getRequest() {
MadokaValue mv = new MadokaValue(0x20, 1,
new byte[] { (byte) (this.powerState == OnOffType.ON ? 0x01 : 0x00) });
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;
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()))
return 16448;
}
- public DecimalType getCoolingSetpoint() {
+ public QuantityType<Temperature> getCoolingSetpoint() {
return coolingSetpoint;
}
- public DecimalType getHeatingSetpoint() {
+ public QuantityType<Temperature> getHeatingSetpoint() {
return heatingSetpoint;
}
}
<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>
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;
@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);
}
}
--- /dev/null
+/**
+ * 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) {
+ }
+}