2 * Copyright (c) 2010-2023 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.nio.charset.StandardCharsets;
21 import java.time.DateTimeException;
22 import java.time.ZoneId;
23 import java.time.ZonedDateTime;
25 import javax.measure.quantity.Dimensionless;
26 import javax.measure.quantity.Frequency;
27 import javax.measure.quantity.Temperature;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.openhab.core.library.types.DateTimeType;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.OnOffType;
33 import org.openhab.core.library.types.PercentType;
34 import org.openhab.core.library.types.QuantityType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.library.unit.SIUnits;
37 import org.openhab.core.library.unit.Units;
38 import org.openhab.core.types.Command;
41 * The {@link DanfossAirUnit} class represents the air unit device and build the commands to be sent by
42 * {@link DanfossAirUnitCommunicationController}
44 * @author Ralf Duckstein - Initial contribution
45 * @author Robert Bach - heavy refactorings
46 * @author Jacob Laursen - Refactoring, bugfixes and enhancements
50 public class DanfossAirUnit {
52 private final CommunicationController communicationController;
54 public DanfossAirUnit(CommunicationController communicationController) {
55 this.communicationController = communicationController;
58 private boolean getBoolean(byte[] operation, byte[] register) throws IOException {
59 return communicationController.sendRobustRequest(operation, register)[0] != 0;
62 private short getWord(byte[] operation, byte[] register) throws IOException {
63 byte[] resultBytes = communicationController.sendRobustRequest(operation, register);
64 return (short) ((resultBytes[0] << 8) | (resultBytes[1] & 0xFF));
67 private byte getByte(byte[] operation, byte[] register) throws IOException {
68 return communicationController.sendRobustRequest(operation, register)[0];
71 private String getString(byte[] operation, byte[] register) throws IOException {
72 // length of the string is stored in the first byte
73 byte[] result = communicationController.sendRobustRequest(operation, register);
74 return new String(result, 1, result[0], StandardCharsets.US_ASCII);
77 private void set(byte[] operation, byte[] register, byte value) throws IOException {
78 byte[] valueArray = { value };
79 communicationController.sendRobustRequest(operation, register, valueArray);
82 private short getShort(byte[] operation, byte[] register) throws IOException {
83 byte[] result = communicationController.sendRobustRequest(operation, register);
84 return (short) ((result[0] << 8) + (result[1] & 0xff));
87 private float getTemperature(byte[] operation, byte[] register)
88 throws IOException, UnexpectedResponseValueException {
89 short shortTemp = getShort(operation, register);
90 float temp = ((float) shortTemp) / 100;
91 if (temp <= -274 || temp > 100) {
92 throw new UnexpectedResponseValueException(String.format("Invalid temperature: %s", temp));
97 private ZonedDateTime getTimestamp(byte[] operation, byte[] register)
98 throws IOException, UnexpectedResponseValueException {
99 byte[] result = communicationController.sendRobustRequest(operation, register);
100 return asZonedDateTime(result);
103 private ZonedDateTime asZonedDateTime(byte[] data) throws UnexpectedResponseValueException {
104 int second = data[0];
105 int minute = data[1];
106 int hour = data[2] & 0x1f;
107 int day = data[3] & 0x1f;
109 int year = data[5] + 2000;
111 return ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.systemDefault());
112 } catch (DateTimeException e) {
113 String msg = String.format("Ignoring invalid timestamp %s.%s.%s %s:%s:%s", day, month, year, hour, minute,
115 throw new UnexpectedResponseValueException(msg, e);
119 private static int asUnsignedByte(byte b) {
123 private static float asPercentByte(byte b) {
124 float f = asUnsignedByte(b);
125 return f * 100 / 255;
128 public String getUnitName() throws IOException {
129 return getString(REGISTER_1_READ, UNIT_NAME);
132 public String getUnitSerialNumber() throws IOException {
133 return String.valueOf(getShort(REGISTER_4_READ, UNIT_SERIAL));
136 public StringType getMode() throws IOException {
137 return new StringType(Mode.values()[getByte(REGISTER_1_READ, MODE)].name());
140 public PercentType getManualFanStep() throws IOException, UnexpectedResponseValueException {
141 byte value = getByte(REGISTER_1_READ, MANUAL_FAN_SPEED_STEP);
142 if (value < 0 || value > 10) {
143 throw new UnexpectedResponseValueException(String.format("Invalid fan step: %d", value));
145 return new PercentType(BigDecimal.valueOf(value * 10));
148 public QuantityType<Frequency> getSupplyFanSpeed() throws IOException {
149 return new QuantityType<>(BigDecimal.valueOf(getWord(REGISTER_4_READ, SUPPLY_FAN_SPEED)), Units.RPM);
152 public QuantityType<Frequency> getExtractFanSpeed() throws IOException {
153 return new QuantityType<>(BigDecimal.valueOf(getWord(REGISTER_4_READ, EXTRACT_FAN_SPEED)), Units.RPM);
156 public PercentType getSupplyFanStep() throws IOException {
157 return new PercentType(BigDecimal.valueOf(getByte(REGISTER_4_READ, SUPPLY_FAN_STEP)));
160 public PercentType getExtractFanStep() throws IOException {
161 return new PercentType(BigDecimal.valueOf(getByte(REGISTER_4_READ, EXTRACT_FAN_STEP)));
164 public OnOffType getBoost() throws IOException {
165 return getBoolean(REGISTER_1_READ, BOOST) ? OnOffType.ON : OnOffType.OFF;
168 public OnOffType getNightCooling() throws IOException {
169 return getBoolean(REGISTER_1_READ, NIGHT_COOLING) ? OnOffType.ON : OnOffType.OFF;
172 public OnOffType getBypass() throws IOException {
173 return getBoolean(REGISTER_1_READ, BYPASS) ? OnOffType.ON : OnOffType.OFF;
176 public QuantityType<Dimensionless> getHumidity() throws IOException {
177 BigDecimal value = BigDecimal.valueOf(asPercentByte(getByte(REGISTER_1_READ, HUMIDITY)));
178 return new QuantityType<>(value.setScale(1, RoundingMode.HALF_UP), Units.PERCENT);
181 public QuantityType<Temperature> getRoomTemperature() throws IOException, UnexpectedResponseValueException {
182 return getTemperatureAsDecimalType(REGISTER_1_READ, ROOM_TEMPERATURE);
185 public QuantityType<Temperature> getRoomTemperatureCalculated()
186 throws IOException, UnexpectedResponseValueException {
187 return getTemperatureAsDecimalType(REGISTER_0_READ, ROOM_TEMPERATURE_CALCULATED);
190 public QuantityType<Temperature> getOutdoorTemperature() throws IOException, UnexpectedResponseValueException {
191 return getTemperatureAsDecimalType(REGISTER_1_READ, OUTDOOR_TEMPERATURE);
194 public QuantityType<Temperature> getSupplyTemperature() throws IOException, UnexpectedResponseValueException {
195 return getTemperatureAsDecimalType(REGISTER_4_READ, SUPPLY_TEMPERATURE);
198 public QuantityType<Temperature> getExtractTemperature() throws IOException, UnexpectedResponseValueException {
199 return getTemperatureAsDecimalType(REGISTER_4_READ, EXTRACT_TEMPERATURE);
202 public QuantityType<Temperature> getExhaustTemperature() throws IOException, UnexpectedResponseValueException {
203 return getTemperatureAsDecimalType(REGISTER_4_READ, EXHAUST_TEMPERATURE);
206 private QuantityType<Temperature> getTemperatureAsDecimalType(byte[] operation, byte[] register)
207 throws IOException, UnexpectedResponseValueException {
208 BigDecimal value = BigDecimal.valueOf(getTemperature(operation, register));
209 return new QuantityType<>(value.setScale(1, RoundingMode.HALF_UP), SIUnits.CELSIUS);
212 public DecimalType getBatteryLife() throws IOException {
213 return new DecimalType(BigDecimal.valueOf(asUnsignedByte(getByte(REGISTER_1_READ, BATTERY_LIFE))));
216 public DecimalType getFilterLife() throws IOException {
217 BigDecimal value = BigDecimal.valueOf(asPercentByte(getByte(REGISTER_1_READ, FILTER_LIFE)));
218 return new DecimalType(value.setScale(1, RoundingMode.HALF_UP));
221 public DecimalType getFilterPeriod() throws IOException {
222 return new DecimalType(BigDecimal.valueOf(getByte(REGISTER_1_READ, FILTER_PERIOD)));
225 public DecimalType setFilterPeriod(Command cmd) throws IOException {
226 return setNumberTypeRegister(cmd, FILTER_PERIOD);
229 public DateTimeType getCurrentTime() throws IOException, UnexpectedResponseValueException {
230 ZonedDateTime timestamp = getTimestamp(REGISTER_1_READ, CURRENT_TIME);
231 return new DateTimeType(timestamp);
234 public PercentType setManualFanStep(Command cmd) throws IOException {
235 return setPercentTypeRegister(cmd, MANUAL_FAN_SPEED_STEP);
238 private DecimalType setNumberTypeRegister(Command cmd, byte[] register) throws IOException {
239 if (cmd instanceof DecimalType) {
240 byte value = (byte) ((DecimalType) cmd).intValue();
241 set(REGISTER_1_WRITE, register, value);
243 return new DecimalType(BigDecimal.valueOf(getByte(REGISTER_1_READ, register)));
246 private PercentType setPercentTypeRegister(Command cmd, byte[] register) throws IOException {
247 if (cmd instanceof PercentType) {
248 byte value = (byte) ((((PercentType) cmd).intValue() + 5) / 10);
249 set(REGISTER_1_WRITE, register, value);
251 return new PercentType(BigDecimal.valueOf(getByte(REGISTER_1_READ, register) * 10));
254 private OnOffType setOnOffTypeRegister(Command cmd, byte[] register) throws IOException {
255 if (cmd instanceof OnOffType) {
256 set(REGISTER_1_WRITE, register, OnOffType.ON.equals(cmd) ? (byte) 1 : (byte) 0);
258 return getBoolean(REGISTER_1_READ, register) ? OnOffType.ON : OnOffType.OFF;
261 private StringType setStringTypeRegister(Command cmd, byte[] register) throws IOException {
262 if (cmd instanceof StringType) {
263 byte value = (byte) (Mode.valueOf(cmd.toString()).ordinal());
264 set(REGISTER_1_WRITE, register, value);
267 return new StringType(Mode.values()[getByte(REGISTER_1_READ, register)].name());
270 public StringType setMode(Command cmd) throws IOException {
271 return setStringTypeRegister(cmd, MODE);
274 public OnOffType setBoost(Command cmd) throws IOException {
275 return setOnOffTypeRegister(cmd, BOOST);
278 public OnOffType setNightCooling(Command cmd) throws IOException {
279 return setOnOffTypeRegister(cmd, NIGHT_COOLING);
282 public OnOffType setBypass(Command cmd) throws IOException {
283 return setOnOffTypeRegister(cmd, BYPASS);