[Daikinmadoka] New channels and fixes (#9368)

* 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>
pull/9726/head
Benjamin Lafois 2021-01-06 21:50:55 +01:00 committed by GitHub
parent 7edcdef865
commit cb5d659c9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1060 additions and 83 deletions

View 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

View 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)
*/

View File

@ -13,12 +13,16 @@
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
*/

View 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()) {
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();
// 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);
for (byte[] msg : uartMessages) {
if (msg.length > 1) {
bos.write(msg, 1, msg.length - 1);
}
}
this.uartMessages.clear();
fullReceivedMessage = bos.toByteArray();
}
} finally {
stateLock.unlock();
}
this.uartMessages.clear();
this.responseListener.receivedResponse(bos.toByteArray());
if (fullReceivedMessage != null) {
this.responseListener.receivedResponse(fullReceivedMessage);
}
}

View 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);
return ret;
// 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 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");

View 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;
}
}

View File

@ -12,12 +12,16 @@
*/
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;
}
}

View 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;
}

View 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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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);
}

View 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;
}

View File

@ -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;
}
}

View 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);
}

View 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);
}

View 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;
}
}

View 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);
}

View File

@ -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;
}
}

View 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);
}

View File

@ -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;
}
}

View 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() });

View 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);
}

View 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) });

View 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;
}
}

View File

@ -24,6 +24,11 @@
<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>
@ -52,6 +57,11 @@
<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>
@ -129,4 +139,28 @@
</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>

View 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);
}
}

View File

@ -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) {
}
}