]> git.basschouten.com Git - openhab-addons.git/blob
d4fa6d585c5fc0e86a494883439ce802b6111e1d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mqtt.generic.values;
14
15 import java.math.BigDecimal;
16 import java.math.MathContext;
17 import java.util.stream.Collectors;
18 import java.util.stream.Stream;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.core.library.CoreItemFactory;
23 import org.openhab.core.library.types.DecimalType;
24 import org.openhab.core.library.types.IncreaseDecreaseType;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.library.types.PercentType;
27 import org.openhab.core.library.types.QuantityType;
28 import org.openhab.core.library.types.StringType;
29 import org.openhab.core.library.types.UpDownType;
30 import org.openhab.core.types.Command;
31 import org.openhab.core.types.StateDescriptionFragmentBuilder;
32 import org.openhab.core.types.UnDefType;
33
34 import tec.uom.se.unit.Units;
35
36 /**
37  * Implements a percentage value. Minimum and maximum are definable.
38  *
39  * <p>
40  * Accepts user updates from a DecimalType, IncreaseDecreaseType and UpDownType.
41  * If this is a percent value, PercentType
42  * </p>
43  * Accepts MQTT state updates as DecimalType, IncreaseDecreaseType and UpDownType
44  * StringType with comma separated HSB ("h,s,b"), RGB ("r,g,b") and on, off strings.
45  * On, Off strings can be customized.
46  *
47  * @author David Graeff - Initial contribution
48  */
49 @NonNullByDefault
50 public class PercentageValue extends Value {
51     private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
52     private final BigDecimal min;
53     private final BigDecimal max;
54     private final BigDecimal span;
55     private final BigDecimal step;
56     private final BigDecimal stepPercent;
57     private final @Nullable String onValue;
58     private final @Nullable String offValue;
59
60     public PercentageValue(@Nullable BigDecimal min, @Nullable BigDecimal max, @Nullable BigDecimal step,
61             @Nullable String onValue, @Nullable String offValue) {
62         super(CoreItemFactory.DIMMER, Stream.of(DecimalType.class, QuantityType.class, IncreaseDecreaseType.class,
63                 OnOffType.class, UpDownType.class, StringType.class).collect(Collectors.toList()));
64         this.onValue = onValue;
65         this.offValue = offValue;
66         this.min = min == null ? BigDecimal.ZERO : min;
67         this.max = max == null ? HUNDRED : max;
68         if (this.min.compareTo(this.max) >= 0) {
69             throw new IllegalArgumentException("Min need to be smaller than max!");
70         }
71         this.span = this.max.subtract(this.min);
72         this.step = step == null ? BigDecimal.ONE : step;
73         this.stepPercent = this.step.multiply(HUNDRED).divide(this.span, MathContext.DECIMAL128);
74     }
75
76     @Override
77     public void update(Command command) throws IllegalArgumentException {
78         PercentType oldvalue = (state == UnDefType.UNDEF) ? new PercentType() : (PercentType) state;
79         // Nothing do to -> We have received a percentage
80         if (command instanceof PercentType) {
81             state = (PercentType) command;
82         } else //
83                // A decimal type need to be converted according to the current min/max values
84         if (command instanceof DecimalType) {
85             BigDecimal v = ((DecimalType) command).toBigDecimal();
86             v = v.subtract(min).multiply(HUNDRED).divide(max.subtract(min), MathContext.DECIMAL128);
87             state = new PercentType(v);
88         } else //
89                // A quantity type need to be converted according to the current min/max values
90         if (command instanceof QuantityType) {
91             QuantityType<?> qty = ((QuantityType<?>) command).toUnit(Units.PERCENT);
92             if (qty != null) {
93                 BigDecimal v = qty.toBigDecimal();
94                 v = v.subtract(min).multiply(HUNDRED).divide(max.subtract(min), MathContext.DECIMAL128);
95                 state = new PercentType(v);
96             }
97         } else //
98                // Increase or decrease by "step"
99         if (command instanceof IncreaseDecreaseType) {
100             if (((IncreaseDecreaseType) command) == IncreaseDecreaseType.INCREASE) {
101                 final BigDecimal v = oldvalue.toBigDecimal().add(stepPercent);
102                 state = v.compareTo(HUNDRED) <= 0 ? new PercentType(v) : PercentType.HUNDRED;
103             } else {
104                 final BigDecimal v = oldvalue.toBigDecimal().subtract(stepPercent);
105                 state = v.compareTo(BigDecimal.ZERO) >= 0 ? new PercentType(v) : PercentType.ZERO;
106             }
107         } else //
108                // On/Off equals 100 or 0 percent
109         if (command instanceof OnOffType) {
110             state = ((OnOffType) command) == OnOffType.ON ? PercentType.HUNDRED : PercentType.ZERO;
111         } else//
112               // Increase or decrease by "step"
113         if (command instanceof UpDownType) {
114             if (((UpDownType) command) == UpDownType.UP) {
115                 final BigDecimal v = oldvalue.toBigDecimal().add(stepPercent);
116                 state = v.compareTo(HUNDRED) <= 0 ? new PercentType(v) : PercentType.HUNDRED;
117             } else {
118                 final BigDecimal v = oldvalue.toBigDecimal().subtract(stepPercent);
119                 state = v.compareTo(BigDecimal.ZERO) >= 0 ? new PercentType(v) : PercentType.ZERO;
120             }
121         } else //
122                // Check against custom on/off values
123         if (command instanceof StringType) {
124             if (onValue != null && command.toString().equals(onValue)) {
125                 state = new PercentType(max);
126             } else if (offValue != null && command.toString().equals(offValue)) {
127                 state = new PercentType(min);
128             } else {
129                 throw new IllegalStateException("Unknown String!");
130             }
131         } else {
132             // We are desperate -> Try to parse the command as number value
133             state = PercentType.valueOf(command.toString());
134         }
135     }
136
137     @Override
138     public String getMQTTpublishValue(@Nullable String pattern) {
139         if (state == UnDefType.UNDEF) {
140             return "";
141         }
142         // Formula: From percentage to custom min/max: value*span/100+min
143         // Calculation need to happen with big decimals to either return a straight integer or a decimal depending on
144         // the value.
145         BigDecimal value = ((PercentType) state).toBigDecimal().multiply(span).divide(HUNDRED, MathContext.DECIMAL128)
146                 .add(min).stripTrailingZeros();
147
148         String formatPattern = pattern;
149         if (formatPattern == null) {
150             formatPattern = "%s";
151         }
152
153         return new DecimalType(value).format(formatPattern);
154     }
155
156     @Override
157     public StateDescriptionFragmentBuilder createStateDescription(boolean readOnly) {
158         return super.createStateDescription(readOnly).withMaximum(max).withMinimum(min).withStep(step)
159                 .withPattern("%s %%");
160     }
161 }