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.lcn.internal.subhandler;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.openhab.binding.lcn.internal.LcnBindingConstants;
22 import org.openhab.binding.lcn.internal.LcnModuleHandler;
23 import org.openhab.binding.lcn.internal.common.DimmerOutputCommand;
24 import org.openhab.binding.lcn.internal.common.LcnChannelGroup;
25 import org.openhab.binding.lcn.internal.common.LcnDefs;
26 import org.openhab.binding.lcn.internal.common.LcnException;
27 import org.openhab.binding.lcn.internal.common.PckGenerator;
28 import org.openhab.binding.lcn.internal.connection.ModInfo;
29 import org.openhab.core.library.types.HSBType;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.library.types.UpDownType;
34 import org.openhab.core.util.ColorUtil;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * Handles Commands and State changes of dimmer outputs of an LCN module.
41 * @author Fabian Wolter - Initial contribution
44 public class LcnModuleOutputSubHandler extends AbstractLcnModuleSubHandler {
45 private final Logger logger = LoggerFactory.getLogger(LcnModuleOutputSubHandler.class);
46 private static final int COLOR_RAMP_MS = 1000;
47 private static final String OUTPUT_COLOR = "color";
48 private static final Pattern PERCENT_PATTERN;
49 private static final Pattern NATIVE_PATTERN;
50 private volatile HSBType currentColor = new HSBType();
51 private volatile PercentType output4 = new PercentType();
53 public LcnModuleOutputSubHandler(LcnModuleHandler handler, ModInfo info) {
58 PERCENT_PATTERN = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + "A(?<outputId>\\d)(?<percent>\\d{3})");
59 NATIVE_PATTERN = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + "O(?<outputId>\\d)(?<value>\\d{3})");
63 public Collection<Pattern> getPckStatusMessagePatterns() {
64 return Arrays.asList(NATIVE_PATTERN, PERCENT_PATTERN);
68 public void handleRefresh(LcnChannelGroup channelGroup, int number) {
69 info.refreshOutput(number);
73 public void handleRefresh(String groupId) {
74 if (OUTPUT_COLOR.equals(groupId)) {
75 info.refreshAllOutputs();
80 public void handleCommandOnOff(OnOffType command, LcnChannelGroup channelGroup, int number) throws LcnException {
81 // don't use OnOffType.as() here, because it returns @Nullable
82 handler.sendPck(PckGenerator.dimOutput(number, command == OnOffType.ON ? 100 : 0, 0));
86 public void handleCommandPercent(PercentType command, LcnChannelGroup channelGroup, int number)
88 handler.sendPck(PckGenerator.dimOutput(number, command.doubleValue(), 0));
92 public void handleCommandPercent(PercentType command, LcnChannelGroup channelGroup, String idWithoutGroup)
94 if (!OUTPUT_COLOR.equals(idWithoutGroup)) {
95 throw new LcnException("Unknown group ID: " + idWithoutGroup);
97 updateAndSendColor(new HSBType(currentColor.getHue(), currentColor.getSaturation(), command));
101 public void handleCommandHsb(HSBType command, String groupId) throws LcnException {
102 if (!OUTPUT_COLOR.equals(groupId)) {
103 throw new LcnException("Unknown group ID: " + groupId);
105 updateAndSendColor(command);
108 private synchronized void updateAndSendColor(HSBType hsbType) throws LcnException {
109 currentColor = hsbType;
110 handler.updateChannel(LcnChannelGroup.OUTPUT, OUTPUT_COLOR, currentColor);
112 PercentType[] rgb = ColorUtil.hsbToRgbPercent(currentColor);
114 if (info.getFirmwareVersion().map(v -> v >= LcnBindingConstants.FIRMWARE_2014).orElse(true)) {
115 handler.sendPck(PckGenerator.dimAllOutputs(rgb[0].doubleValue(), rgb[1].doubleValue(), rgb[2].doubleValue(),
116 output4.doubleValue(), COLOR_RAMP_MS));
118 handler.sendPck(PckGenerator.dimOutput(0, rgb[0].doubleValue(), COLOR_RAMP_MS));
119 handler.sendPck(PckGenerator.dimOutput(1, rgb[1].doubleValue(), COLOR_RAMP_MS));
120 handler.sendPck(PckGenerator.dimOutput(2, rgb[2].doubleValue(), COLOR_RAMP_MS));
125 public void handleCommandDimmerOutput(DimmerOutputCommand command, int number) throws LcnException {
126 int rampMs = command.getRampMs();
127 if (command.isControlAllOutputs()) { // control all dimmer outputs
128 if (rampMs == LcnDefs.FIXED_RAMP_MS) {
129 // compatibility command
130 handler.sendPck(PckGenerator.controlAllOutputs(command.intValue()));
132 // command since firmware 180501
133 handler.sendPck(PckGenerator.dimAllOutputs(command.doubleValue(), command.doubleValue(),
134 command.doubleValue(), command.doubleValue(), rampMs));
136 } else if (command.isControlOutputs12()) { // control dimmer outputs 1+2
137 if (command.intValue() == 0 || command.intValue() == 100) {
138 handler.sendPck(PckGenerator.controlOutputs12(command.intValue() > 0, rampMs >= LcnDefs.FIXED_RAMP_MS));
140 // ignore ramp when dimming
141 handler.sendPck(PckGenerator.dimOutputs12(command.doubleValue()));
144 handler.sendPck(PckGenerator.dimOutput(number, command.doubleValue(), rampMs));
149 public void handleCommandString(StringType command, int number) throws LcnException {
152 switch (command.toString()) {
164 handler.sendPck(PckGenerator.setTunableWhiteMode(mode));
168 public void handleStatusMessage(Matcher matcher) {
169 int outputId = Integer.parseInt(matcher.group("outputId")) - 1;
171 if (!LcnChannelGroup.OUTPUT.isValidId(outputId)) {
172 logger.warn("outputId out of range: {}", outputId);
176 if (matcher.pattern() == PERCENT_PATTERN) {
177 percent = Integer.parseInt(matcher.group("percent"));
178 } else if (matcher.pattern() == NATIVE_PATTERN) {
179 percent = (double) Integer.parseInt(matcher.group("value")) / 2;
181 logger.warn("Unexpected pattern: {}", matcher.pattern());
185 info.onOutputResponseReceived(outputId);
187 percent = Math.min(100, Math.max(0, percent));
189 PercentType percentType = new PercentType((int) Math.round(percent));
190 fireUpdate(LcnChannelGroup.OUTPUT, outputId, percentType);
193 output4 = percentType;
198 fireUpdate(LcnChannelGroup.ROLLERSHUTTEROUTPUT, 0, UpDownType.UP);
199 } else if (outputId == 1) {
200 fireUpdate(LcnChannelGroup.ROLLERSHUTTEROUTPUT, 0, UpDownType.DOWN);