2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.danfossairunit.internal;
15 import static org.openhab.binding.danfossairunit.internal.Commands.*;
17 import java.io.IOException;
18 import java.math.BigDecimal;
19 import java.math.RoundingMode;
20 import java.net.InetAddress;
21 import java.nio.charset.StandardCharsets;
22 import java.time.DateTimeException;
23 import java.time.ZoneId;
24 import java.time.ZonedDateTime;
26 import javax.measure.quantity.Temperature;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.openhab.core.library.types.DateTimeType;
30 import org.openhab.core.library.types.DecimalType;
31 import org.openhab.core.library.types.OnOffType;
32 import org.openhab.core.library.types.PercentType;
33 import org.openhab.core.library.types.QuantityType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.library.unit.SIUnits;
36 import org.openhab.core.types.Command;
39 * The {@link DanfossAirUnit} class represents the air unit device and build the commands to be sent by
40 * {@link DanfossAirUnitCommunicationController}
42 * @author Ralf Duckstein - Initial contribution
43 * @author Robert Bach - heavy refactorings
46 @SuppressWarnings("SameParameterValue")
48 public class DanfossAirUnit {
50 private final DanfossAirUnitCommunicationController communicationController;
52 public DanfossAirUnit(InetAddress inetAddr, int port) {
53 this.communicationController = new DanfossAirUnitCommunicationController(inetAddr, port);
56 public void cleanUp() {
57 this.communicationController.disconnect();
60 private boolean getBoolean(byte[] operation, byte[] register) throws IOException {
61 return communicationController.sendRobustRequest(operation, register)[0] != 0;
64 private void setSetting(byte[] register, boolean value) throws IOException {
65 setSetting(register, value ? (byte) 1 : (byte) 0);
68 private short getWord(byte[] operation, byte[] register) throws IOException {
69 byte[] resultBytes = communicationController.sendRobustRequest(operation, register);
70 return (short) ((resultBytes[0] << 8) | (resultBytes[1] & 0xFF));
73 private byte getByte(byte[] operation, byte[] register) throws IOException {
74 return communicationController.sendRobustRequest(operation, register)[0];
77 private String getString(byte[] operation, byte[] register) throws IOException {
78 // length of the string is stored in the first byte
79 byte[] result = communicationController.sendRobustRequest(operation, register);
80 return new String(result, 1, result[0], StandardCharsets.US_ASCII);
83 private void set(byte[] operation, byte[] register, byte value) throws IOException {
84 byte[] valueArray = { value };
85 communicationController.sendRobustRequest(operation, register, valueArray);
88 private void set(byte[] operation, byte[] register, short value) throws IOException {
89 communicationController.sendRobustRequest(operation, register, shortToBytes(value));
92 private byte[] shortToBytes(short s) {
93 return new byte[] { (byte) ((s & 0xFF00) >> 8), (byte) (s & 0x00FF) };
96 private short getShort(byte[] operation, byte[] register) throws IOException {
97 byte[] result = communicationController.sendRobustRequest(operation, register);
98 return (short) ((result[0] << 8) + (result[1] & 0xff));
101 private float getTemperature(byte[] operation, byte[] register)
102 throws IOException, UnexpectedResponseValueException {
103 short shortTemp = getShort(operation, register);
104 float temp = ((float) shortTemp) / 100;
105 if (temp <= -274 || temp > 100) {
106 throw new UnexpectedResponseValueException(String.format("Invalid temperature: %s", temp));
111 private ZonedDateTime getTimestamp(byte[] operation, byte[] register)
112 throws IOException, UnexpectedResponseValueException {
113 byte[] result = communicationController.sendRobustRequest(operation, register);
114 return asZonedDateTime(result);
117 private ZonedDateTime asZonedDateTime(byte[] data) throws UnexpectedResponseValueException {
118 int second = data[0];
119 int minute = data[1];
120 int hour = data[2] & 0x1f;
121 int day = data[3] & 0x1f;
123 int year = data[5] + 2000;
125 return ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.systemDefault());
126 } catch (DateTimeException e) {
127 String msg = String.format("Ignoring invalid timestamp %s.%s.%s %s:%s:%s", day, month, year, hour, minute,
129 throw new UnexpectedResponseValueException(msg, e);
133 private static int asUnsignedByte(byte b) {
137 private static float asPercentByte(byte b) {
138 float f = asUnsignedByte(b);
139 return f * 100 / 255;
142 private void setSetting(byte[] register, short value) throws IOException {
143 byte[] valueArray = new byte[2];
144 valueArray[0] = (byte) (value >> 8);
145 valueArray[1] = (byte) value;
147 communicationController.sendRobustRequest(REGISTER_1_WRITE, register, valueArray);
150 public String getUnitName() throws IOException {
151 return getString(REGISTER_1_READ, UNIT_NAME);
154 public String getUnitSerialNumber() throws IOException {
155 return String.valueOf(getShort(REGISTER_4_READ, UNIT_SERIAL));
158 public StringType getMode() throws IOException {
159 return new StringType(Mode.values()[getByte(REGISTER_1_READ, MODE)].name());
162 public PercentType getManualFanSpeed() throws IOException {
163 return new PercentType(BigDecimal.valueOf(getByte(REGISTER_1_READ, MANUAL_FAN_SPEED_STEP) * 10));
166 public DecimalType getSupplyFanSpeed() throws IOException {
167 return new DecimalType(BigDecimal.valueOf(getWord(REGISTER_4_READ, SUPPLY_FAN_SPEED)));
170 public DecimalType getExtractFanSpeed() throws IOException {
171 return new DecimalType(BigDecimal.valueOf(getWord(REGISTER_4_READ, EXTRACT_FAN_SPEED)));
174 public PercentType getSupplyFanStep() throws IOException {
175 return new PercentType(BigDecimal.valueOf(getByte(REGISTER_4_READ, SUPPLY_FAN_STEP)));
178 public PercentType getExtractFanStep() throws IOException {
179 return new PercentType(BigDecimal.valueOf(getByte(REGISTER_4_READ, EXTRACT_FAN_STEP)));
182 public OnOffType getBoost() throws IOException {
183 return getBoolean(REGISTER_1_READ, BOOST) ? OnOffType.ON : OnOffType.OFF;
186 public OnOffType getNightCooling() throws IOException {
187 return getBoolean(REGISTER_1_READ, NIGHT_COOLING) ? OnOffType.ON : OnOffType.OFF;
190 public OnOffType getBypass() throws IOException {
191 return getBoolean(REGISTER_1_READ, BYPASS) ? OnOffType.ON : OnOffType.OFF;
194 public DecimalType getHumidity() throws IOException {
195 BigDecimal value = BigDecimal.valueOf(asPercentByte(getByte(REGISTER_1_READ, HUMIDITY)));
196 return new DecimalType(value.setScale(1, RoundingMode.HALF_UP));
199 public QuantityType<Temperature> getRoomTemperature() throws IOException, UnexpectedResponseValueException {
200 return getTemperatureAsDecimalType(REGISTER_1_READ, ROOM_TEMPERATURE);
203 public QuantityType<Temperature> getRoomTemperatureCalculated()
204 throws IOException, UnexpectedResponseValueException {
205 return getTemperatureAsDecimalType(REGISTER_0_READ, ROOM_TEMPERATURE_CALCULATED);
208 public QuantityType<Temperature> getOutdoorTemperature() throws IOException, UnexpectedResponseValueException {
209 return getTemperatureAsDecimalType(REGISTER_1_READ, OUTDOOR_TEMPERATURE);
212 public QuantityType<Temperature> getSupplyTemperature() throws IOException, UnexpectedResponseValueException {
213 return getTemperatureAsDecimalType(REGISTER_4_READ, SUPPLY_TEMPERATURE);
216 public QuantityType<Temperature> getExtractTemperature() throws IOException, UnexpectedResponseValueException {
217 return getTemperatureAsDecimalType(REGISTER_4_READ, EXTRACT_TEMPERATURE);
220 public QuantityType<Temperature> getExhaustTemperature() throws IOException, UnexpectedResponseValueException {
221 return getTemperatureAsDecimalType(REGISTER_4_READ, EXHAUST_TEMPERATURE);
224 private QuantityType<Temperature> getTemperatureAsDecimalType(byte[] operation, byte[] register)
225 throws IOException, UnexpectedResponseValueException {
226 BigDecimal value = BigDecimal.valueOf(getTemperature(operation, register));
227 return new QuantityType<>(value.setScale(1, RoundingMode.HALF_UP), SIUnits.CELSIUS);
230 public DecimalType getBatteryLife() throws IOException {
231 return new DecimalType(BigDecimal.valueOf(asUnsignedByte(getByte(REGISTER_1_READ, BATTERY_LIFE))));
234 public DecimalType getFilterLife() throws IOException {
235 return new DecimalType(BigDecimal.valueOf(asPercentByte(getByte(REGISTER_1_READ, FILTER_LIFE))));
238 public DateTimeType getCurrentTime() throws IOException, UnexpectedResponseValueException {
239 ZonedDateTime timestamp = getTimestamp(REGISTER_1_READ, CURRENT_TIME);
240 return new DateTimeType(timestamp);
243 public PercentType setManualFanSpeed(Command cmd) throws IOException {
244 return setPercentTypeRegister(cmd, MANUAL_FAN_SPEED_STEP);
247 private PercentType setPercentTypeRegister(Command cmd, byte[] register) throws IOException {
248 if (cmd instanceof PercentType) {
249 byte value = (byte) ((((PercentType) cmd).intValue() + 5) / 10);
250 set(REGISTER_1_WRITE, register, value);
252 return new PercentType(BigDecimal.valueOf(getByte(REGISTER_1_READ, register) * 10));
255 private OnOffType setOnOffTypeRegister(Command cmd, byte[] register) throws IOException {
256 if (cmd instanceof OnOffType) {
257 set(REGISTER_1_WRITE, register, OnOffType.ON.equals(cmd) ? (byte) 1 : (byte) 0);
259 return getBoolean(REGISTER_1_READ, register) ? OnOffType.ON : OnOffType.OFF;
262 private StringType setStringTypeRegister(Command cmd, byte[] register) throws IOException {
263 if (cmd instanceof StringType) {
264 byte value = (byte) (Mode.valueOf(cmd.toString()).ordinal());
265 set(REGISTER_1_WRITE, register, value);
268 return new StringType(Mode.values()[getByte(REGISTER_1_READ, register)].name());
271 public StringType setMode(Command cmd) throws IOException {
272 return setStringTypeRegister(cmd, MODE);
275 public OnOffType setBoost(Command cmd) throws IOException {
276 return setOnOffTypeRegister(cmd, BOOST);
279 public OnOffType setNightCooling(Command cmd) throws IOException {
280 return setOnOffTypeRegister(cmd, NIGHT_COOLING);
283 public OnOffType setBypass(Command cmd) throws IOException {
284 return setOnOffTypeRegister(cmd, BYPASS);