mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[danfossairunit] Fix network reliability issues and setting of all channel values to zero (#11172)
* Fix Potential null pointer accesses * Added constants for TCP port and poll interval in seconds. * Extract interface from DanfossAirUnitCommunicationController. * Remove unused constants which seems to be left-overs from skeleton. * Added constant for discovery timeout value for readability. * Created handler subfolder for DanfossAirUnitHandler (like discovery) for consistency with other bindings. * Handle lost connection gracefully without updating all channels to zero. * Fix infinitly blocking network calls preventing proper error handling. * Fix thing status being reset to ONLINE after failing to update all channels. * Fix error handling when receiving invalid manual fan step. Fixes #11167 Fixes #11188 Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
2f4a27217f
commit
8255f29320
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.danfossairunit.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This interface defines a communication controller that can be used to send requests to the Danfoss Air Unit.
|
||||
*
|
||||
* @author Jacob Laursen - Refactoring, bugfixes and enhancements
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface CommunicationController {
|
||||
void connect() throws IOException;
|
||||
|
||||
void disconnect();
|
||||
|
||||
byte[] sendRobustRequest(byte[] operation, byte[] register) throws IOException;
|
||||
|
||||
byte[] sendRobustRequest(byte[] operation, byte[] register, byte[] value) throws IOException;
|
||||
}
|
@ -17,7 +17,6 @@ import static org.openhab.binding.danfossairunit.internal.Commands.*;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.ZoneId;
|
||||
@ -43,30 +42,22 @@ import org.openhab.core.types.Command;
|
||||
*
|
||||
* @author Ralf Duckstein - Initial contribution
|
||||
* @author Robert Bach - heavy refactorings
|
||||
* @author Jacob Laursen - Refactoring, bugfixes and enhancements
|
||||
*/
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
@NonNullByDefault
|
||||
public class DanfossAirUnit {
|
||||
|
||||
private final DanfossAirUnitCommunicationController communicationController;
|
||||
private final CommunicationController communicationController;
|
||||
|
||||
public DanfossAirUnit(InetAddress inetAddr, int port) {
|
||||
this.communicationController = new DanfossAirUnitCommunicationController(inetAddr, port);
|
||||
}
|
||||
|
||||
public void cleanUp() {
|
||||
this.communicationController.disconnect();
|
||||
public DanfossAirUnit(CommunicationController communicationController) {
|
||||
this.communicationController = communicationController;
|
||||
}
|
||||
|
||||
private boolean getBoolean(byte[] operation, byte[] register) throws IOException {
|
||||
return communicationController.sendRobustRequest(operation, register)[0] != 0;
|
||||
}
|
||||
|
||||
private void setSetting(byte[] register, boolean value) throws IOException {
|
||||
setSetting(register, value ? (byte) 1 : (byte) 0);
|
||||
}
|
||||
|
||||
private short getWord(byte[] operation, byte[] register) throws IOException {
|
||||
byte[] resultBytes = communicationController.sendRobustRequest(operation, register);
|
||||
return (short) ((resultBytes[0] << 8) | (resultBytes[1] & 0xFF));
|
||||
@ -87,14 +78,6 @@ public class DanfossAirUnit {
|
||||
communicationController.sendRobustRequest(operation, register, valueArray);
|
||||
}
|
||||
|
||||
private void set(byte[] operation, byte[] register, short value) throws IOException {
|
||||
communicationController.sendRobustRequest(operation, register, shortToBytes(value));
|
||||
}
|
||||
|
||||
private byte[] shortToBytes(short s) {
|
||||
return new byte[] { (byte) ((s & 0xFF00) >> 8), (byte) (s & 0x00FF) };
|
||||
}
|
||||
|
||||
private short getShort(byte[] operation, byte[] register) throws IOException {
|
||||
byte[] result = communicationController.sendRobustRequest(operation, register);
|
||||
return (short) ((result[0] << 8) + (result[1] & 0xff));
|
||||
@ -141,14 +124,6 @@ public class DanfossAirUnit {
|
||||
return f * 100 / 255;
|
||||
}
|
||||
|
||||
private void setSetting(byte[] register, short value) throws IOException {
|
||||
byte[] valueArray = new byte[2];
|
||||
valueArray[0] = (byte) (value >> 8);
|
||||
valueArray[1] = (byte) value;
|
||||
|
||||
communicationController.sendRobustRequest(REGISTER_1_WRITE, register, valueArray);
|
||||
}
|
||||
|
||||
public String getUnitName() throws IOException {
|
||||
return getString(REGISTER_1_READ, UNIT_NAME);
|
||||
}
|
||||
@ -161,8 +136,12 @@ public class DanfossAirUnit {
|
||||
return new StringType(Mode.values()[getByte(REGISTER_1_READ, MODE)].name());
|
||||
}
|
||||
|
||||
public PercentType getManualFanStep() throws IOException {
|
||||
return new PercentType(BigDecimal.valueOf(getByte(REGISTER_1_READ, MANUAL_FAN_SPEED_STEP) * 10));
|
||||
public PercentType getManualFanStep() throws IOException, UnexpectedResponseValueException {
|
||||
byte value = getByte(REGISTER_1_READ, MANUAL_FAN_SPEED_STEP);
|
||||
if (value < 0 || value > 10) {
|
||||
throw new UnexpectedResponseValueException(String.format("Invalid fan step: %d", value));
|
||||
}
|
||||
return new PercentType(BigDecimal.valueOf(value * 10));
|
||||
}
|
||||
|
||||
public DecimalType getSupplyFanSpeed() throws IOException {
|
||||
|
@ -30,12 +30,6 @@ public class DanfossAirUnitBindingConstants {
|
||||
|
||||
public static String BINDING_ID = "danfossairunit";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static ThingTypeUID THING_TYPE_SAMPLE = new ThingTypeUID(BINDING_ID, "sample");
|
||||
|
||||
// List of all Channel ids
|
||||
public static String CHANNEL_1 = "channel1";
|
||||
|
||||
// The only thing type UIDs
|
||||
public static ThingTypeUID THING_TYPE_AIRUNIT = new ThingTypeUID(BINDING_ID, "airunit");
|
||||
|
||||
|
@ -30,10 +30,13 @@ import org.slf4j.LoggerFactory;
|
||||
* The {@link DanfossAirUnitCommunicationController} class does the actual network communication with the air unit.
|
||||
*
|
||||
* @author Robert Bach - initial contribution
|
||||
* @author Jacob Laursen - Refactoring, bugfixes and enhancements
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class DanfossAirUnitCommunicationController {
|
||||
public class DanfossAirUnitCommunicationController implements CommunicationController {
|
||||
|
||||
private static final int SOCKET_TIMEOUT_MILLISECONDS = 5_000;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DanfossAirUnitCommunicationController.class);
|
||||
|
||||
@ -41,8 +44,8 @@ public class DanfossAirUnitCommunicationController {
|
||||
private final int port;
|
||||
private boolean connected = false;
|
||||
private @Nullable Socket socket;
|
||||
private @Nullable OutputStream oStream;
|
||||
private @Nullable InputStream iStream;
|
||||
private @Nullable OutputStream outputStream;
|
||||
private @Nullable InputStream inputStream;
|
||||
|
||||
public DanfossAirUnitCommunicationController(InetAddress inetAddr, int port) {
|
||||
this.inetAddr = inetAddr;
|
||||
@ -53,9 +56,11 @@ public class DanfossAirUnitCommunicationController {
|
||||
if (connected) {
|
||||
return;
|
||||
}
|
||||
socket = new Socket(inetAddr, port);
|
||||
oStream = socket.getOutputStream();
|
||||
iStream = socket.getInputStream();
|
||||
Socket localSocket = new Socket(inetAddr, port);
|
||||
localSocket.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
|
||||
this.outputStream = localSocket.getOutputStream();
|
||||
this.inputStream = localSocket.getInputStream();
|
||||
this.socket = localSocket;
|
||||
connected = true;
|
||||
}
|
||||
|
||||
@ -64,15 +69,16 @@ public class DanfossAirUnitCommunicationController {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
Socket localSocket = this.socket;
|
||||
if (localSocket != null) {
|
||||
localSocket.close();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Connection to air unit could not be closed gracefully. {}", ioe.getMessage());
|
||||
} finally {
|
||||
socket = null;
|
||||
iStream = null;
|
||||
oStream = null;
|
||||
this.socket = null;
|
||||
this.inputStream = null;
|
||||
this.outputStream = null;
|
||||
}
|
||||
connected = false;
|
||||
}
|
||||
@ -98,21 +104,27 @@ public class DanfossAirUnitCommunicationController {
|
||||
}
|
||||
|
||||
private synchronized byte[] sendRequestInternal(byte[] request) throws IOException {
|
||||
OutputStream localOutputStream = this.outputStream;
|
||||
|
||||
if (oStream == null) {
|
||||
if (localOutputStream == null) {
|
||||
throw new IOException(
|
||||
String.format("Output stream is null while sending request: %s", Arrays.toString(request)));
|
||||
}
|
||||
oStream.write(request);
|
||||
oStream.flush();
|
||||
localOutputStream.write(request);
|
||||
localOutputStream.flush();
|
||||
|
||||
byte[] result = new byte[63];
|
||||
if (iStream == null) {
|
||||
InputStream localInputStream = this.inputStream;
|
||||
if (localInputStream == null) {
|
||||
throw new IOException(
|
||||
String.format("Input stream is null while sending request: %s", Arrays.toString(request)));
|
||||
}
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
iStream.read(result, 0, 63);
|
||||
|
||||
int bytesRead = localInputStream.read(result, 0, 63);
|
||||
if (bytesRead < 63) {
|
||||
throw new IOException(String.format(
|
||||
"Error reading from stream, read returned %d as number of bytes read into the buffer", bytesRead));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ public class ValueCache {
|
||||
return writeToCache;
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
private static class StateWithTimestamp {
|
||||
State state;
|
||||
long timestamp;
|
||||
|
@ -51,11 +51,12 @@ public class DanfossAirUnitDiscoveryService extends AbstractDiscoveryService {
|
||||
private static final int BROADCAST_PORT = 30045;
|
||||
private static final byte[] DISCOVER_SEND = { 0x0c, 0x00, 0x30, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13 };
|
||||
private static final byte[] DISCOVER_RECEIVE = { 0x0d, 0x00, 0x07, 0x00, 0x02, 0x02, 0x00 };
|
||||
private static final int TIMEOUT_IN_SECONDS = 15;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DanfossAirUnitDiscoveryService.class);
|
||||
|
||||
public DanfossAirUnitDiscoveryService() {
|
||||
super(SUPPORTED_THING_TYPES_UIDS, 15, true);
|
||||
super(SUPPORTED_THING_TYPES_UIDS, TIMEOUT_IN_SECONDS, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -40,15 +40,19 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* @author Ralf Duckstein - Initial contribution
|
||||
* @author Robert Bach - heavy refactorings
|
||||
* @author Jacob Laursen - Refactoring, bugfixes and enhancements
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DanfossAirUnitHandler extends BaseThingHandler {
|
||||
|
||||
private static final int TCP_PORT = 30046;
|
||||
private static final int POLLING_INTERVAL_SECONDS = 5;
|
||||
private final Logger logger = LoggerFactory.getLogger(DanfossAirUnitHandler.class);
|
||||
private @NonNullByDefault({}) DanfossAirUnitConfiguration config;
|
||||
private @Nullable ValueCache valueCache;
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
private @Nullable DanfossAirUnit hrv;
|
||||
private @Nullable DanfossAirUnitCommunicationController communicationController;
|
||||
private @Nullable DanfossAirUnit airUnit;
|
||||
|
||||
public DanfossAirUnitHandler(Thing thing) {
|
||||
super(thing);
|
||||
@ -60,12 +64,12 @@ public class DanfossAirUnitHandler extends BaseThingHandler {
|
||||
updateAllChannels();
|
||||
} else {
|
||||
try {
|
||||
DanfossAirUnit danfossAirUnit = hrv;
|
||||
if (danfossAirUnit != null) {
|
||||
DanfossAirUnit localAirUnit = this.airUnit;
|
||||
if (localAirUnit != null) {
|
||||
Channel channel = Channel.getByName(channelUID.getIdWithoutGroup());
|
||||
DanfossAirUnitWriteAccessor writeAccessor = channel.getWriteAccessor();
|
||||
if (writeAccessor != null) {
|
||||
updateState(channelUID, writeAccessor.access(danfossAirUnit, command));
|
||||
updateState(channelUID, writeAccessor.access(localAirUnit, command));
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE,
|
||||
@ -86,14 +90,16 @@ public class DanfossAirUnitHandler extends BaseThingHandler {
|
||||
config = getConfigAs(DanfossAirUnitConfiguration.class);
|
||||
valueCache = new ValueCache(config.updateUnchangedValuesEveryMillis);
|
||||
try {
|
||||
hrv = new DanfossAirUnit(InetAddress.getByName(config.host), 30046);
|
||||
DanfossAirUnit danfossAirUnit = hrv;
|
||||
var localCommunicationController = new DanfossAirUnitCommunicationController(
|
||||
InetAddress.getByName(config.host), TCP_PORT);
|
||||
this.communicationController = localCommunicationController;
|
||||
var localAirUnit = new DanfossAirUnit(localCommunicationController);
|
||||
this.airUnit = localAirUnit;
|
||||
scheduler.execute(() -> {
|
||||
try {
|
||||
thing.setProperty(PROPERTY_UNIT_NAME, danfossAirUnit.getUnitName());
|
||||
thing.setProperty(PROPERTY_SERIAL, danfossAirUnit.getUnitSerialNumber());
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::updateAllChannels, 5, config.refreshInterval,
|
||||
TimeUnit.SECONDS);
|
||||
thing.setProperty(PROPERTY_UNIT_NAME, localAirUnit.getUnitName());
|
||||
thing.setProperty(PROPERTY_SERIAL, localAirUnit.getUnitSerialNumber());
|
||||
startPolling();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
||||
@ -107,33 +113,37 @@ public class DanfossAirUnitHandler extends BaseThingHandler {
|
||||
}
|
||||
|
||||
private void updateAllChannels() {
|
||||
DanfossAirUnit danfossAirUnit = hrv;
|
||||
if (danfossAirUnit != null) {
|
||||
logger.debug("Updating DanfossHRV data '{}'", getThing().getUID());
|
||||
DanfossAirUnit localAirUnit = this.airUnit;
|
||||
if (localAirUnit == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Channel channel : Channel.values()) {
|
||||
if (Thread.interrupted()) {
|
||||
logger.debug("Polling thread interrupted...");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
updateState(channel.getGroup().getGroupName(), channel.getChannelName(),
|
||||
channel.getReadAccessor().access(danfossAirUnit));
|
||||
} catch (UnexpectedResponseValueException e) {
|
||||
updateState(channel.getGroup().getGroupName(), channel.getChannelName(), UnDefType.UNDEF);
|
||||
logger.debug(
|
||||
"Cannot update channel {}: an unexpected or invalid response has been received from the air unit: {}",
|
||||
channel.getChannelName(), e.getMessage());
|
||||
} catch (IOException e) {
|
||||
updateState(channel.getGroup().getGroupName(), channel.getChannelName(), UnDefType.UNDEF);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
||||
logger.debug("Cannot update channel {}: an error occurred retrieving the value: {}",
|
||||
channel.getChannelName(), e.getMessage());
|
||||
}
|
||||
logger.debug("Updating DanfossHRV data '{}'", getThing().getUID());
|
||||
|
||||
for (Channel channel : Channel.values()) {
|
||||
if (Thread.interrupted()) {
|
||||
logger.debug("Polling thread interrupted...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (getThing().getStatus() == ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
try {
|
||||
updateState(channel.getGroup().getGroupName(), channel.getChannelName(),
|
||||
channel.getReadAccessor().access(localAirUnit));
|
||||
if (getThing().getStatus() == ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (UnexpectedResponseValueException e) {
|
||||
updateState(channel.getGroup().getGroupName(), channel.getChannelName(), UnDefType.UNDEF);
|
||||
logger.debug(
|
||||
"Cannot update channel {}: an unexpected or invalid response has been received from the air unit: {}",
|
||||
channel.getChannelName(), e.getMessage());
|
||||
if (getThing().getStatus() == ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
updateState(channel.getGroup().getGroupName(), channel.getChannelName(), UnDefType.UNDEF);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
||||
logger.debug("Cannot update channel {}: an error occurred retrieving the value: {}",
|
||||
channel.getChannelName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -142,19 +152,37 @@ public class DanfossAirUnitHandler extends BaseThingHandler {
|
||||
public void dispose() {
|
||||
logger.debug("Disposing Danfoss HRV handler '{}'", getThing().getUID());
|
||||
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
stopPolling();
|
||||
|
||||
if (hrv != null) {
|
||||
hrv.cleanUp();
|
||||
hrv = null;
|
||||
this.airUnit = null;
|
||||
|
||||
DanfossAirUnitCommunicationController localCommunicationController = this.communicationController;
|
||||
if (localCommunicationController != null) {
|
||||
localCommunicationController.disconnect();
|
||||
}
|
||||
this.communicationController = null;
|
||||
}
|
||||
|
||||
private synchronized void startPolling() {
|
||||
this.pollingJob = scheduler.scheduleWithFixedDelay(this::updateAllChannels, POLLING_INTERVAL_SECONDS,
|
||||
config.refreshInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private synchronized void stopPolling() {
|
||||
ScheduledFuture<?> localPollingJob = this.pollingJob;
|
||||
if (localPollingJob != null) {
|
||||
localPollingJob.cancel(true);
|
||||
}
|
||||
this.pollingJob = null;
|
||||
}
|
||||
|
||||
private void updateState(String groupId, String channelId, State state) {
|
||||
if (valueCache.updateValue(channelId, state)) {
|
||||
ValueCache cache = valueCache;
|
||||
if (cache == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cache.updateValue(channelId, state)) {
|
||||
updateState(new ChannelUID(thing.getUID(), groupId, channelId), state);
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.danfossairunit.internal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.openhab.binding.danfossairunit.internal.Commands.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.test.java.JavaTest;
|
||||
|
||||
/**
|
||||
* This class provides test cases for {@link DanfossAirUnit}
|
||||
*
|
||||
* @author Jacob Laursen - Refactoring, bugfixes and enhancements
|
||||
*/
|
||||
public class DanfossAirUnitTest extends JavaTest {
|
||||
|
||||
private CommunicationController communicationController;
|
||||
|
||||
@BeforeEach
|
||||
private void setUp() {
|
||||
this.communicationController = mock(CommunicationController.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUnitNameIsReturned() throws IOException {
|
||||
byte[] response = new byte[] { (byte) 0x05, (byte) 'w', (byte) '2', (byte) '/', (byte) 'a', (byte) '2' };
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_1_READ, UNIT_NAME)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertEquals("w2/a2", airUnit.getUnitName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getHumidityWhenNearestNeighborIsBelowRoundsDown() throws IOException {
|
||||
byte[] response = new byte[] { (byte) 0x64 };
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_1_READ, HUMIDITY)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertEquals(new QuantityType<>("39.2 %"), airUnit.getHumidity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getHumidityWhenNearestNeighborIsAboveRoundsUp() throws IOException {
|
||||
byte[] response = new byte[] { (byte) 0x67 };
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_1_READ, HUMIDITY)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertEquals(new QuantityType<>("40.4 %"), airUnit.getHumidity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSupplyTemperatureWhenNearestNeighborIsBelowRoundsDown()
|
||||
throws IOException, UnexpectedResponseValueException {
|
||||
byte[] response = new byte[] { (byte) 0x09, (byte) 0xf0 }; // 0x09f0 = 2544 => 25.44
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_4_READ, SUPPLY_TEMPERATURE)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertEquals(new QuantityType<>("25.4 °C"), airUnit.getSupplyTemperature());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSupplyTemperatureWhenBothNeighborsAreEquidistantRoundsUp()
|
||||
throws IOException, UnexpectedResponseValueException {
|
||||
byte[] response = new byte[] { (byte) 0x09, (byte) 0xf1 }; // 0x09f1 = 2545 => 25.45
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_4_READ, SUPPLY_TEMPERATURE)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertEquals(new QuantityType<>("25.5 °C"), airUnit.getSupplyTemperature());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSupplyTemperatureWhenBelowValidRangeThrows() throws IOException {
|
||||
byte[] response = new byte[] { (byte) 0x94, (byte) 0xf8 }; // 0x94f8 = -27400 => -274
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_4_READ, SUPPLY_TEMPERATURE)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertThrows(UnexpectedResponseValueException.class, () -> airUnit.getSupplyTemperature());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSupplyTemperatureWhenAboveValidRangeThrows() throws IOException {
|
||||
byte[] response = new byte[] { (byte) 0x27, (byte) 0x11 }; // 0x2711 = 10001 => 100,01
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_4_READ, SUPPLY_TEMPERATURE)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertThrows(UnexpectedResponseValueException.class, () -> airUnit.getSupplyTemperature());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentTimeWhenWellFormattedIsParsed() throws IOException, UnexpectedResponseValueException {
|
||||
byte[] response = new byte[] { (byte) 0x03, (byte) 0x02, (byte) 0x0f, (byte) 0x1d, (byte) 0x08, (byte) 0x15 }; // 29.08.21
|
||||
// 15:02:03
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_1_READ, CURRENT_TIME)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertEquals(new DateTimeType(ZonedDateTime.of(2021, 8, 29, 15, 2, 3, 0, ZoneId.systemDefault())),
|
||||
airUnit.getCurrentTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentTimeWhenInvalidDateThrows() throws IOException {
|
||||
byte[] response = new byte[] { (byte) 0x03, (byte) 0x02, (byte) 0x0f, (byte) 0x20, (byte) 0x08, (byte) 0x15 }; // 32.08.21
|
||||
// 15:02:03
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_1_READ, CURRENT_TIME)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertThrows(UnexpectedResponseValueException.class, () -> airUnit.getCurrentTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBoostWhenZeroIsOff() throws IOException {
|
||||
byte[] response = new byte[] { (byte) 0x00 };
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_1_READ, BOOST)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertEquals(OnOffType.OFF, airUnit.getBoost());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBoostWhenNonZeroIsOn() throws IOException {
|
||||
byte[] response = new byte[] { (byte) 0x66 };
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_1_READ, BOOST)).thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertEquals(OnOffType.ON, airUnit.getBoost());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getManualFanStepWhenWithinValidRangeIsConvertedIntoPercent()
|
||||
throws IOException, UnexpectedResponseValueException {
|
||||
byte[] response = new byte[] { (byte) 0x05 };
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_1_READ, MANUAL_FAN_SPEED_STEP))
|
||||
.thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertEquals(new PercentType(50), airUnit.getManualFanStep());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getManualFanStepWhenOutOfRangeThrows() throws IOException {
|
||||
byte[] response = new byte[] { (byte) 0x0b };
|
||||
when(this.communicationController.sendRobustRequest(REGISTER_1_READ, MANUAL_FAN_SPEED_STEP))
|
||||
.thenReturn(response);
|
||||
var airUnit = new DanfossAirUnit(communicationController);
|
||||
assertThrows(UnexpectedResponseValueException.class, () -> airUnit.getManualFanStep());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user