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.loxone.internal.controls;
15 import static org.openhab.binding.loxone.internal.LxBindingConstants.*;
17 import java.io.IOException;
18 import java.math.BigDecimal;
20 import org.openhab.binding.loxone.internal.types.LxState;
21 import org.openhab.binding.loxone.internal.types.LxUuid;
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.thing.ChannelUID;
27 import org.openhab.core.thing.type.ChannelTypeUID;
28 import org.openhab.core.types.Command;
29 import org.openhab.core.types.StateDescriptionFragment;
30 import org.openhab.core.types.StateDescriptionFragmentBuilder;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
35 * A ValueSelector type of control on Loxone Miniserver.
37 * According to Loxone API documentation, this control covers: Push-button +/- and Push-button + functional blocks.
39 * @author Pawel Pieczul - initial contribution
42 class LxControlValueSelector extends LxControl {
44 static class Factory extends LxControlInstance {
46 LxControl create(LxUuid uuid) {
47 return new LxControlValueSelector(uuid);
52 return "valueselector";
56 private static final String STATE_VALUE = "value";
57 private static final String STATE_MIN = "min";
58 private static final String STATE_MAX = "max";
59 private static final String STATE_STEP = "step";
61 private final Logger logger = LoggerFactory.getLogger(LxControlValueSelector.class);
63 private Boolean increaseOnly = false;
64 private String format = "%.1f";
65 private Double minValue;
66 private Double maxValue;
67 private Double stepValue;
68 private ChannelUID channelId;
69 private ChannelUID numberChannelId;
71 private LxControlValueSelector(LxUuid uuid) {
76 public void initialize(LxControlConfig config) {
77 super.initialize(config);
78 channelId = addChannel("Dimmer", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_DIMMER),
79 defaultChannelLabel, "Value Selector", tags, this::handleCommand, this::getChannelState);
80 numberChannelId = addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_NUMBER),
81 defaultChannelLabel + " / Number", "Value Selector by number", tags, this::handleNumberCommand,
82 this::getChannelNumberState);
84 if (details != null) {
85 if (details.format != null) {
86 this.format = details.format;
88 if (details.increaseOnly != null) {
89 this.increaseOnly = details.increaseOnly;
94 private void handleCommand(Command command) throws IOException {
95 if (minValue == null || maxValue == null || minValue >= maxValue) {
96 logger.debug("Value selector min or max value missing or min>max.");
99 if (command instanceof OnOffType) {
100 if ((OnOffType) command == OnOffType.ON) {
101 sendAction(maxValue.toString());
103 sendAction(minValue.toString());
105 } else if (command instanceof IncreaseDecreaseType) {
106 if (stepValue == null) {
107 logger.debug("Value selector step value missing.");
110 IncreaseDecreaseType type = (IncreaseDecreaseType) command;
111 if (increaseOnly != null && type == IncreaseDecreaseType.DECREASE && increaseOnly) {
112 logger.debug("Value selector configured to allow increase only.");
115 Double currentValue = getStateDoubleValue(STATE_VALUE);
116 if (currentValue != null) {
117 Double nextValue = currentValue + (type == IncreaseDecreaseType.INCREASE ? stepValue : -stepValue);
118 if (nextValue > maxValue) {
119 nextValue = maxValue;
121 if (nextValue < minValue) {
122 nextValue = minValue;
124 sendAction(nextValue.toString());
126 } else if (command instanceof PercentType) {
127 Double value = ((PercentType) command).doubleValue() * (maxValue - minValue) / 100.0 + minValue;
128 sendAction(value.toString());
132 private void handleNumberCommand(Command command) throws IOException {
133 if (minValue == null || maxValue == null || minValue >= maxValue) {
134 logger.debug("Value selector min or max value missing or min>max.");
137 if (command instanceof DecimalType) {
138 Double value = ((DecimalType) command).doubleValue();
139 if (value < minValue || value > maxValue) {
140 logger.debug("Value {} out of {}-{} range", value, minValue, maxValue);
143 sendAction(value.toString());
147 private PercentType getChannelState() {
148 Double value = getStateDoubleValue(STATE_VALUE);
149 if (value != null && minValue != null && maxValue != null && minValue <= value && value <= maxValue) {
150 value = ((value - minValue) * 100.0) / (maxValue - minValue);
151 return new PercentType(value.intValue());
156 private DecimalType getChannelNumberState() {
157 Double value = getStateDoubleValue(STATE_VALUE);
158 if (value != null && minValue != null && maxValue != null && minValue <= value && value <= maxValue) {
159 return new DecimalType(value);
165 * Get dynamic updates to the minimum, maximum and step values. Sets a new state description if these values are
166 * available. If no updates to the min/max/step, calls parent class method to update selector value in the
169 * @param state state update from the Miniserver
172 public void onStateChange(LxState state) {
173 String stateName = state.getName();
174 Object value = state.getStateValue();
176 if (value instanceof Double) {
177 if (STATE_MIN.equals(stateName)) {
178 minValue = (Double) value;
179 } else if (STATE_MAX.equals(stateName)) {
180 maxValue = (Double) value;
181 } else if (STATE_STEP.equals(stateName)) {
182 stepValue = (Double) value;
183 if (stepValue <= 0) {
184 logger.warn("Value selector step value <= 0: {}", stepValue);
188 super.onStateChange(state);
192 } catch (NumberFormatException e) {
193 logger.debug("Error parsing value for state {}: {}", stateName, e.getMessage());
195 if (minValue != null && maxValue != null && stepValue != null && minValue < maxValue) {
196 StateDescriptionFragment fragment = StateDescriptionFragmentBuilder.create()
197 .withMinimum(new BigDecimal(minValue)).withMaximum(new BigDecimal(maxValue))
198 .withStep(new BigDecimal(stepValue)).withPattern(format).withReadOnly(false).build();
199 addChannelStateDescriptionFragment(channelId, fragment);
200 addChannelStateDescriptionFragment(numberChannelId, fragment);