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