/** * 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.common; import java.text.DecimalFormat; /** * Common definitions and helpers for LCN. * * @author Tobias J�ttner */ public final class LcnDefs { /** Text encoding used by LCN-PCHK. */ public static final String LCN_ENCODING = "UTF-8"; /** * LCN dimming mode. * If solely modules with firmware 170206 or newer are present, LCN-PRO automatically programs {@link #STEPS200}. * Otherwise the default is {@link #STEPS50}. * Since LCN-PCHK doesn't know the current mode, it must explicitly be set. */ public enum OutputPortDimMode { STEPS50, // 0..50 dimming steps (all LCN module generations) STEPS200 // 0..200 dimming steps (since 170206) } /** * Tells LCN-PCHK how to format output-port status-messages. * {@link #NATIVE} allows to show the status in half-percent steps (e.g. "10.5"). * {@link #NATIVE} is completely backward compatible and there are no restrictions * concerning the LCN module generations. It requires LCN-PCHK 2.3 or higher though. */ public enum OutputPortStatusMode { PERCENT, // Default (compatible with all versions of LCN-PCHK) NATIVE // 0..200 steps (since LCN-PCHK 2.3) } /** * Converts the given time into an LCN ramp value. * * @param timeMSec the time in milliseconds * @return the (LCN-internal) ramp value (0..250) */ public static int timeToRampValue(int timeMSec) { int ret; if (timeMSec < 250) { ret = 0; } else if (timeMSec < 500) { ret = 1; } else if (timeMSec < 660) { ret = 2; } else if (timeMSec < 1000) { ret = 3; } else if (timeMSec < 1400) { ret = 4; } else if (timeMSec < 2000) { ret = 5; } else if (timeMSec < 3000) { ret = 6; } else if (timeMSec < 4000) { ret = 7; } else if (timeMSec < 5000) { ret = 8; } else if (timeMSec < 6000) { ret = 9; } else { ret = (timeMSec / 1000 - 6) / 2 + 10; if (ret >= 250) { ret = 250; } } return ret; } /** LCN variable types. */ public enum Var { UNKNOWN, // Used if the real type is not known (yet) VAR1ORTVAR, VAR2ORR1VAR, VAR3ORR2VAR, VAR4, VAR5, VAR6, VAR7, VAR8, VAR9, VAR10, VAR11, VAR12, // Since 170206 R1VARSETPOINT, R2VARSETPOINT, // Set-points for regulators THRS1, THRS2, THRS3, THRS4, THRS5, // Register 1 (THRS5 only before 170206) THRS2_1, THRS2_2, THRS2_3, THRS2_4, // Register 2 (since 2012) THRS3_1, THRS3_2, THRS3_3, THRS3_4, // Register 3 (since 2012) THRS4_1, THRS4_2, THRS4_3, THRS4_4, // Register 4 (since 2012) S0INPUT1, S0INPUT2, S0INPUT3, S0INPUT4; // LCN-BU4L /** Helper array to get {@link Var} by numeric id. */ private static final Var[] varIdToVarArray = new Var[] { Var.VAR1ORTVAR, Var.VAR2ORR1VAR, Var.VAR3ORR2VAR, Var.VAR4, Var.VAR5, Var.VAR6, Var.VAR7, Var.VAR8, Var.VAR9, Var.VAR10, Var.VAR11, Var.VAR12 }; /** Helper array to get set-point {@link Var} by numeric id. */ private static final Var[] setVarIdToVarArray = new Var[] { Var.R1VARSETPOINT, Var.R2VARSETPOINT }; /** Helper arrays to get threshold {@link Var} by numeric id. */ private static final Var[][] thrsToVarArrays = new Var[][] { new Var[] { THRS1, THRS2, THRS3, THRS4, THRS5 }, new Var[] { THRS2_1, THRS2_2, THRS2_3, THRS2_4 }, new Var[] { THRS3_1, THRS3_2, THRS3_3, THRS3_4 }, new Var[] { THRS4_1, THRS4_2, THRS4_3, THRS4_4 } }; /** Helper array to get S0-input {@link Var} by numeric id. */ private static final Var[] s0IdToVarArray = new Var[] { Var.S0INPUT1, Var.S0INPUT2, Var.S0INPUT3, Var.S0INPUT4 }; /** * Translates a given id into a variable type. * * @param varId 0..11 * @return the translated {@link Var} * @throws IllegalArgumentException if out of range */ public static Var varIdToVar(int varId) throws IllegalArgumentException { if (varId < 0 || varId >= varIdToVarArray.length) { throw new IllegalArgumentException(); } return varIdToVarArray[varId]; } /** * Translates a given id into a LCN set-point variable type. * * @param setPointId 0..1 * @return the translated {@link Var} * @throws IllegalArgumentException if out of range */ public static Var setPointIdToVar(int setPointId) throws IllegalArgumentException { if (setPointId < 0 || setPointId >= setVarIdToVarArray.length) { throw new IllegalArgumentException(); } return setVarIdToVarArray[setPointId]; } /** * Translates given ids into a LCN threshold variable type. * * @param registerId 0..3 * @param thrsId 0..4 for register 0, 0..3 for registers 1..3 * @return the translated {@link Var} * @throws IllegalArgumentException if out of range */ public static Var thrsIdToVar(int registerId, int thrsId) throws IllegalArgumentException { if (registerId < 0 || registerId >= thrsToVarArrays.length || thrsId < 0 || thrsId >= (registerId == 0 ? 5 : 4)) { throw new IllegalArgumentException(); } return thrsToVarArrays[registerId][thrsId]; } /** * Translates a given id into a LCN S0-input variable type. * * @param s0Id 0..3 * @return the translated {@link Var} * @throws IllegalArgumentException if out of range */ public static Var s0IdToVar(int s0Id) throws IllegalArgumentException { if (s0Id < 0 || s0Id >= s0IdToVarArray.length) { throw new IllegalArgumentException(); } return s0IdToVarArray[s0Id]; } /** * Translates a given variable type into a variable id. * * @param var the variable type to translate * @return 0..11 or -1 if wrong type */ public static int toVarId(Var var) { switch (var) { case VAR1ORTVAR: return 0; case VAR2ORR1VAR: return 1; case VAR3ORR2VAR: return 2; case VAR4: return 3; case VAR5: return 4; case VAR6: return 5; case VAR7: return 6; case VAR8: return 7; case VAR9: return 8; case VAR10: return 9; case VAR11: return 10; case VAR12: return 11; default: return -1; } } /** * Translates a given variable type into a set-point id. * * @param var the variable type to translate * @return 0..1 or -1 if wrong type */ public static int toSetPointId(Var var) { switch (var) { case R1VARSETPOINT: return 0; case R2VARSETPOINT: return 1; default: return -1; } } /** * Translates a given variable type into a threshold register id. * * @param var the variable type to translate * @return 0..3 or -1 if wrong type */ public static int toThrsRegisterId(Var var) { switch (var) { case THRS1: case THRS2: case THRS3: case THRS4: case THRS5: return 0; case THRS2_1: case THRS2_2: case THRS2_3: case THRS2_4: return 1; case THRS3_1: case THRS3_2: case THRS3_3: case THRS3_4: return 2; case THRS4_1: case THRS4_2: case THRS4_3: case THRS4_4: return 3; default: return -1; } } /** * Translates a given variable type into a threshold id. * * @param var the variable type to translate * @return 0..4 or -1 if wrong type */ public static int toThrsId(Var var) { switch (var) { case THRS1: case THRS2_1: case THRS3_1: case THRS4_1: return 0; case THRS2: case THRS2_2: case THRS3_2: case THRS4_2: return 1; case THRS3: case THRS2_3: case THRS3_3: case THRS4_3: return 2; case THRS4: case THRS2_4: case THRS3_4: case THRS4_4: return 3; case THRS5: return 4; default: return -1; } } /** * Translates a given variable type into an S0-input id. * * @param var the variable type to translate * @return 0..3 or -1 if wrong type */ public static int toS0Id(Var var) { switch (var) { case S0INPUT1: return 0; case S0INPUT2: return 1; case S0INPUT3: return 2; case S0INPUT4: return 3; default: return -1; } } /** * Checks if the the given variable type is lockable. * * @param var the variable type to check * @return true if lockable */ public static boolean isLockableRegulatorSource(Var var) { return var == R1VARSETPOINT || var == Var.R2VARSETPOINT; } /** * Checks if the given variable type uses special values. * Examples for special values: "No value yet", "sensor defective" etc. * * @param var the variable type to check * @return true if special values are in use */ public static boolean useLcnSpecialValues(Var var) { return var != S0INPUT1 && var != S0INPUT2 && var != S0INPUT3 && var != S0INPUT4; } /** * Module-generation check. * Checks if the given variable type would receive a typed response if * its status was requested. * * @param var the variable type to check * @param swAge the target LCN-modules firmware version * @return true if a response would contain the variable's type */ public static boolean hasTypeInResponse(Var var, int swAge) { if (swAge < 0x170206) { switch (var) { case VAR1ORTVAR: case VAR2ORR1VAR: case VAR3ORR2VAR: case R1VARSETPOINT: case R2VARSETPOINT: return false; default: break; } } return true; } /** * Module-generation check. * Checks if the given variable type automatically sends status-updates on * value-change. It must be polled otherwise. * * @param var the variable type to check * @param swAge the target LCN-module's firmware version * @return true if the LCN module supports automatic status-messages for this {@link Var} */ public static boolean isEventBased(Var var, int swAge) { if (toSetPointId(var) != -1 || toS0Id(var) != -1) { return true; } return swAge >= 0x170206; } /** * Module-generation check. * Checks if the target LCN module would automatically send status-updates if * the given variable type was changed by command. * * @param var the variable type to check * @param is2013 the target module's-generation * @return true if a poll is required to get the new status-value */ public static boolean shouldPollStatusAfterCommand(Var var, boolean is2013) { // Regulator set-points will send status-messages on every change (all firmware versions) if (toSetPointId(var) != -1) { return false; } // Thresholds since 170206 will send status-messages on every change if (is2013 && toThrsRegisterId(var) != -1) { return false; } // Others: // - Variables before 170206 will never send any status-messages // - Variables since 170206 only send status-messages on "big" changes // - Thresholds before 170206 will never send any status-messages // - S0-inputs only send status-messages on "big" changes // (all "big changes" cases force us to poll the status to get faster updates) return true; } /** * Module-generation check. * Checks if the target LCN module would automatically send status-updates if * the given regulator's lock-state was changed by command. * * @param swAge the target LCN-module's firmware version * @param lockState the lock-state sent via command * @return true if a poll is required to get the new status-value */ public static boolean shouldPollStatusAfterRegulatorLock(int swAge, boolean lockState) { // LCN modules before 170206 will send an automatic status-message for "lock", but not for "unlock" return lockState == false && swAge < 0x170206; } } /** Measurement units used with LCN variables. */ public enum VarUnit { NATIVE, // LCN internal representation (0 = -100�C for absolute values) CELSIUS, KELVIN, FAHRENHEIT, LUX_T, LUX_I, METERPERSECOND, // Used for LCN-WIH wind speed PERCENT, // Used for humidity PPM, // Used by CO2 sensor VOLT, AMPERE, DEGREE; // Used for angles /** * Parses the given input text into a variable unit. * * @param input the text to parse * @return the parsed {@link VarValue} * @throws IllegalArgumentException if input could not be parsed */ public static VarUnit parse(String input) throws IllegalArgumentException { switch (input.toUpperCase()) { case "LCN": return NATIVE; case "CELSIUS": case "�CELSIUS": case "�C": return CELSIUS; case "KELVIN": case "�KELVIN": case "�K": return KELVIN; case "FAHRENHEIT": case "�FAHRENHEIT": case "�F": return FAHRENHEIT; case "LUX_T": case "LX_T": return LUX_T; case "LUX": case "LX": return LUX_I; case "M/S": return METERPERSECOND; case "%": return PERCENT; case "PPM": return PPM; case "VOLT": case "V": return VOLT; case "AMPERE": case "AMP": // Also a correct abbreviation case "A": return AMPERE; case "DEGREE": case "�": return DEGREE; default: throw new IllegalArgumentException(); } } } /** * A value of an LCN variable. * <p> * It internally stores the native LCN value and allows to convert from/into other units. * Some conversions allow to specify whether the source value is absolute or relative. * Relative values are used to create {@link VarValue}s that can be added/subtracted from * other (absolute) {@link VarValue}s. */ public static class VarValue { /** The absolute, native LCN value. */ private int nativeValue; /** * Returns the lock-state if value comes from a regulator set-point. * Only valid if {@link Var#isLockableRegulatorSource(Var)} returns true. * * @return the lock state */ public boolean isLockedRegulator() { return (this.nativeValue & 0x8000) != 0; } /** * Constructor with native LCN value. * * @param nativeValue the native value */ private VarValue(int nativeValue) { this.nativeValue = nativeValue; } /** * Creates a variable value from any input. * * @param v the input value * @param unit the input value's unit * @param abs true for absolute values (relative values are used to add/subtract from other {@link VarValue}s) * @return the variable value (never null) */ public static VarValue fromVarUnit(double v, VarUnit unit, boolean abs) { switch (unit) { case NATIVE: return fromNative((int) v); case CELSIUS: return fromCelsius(v, abs); case KELVIN: return fromKelvin(v, abs); case FAHRENHEIT: return fromFahrenheit(v, abs); case LUX_T: return fromLuxT(v); case LUX_I: return fromLuxI(v); case METERPERSECOND: return fromMetersPerSec(v); case PERCENT: return fromPercent(v); case PPM: return fromPpm(v); case VOLT: return fromVolt(v); case AMPERE: return fromAmpere(v); case DEGREE: return fromDegree(v); } throw new Error(); } /** * Creates a variable value from native input. * * @param n the input value * @return the variable value (never null) */ public static VarValue fromNative(int n) { return new VarValue(n); } /** * Creates a variable value from �C input. * * @param c the input value * @param abs true for absolute values (relative values are used to add/subtract from other {@link VarValue}s) * @return the variable value (never null) */ public static VarValue fromCelsius(double c, boolean abs) { int n = (int) Math.round(c * 10); return new VarValue(abs ? n + 1000 : n); } /** * Creates an absolute variable value from �C input. * * @param c the input value * @return the variable value (never null) */ public static VarValue fromCelsius(double c) { return fromCelsius(c, true); } /** * Creates a variable value from �K input. * * @param k the input value * @param abs true for absolute values (relative values are used to add/subtract from other {@link VarValue}s) * @return the variable value (never null) */ public static VarValue fromKelvin(double k, boolean abs) { if (abs) { k -= 273.15; } int n = (int) Math.round(k * 10); return new VarValue(abs ? n + 1000 : n); } /** * Creates an absolute variable value from �K input. * * @param k the input value * @return the variable value (never null) */ public static VarValue fromKelvin(double k) { return fromKelvin(k, true); } /** * Creates a variable value from �F input. * * @param f the input value * @param abs true for absolute values (relative values are used to add/subtract from other {@link VarValue}s) * @return the variable value (never null) */ public static VarValue fromFahrenheit(double f, boolean abs) { if (abs) { f -= 32; } int n = (int) Math.round(f / 0.18); return new VarValue(abs ? n + 1000 : n); } /** * Creates an absolute variable value from �F input. * * @param f the input value * @return the variable value (never null) */ public static VarValue fromFahrenheit(double f) { return fromFahrenheit(f, true); } /** * Creates a variable value from lx input. * Target must be connected to T-port. * * @param l the input value * @return the variable value (never null) */ public static VarValue fromLuxT(double l) { return new VarValue((int) Math.round((Math.log(l) - 1.689646994) / 0.010380664)); } /** * Creates a variable value from lx input. * Target must be connected to I-port. * * @param l the input value * @return the variable value (never null) */ public static VarValue fromLuxI(double l) { return new VarValue((int) Math.round(Math.log(l) * 100)); } /** * Creates a variable value from % input. * * @param p the input value * @return the variable value (never null) */ public static VarValue fromPercent(double p) { return new VarValue((int) Math.round(p)); } /** * Creates a variable value from ppm input. * Used for CO2 sensors. * * @param p the input value * @return the variable value (never null) */ public static VarValue fromPpm(double p) { return new VarValue((int) Math.round(p)); } /** * Creates a variable value from m/s input. * Used for LCN-WIH wind speed. * * @param ms the input value * @return the variable value (never null) */ public static VarValue fromMetersPerSec(double ms) { return new VarValue((int) Math.round(ms * 10)); } /** * Creates a variable value from V input. * * @param v the input value * @return the variable value (never null) */ public static VarValue fromVolt(double v) { return new VarValue((int) Math.round(v * 400)); } /** * Creates a variable value from A input. * * @param a the input value * @return the variable value (never null) */ public static VarValue fromAmpere(double a) { return new VarValue((int) Math.round(a * 100)); } /** * Creates a variable value from � (angle) input. * * @param d the input value * @param abs true for absolute values (relative values are used to add/subtract from other {@link VarValue}s) * @return the variable value (never null) */ public static VarValue fromDegree(double d, boolean abs) { int n = (int) Math.round(d * 10); return new VarValue(abs ? n + 1000 : n); } /** * Creates an absolute variable value from � (angle) input. * * @param d the input value * @return the variable value (never null) */ public static VarValue fromDegree(double d) { return fromDegree(d, true); } /** * Converts the value to double using the given variable unit. * * @param unit the target {@link VarUnit} * @param isLockableRegulatorSource use {@link Var#isLockableRegulatorSource(Var)} * @return the converted value (as double) */ public double toVarUnit(VarUnit unit, boolean isLockableRegulatorSource) { VarValue v = new VarValue(isLockableRegulatorSource ? this.nativeValue & 0x7fff : this.nativeValue); switch (unit) { case NATIVE: return v.toNative(); case CELSIUS: return v.toCelsius(); case KELVIN: return v.toKelvin(); case FAHRENHEIT: return v.toFahrenheit(); case LUX_T: return v.toLuxT(); case LUX_I: return v.toLuxI(); case METERPERSECOND: return v.toMetersPerSec(); case PERCENT: return v.toPercent(); case PPM: return v.toPpm(); case VOLT: return v.toVolt(); case AMPERE: return v.toAmpere(); case DEGREE: return v.toDegree(); } throw new Error(); } /** * Converts to native value. * * @return the converted value */ public int toNative() { return this.nativeValue; } /** * Converts to �C value. * * @return the converted value */ public double toCelsius() { return (double) (this.nativeValue - 1000) / 10; } /** * Converts to �K value. * * @return the converted value */ public double toKelvin() { return (double) (this.nativeValue - 1000) / 10 + 273.15; } /** * Converts to �F value. * * @return the converted value */ public double toFahrenheit() { return (this.nativeValue - 1000) * 0.18 + 32; } /** * Converts to lx value. * Source must be connected to T-port. * * @return the converted value */ public double toLuxT() { return Math.exp(0.010380664 * this.nativeValue + 1.689646994); } /** * Converts to lx value. * Source must be connected to I-port. * * @return the converted value */ public double toLuxI() { return Math.exp((double) this.nativeValue / 100); } /** * Converts to % value. * * @return the converted value */ public double toPercent() { return this.nativeValue; } /** * Converts to ppm value. * * @return the converted value */ public double toPpm() { return this.nativeValue; } /** * Converts to m/s value. * * @return the converted value */ public double toMetersPerSec() { return (double) this.nativeValue / 10; } /** * Converts to V value. * * @return the converted value */ public double toVolt() { return (double) this.nativeValue / 400; } /** * Converts to A value. * * @return the converted value */ public double toAmpere() { return (double) this.nativeValue / 100; } /** * Converts to � (angle) value. * * @return the converted value */ public double toDegree() { return (double) (this.nativeValue - 1000) / 10; } /** * Converts the value to a human readable form using the given variable unit. * * @param unit the target {@link VarUnit} * @param isLockableRegulatorSource use {@link Var#isLockableRegulatorSource(Var)} * @param useLcnSpecialValues use {@link Var#useLcnSpecialValues(Var)} * @return the value as text */ public String toVarUnitString(VarUnit unit, boolean isLockableRegulatorSource, boolean useLcnSpecialValues) { String ret; if (useLcnSpecialValues && this.nativeValue == 0xffff) { // No value ret = "---"; } else if (useLcnSpecialValues && (this.nativeValue & 0xff00) == 0x8100) { // Undefined ret = "---"; } else if (useLcnSpecialValues && (this.nativeValue & 0xff00) == 0x7f00) { // Defective ret = "--- (!!!)"; } else { VarValue v = new VarValue(isLockableRegulatorSource ? this.nativeValue & 0x7fff : this.nativeValue); switch (unit) { case NATIVE: ret = new DecimalFormat("0").format(v.toNative()); break; case CELSIUS: ret = new DecimalFormat("0.0").format(v.toCelsius()); break; case KELVIN: ret = new DecimalFormat("0.0").format(v.toKelvin()); break; case FAHRENHEIT: ret = new DecimalFormat("0.0").format(v.toFahrenheit()); break; case LUX_T: if (v.toNative() > 1152) { // Max. value the HW can do ret = "---"; } else { ret = new DecimalFormat("0").format(v.toLuxT()); } case LUX_I: if (v.toNative() > 1152) { // Max. value the HW can do ret = "---"; } else { ret = new DecimalFormat("0").format(v.toLuxI()); } break; case METERPERSECOND: ret = new DecimalFormat("0").format(v.toMetersPerSec()); break; case PERCENT: ret = new DecimalFormat("0").format(v.toPercent()); break; case PPM: ret = new DecimalFormat("0").format(v.toPpm()); break; case VOLT: ret = new DecimalFormat("0").format(v.toVolt()); break; case AMPERE: ret = new DecimalFormat("0").format(v.toAmpere()); break; case DEGREE: ret = new DecimalFormat("0.0").format(v.toDegree()); break; default: throw new Error(); } // Handle locked regulators if (isLockableRegulatorSource && this.isLockedRegulator()) { ret = "(" + ret + ")"; } } return ret; } } /** Possible states for LCN LEDs. */ public enum LedStatus { OFF, ON, BLINK, FLICKER } /** Possible states for LCN logic-operations. */ public enum LogicOpStatus { NOT, OR, // Note: Actually not correct since AND won't be OR also AND } /** Time units used for several LCN commands. */ public enum TimeUnit { SECONDS, MINUTES, HOURS, DAYS; /** * Parses the given input into a time unit. * It supports several alternative terms. * * @param input the text to parse * @return the parsed {@link TimeUnit} * @throws IllegalArgumentException if input could not be parsed */ public static TimeUnit parse(String input) throws IllegalArgumentException { switch (input.toUpperCase()) { case "SECONDS": case "SECOND": // Allow singular too case "SEC": case "S": return SECONDS; case "MINUTES": case "MINUTE": // Allow singular too case "MIN": case "M": return MINUTES; case "HOURS": case "HOUR": // Allow singular too case "H": return HOURS; case "DAYS": case "DAY": // Allow singular too case "D": return DAYS; } throw new IllegalArgumentException(); } } /** Relay-state modifiers used in LCN commands. */ public enum RelayStateModifier { ON, OFF, TOGGLE, NOCHANGE } /** Value-reference for relative LCN variable commands. */ public enum RelVarRef { CURRENT, PROG // Programmed value (LCN-PRO). Relevant for set-points and thresholds. } /** Command types used when sending LCN keys. */ public enum SendKeyCommand { HIT, MAKE, BREAK, DONTSEND } /** Key-lock modifiers used in LCN commands. */ public enum KeyLockStateModifier { ON, OFF, TOGGLE, NOCHANGE } }