added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.bluetooth.daikinmadoka-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-bluetooth-daikinmadoka" description="DaikinMadoka Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth/${project.version}</bundle>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.daikinmadoka/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link DaikinMadokaBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DaikinMadokaBindingConstants {
|
||||
|
||||
private DaikinMadokaBindingConstants() {
|
||||
}
|
||||
|
||||
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_INDOOR_TEMPERATURE = "indoorTemperature";
|
||||
public static final String CHANNEL_ID_OUTDOOR_TEMPERATURE = "outdoorTemperature";
|
||||
public static final String CHANNEL_ID_COMMUNICATION_CONTROLLER_VERSION = "commCtrlVersion";
|
||||
public static final String CHANNEL_ID_REMOTE_CONTROLLER_VERSION = "remoteCtrlVersion";
|
||||
|
||||
public static final String CHANNEL_ID_OPERATION_MODE = "operationMode";
|
||||
public static final String CHANNEL_ID_FAN_SPEED = "fanSpeed";
|
||||
public static final String CHANNEL_ID_SETPOINT = "setpoint";
|
||||
public static final String CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE = "homekitCurrentHeatingCoolingMode";
|
||||
public static final String CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE = "homekitTargetHeatingCoolingMode";
|
||||
public static final String CHANNEL_ID_HOMEBRIDGE_MODE = "homebridgeMode";
|
||||
|
||||
/**
|
||||
* BLUETOOTH UUID (service + chars)
|
||||
*/
|
||||
public static final UUID SERVICE_UART_UUID = UUID.fromString("2141E110-213A-11E6-B67B-9E71128CAE77");
|
||||
public static final UUID CHAR_WRITE_WITHOUT_RESPONSE_UUID = UUID.fromString("2141E112-213A-11E6-B67B-9E71128CAE77");
|
||||
public static final UUID CHAR_NOTIF_UUID = UUID.fromString("2141E111-213A-11E6-B67B-9E71128CAE77");
|
||||
}
|
||||
@@ -0,0 +1,770 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.handler;
|
||||
|
||||
import java.util.Arrays;
|
||||
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 org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
|
||||
import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
|
||||
import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
|
||||
import org.openhab.binding.bluetooth.ConnectedBluetoothHandler;
|
||||
import org.openhab.binding.bluetooth.daikinmadoka.DaikinMadokaBindingConstants;
|
||||
import org.openhab.binding.bluetooth.daikinmadoka.internal.BRC1HUartProcessor;
|
||||
import org.openhab.binding.bluetooth.daikinmadoka.internal.DaikinMadokaConfiguration;
|
||||
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.MadokaProperties.FanSpeed;
|
||||
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.GetFanspeedCommand;
|
||||
import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetIndoorOutoorTemperatures;
|
||||
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.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;
|
||||
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.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DaikinMadokaHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels as well as updating channel values.
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements ResponseListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DaikinMadokaHandler.class);
|
||||
|
||||
private @Nullable DaikinMadokaConfiguration config;
|
||||
|
||||
private @Nullable ExecutorService commandExecutor;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
|
||||
// UART Processor is in charge of reassembling chunks
|
||||
private BRC1HUartProcessor uartProcessor = new BRC1HUartProcessor(this);
|
||||
|
||||
private volatile @Nullable BRC1HCommand currentCommand = null;
|
||||
|
||||
private MadokaSettings madokaSettings = new MadokaSettings();
|
||||
|
||||
public DaikinMadokaHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
|
||||
logger.debug("[{}] Start initializing!", super.thing.getUID().getId());
|
||||
|
||||
// Load Configuration
|
||||
config = getConfigAs(DaikinMadokaConfiguration.class);
|
||||
DaikinMadokaConfiguration c = config;
|
||||
|
||||
logger.debug("[{}] Parameter value [refreshInterval]: {}", super.thing.getUID().getId(), c.refreshInterval);
|
||||
logger.debug("[{}] Parameter value [commandTimeout]: {}", super.thing.getUID().getId(), c.commandTimeout);
|
||||
|
||||
if (getBridge() == null) {
|
||||
logger.debug("[{}] Bridge is null. Exiting.", super.thing.getUID().getId());
|
||||
return;
|
||||
}
|
||||
|
||||
this.commandExecutor = Executors
|
||||
.newSingleThreadExecutor(new NamedThreadFactory(thing.getUID().getAsString(), true));
|
||||
|
||||
this.refreshJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||
// It is useless to refresh version all the time ! Just once.
|
||||
if (this.madokaSettings.getCommunicationControllerVersion() == null
|
||||
|| this.madokaSettings.getRemoteControllerVersion() == null) {
|
||||
submitCommand(new GetVersionCommand());
|
||||
}
|
||||
submitCommand(new GetIndoorOutoorTemperatures());
|
||||
submitCommand(new GetOperationmodeCommand());
|
||||
submitCommand(new GetPowerstateCommand()); // always keep the "GetPowerState" aftern the "GetOperationMode"
|
||||
submitCommand(new GetSetpointCommand());
|
||||
submitCommand(new GetFanspeedCommand());
|
||||
}, 10, c.refreshInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("[{}] dispose()", super.thing.getUID().getId());
|
||||
|
||||
dispose(refreshJob);
|
||||
dispose(commandExecutor);
|
||||
dispose(currentCommand);
|
||||
|
||||
// Unsubscribe to characteristic notifications
|
||||
if (this.device != null) {
|
||||
BluetoothCharacteristic charNotif = this.device
|
||||
.getCharacteristic(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID);
|
||||
|
||||
if (charNotif != null) {
|
||||
@NonNull
|
||||
BluetoothCharacteristic c = charNotif;
|
||||
this.device.disableNotifications(c);
|
||||
}
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private static void dispose(@Nullable ExecutorService executor) {
|
||||
if (executor != null) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private static void dispose(@Nullable ScheduledFuture<?> future) {
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dispose(@Nullable BRC1HCommand command) {
|
||||
if (command != null) {
|
||||
// even if it already completed it doesn't really matter.
|
||||
// on the off chance that the commandExecutor is waiting on the command, we can wake it up and cause it to
|
||||
// terminate
|
||||
command.setState(BRC1HCommand.State.FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("[{}] Channel: {}, Command: {}", super.thing.getUID().getId(), channelUID, command);
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
// The refresh commands are not supported in query mode.
|
||||
// The binding will notify updates on channels
|
||||
return;
|
||||
}
|
||||
|
||||
switch (channelUID.getId()) {
|
||||
case DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT:
|
||||
try {
|
||||
QuantityType<?> setpoint = (QuantityType<?>) command;
|
||||
DecimalType dt = new DecimalType(setpoint.intValue());
|
||||
submitCommand(new SetSetpointCommand(dt, dt));
|
||||
} catch (Exception e) {
|
||||
logger.warn("Data received is not a valid temperature.", e);
|
||||
}
|
||||
break;
|
||||
case DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS:
|
||||
try {
|
||||
OnOffType oot = (OnOffType) command;
|
||||
submitCommand(new SetPowerstateCommand(oot));
|
||||
} catch (Exception e) {
|
||||
logger.warn("Data received is not a valid on/off status", e);
|
||||
}
|
||||
break;
|
||||
case DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED:
|
||||
try {
|
||||
DecimalType fanSpeed = (DecimalType) command;
|
||||
FanSpeed fs = FanSpeed.valueOf(fanSpeed.intValue());
|
||||
submitCommand(new SetFanspeedCommand(fs, fs));
|
||||
} catch (Exception e) {
|
||||
logger.warn("Data received is not a valid FanSpeed status", e);
|
||||
}
|
||||
break;
|
||||
case DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE:
|
||||
try {
|
||||
StringType operationMode = (StringType) command;
|
||||
OperationMode m = OperationMode.valueOf(operationMode.toFullString());
|
||||
|
||||
submitCommand(new SetOperationmodeCommand(m));
|
||||
} catch (Exception e) {
|
||||
logger.warn("Data received is not a valid OPERATION MODE", e);
|
||||
}
|
||||
break;
|
||||
case DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE:
|
||||
try {
|
||||
// Homebridge are discrete value different from Daikin
|
||||
// 0 - Off
|
||||
// 1 - Heating
|
||||
// 2 - Cooling
|
||||
// 3 - Auto
|
||||
DecimalType homebridgeMode = (DecimalType) command;
|
||||
switch (homebridgeMode.intValue()) {
|
||||
case 0: // Off
|
||||
submitCommand(new SetPowerstateCommand(OnOffType.OFF));
|
||||
break;
|
||||
case 1: // Heating
|
||||
submitCommand(new SetOperationmodeCommand(OperationMode.HEAT));
|
||||
if (madokaSettings.getOnOffState() == OnOffType.OFF) {
|
||||
submitCommand(new SetPowerstateCommand(OnOffType.ON));
|
||||
}
|
||||
break;
|
||||
case 2: // Cooling
|
||||
submitCommand(new SetOperationmodeCommand(OperationMode.COOL));
|
||||
if (madokaSettings.getOnOffState() == OnOffType.OFF) {
|
||||
submitCommand(new SetPowerstateCommand(OnOffType.ON));
|
||||
}
|
||||
break;
|
||||
case 3: // Auto
|
||||
submitCommand(new SetOperationmodeCommand(OperationMode.AUTO));
|
||||
if (madokaSettings.getOnOffState() == OnOffType.OFF) {
|
||||
submitCommand(new SetPowerstateCommand(OnOffType.ON));
|
||||
}
|
||||
break;
|
||||
default: // Invalid Value - in case of new FW
|
||||
logger.warn("Invalid value received for channel {}. Ignoring.", channelUID);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Data received is not a valid HOMEBRIDGE OPERATION MODE", e);
|
||||
}
|
||||
break;
|
||||
case DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE:
|
||||
try {
|
||||
StringType homekitOperationMode = (StringType) command;
|
||||
|
||||
switch (homekitOperationMode.toString()) {
|
||||
case "Off":
|
||||
submitCommand(new SetPowerstateCommand(OnOffType.OFF));
|
||||
break;
|
||||
case "CoolOn":
|
||||
submitCommand(new SetOperationmodeCommand(OperationMode.COOL));
|
||||
if (madokaSettings.getOnOffState() == OnOffType.OFF) {
|
||||
submitCommand(new SetPowerstateCommand(OnOffType.ON));
|
||||
}
|
||||
break;
|
||||
case "HeatOn":
|
||||
submitCommand(new SetOperationmodeCommand(OperationMode.HEAT));
|
||||
if (madokaSettings.getOnOffState() == OnOffType.OFF) {
|
||||
submitCommand(new SetPowerstateCommand(OnOffType.ON));
|
||||
}
|
||||
break;
|
||||
case "Auto":
|
||||
submitCommand(new SetOperationmodeCommand(OperationMode.AUTO));
|
||||
if (madokaSettings.getOnOffState() == OnOffType.OFF) {
|
||||
submitCommand(new SetPowerstateCommand(OnOffType.ON));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.info("Error while setting mode through HomeKIt received Mode");
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
|
||||
super.onCharacteristicUpdate(characteristic);
|
||||
|
||||
// Check that arguments are valid.
|
||||
if (characteristic.getUuid() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We are only interested in the Notify Characteristic of UART service
|
||||
if (!characteristic.getUuid().equals(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A message cannot be null or have a 0-byte length
|
||||
byte[] msgBytes = characteristic.getByteValue();
|
||||
if (msgBytes == null || msgBytes.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uartProcessor.chunkReceived(msgBytes);
|
||||
}
|
||||
|
||||
private void submitCommand(BRC1HCommand command) {
|
||||
Executor executor = commandExecutor;
|
||||
|
||||
if (executor != null) {
|
||||
executor.execute(() -> processCommand(command));
|
||||
}
|
||||
}
|
||||
|
||||
private void processCommand(BRC1HCommand command) {
|
||||
logger.debug("[{}] ProcessCommand {}", super.thing.getUID().getId(), command.getClass().getSimpleName());
|
||||
|
||||
try {
|
||||
currentCommand = command;
|
||||
uartProcessor.abandon();
|
||||
|
||||
if (device == null || device.getConnectionState() != ConnectionState.CONNECTED) {
|
||||
logger.debug("Unable to send command {} to device {}: not connected",
|
||||
command.getClass().getSimpleName(), address);
|
||||
command.setState(BRC1HCommand.State.FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
logger.debug("Unable to send command {} to device {}: services not resolved",
|
||||
command.getClass().getSimpleName(), device.getAddress());
|
||||
command.setState(BRC1HCommand.State.FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothCharacteristic charWrite = device
|
||||
.getCharacteristic(DaikinMadokaBindingConstants.CHAR_WRITE_WITHOUT_RESPONSE_UUID);
|
||||
if (charWrite == null) {
|
||||
logger.warn("Unable to execute {}. Characteristic '{}' could not be found.",
|
||||
command.getClass().getSimpleName(),
|
||||
DaikinMadokaBindingConstants.CHAR_WRITE_WITHOUT_RESPONSE_UUID);
|
||||
command.setState(BRC1HCommand.State.FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothCharacteristic charNotif = this.device
|
||||
.getCharacteristic(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID);
|
||||
|
||||
if (charNotif != null) {
|
||||
device.enableNotifications(charNotif);
|
||||
}
|
||||
|
||||
charWrite.setValue(command.getRequest());
|
||||
command.setState(BRC1HCommand.State.ENQUEUED);
|
||||
device.writeCharacteristic(charWrite);
|
||||
|
||||
if (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());
|
||||
command.setState(BRC1HCommand.State.FAILED);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
currentCommand = null;
|
||||
// Let the exception bubble the stack!
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWriteComplete(BluetoothCharacteristic characteristic,
|
||||
BluetoothCompletionStatus status) {
|
||||
super.onCharacteristicWriteComplete(characteristic, status);
|
||||
|
||||
byte[] request = characteristic.getByteValue();
|
||||
BRC1HCommand command = currentCommand;
|
||||
|
||||
if (command != null) {
|
||||
if (!Arrays.equals(request, command.getRequest())) {
|
||||
logger.debug("Write completed for unknown command");
|
||||
return;
|
||||
}
|
||||
switch (status) {
|
||||
case SUCCESS:
|
||||
command.setState(BRC1HCommand.State.SENT);
|
||||
break;
|
||||
case ERROR:
|
||||
command.setState(BRC1HCommand.State.FAILED);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No command found that matches request {}", HexUtils.bytesToHex(request));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the method is triggered, it means that all message chunks have been received, re-assembled in the right
|
||||
* order and that the payload is ready to be processed.
|
||||
*/
|
||||
@Override
|
||||
public void receivedResponse(byte[] response) {
|
||||
logger.debug("Received Response");
|
||||
BRC1HCommand command = currentCommand;
|
||||
|
||||
if (command == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No command present to handle response {}", HexUtils.bytesToHex(response));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
command.handleResponse(scheduler, this, MadokaMessage.parse(response));
|
||||
} catch (MadokaParsingException e) {
|
||||
logger.debug("Response message could not be parsed correctly ({}): {}. Reason: {}",
|
||||
command.getClass().getSimpleName(), HexUtils.bytesToHex(response), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedResponse(GetVersionCommand command) {
|
||||
String commCtrlVers = command.getCommunicationControllerVersion();
|
||||
if (commCtrlVers != null) {
|
||||
this.madokaSettings.setCommunicationControllerVersion(commCtrlVers);
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_COMMUNICATION_CONTROLLER_VERSION,
|
||||
new StringType(commCtrlVers));
|
||||
}
|
||||
|
||||
String remoteCtrlVers = command.getRemoteControllerVersion();
|
||||
if (remoteCtrlVers != null) {
|
||||
this.madokaSettings.setRemoteControllerVersion(remoteCtrlVers);
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_REMOTE_CONTROLLER_VERSION,
|
||||
new StringType(remoteCtrlVers));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedResponse(GetFanspeedCommand command) {
|
||||
if (command.getCoolingFanSpeed() == null || command.getHeatingFanSpeed() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need the current operation mode to determine which Fan Speed we use (cooling or heating)
|
||||
OperationMode operationMode = this.madokaSettings.getOperationMode();
|
||||
if (operationMode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
FanSpeed fs;
|
||||
|
||||
switch (operationMode) {
|
||||
case AUTO:
|
||||
logger.debug("In AutoMode, CoolingFanSpeed = {}, HeatingFanSpeed = {}", command.getCoolingFanSpeed(),
|
||||
command.getHeatingFanSpeed());
|
||||
fs = command.getHeatingFanSpeed();
|
||||
break;
|
||||
case HEAT:
|
||||
fs = command.getHeatingFanSpeed();
|
||||
break;
|
||||
case COOL:
|
||||
fs = command.getCoolingFanSpeed();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (fs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to re-set if it is the same value
|
||||
if (fs.equals(this.madokaSettings.getFanspeed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.madokaSettings.setFanspeed(fs);
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED, new DecimalType(fs.value()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedResponse(GetSetpointCommand command) {
|
||||
if (command.getCoolingSetpoint() == null || command.getHeatingSetpoint() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need the current operation mode to determine which Fan Speed we use (cooling or heating)
|
||||
OperationMode operationMode = this.madokaSettings.getOperationMode();
|
||||
if (operationMode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DecimalType sp;
|
||||
|
||||
switch (operationMode) {
|
||||
case AUTO:
|
||||
logger.debug("In AutoMode, CoolingSetpoint = {}, HeatingSetpoint = {}", command.getCoolingSetpoint(),
|
||||
command.getHeatingSetpoint());
|
||||
sp = command.getHeatingSetpoint();
|
||||
break;
|
||||
case HEAT:
|
||||
sp = command.getHeatingSetpoint();
|
||||
break;
|
||||
case COOL:
|
||||
sp = command.getCoolingSetpoint();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (sp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to re-set if it is the same value
|
||||
if (sp.equals(this.madokaSettings.getSetpoint())) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.madokaSettings.setSetpoint(sp);
|
||||
|
||||
DecimalType dt = this.madokaSettings.getSetpoint();
|
||||
if (dt != null) {
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedResponse(GetOperationmodeCommand command) {
|
||||
if (command.getOperationMode() == null) {
|
||||
logger.debug("OperationMode is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
OperationMode newMode = command.getOperationMode();
|
||||
|
||||
if (newMode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.madokaSettings.setOperationMode(newMode);
|
||||
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE, new StringType(newMode.name()));
|
||||
|
||||
// For HomeKit channel, we need to map it to HomeKit supported strings
|
||||
OnOffType ooStatus = madokaSettings.getOnOffState();
|
||||
|
||||
if (ooStatus != null && ooStatus == OnOffType.ON) {
|
||||
switch (newMode) {
|
||||
case COOL:
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
|
||||
new StringType("Cooling"));
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(2));
|
||||
break;
|
||||
case HEAT:
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
|
||||
new StringType("Heating"));
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(1));
|
||||
break;
|
||||
case AUTO:
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
|
||||
new StringType("Auto"));
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(3));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the first channel update - then we set target = current mode
|
||||
if (this.madokaSettings.getHomekitTargetMode() == null) {
|
||||
String newHomekitTargetStatus = null;
|
||||
|
||||
// For HomeKit channel, we need to map it to HomeKit supported strings
|
||||
switch (newMode) {
|
||||
case COOL:
|
||||
newHomekitTargetStatus = "CoolOn";
|
||||
break;
|
||||
case HEAT:
|
||||
newHomekitTargetStatus = "HeatOn";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (ooStatus != null && ooStatus == OnOffType.ON) {
|
||||
this.madokaSettings.setHomekitTargetMode(newHomekitTargetStatus);
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
|
||||
new StringType(newHomekitTargetStatus));
|
||||
} else if (ooStatus != null && ooStatus == OnOffType.OFF) {
|
||||
newHomekitTargetStatus = "Off";
|
||||
this.madokaSettings.setHomekitTargetMode(newHomekitTargetStatus);
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
|
||||
new StringType(newHomekitTargetStatus));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedResponse(GetPowerstateCommand command) {
|
||||
if (command.isPowerState() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
OnOffType oot = command.isPowerState() ? OnOffType.ON : OnOffType.OFF;
|
||||
|
||||
this.madokaSettings.setOnOffState(oot);
|
||||
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS, oot);
|
||||
|
||||
if (oot.equals(OnOffType.OFF)) {
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
|
||||
new StringType("Off"));
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
|
||||
new StringType("Off"));
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedResponse(GetIndoorOutoorTemperatures command) {
|
||||
DecimalType newIndoorTemp = command.getIndoorTemperature();
|
||||
if (newIndoorTemp != null) {
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_TEMPERATURE, newIndoorTemp);
|
||||
this.madokaSettings.setIndoorTemperature(newIndoorTemp);
|
||||
}
|
||||
|
||||
DecimalType newOutdoorTemp = command.getOutdoorTemperature();
|
||||
if (newOutdoorTemp == null) {
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OUTDOOR_TEMPERATURE, UnDefType.UNDEF);
|
||||
} else {
|
||||
this.madokaSettings.setOutdoorTemperature(newOutdoorTemp);
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OUTDOOR_TEMPERATURE, newOutdoorTemp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedResponse(SetPowerstateCommand command) {
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS, command.getPowerState());
|
||||
|
||||
madokaSettings.setOnOffState(command.getPowerState());
|
||||
|
||||
if (command.getPowerState() == OnOffType.ON) {
|
||||
// Depending on the state
|
||||
|
||||
OperationMode operationMode = madokaSettings.getOperationMode();
|
||||
if (operationMode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (operationMode) {
|
||||
case AUTO:
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
|
||||
new StringType("Auto"));
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(3));
|
||||
break;
|
||||
case HEAT:
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
|
||||
new StringType("Heating"));
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(1));
|
||||
break;
|
||||
case COOL:
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
|
||||
new StringType("Cooling"));
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(2));
|
||||
break;
|
||||
default: // Other Modes are not [yet] supported
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
|
||||
new StringType("Off"));
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedResponse(SetSetpointCommand command) {
|
||||
// The update depends on the mode - so if not set - skip
|
||||
OperationMode operationMode = this.madokaSettings.getOperationMode();
|
||||
if (operationMode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (operationMode) {
|
||||
case HEAT:
|
||||
this.madokaSettings.setSetpoint(command.getHeatingSetpoint());
|
||||
break;
|
||||
case COOL:
|
||||
this.madokaSettings.setSetpoint(command.getCoolingSetpoint());
|
||||
break;
|
||||
case AUTO:
|
||||
// Here we don't really care if we are taking cooling or heating...
|
||||
this.madokaSettings.setSetpoint(command.getCoolingSetpoint());
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
DecimalType dt = madokaSettings.getSetpoint();
|
||||
if (dt != null) {
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Received response to "SetOperationmodeCommand" command
|
||||
*/
|
||||
@Override
|
||||
public void receivedResponse(SetOperationmodeCommand command) {
|
||||
this.madokaSettings.setOperationMode(command.getOperationMode());
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE,
|
||||
new StringType(command.getOperationMode().toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Received response to "SetFanSpeed" command
|
||||
*/
|
||||
@Override
|
||||
public void receivedResponse(SetFanspeedCommand command) {
|
||||
// The update depends on the mode - so if not set - skip
|
||||
OperationMode operationMode = this.madokaSettings.getOperationMode();
|
||||
if (operationMode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
FanSpeed fanSpeed;
|
||||
switch (operationMode) {
|
||||
case HEAT:
|
||||
fanSpeed = command.getHeatingFanSpeed();
|
||||
this.madokaSettings.setFanspeed(fanSpeed);
|
||||
break;
|
||||
case COOL:
|
||||
fanSpeed = command.getCoolingFanSpeed();
|
||||
this.madokaSettings.setFanspeed(fanSpeed);
|
||||
break;
|
||||
case AUTO:
|
||||
fanSpeed = command.getCoolingFanSpeed(); // Arbitrary cooling or heating... They are the same!
|
||||
this.madokaSettings.setFanspeed(fanSpeed);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED, new DecimalType(fanSpeed.value()));
|
||||
}
|
||||
|
||||
private void updateStateIfLinked(String channelId, State state) {
|
||||
ChannelUID channelUID = new ChannelUID(getThing().getUID(), channelId);
|
||||
if (isLinked(channelUID)) {
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 java.io.ByteArrayOutputStream;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.ResponseListener;
|
||||
|
||||
/**
|
||||
* As the protocol emutes an UART communication over BLE (characteristics write/notify), this class takes care of BLE
|
||||
* transport.
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BRC1HUartProcessor {
|
||||
|
||||
/**
|
||||
* Maximum number of bytes per message chunk, including headers
|
||||
*/
|
||||
public static final int MAX_CHUNK_SIZE = 20;
|
||||
|
||||
/**
|
||||
* In the unlikely event of messages arrive in wrong order, this comparator will sort the queue
|
||||
*/
|
||||
private Comparator<byte[]> chunkSorter = (byte[] m1, byte[] m2) -> m1[0] - m2[0];
|
||||
|
||||
private ConcurrentSkipListSet<byte[]> uartMessages = new ConcurrentSkipListSet<>(chunkSorter);
|
||||
|
||||
private ResponseListener responseListener;
|
||||
|
||||
public BRC1HUartProcessor(ResponseListener responseListener) {
|
||||
this.responseListener = responseListener;
|
||||
}
|
||||
|
||||
private boolean isMessageComplete() {
|
||||
int messagesInQueue = this.uartMessages.size();
|
||||
|
||||
if (messagesInQueue <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] firstMessageInQueue = uartMessages.first();
|
||||
if (firstMessageInQueue.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int expectedChunks = (int) Math.ceil(firstMessageInQueue[1] / (MAX_CHUNK_SIZE - 1.0));
|
||||
if (expectedChunks != messagesInQueue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that we have every single ID
|
||||
int expected = 0;
|
||||
for (byte[] m : this.uartMessages) {
|
||||
if (m.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m[0] != expected++) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
this.uartMessages.clear();
|
||||
|
||||
this.responseListener.receivedResponse(bos.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
public void abandon() {
|
||||
this.uartMessages.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
/**
|
||||
* The {@link DaikinMadokaConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*/
|
||||
public class DaikinMadokaConfiguration {
|
||||
|
||||
public String address;
|
||||
public Integer refreshInterval;
|
||||
public Integer commandTimeout;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.openhab.binding.bluetooth.daikinmadoka.DaikinMadokaBindingConstants.THING_TYPE_BRC1H;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.bluetooth.daikinmadoka.handler.DaikinMadokaHandler;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DaikinMadokaHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.bluetooth.daikinmadoka", service = ThingHandlerFactory.class)
|
||||
public class DaikinMadokaHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_BRC1H);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DaikinMadokaHandlerFactory.class);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
logger.debug("Request to create handler for thing {}", thing.getThingTypeUID());
|
||||
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_BRC1H.equals(thingTypeUID)) {
|
||||
logger.debug("Thing is matching BRC1H");
|
||||
|
||||
return new DaikinMadokaHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.BRC1HCommand;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class represents a message transmitted or received from the BRC1H controller as a serial protocol
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MadokaMessage {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MadokaMessage.class);
|
||||
|
||||
private int messageId;
|
||||
private final Map<Integer, MadokaValue> values;
|
||||
|
||||
private byte @Nullable [] rawMessage;
|
||||
|
||||
private MadokaMessage() {
|
||||
values = new HashMap<>();
|
||||
}
|
||||
|
||||
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);
|
||||
request.writeByte(0);
|
||||
|
||||
// Command ID, coded on 3 bytes
|
||||
request.writeByte(0);
|
||||
request.writeShort(command.getCommandId());
|
||||
|
||||
if (parameters.length == 0) {
|
||||
request.writeByte(0);
|
||||
request.writeByte(0);
|
||||
} else {
|
||||
for (MadokaValue mv : parameters) {
|
||||
request.writeByte(mv.getId());
|
||||
request.writeByte(mv.getSize());
|
||||
request.write(mv.getRawValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, compute array size
|
||||
byte[] ret = output.toByteArray();
|
||||
ret[1] = (byte) (ret.length - 1);
|
||||
|
||||
return ret;
|
||||
} catch (IOException e) {
|
||||
logger.info("Error while building request", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static MadokaMessage parse(byte[] msg) throws MadokaParsingException {
|
||||
// Msg format (bytes):
|
||||
// <Msg Length> <msg id> <msg id> <msg id> ...
|
||||
// So MINIMAL length is 4, to cover the message length + message ID
|
||||
if (msg.length < 4) {
|
||||
throw new MadokaParsingException("Message received is too short to be parsed.");
|
||||
}
|
||||
if (msg[0] != msg.length) {
|
||||
throw new MadokaParsingException("Message size is not valid (different from byte[0]).");
|
||||
}
|
||||
|
||||
MadokaMessage m = new MadokaMessage();
|
||||
m.setRawMessage(msg);
|
||||
m.messageId = ByteBuffer.wrap(msg, 2, 2).getShort();
|
||||
|
||||
MadokaValue mv = null;
|
||||
|
||||
// Starting here, we are not on the safe side with previous msg.length check
|
||||
for (int i = 4; i < msg.length;) {
|
||||
if ((i + 1) >= msg.length) {
|
||||
throw new MadokaParsingException("Truncated message detected while parsing response value header");
|
||||
}
|
||||
|
||||
mv = new MadokaValue();
|
||||
mv.setId(msg[i]);
|
||||
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");
|
||||
}
|
||||
|
||||
mv.setRawValue(Arrays.copyOfRange(msg, i + 2, i + 2 + mv.getSize()));
|
||||
|
||||
i += 2 + mv.getSize();
|
||||
|
||||
m.values.put(mv.getId(), mv);
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
private void setRawMessage(byte[] rawMessage) {
|
||||
this.rawMessage = rawMessage;
|
||||
}
|
||||
|
||||
public byte @Nullable [] getRawMessage() {
|
||||
return this.rawMessage;
|
||||
}
|
||||
|
||||
public int getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public Map<Integer, MadokaValue> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(String.format("{ messageId: %d, values: [", this.messageId));
|
||||
|
||||
for (Map.Entry<Integer, MadokaValue> entry : values.entrySet()) {
|
||||
sb.append(String.format(" { valueId: %d, valueSize: %d },", entry.getKey(), entry.getValue().getSize()));
|
||||
}
|
||||
|
||||
sb.append("] }");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This exception is thrown when an exception happens parsing a message from the BLE controller.
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@NonNullByDefault
|
||||
public class MadokaParsingException extends Exception {
|
||||
|
||||
public MadokaParsingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MadokaParsingException(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This class contains the enums for the various modes supported by the BLE thermostat
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MadokaProperties {
|
||||
|
||||
public enum FanSpeed {
|
||||
MAX(5),
|
||||
MEDIUM(3),
|
||||
LOW(1);
|
||||
|
||||
private int v;
|
||||
|
||||
FanSpeed(int v) {
|
||||
this.v = v;
|
||||
}
|
||||
|
||||
public static FanSpeed valueOf(int v) {
|
||||
if (v == 5) {
|
||||
return MAX;
|
||||
} else if (v >= 2 && v <= 4) {
|
||||
return MEDIUM;
|
||||
} else {
|
||||
return LOW;
|
||||
}
|
||||
}
|
||||
|
||||
public int value() {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
public enum OperationMode {
|
||||
FAN(0),
|
||||
DRY(1),
|
||||
AUTO(2),
|
||||
COOL(3),
|
||||
HEAT(4),
|
||||
VENTILATION(5);
|
||||
|
||||
private int v;
|
||||
|
||||
OperationMode(int v) {
|
||||
this.v = v;
|
||||
}
|
||||
|
||||
public static OperationMode valueOf(int v) {
|
||||
for (OperationMode m : values()) {
|
||||
if (m.v == v) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
// Should never happen
|
||||
return HEAT;
|
||||
}
|
||||
|
||||
public int value() {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* This class contains the current state of the controllerw
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MadokaSettings {
|
||||
|
||||
private @Nullable OnOffType onOffState;
|
||||
|
||||
private @Nullable DecimalType setpoint;
|
||||
|
||||
private @Nullable DecimalType indoorTemperature;
|
||||
private @Nullable DecimalType outdoorTemperature;
|
||||
|
||||
private @Nullable FanSpeed fanspeed;
|
||||
|
||||
private @Nullable OperationMode operationMode;
|
||||
|
||||
private @Nullable String homekitCurrentMode;
|
||||
private @Nullable String homekitTargetMode;
|
||||
|
||||
private @Nullable String communicationControllerVersion;
|
||||
private @Nullable String remoteControllerVersion;
|
||||
|
||||
public @Nullable OnOffType getOnOffState() {
|
||||
return onOffState;
|
||||
}
|
||||
|
||||
public void setOnOffState(OnOffType onOffState) {
|
||||
this.onOffState = onOffState;
|
||||
}
|
||||
|
||||
public @Nullable DecimalType getSetpoint() {
|
||||
return setpoint;
|
||||
}
|
||||
|
||||
public void setSetpoint(DecimalType setpoint) {
|
||||
this.setpoint = setpoint;
|
||||
}
|
||||
|
||||
public @Nullable DecimalType getIndoorTemperature() {
|
||||
return indoorTemperature;
|
||||
}
|
||||
|
||||
public void setIndoorTemperature(DecimalType indoorTemperature) {
|
||||
this.indoorTemperature = indoorTemperature;
|
||||
}
|
||||
|
||||
public @Nullable DecimalType getOutdoorTemperature() {
|
||||
return outdoorTemperature;
|
||||
}
|
||||
|
||||
public void setOutdoorTemperature(DecimalType outdoorTemperature) {
|
||||
this.outdoorTemperature = outdoorTemperature;
|
||||
}
|
||||
|
||||
public @Nullable FanSpeed getFanspeed() {
|
||||
return fanspeed;
|
||||
}
|
||||
|
||||
public void setFanspeed(FanSpeed fanspeed) {
|
||||
this.fanspeed = fanspeed;
|
||||
}
|
||||
|
||||
public @Nullable OperationMode getOperationMode() {
|
||||
return operationMode;
|
||||
}
|
||||
|
||||
public void setOperationMode(OperationMode operationMode) {
|
||||
this.operationMode = operationMode;
|
||||
}
|
||||
|
||||
public @Nullable String getHomekitCurrentMode() {
|
||||
return homekitCurrentMode;
|
||||
}
|
||||
|
||||
public void setHomekitCurrentMode(String homekitCurrentMode) {
|
||||
this.homekitCurrentMode = homekitCurrentMode;
|
||||
}
|
||||
|
||||
public @Nullable String getHomekitTargetMode() {
|
||||
return homekitTargetMode;
|
||||
}
|
||||
|
||||
public void setHomekitTargetMode(String homekitTargetMode) {
|
||||
this.homekitTargetMode = homekitTargetMode;
|
||||
}
|
||||
|
||||
public @Nullable String getCommunicationControllerVersion() {
|
||||
return communicationControllerVersion;
|
||||
}
|
||||
|
||||
public void setCommunicationControllerVersion(String communicationControllerVersion) {
|
||||
this.communicationControllerVersion = communicationControllerVersion;
|
||||
}
|
||||
|
||||
public @Nullable String getRemoteControllerVersion() {
|
||||
return remoteControllerVersion;
|
||||
}
|
||||
|
||||
public void setRemoteControllerVersion(String remoteControllerVersion) {
|
||||
this.remoteControllerVersion = remoteControllerVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This class is in charge of serializing/deserializing values from a message
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MadokaValue {
|
||||
|
||||
private int id;
|
||||
private int size;
|
||||
private byte @Nullable [] rawValue;
|
||||
|
||||
public MadokaValue(int id, int size, byte[] rawValue) {
|
||||
this.id = id;
|
||||
this.size = size;
|
||||
this.rawValue = rawValue;
|
||||
}
|
||||
|
||||
public MadokaValue() {
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public byte @Nullable [] getRawValue() {
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
public void setRawValue(byte[] rawValue) {
|
||||
this.rawValue = rawValue;
|
||||
}
|
||||
|
||||
public long getComputedValue() {
|
||||
byte[] v = rawValue;
|
||||
if (v != null) {
|
||||
switch (size) {
|
||||
case 1:
|
||||
return v[0];
|
||||
case 2:
|
||||
return ByteBuffer.wrap(v, 0, 2).getShort();
|
||||
case 4:
|
||||
return ByteBuffer.wrap(v, 0, 4).getInt();
|
||||
default:
|
||||
// unsupported
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
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.MadokaMessage;
|
||||
import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
|
||||
|
||||
/**
|
||||
* Abstract class for all BLE commands sent to the controller
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class BRC1HCommand {
|
||||
|
||||
public enum State {
|
||||
NEW,
|
||||
ENQUEUED,
|
||||
SENT,
|
||||
SUCCEEDED,
|
||||
FAILED
|
||||
}
|
||||
|
||||
private volatile State state = State.NEW;
|
||||
|
||||
private final Lock stateLock = new ReentrantLock();
|
||||
|
||||
private final Condition stateCondition = stateLock.newCondition();
|
||||
|
||||
public abstract void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
|
||||
throws MadokaParsingException;
|
||||
|
||||
/**
|
||||
* THis command returns the message to be sent
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract byte[] getRequest();
|
||||
|
||||
/**
|
||||
* This is the command number, in the protocol
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract int getCommandId();
|
||||
|
||||
/**
|
||||
* Returns current state of the command.
|
||||
*
|
||||
* @return current state
|
||||
*/
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets state of the command.
|
||||
*
|
||||
* @param state new state
|
||||
*/
|
||||
public void setState(State state) {
|
||||
stateLock.lock();
|
||||
try {
|
||||
this.state = state;
|
||||
stateCondition.signalAll();
|
||||
} finally {
|
||||
stateLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean awaitStateChange(long timeout, TimeUnit unit, State... expectedStates) throws InterruptedException {
|
||||
stateLock.lock();
|
||||
try {
|
||||
long nanosTimeout = unit.toNanos(timeout);
|
||||
while (!isInAnyState(expectedStates)) {
|
||||
if (nanosTimeout <= 0L) {
|
||||
return false;
|
||||
}
|
||||
nanosTimeout = stateCondition.awaitNanos(nanosTimeout);
|
||||
}
|
||||
} finally {
|
||||
stateLock.unlock();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isInAnyState(State[] acceptedStates) {
|
||||
for (State acceptedState : acceptedStates) {
|
||||
if (acceptedState == state) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.MadokaProperties.FanSpeed;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command used to set the FAN speed for all modes (auto/cool/heat...)
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetFanspeedCommand extends BRC1HCommand {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GetFanspeedCommand.class);
|
||||
|
||||
private @Nullable FanSpeed coolingFanSpeed;
|
||||
private @Nullable FanSpeed heatingFanSpeed;
|
||||
|
||||
@Override
|
||||
public byte[] getRequest() {
|
||||
return MadokaMessage.createRequest(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
|
||||
throws MadokaParsingException {
|
||||
|
||||
byte[] valueCoolingFanSpeed = mm.getValues().get(0x20).getRawValue();
|
||||
byte[] valueHeatingFanSpeed = mm.getValues().get(0x21).getRawValue();
|
||||
|
||||
if (valueCoolingFanSpeed == null || valueHeatingFanSpeed == null) {
|
||||
setState(State.FAILED);
|
||||
throw new MadokaParsingException("Incorrect cooling or heating fan speed value");
|
||||
}
|
||||
|
||||
this.coolingFanSpeed = FanSpeed.valueOf(valueCoolingFanSpeed[0]);
|
||||
this.heatingFanSpeed = FanSpeed.valueOf(valueHeatingFanSpeed[0]);
|
||||
|
||||
logger.debug("coolingFanSpeed: {}", coolingFanSpeed);
|
||||
logger.debug("heatingFanSpeed: {}", heatingFanSpeed);
|
||||
|
||||
setState(State.SUCCEEDED);
|
||||
executor.execute(() -> listener.receivedResponse(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return 80;
|
||||
}
|
||||
|
||||
public @Nullable FanSpeed getCoolingFanSpeed() {
|
||||
return coolingFanSpeed;
|
||||
}
|
||||
|
||||
public @Nullable FanSpeed getHeatingFanSpeed() {
|
||||
return heatingFanSpeed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.library.types.DecimalType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This command returns the Indoor and Outdoor temperature. Outdoor is not always supported.
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetIndoorOutoorTemperatures extends BRC1HCommand {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GetIndoorOutoorTemperatures.class);
|
||||
|
||||
private @Nullable DecimalType indoorTemperature;
|
||||
private @Nullable DecimalType outdoorTemperature;
|
||||
|
||||
@Override
|
||||
public byte[] getRequest() {
|
||||
return MadokaMessage.createRequest(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
|
||||
throws MadokaParsingException {
|
||||
byte[] bIndoorTemperature = mm.getValues().get(0x40).getRawValue();
|
||||
byte[] bOutdoorTemperature = mm.getValues().get(0x41).getRawValue();
|
||||
|
||||
if (bIndoorTemperature == null || bOutdoorTemperature == null) {
|
||||
setState(State.FAILED);
|
||||
throw new MadokaParsingException("Incorrect indoor or outdoor temperature");
|
||||
}
|
||||
|
||||
Integer iIndoorTemperature = Integer.valueOf(bIndoorTemperature[0]);
|
||||
Integer iOutdoorTemperature = Integer.valueOf(bOutdoorTemperature[0]);
|
||||
|
||||
if (iOutdoorTemperature == -1) {
|
||||
iOutdoorTemperature = null;
|
||||
} else {
|
||||
if (iOutdoorTemperature < 0) {
|
||||
iOutdoorTemperature = ((iOutdoorTemperature + 256) - 128) * -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (iIndoorTemperature != null) {
|
||||
indoorTemperature = new DecimalType(iIndoorTemperature);
|
||||
}
|
||||
|
||||
if (iOutdoorTemperature != null) {
|
||||
outdoorTemperature = new DecimalType(iOutdoorTemperature);
|
||||
}
|
||||
|
||||
logger.debug("Indoor Temp: {}", indoorTemperature);
|
||||
logger.debug("Outdoor Temp: {}", outdoorTemperature);
|
||||
|
||||
setState(State.SUCCEEDED);
|
||||
executor.execute(() -> listener.receivedResponse(this));
|
||||
}
|
||||
|
||||
public @Nullable DecimalType getIndoorTemperature() {
|
||||
return indoorTemperature;
|
||||
}
|
||||
|
||||
public @Nullable DecimalType getOutdoorTemperature() {
|
||||
return outdoorTemperature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return 272;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.MadokaProperties.OperationMode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This command returns the current AC operation mode
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetOperationmodeCommand extends BRC1HCommand {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GetOperationmodeCommand.class);
|
||||
|
||||
private @Nullable OperationMode operationMode;
|
||||
|
||||
@Override
|
||||
public byte[] getRequest() {
|
||||
return MadokaMessage.createRequest(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
|
||||
throws MadokaParsingException {
|
||||
byte[] bOperationMode = mm.getValues().get(0x20).getRawValue();
|
||||
if (bOperationMode == null) {
|
||||
setState(State.FAILED);
|
||||
throw new MadokaParsingException("Incorrect operation mode");
|
||||
}
|
||||
|
||||
operationMode = OperationMode.valueOf(bOperationMode[0]);
|
||||
|
||||
logger.debug("operationMode: {}", operationMode);
|
||||
|
||||
setState(State.SUCCEEDED);
|
||||
executor.execute(() -> listener.receivedResponse(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return 48;
|
||||
}
|
||||
|
||||
public @Nullable OperationMode getOperationMode() {
|
||||
return operationMode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
/**
|
||||
* This command returns the current AC power state (on or off)
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetPowerstateCommand extends BRC1HCommand {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GetPowerstateCommand.class);
|
||||
|
||||
private @Nullable Boolean powerState;
|
||||
|
||||
@Override
|
||||
public byte[] getRequest() {
|
||||
return MadokaMessage.createRequest(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
|
||||
throws MadokaParsingException {
|
||||
byte[] powerStateValue = mm.getValues().get(0x20).getRawValue();
|
||||
|
||||
if (powerStateValue == null || powerStateValue.length != 1) {
|
||||
setState(State.FAILED);
|
||||
throw new MadokaParsingException("Incorrect value for PowerState");
|
||||
}
|
||||
|
||||
powerState = Integer.valueOf(powerStateValue[0]) == 1;
|
||||
|
||||
logger.debug("PowerState: {}", powerState);
|
||||
|
||||
setState(State.SUCCEEDED);
|
||||
executor.execute(() -> listener.receivedResponse(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
public @Nullable Boolean isPowerState() {
|
||||
return powerState;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.library.types.DecimalType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This command returns the setpoint, whatever is the current mode.
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetSetpointCommand extends BRC1HCommand {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GetSetpointCommand.class);
|
||||
|
||||
private @Nullable DecimalType heatingSetpoint;
|
||||
private @Nullable DecimalType coolingSetpoint;
|
||||
|
||||
@Override
|
||||
public byte[] getRequest() {
|
||||
return MadokaMessage.createRequest(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
|
||||
throws MadokaParsingException {
|
||||
try {
|
||||
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);
|
||||
|
||||
logger.debug("heatingSetpoint: {}", heatingSetpoint);
|
||||
logger.debug("coolingSetpoint: {}", coolingSetpoint);
|
||||
|
||||
setState(State.SUCCEEDED);
|
||||
executor.execute(() -> listener.receivedResponse(this));
|
||||
} catch (Exception e) {
|
||||
setState(State.FAILED);
|
||||
throw new MadokaParsingException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return 64;
|
||||
}
|
||||
|
||||
public @Nullable DecimalType getHeatingSetpoint() {
|
||||
return heatingSetpoint;
|
||||
}
|
||||
|
||||
public @Nullable DecimalType getCoolingSetpoint() {
|
||||
return coolingSetpoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
/**
|
||||
* This command returns the firmware version
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetVersionCommand extends BRC1HCommand {
|
||||
|
||||
private @Nullable String remoteControllerVersion;
|
||||
private @Nullable String communicationControllerVersion;
|
||||
|
||||
@Override
|
||||
public byte[] getRequest() {
|
||||
return MadokaMessage.createRequest(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Executor executor, ResponseListener listener, MadokaMessage mm)
|
||||
throws MadokaParsingException {
|
||||
// In this method, we intentionally do not check for null values in mv45 and mv46. In case of null pointer
|
||||
// access, it will be catched by the global exception and be reported as a Parsing Reponse exception.
|
||||
byte[] mv45 = mm.getValues().get(0x45).getRawValue();
|
||||
byte[] mv46 = mm.getValues().get(0x46).getRawValue();
|
||||
|
||||
if (mv45 == null || mv45.length != 3 || mv46 == null || mv46.length != 2) {
|
||||
setState(State.FAILED);
|
||||
throw new MadokaParsingException("Incorrect version value");
|
||||
}
|
||||
|
||||
int remoteControllerMajor = mv45[0];
|
||||
int remoteControllerMinor = mv45[1];
|
||||
int remoteControllerRevision = mv45[2];
|
||||
this.remoteControllerVersion = remoteControllerMajor + "." + remoteControllerMinor + "."
|
||||
+ remoteControllerRevision;
|
||||
|
||||
int commControllerMajor = mv46[0];
|
||||
int commControllerMinor = mv46[1];
|
||||
this.communicationControllerVersion = commControllerMajor + "." + commControllerMinor;
|
||||
|
||||
setState(State.SUCCEEDED);
|
||||
executor.execute(() -> listener.receivedResponse(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return 304;
|
||||
}
|
||||
|
||||
public @Nullable String getRemoteControllerVersion() {
|
||||
return remoteControllerVersion;
|
||||
}
|
||||
|
||||
public @Nullable String getCommunicationControllerVersion() {
|
||||
return communicationControllerVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Command responses that the listener must implement
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ResponseListener {
|
||||
|
||||
public void receivedResponse(byte[] bytes);
|
||||
|
||||
public void receivedResponse(GetVersionCommand command);
|
||||
|
||||
public void receivedResponse(GetFanspeedCommand command);
|
||||
|
||||
public void receivedResponse(GetOperationmodeCommand command);
|
||||
|
||||
public void receivedResponse(GetPowerstateCommand command);
|
||||
|
||||
public void receivedResponse(GetSetpointCommand command);
|
||||
|
||||
public void receivedResponse(GetIndoorOutoorTemperatures command);
|
||||
|
||||
public void receivedResponse(SetPowerstateCommand command);
|
||||
|
||||
public void receivedResponse(SetSetpointCommand command);
|
||||
|
||||
public void receivedResponse(SetOperationmodeCommand command);
|
||||
|
||||
public void receivedResponse(SetFanspeedCommand command);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.MadokaProperties.FanSpeed;
|
||||
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 sets the fanspeed for the current mode
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetFanspeedCommand extends BRC1HCommand {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SetFanspeedCommand.class);
|
||||
|
||||
private FanSpeed coolingFanSpeed;
|
||||
private FanSpeed heatingFanSpeed;
|
||||
|
||||
public SetFanspeedCommand(FanSpeed coolingFanSpeed, FanSpeed heatingFanSpeed) {
|
||||
this.coolingFanSpeed = coolingFanSpeed;
|
||||
this.heatingFanSpeed = heatingFanSpeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
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() });
|
||||
|
||||
return MadokaMessage.createRequest(this, paramCoolingFanSpeed, paramHeatingFanSpeed);
|
||||
}
|
||||
|
||||
@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);
|
||||
executor.execute(() -> listener.receivedResponse(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return 16464;
|
||||
}
|
||||
|
||||
public FanSpeed getCoolingFanSpeed() {
|
||||
return coolingFanSpeed;
|
||||
}
|
||||
|
||||
public FanSpeed getHeatingFanSpeed() {
|
||||
return heatingFanSpeed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.MadokaProperties.OperationMode;
|
||||
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 is in charge of changing the current operation mode
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetOperationmodeCommand extends BRC1HCommand {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SetOperationmodeCommand.class);
|
||||
|
||||
private OperationMode operationMode;
|
||||
|
||||
public SetOperationmodeCommand(OperationMode operationMode) {
|
||||
this.operationMode = operationMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRequest() {
|
||||
MadokaValue mv = new MadokaValue(0x20, 1, new byte[] { (byte) this.operationMode.value() });
|
||||
return MadokaMessage.createRequest(this, mv);
|
||||
}
|
||||
|
||||
@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);
|
||||
executor.execute(() -> listener.receivedResponse(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return 16432;
|
||||
}
|
||||
|
||||
public OperationMode getOperationMode() {
|
||||
return operationMode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.library.types.OnOffType;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This command is in charge of turning on or off the AC.
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetPowerstateCommand extends BRC1HCommand {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SetPowerstateCommand.class);
|
||||
|
||||
private OnOffType powerState;
|
||||
|
||||
public SetPowerstateCommand(OnOffType powerState) {
|
||||
this.powerState = powerState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRequest() {
|
||||
MadokaValue mv = new MadokaValue(0x20, 1,
|
||||
new byte[] { (byte) (this.powerState == OnOffType.ON ? 0x01 : 0x00) });
|
||||
|
||||
return MadokaMessage.createRequest(this, mv);
|
||||
}
|
||||
|
||||
@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);
|
||||
executor.execute(() -> listener.receivedResponse(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return 16416;
|
||||
}
|
||||
|
||||
public OnOffType getPowerState() {
|
||||
return powerState;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.ByteBuffer;
|
||||
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.library.types.DecimalType;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* THis command is in charge of changing the AC setpoint
|
||||
*
|
||||
* @author Benjamin Lafois - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetSetpointCommand extends BRC1HCommand {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SetSetpointCommand.class);
|
||||
|
||||
private DecimalType coolingSetpoint;
|
||||
private DecimalType heatingSetpoint;
|
||||
|
||||
public SetSetpointCommand(DecimalType coolingSetpoint, DecimalType heatingSetpoint) {
|
||||
this.coolingSetpoint = coolingSetpoint;
|
||||
this.heatingSetpoint = heatingSetpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRequest() {
|
||||
byte[] heatingSetpointBytes = ByteBuffer.allocate(2).putShort((short) (128. * heatingSetpoint.shortValue()))
|
||||
.array();
|
||||
byte[] coolingSetpointBytes = ByteBuffer.allocate(2).putShort((short) (128. * coolingSetpoint.shortValue()))
|
||||
.array();
|
||||
|
||||
MadokaValue mvHeatingSetpoint = new MadokaValue(0x21, 2, heatingSetpointBytes);
|
||||
|
||||
MadokaValue mvCoolingSetpoint = new MadokaValue(0x20, 2, coolingSetpointBytes);
|
||||
|
||||
return MadokaMessage.createRequest(this, mvCoolingSetpoint, mvHeatingSetpoint);
|
||||
}
|
||||
|
||||
@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);
|
||||
executor.execute(() -> listener.receivedResponse(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return 16448;
|
||||
}
|
||||
|
||||
public DecimalType getCoolingSetpoint() {
|
||||
return coolingSetpoint;
|
||||
}
|
||||
|
||||
public DecimalType getHeatingSetpoint() {
|
||||
return heatingSetpoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bluetooth"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="brc1h">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="dbusbluez"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Daikin BRC1H Thermostat</label>
|
||||
<description>A Daikin Madoka BRC1H Thermostat (BLE)</description>
|
||||
|
||||
<channels>
|
||||
<channel id="rssi" typeId="rssi"/>
|
||||
<channel id="onOffStatus" typeId="brc1h_onOffStatus"/>
|
||||
<channel id="indoorTemperature" typeId="brc1h_indoorTemperature"/>
|
||||
<channel id="outdoorTemperature" typeId="brc1h_outdoorTemperature"/>
|
||||
<channel id="remoteCtrlVersion" typeId="brc1h_remoteCtrlVersion"/>
|
||||
<channel id="commCtrlVersion" typeId="brc1h_commCtrlVersion"/>
|
||||
<channel id="operationMode" typeId="brc1h_operationMode"/>
|
||||
<channel id="setpoint" typeId="brc1h_setpoint"/>
|
||||
<channel id="fanSpeed" typeId="brc1h_fanSpeed"/>
|
||||
<channel id="homekitCurrentHeatingCoolingMode" typeId="brc1h_homekitCurrentHeatingCoolingMode"/>
|
||||
<channel id="homekitTargetHeatingCoolingMode" typeId="brc1h_homekitTargetHeatingCoolingMode"/>
|
||||
<channel id="homebridgeMode" typeId="brc1h_homebridgeMode"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text" required="true">
|
||||
<label>Address</label>
|
||||
<description>Bluetooth address in XX:XX:XX:XX:XX:XX format</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="refreshInterval" type="integer" min="1" max="86400" unit="s">
|
||||
<label>Refresh Interval</label>
|
||||
<advanced>true</advanced>
|
||||
<description>Refresh interval for battery and light sensor data (in seconds). This could impact battery lifetime</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
<parameter name="commandTimeout" type="integer" unit="ms">
|
||||
<label>Command Timeout</label>
|
||||
<advanced>true</advanced>
|
||||
<description>The amount of time, in milliseconds, a command should take before it times out.</description>
|
||||
<default>1000</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="brc1h_onOffStatus">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Unit Power Status</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brc1h_indoorTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Indoor Temperature</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brc1h_outdoorTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Outdoor Temperature</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brc1h_remoteCtrlVersion">
|
||||
<item-type>String</item-type>
|
||||
<label>Remote Controller Version</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brc1h_commCtrlVersion">
|
||||
<item-type>String</item-type>
|
||||
<label>Communication Controller Version</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brc1h_operationMode">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<command>
|
||||
<options>
|
||||
<option value="FAN">Fan</option>
|
||||
<option value="DRY">Dry</option>
|
||||
<option value="AUTO">Auto</option>
|
||||
<option value="COOL">Cool</option>
|
||||
<option value="HEAT">Heat</option>
|
||||
<option value="VENTILATION">Ventilation</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brc1h_setpoint">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Setpoint</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brc1h_fanSpeed">
|
||||
<item-type>Number</item-type>
|
||||
<label>Fan Speed</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brc1h_homekitCurrentHeatingCoolingMode">
|
||||
<item-type>String</item-type>
|
||||
<label>HomeKit CurrentMode</label>
|
||||
<description>Readonly value. Off, Heating, Cooling, Auto</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brc1h_homekitTargetHeatingCoolingMode">
|
||||
<item-type>String</item-type>
|
||||
<label>HomeKit Target Mode</label>
|
||||
<command>
|
||||
<options>
|
||||
<option value="Off">Off</option>
|
||||
<option value="CoolOn">Cool</option>
|
||||
<option value="HeatOn">Heat</option>
|
||||
<option value="Auto">Auto</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brc1h_homebridgeMode">
|
||||
<item-type>Number</item-type>
|
||||
<label>Mode from Homebridge</label>
|
||||
<command>
|
||||
<options>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">Heating</option>
|
||||
<option value="2">Cooling</option>
|
||||
<option value="3">Auto</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user