* Add filter period channel.
Fixes #11310
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
* Round remaining filter life percentage to one decimal.
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
* Improve and extend example configuration.
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
* Mark filter period channel as advanced due to Link CC/Air Dial conflicts.
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
* Add comment about value getting overwritten.
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
* Remove redundant parentheses.
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
* Fix SCA report: First javadoc author should have 'Initial contribution' contribution description.
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
* Fix SCA issue: NoEmptyLineSeparatorCheck
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
| exhaust_temp | recuperator | Number | RO | Temperature of the air when pushed outside |
| battery_life | service | Number | RO | Remaining Air Dial Battery Level (percentage) |
| filter_life | service | Number | RO | Remaining life of filter until exchange is necessary (percentage) |
+| filter_period | service | Number | RW | Number of months between filter replacements (between 3 and 12). This value affects calculation of filter_life by the unit, and might get overwritten by Air Dial or Link CC Controller. |
## Full Example
### Items
```
-Dimmer Lueftung_Drehzahl_Manuell "Drehzahl Lüftung %" (All,Lueftung) {channel = "danfossairunit:airunit:myairunit:main#manual_fan_speed"}
-Number Lueftung_Drehzahl_Supply "Drehzahl Lüftung Zuluft (rpm)" (All,Lueftung) {channel = "danfossairunit:airunit:myairunit:main#supply_fan_speed"}
-Number Lueftung_Drehzahl_Extract "Drehzahl Lüftung Abluft (rpm)" (All,Lueftung) {channel = "danfossairunit:airunit:myairunit:main#extract_fan_speed"}
-String Lueftung_Mode "Betriebsart Lüftung" (All,Lueftung) {channel = "danfossairunit:airunit:myairunit:main#mode"}
-Switch Lueftung_Boost "Stoßlüftung" (All,Lueftung) {channel = "danfossairunit:airunit:myairunit:main#boost"}
-Switch Lueftung_Bypass "Lüftung Bypass" (All,Lueftung) {channel = "danfossairunit:airunit:myairunit:recuperator#bypass"}
+Dimmer DanfossHRV_ManualFanStep "Manual Fan Step [%s]" {channel = "danfossairunit:airunit:myairunit:main#manual_fan_step"}
+Number DanfossHRV_SupplyFanSpeed "Supply Fan Speed" {channel = "danfossairunit:airunit:myairunit:main#supply_fan_speed"}
+Number DanfossHRV_ExtractFanSpeed "Extract Fan Speed" {channel = "danfossairunit:airunit:myairunit:main#extract_fan_speed"}
+String DanfossHRV_Mode "Operation Mode" {channel = "danfossairunit:airunit:myairunit:main#mode"}
+Switch DanfossHRV_Boost "Boost" {channel = "danfossairunit:airunit:myairunit:main#boost"}
+Switch DanfossHRV_Bypass "Bypass" {channel = "danfossairunit:airunit:myairunit:recuperator#bypass"}
+Number:Dimensionless DanfossHRV_Humidity "Relative humidity" <humidity> { channel = "danfossairunit:airunit:myairunit:humidity#humidity" }
+Number:Temperature DanfossHRV_RoomTemperature "Room air temperatuyre" <temperature> { channel = "danfossairunit:airunit:myairunit:temps#room_temp" }
+Number:Temperature DanfossHRV_OutdoorTemperature "Outdoor air temperature" <temperature> { channel = "danfossairunit:airunit:myairunit:temps#outdoor_temp" }
+Number:Temperature DanfossHRV_SupplyAirTemperature "Supply air temperature" <temperature> { channel = "danfossairunit:airunit:myairunit:recuperator#supply_temp" }
+Number:Temperature DanfossHRV_ExtractAirTemperature "Extract air temperature" <temperature> { channel = "danfossairunit:airunit:myairunit:recuperator#extract_temp" }
+Number:Temperature DanfossHRV_ExhaustAirTemperature "Exhaust air temperature" <temperature> { channel = "danfossairunit:airunit:myairunit:recuperator#exhaust_temp" }
+Number DanfossHRV_RemainingFilterLife "Remaining filter life" { channel = "danfossairunit:airunit:myairunit:service#filter_life" }
+Number DanfossHRV_FilterPeriod "Filter period" { channel = "danfossairunit:airunit:myairunit:service#filter_period" }
```
### Sitemap
```
-Slider item=Lueftung_Drehzahl_Manuell
-Text item=Lueftung_Drehzahl_Supply
-Text item=Lueftung_Drehzahl_Extract
-Selection item=Lueftung_Mode mappings=[DEMAND="Bedarfslüftung", OFF="Aus", PROGRAM="Programm", MANUAL="manuell"]
-Switch item=Lueftung_Boost
-Switch item=Lueftung_Bypass
+sitemap danfoss label="Danfoss" {
+ Frame label="Control" {
+ Selection item=DanfossHRV_Mode mappings=[DEMAND="Demand", OFF="Off", PROGRAM="Program", MANUAL="Manual"]
+ Slider item=DanfossHRV_ManualFanStep step=10 visibility=[DanfossHRV_Mode=="MANUAL"]
+ Switch item=DanfossHRV_Bypass
+ Switch item=DanfossHRV_Boost
+ }
+ Frame label="Measurements" {
+ Text item=DanfossHRV_Humidity
+ Text item=DanfossHRV_RoomTemperature
+ Text item=DanfossHRV_OutdoorTemperature
+ Text item=DanfossHRV_SupplyAirTemperature
+ Text item=DanfossHRV_ExtractAirTemperature
+ Text item=DanfossHRV_ExhaustAirTemperature
+ }
+ Frame label="Fan" {
+ Text item=DanfossHRV_SupplyFanSpeed
+ Text item=DanfossHRV_ExtractFanSpeed
+ }
+ Frame label="Filter" {
+ Text item=DanfossHRV_RemainingFilterLife
+ Slider item=DanfossHRV_FilterPeriod minValue=3 maxValue=12
+ }
+}
```
// service channels
CHANNEL_BATTERY_LIFE("battery_life", ChannelGroup.SERVICE, DanfossAirUnit::getBatteryLife),
- CHANNEL_FILTER_LIFE("filter_life", ChannelGroup.SERVICE, DanfossAirUnit::getFilterLife);
+ CHANNEL_FILTER_LIFE("filter_life", ChannelGroup.SERVICE, DanfossAirUnit::getFilterLife),
+ CHANNEL_FILTER_PERIOD("filter_period", ChannelGroup.SERVICE, DanfossAirUnit::getFilterPeriod,
+ DanfossAirUnit::setFilterPeriod);
private final String channelName;
private final ChannelGroup group;
public static byte[] EXHAUST_TEMPERATURE = { 0x14, 0x75 };
public static byte[] BATTERY_LIFE = { 0x03, 0x0f };
public static byte[] FILTER_LIFE = { 0x14, 0x6a };
+ public static byte[] FILTER_PERIOD = { 0x14, 0x69 };
public static byte[] CURRENT_TIME = { 0x15, (byte) 0xe0 };
public static byte[] AWAY_TO = { 0x15, 0x20 };
public static byte[] AWAY_FROM = { 0x15, 0x21 };
/**
* 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
+ * @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public interface CommunicationController {
}
public DecimalType getFilterLife() throws IOException {
- return new DecimalType(BigDecimal.valueOf(asPercentByte(getByte(REGISTER_1_READ, FILTER_LIFE))));
+ BigDecimal value = BigDecimal.valueOf(asPercentByte(getByte(REGISTER_1_READ, FILTER_LIFE)));
+ return new DecimalType(value.setScale(1, RoundingMode.HALF_UP));
+ }
+
+ public DecimalType getFilterPeriod() throws IOException {
+ return new DecimalType(BigDecimal.valueOf(getByte(REGISTER_1_READ, FILTER_PERIOD)));
+ }
+
+ public DecimalType setFilterPeriod(Command cmd) throws IOException {
+ return setNumberTypeRegister(cmd, FILTER_PERIOD);
}
public DateTimeType getCurrentTime() throws IOException, UnexpectedResponseValueException {
return setPercentTypeRegister(cmd, MANUAL_FAN_SPEED_STEP);
}
+ private DecimalType setNumberTypeRegister(Command cmd, byte[] register) throws IOException {
+ if (cmd instanceof DecimalType) {
+ byte value = (byte) ((DecimalType) cmd).intValue();
+ set(REGISTER_1_WRITE, register, value);
+ }
+ return new DecimalType(BigDecimal.valueOf(getByte(REGISTER_1_READ, register)));
+ }
+
private PercentType setPercentTypeRegister(Command cmd, byte[] register) throws IOException {
if (cmd instanceof PercentType) {
byte value = (byte) ((((PercentType) cmd).intValue() + 5) / 10);
logger.debug("Try to discover all Danfoss Air CCM devices");
try (DatagramSocket socket = new DatagramSocket()) {
-
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
sendBroadcastToDiscoverThing(socket, interfaceAddress.getBroadcast());
}
}
-
} catch (IOException e) {
logger.debug("No Danfoss Air CCM device found. Diagnostic: {}", e.getMessage());
}
<label>Remaining Filter Life</label>
<description>Remaining life of filter until exchange is necessary</description>
</channel>
+ <channel id="filter_period" typeId="filterPeriod"/>
</channels>
</channel-group-type>
<category>Fan</category>
<state step="10" min="0" max="100" readOnly="true"/>
</channel-type>
+ <channel-type id="filterPeriod" advanced="true">
+ <item-type>Number</item-type>
+ <label>Filter Period</label>
+ <description>Number of months between filter replacements</description>
+ <state pattern="%d" min="3" max="12"/>
+ </channel-type>
<channel-type id="percentage">
<item-type>Number</item-type>
<label>Percentage</label>
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.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
/**
* This class provides test cases for {@link DanfossAirUnit}
*
- * @author Jacob Laursen - Refactoring, bugfixes and enhancements
+ * @author Jacob Laursen - Initial contribution
*/
public class DanfossAirUnitTest extends JavaTest {
var airUnit = new DanfossAirUnit(communicationController);
assertThrows(UnexpectedResponseValueException.class, () -> airUnit.getManualFanStep());
}
+
+ @Test
+ public void getFilterLifeWhenNearestNeighborIsBelowRoundsDown() throws IOException {
+ byte[] response = new byte[] { (byte) 0xf0 };
+ when(this.communicationController.sendRobustRequest(REGISTER_1_READ, FILTER_LIFE)).thenReturn(response);
+ var airUnit = new DanfossAirUnit(communicationController);
+ assertEquals(new DecimalType("94.1"), airUnit.getFilterLife());
+ }
}