/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.lcn.mappingtarget; import java.text.NumberFormat; import java.text.ParseException; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.openhab.binding.lcn.common.LcnAddr; import org.openhab.binding.lcn.common.LcnAddrMod; import org.openhab.binding.lcn.common.LcnDefs; import org.openhab.binding.lcn.common.PckGenerator; import org.openhab.binding.lcn.connection.Connection; import org.openhab.binding.lcn.connection.ModInfo; import org.openhab.binding.lcn.input.ModStatusBinSensors; import org.openhab.binding.lcn.input.ModStatusKeyLocks; import org.openhab.binding.lcn.input.ModStatusLedsAndLogicOps; import org.openhab.binding.lcn.input.ModStatusOutput; import org.openhab.binding.lcn.input.ModStatusRelays; import org.openhab.binding.lcn.input.ModStatusVar; import org.openhab.core.events.EventPublisher; import org.openhab.core.items.Item; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.types.Command; import org.openhab.core.types.State; /** * Controls output-ports. * Can also visualize single output-ports. * * @author Tobias J�ttner */ public class OutputDimAbs extends TargetWithLcnAddr { /** Pattern to parse ON and OFF commands (shortcuts for DIM 0 and DIM 100). */ private static final Pattern PATTERN_ONOFF = Pattern.compile( "(?<outputId>[1234]|(ALL))(\\.(?<ramp>\\d+(,\\d+)?(?<timeUnit>.+)))?", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); /** Pattern to parse DIM commands. */ private static final Pattern PATTERN_DIM = Pattern.compile( "(?<outputId>[1234]|(ALL))\\.(?<value>\\d+(,\\d+)?)%(\\.(?<ramp>\\d+(,\\d+)?(?<timeUnit>.+)))?", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); /** Pattern to parse DIM commands with i%. */ private static final Pattern PATTERN_DIMI = Pattern.compile( "(?<outputId>[1234]|(ALL))\\.%i(\\.(?<ramp>\\d+(,\\d+)?(?<timeUnit>.+)))?", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); /** The target output-port (0..3) or -1 (all, no visualization). */ private final int outputId; /** The target value (0..100) or -1 if %i will be used. */ private final double percent; /** The ramp to use. */ private final int rampMSec; /** * Constructor. * * @param addr the target LCN address * @param outputId 0..3 * @param percent the absolute value (0..100) or -1 if %i will be used * @param rampMSec the ramp in milliseconds */ OutputDimAbs(LcnAddr addr, int outputId, double percent, int rampMSec) { super(addr); this.outputId = outputId; this.percent = percent; this.rampMSec = rampMSec; } /** * Tries to parse the given input text. * * @param input the text to parse * @return the parsed {@link OutputDimAbs} or null */ static Target tryParseTarget(String input) { CmdAndAddressRet header = CmdAndAddressRet.parse(input, true); if (header != null) { try { Matcher matcher; switch (header.getCmd().toUpperCase()) { case "ON": case "OFF": if ((matcher = PATTERN_ONOFF.matcher(header.getRestInput())).matches()) { int outputId = matcher.group("outputId").equalsIgnoreCase("ALL") ? -1 : Integer.parseInt(matcher.group("outputId")) - 1; double value = header.getCmd().equalsIgnoreCase("ON") ? 100 : 0; double ramp = 0; if (matcher.group("ramp") != null) { // Optional if (LcnDefs.TimeUnit.parse(matcher.group("timeUnit")) != LcnDefs.TimeUnit.SECONDS) { throw new IllegalArgumentException(); } ramp = NumberFormat.getInstance(Locale.GERMANY).parse(matcher.group("ramp")) .doubleValue(); } return new OutputDimAbs(header.getAddr(), outputId, value, (int) (ramp * 1000)); } break; case "DIM": if ((matcher = PATTERN_DIM.matcher(header.getRestInput())).matches()) { int outputId = matcher.group("outputId").equalsIgnoreCase("ALL") ? -1 : Integer.parseInt(matcher.group("outputId")) - 1; double value = NumberFormat.getInstance(Locale.GERMANY).parse(matcher.group("value")) .doubleValue(); double ramp = 0; if (matcher.group("ramp") != null) { // Optional if (LcnDefs.TimeUnit.parse(matcher.group("timeUnit")) != LcnDefs.TimeUnit.SECONDS) { throw new IllegalArgumentException(); } ramp = NumberFormat.getInstance(Locale.GERMANY).parse(matcher.group("ramp")) .doubleValue(); } return new OutputDimAbs(header.getAddr(), outputId, value, (int) (ramp * 1000)); } else if ((matcher = PATTERN_DIMI.matcher(header.getRestInput())).matches()) { int outputId = matcher.group("outputId").equalsIgnoreCase("ALL") ? -1 : Integer.parseInt(matcher.group("outputId")) - 1; double ramp = 0; if (matcher.group("ramp") != null) { // Optional if (LcnDefs.TimeUnit.parse(matcher.group("timeUnit")) != LcnDefs.TimeUnit.SECONDS) { throw new IllegalArgumentException(); } ramp = NumberFormat.getInstance(Locale.GERMANY).parse(matcher.group("ramp")) .doubleValue(); } return new OutputDimAbs(header.getAddr(), outputId, -1, (int) (ramp * 1000)); } break; } } catch (ParseException ex) { } catch (IllegalArgumentException ex) { } } return null; } /** {@inheritDoc} */ @Override public void send(Connection conn, Item item, Command cmd) { double value = this.percent; if (value == -1 && cmd instanceof PercentType) { value = ((PercentType) cmd).doubleValue(); } if (value >= 0 && value <= 100) { if (this.outputId == -1) { // All boolean is1805 = false; // Default if (!this.addr.isGroup()) { ModInfo info = conn.getModInfo((LcnAddrMod) this.addr); if (info != null) { is1805 = info.getSwAge() >= 0x180501; } } conn.queue(this.addr, !this.addr.isGroup(), PckGenerator.dimAllOutputs(value, LcnDefs.timeToRampValue(this.rampMSec), is1805)); } else { // Single conn.queue(this.addr, !this.addr.isGroup(), PckGenerator.dimOutput(this.outputId, value, LcnDefs.timeToRampValue(this.rampMSec))); } } } /** {@inheritDoc} */ @Override public void register(Connection conn) { if (!this.addr.isGroup()) { long currTime = System.nanoTime(); ModInfo info = conn.updateModuleData((LcnAddrMod) this.addr); if (this.outputId == -1) { // All if (!info.requestSwAge.isActive()) { info.requestSwAge.nextRequestIn(0, currTime); // Firmware version is required } } else { // Single: Allow visualization if (!info.requestStatusOutputs.get(this.outputId).isActive()) { info.requestStatusOutputs.get(this.outputId).nextRequestIn(0, currTime); } } } } /** * Visualization for {@link PercentType} and {@link OnOffType}. * {@inheritDoc} */ @Override public boolean visualizationHandleOutputStatus(ModStatusOutput pchkInput, Command cmd, Item item, EventPublisher eventPublisher) { // We are actually not meant to visualize anything. // But (just in case) someone is really lazy in doing the item-definitions, we try to be helpful by implementing // some ON/OFF and DIM logic. if (pchkInput.getLogicalSourceAddr().equals(this.addr) && pchkInput.getOutputId() == this.outputId) { if (this.percent == -1 && item.getAcceptedDataTypes().contains(PercentType.class)) { eventPublisher.postUpdate(item.getName(), new PercentType((int) Math.round(pchkInput.getPercent()))); return true; } else if (item.getAcceptedDataTypes().contains(OnOffType.class)) { State reportedState = pchkInput.getPercent() != 0 ? OnOffType.ON : OnOffType.OFF; // Only update if the state we are bound to is equal to the reported one if (cmd == reportedState) { eventPublisher.postUpdate(item.getName(), reportedState); return true; } } } return false; } /** {@inheritDoc} */ @Override public boolean visualizationHandleRelaysStatus(ModStatusRelays pchkInput, Command cmd, Item item, EventPublisher eventPublisher) { return false; } /** {@inheritDoc} */ @Override public boolean visualizationBinSensorsStatus(ModStatusBinSensors pchkInput, Command cmd, Item item, EventPublisher eventPublisher) { return false; } /** {@inheritDoc} */ @Override public boolean visualizationVarStatus(ModStatusVar pchkInput, Command cmd, Item item, EventPublisher eventPublisher) { return false; } /** {@inheritDoc} */ @Override public boolean visualizationLedsAndLogicOpsStatus(ModStatusLedsAndLogicOps pchkInput, Command cmd, Item item, EventPublisher eventPublisher) { return false; } /** {@inheritDoc} */ @Override public boolean visualizationKeyLocksStatus(ModStatusKeyLocks pchkInput, Command cmd, Item item, EventPublisher eventPublisher) { return false; } }