/** * 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.knx.internal.dpt; import java.awt.Color; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import org.openhab.binding.knx.config.KNXTypeMapper; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.StopMoveType; import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.UpDownType; import org.openhab.core.types.Type; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tuwien.auto.calimero.datapoint.Datapoint; import tuwien.auto.calimero.dptxlator.DPT; import tuwien.auto.calimero.dptxlator.DPTXlator; import tuwien.auto.calimero.dptxlator.DPTXlator1BitControlled; import tuwien.auto.calimero.dptxlator.DPTXlator2ByteFloat; import tuwien.auto.calimero.dptxlator.DPTXlator2ByteUnsigned; import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled; import tuwien.auto.calimero.dptxlator.DPTXlator4ByteFloat; import tuwien.auto.calimero.dptxlator.DPTXlator4ByteSigned; import tuwien.auto.calimero.dptxlator.DPTXlator4ByteUnsigned; import tuwien.auto.calimero.dptxlator.DPTXlator8BitSigned; import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned; import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean; import tuwien.auto.calimero.dptxlator.DPTXlatorDate; import tuwien.auto.calimero.dptxlator.DPTXlatorDateTime; import tuwien.auto.calimero.dptxlator.DPTXlatorRGB; import tuwien.auto.calimero.dptxlator.DPTXlatorSceneControl; import tuwien.auto.calimero.dptxlator.DPTXlatorSceneNumber; import tuwien.auto.calimero.dptxlator.DPTXlatorString; import tuwien.auto.calimero.dptxlator.DPTXlatorTime; import tuwien.auto.calimero.dptxlator.TranslatorTypes; import tuwien.auto.calimero.exception.KNXException; import tuwien.auto.calimero.exception.KNXFormatException; import tuwien.auto.calimero.exception.KNXIllegalArgumentException; /** * This class provides type mapping between all openHAB core types and KNX data point types. * * @author Kai Kreuzer * @author Volker Daube * @author Jan N. Klug * @since 0.3.0 * */ public class KNXCoreTypeMapper implements KNXTypeMapper { static private final Logger logger = LoggerFactory.getLogger(KNXCoreTypeMapper.class); static private final String TIME_DAY_FORMAT = new String("EEE, HH:mm:ss"); static private final String DATE_FORMAT = new String("yyyy-MM-dd"); /** stores the openHAB type class for all (supported) KNX datapoint types */ static private Map<String, Class<? extends Type>> dptTypeMap; /** stores the default KNX DPT to use for each openHAB type */ static private Map<Class<? extends Type>, String> defaultDptMap; static { dptTypeMap = new HashMap<String, Class<? extends Type>>(); // Datapoint Types "B1", Main number 1 dptTypeMap.put(DPTXlatorBoolean.DPT_SWITCH.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_BOOL.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_ENABLE.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_RAMP.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_ALARM.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_BINARYVALUE.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_STEP.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_UPDOWN.getID(), UpDownType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_OPENCLOSE.getID(), OpenClosedType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_START.getID(), StopMoveType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_STATE.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_INVERT.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_DIMSENDSTYLE.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_INPUTSOURCE.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_RESET.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_OCCUPANCY.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_WINDOW_DOOR.getID(), OpenClosedType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_LOGICAL_FUNCTION.getID(), OnOffType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_SCENE_AB.getID(), DecimalType.class); dptTypeMap.put(DPTXlatorBoolean.DPT_SHUTTER_BLINDS_MODE.getID(), OnOffType.class); // Datapoint Types "B2", Main number 2 dptTypeMap.put(DPTXlator1BitControlled.DPT_SWITCH_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_BOOL_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_ENABLE_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_RAMP_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_ALARM_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_BINARY_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_STEP_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_UPDOWN_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_OPENCLOSE_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_START_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_STATE_CONTROL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator1BitControlled.DPT_INVERT_CONTROL.getID(), DecimalType.class); // Datapoint Types "B1U3", Main number 3 dptTypeMap.put(DPTXlator3BitControlled.DPT_CONTROL_DIMMING.getID(), IncreaseDecreaseType.class); // Datapoint Types "8-Bit Unsigned Value", Main number 5 dptTypeMap.put(DPTXlator8BitUnsigned.DPT_SCALING.getID(), PercentType.class); dptTypeMap.put(DPTXlator8BitUnsigned.DPT_ANGLE.getID(), DecimalType.class); dptTypeMap.put(DPTXlator8BitUnsigned.DPT_PERCENT_U8.getID(), DecimalType.class); dptTypeMap.put(DPTXlator8BitUnsigned.DPT_DECIMALFACTOR.getID(), DecimalType.class); dptTypeMap.put(DPTXlator8BitUnsigned.DPT_TARIFF.getID(), DecimalType.class); dptTypeMap.put(DPTXlator8BitUnsigned.DPT_VALUE_1_UCOUNT.getID(), DecimalType.class); // Datapoint Types "8-bit Signed Value", Main number 6 dptTypeMap.put(DPTXlator8BitSigned.DPT_PERCENT_V8.getID(), DecimalType.class); dptTypeMap.put(DPTXlator8BitSigned.DPT_VALUE_1_UCOUNT.getID(), DecimalType.class); dptTypeMap.put(DPTXlator8BitSigned.DPT_STATUS_MODE3.getID(), DecimalType.class); // Datapoint Types "2-Octet Unsigned Value", Main number 7 dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_VALUE_2_UCOUNT.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_10.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_100.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_SEC.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_MIN.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_TIMEPERIOD_HOURS.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_PROP_DATATYPE.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_LENGTH.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_ELECTRICAL_CURRENT.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteUnsigned.DPT_BRIGHTNESS.getID(), DecimalType.class); // Datapoint Types "2-Octet Float Value", Main number 9 dptTypeMap.put(DPTXlator2ByteFloat.DPT_TEMPERATURE.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_TEMPERATURE_DIFFERENCE.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_TEMPERATURE_GRADIENT.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_INTENSITY_OF_LIGHT.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_WIND_SPEED.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_AIR_PRESSURE.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_HUMIDITY.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_AIRQUALITY.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_TIME_DIFFERENCE1.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_TIME_DIFFERENCE2.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_VOLTAGE.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_ELECTRICAL_CURRENT.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_POWERDENSITY.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_KELVIN_PER_PERCENT.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_POWER.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_VOLUME_FLOW.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_RAIN_AMOUNT.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_TEMP_F.getID(), DecimalType.class); dptTypeMap.put(DPTXlator2ByteFloat.DPT_WIND_SPEED_KMH.getID(), DecimalType.class); // Datapoint Types "Time", Main number 10 dptTypeMap.put(DPTXlatorTime.DPT_TIMEOFDAY.getID(), DateTimeType.class); // Datapoint Types “Date”", Main number 11 dptTypeMap.put(DPTXlatorDate.DPT_DATE.getID(), DateTimeType.class); // Datapoint Types "4-Octet Unsigned Value", Main number 12 dptTypeMap.put(DPTXlator4ByteUnsigned.DPT_VALUE_4_UCOUNT.getID(), DecimalType.class); // Datapoint Types "4-Octet Signed Value", Main number 13 dptTypeMap.put(DPTXlator4ByteSigned.DPT_COUNT.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteSigned.DPT_FLOWRATE.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteSigned.DPT_ACTIVE_ENERGY.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteSigned.DPT_APPARENT_ENERGY.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteSigned.DPT_REACTIVE_ENERGY.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteSigned.DPT_ACTIVE_ENERGY_KWH.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteSigned.DPT_APPARENT_ENERGY_KVAH.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteSigned.DPT_REACTIVE_ENERGY_KVARH.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteSigned.DPT_DELTA_TIME.getID(), DecimalType.class); // Datapoint Types "4-Octet Float Value", Main number 14 dptTypeMap.put(DPTXlator4ByteFloat.DPT_ACCELERATION_ANGULAR.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteFloat.DPT_ANGLE_DEG.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteFloat.DPT_ELECTRIC_CURRENT.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteFloat.DPT_ELECTRIC_POTENTIAL.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteFloat.DPT_FREQUENCY.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteFloat.DPT_POWER.getID(), DecimalType.class); dptTypeMap.put(DPTXlator4ByteFloat.DPT_PRESSURE.getID(), DecimalType.class); // Datapoint Types "String", Main number 16 dptTypeMap.put(DPTXlatorString.DPT_STRING_8859_1.getID(), StringType.class); // Datapoint Types "Scene Number", Main number 17 dptTypeMap.put(DPTXlatorSceneNumber.DPT_SCENE_NUMBER.getID(), DecimalType.class); // Datapoint Types "Scene Control", Main number 18 dptTypeMap.put(DPTXlatorSceneControl.DPT_SCENE_CONTROL.getID(), DecimalType.class); // Datapoint Types "DateTime", Main number 19 dptTypeMap.put(DPTXlatorDateTime.DPT_DATE_TIME.getID(), DateTimeType.class); // Datapoint Types "RGB Color", Main number 232 dptTypeMap.put(DPTXlatorRGB.DPT_RGB.getID(), HSBType.class); defaultDptMap = new HashMap<Class<? extends Type>, String>(); defaultDptMap.put(OnOffType.class, DPTXlatorBoolean.DPT_SWITCH.getID()); defaultDptMap.put(UpDownType.class, DPTXlatorBoolean.DPT_UPDOWN.getID()); defaultDptMap.put(StopMoveType.class, DPTXlatorBoolean.DPT_START.getID()); defaultDptMap.put(OpenClosedType.class, DPTXlatorBoolean.DPT_WINDOW_DOOR.getID()); defaultDptMap.put(IncreaseDecreaseType.class, DPTXlator3BitControlled.DPT_CONTROL_DIMMING.getID()); defaultDptMap.put(PercentType.class, DPTXlator8BitUnsigned.DPT_SCALING.getID()); defaultDptMap.put(DecimalType.class, DPTXlator2ByteFloat.DPT_TEMPERATURE.getID()); defaultDptMap.put(DateTimeType.class, DPTXlatorTime.DPT_TIMEOFDAY.getID()); defaultDptMap.put(StringType.class, DPTXlatorString.DPT_STRING_8859_1.getID()); defaultDptMap.put(HSBType.class, DPTXlatorRGB.DPT_RGB.getID()); } /* * (non-Javadoc) * * @see org.openhab.binding.knx.config.KNXTypeMapper#toDPTValue(org.openhab.core.types.Type, java.lang.String) */ @Override public String toDPTValue(Type type, String dptID) { DPT dpt; int mainNumber = getMainNumber(dptID); if (mainNumber == -1) { logger.error("toDPTValue couldn't identify mainnumber in dptID: {}", dptID); return null; } try { DPTXlator translator = TranslatorTypes.createTranslator(mainNumber, dptID); dpt = translator.getType(); } catch (KNXException e) { e.printStackTrace(); return null; } // check for HSBType first, because it extends PercentType as well if (type instanceof HSBType) { Color color = ((HSBType) type).toColor(); return "r:" + Integer.toString(color.getRed()) + " g:" + Integer.toString(color.getGreen()) + " b:" + Integer.toString(color.getBlue()); } else if (type instanceof OnOffType) { return type.equals(OnOffType.OFF) ? dpt.getLowerValue() : dpt.getUpperValue(); } else if (type instanceof UpDownType) { return type.equals(UpDownType.UP) ? dpt.getLowerValue() : dpt.getUpperValue(); } else if (type instanceof IncreaseDecreaseType) { DPT valueDPT = ((DPTXlator3BitControlled.DPT3BitControlled) dpt).getControlDPT(); return type.equals(IncreaseDecreaseType.DECREASE) ? valueDPT.getLowerValue() + " 5" : valueDPT.getUpperValue() + " 5"; } else if (type instanceof OpenClosedType) { return type.equals(OpenClosedType.CLOSED) ? dpt.getLowerValue() : dpt.getUpperValue(); } else if (type instanceof StopMoveType) { return type.equals(StopMoveType.STOP) ? dpt.getLowerValue() : dpt.getUpperValue(); } else if (type instanceof PercentType) { return type.toString(); } else if (type instanceof DecimalType) { switch (mainNumber) { case 2: DPT valueDPT = ((DPTXlator1BitControlled.DPT1BitControlled) dpt).getValueDPT(); switch (((DecimalType) type).intValue()) { case 0: return "0 " + valueDPT.getLowerValue(); case 1: return "0 " + valueDPT.getUpperValue(); case 2: return "1 " + valueDPT.getLowerValue(); default: return "1 " + valueDPT.getUpperValue(); } case 18: int intVal = ((DecimalType) type).intValue(); if (intVal > 63) { return "learn " + (intVal - 0x80); } else { return "activate " + intVal; } default: return type.toString(); } } else if (type instanceof StringType) { return type.toString(); } else if (type instanceof DateTimeType) { return formatDateTime((DateTimeType) type, dptID); } logger.debug("toDPTValue: Couldn't get value for {} dpt id {} (no mapping).", type, dptID); return null; } /* * (non-Javadoc) * * @see org.openhab.binding.knx.config.KNXTypeMapper#toType(tuwien.auto.calimero.datapoint.Datapoint, byte[]) */ @Override public Type toType(Datapoint datapoint, byte[] data) { try { DPTXlator translator = TranslatorTypes.createTranslator(datapoint.getMainNumber(), datapoint.getDPT()); translator.setData(data); String value = translator.getValue(); String id = translator.getType().getID(); logger.trace("toType datapoint DPT = " + datapoint.getDPT()); int mainNumber = getMainNumber(id); if (mainNumber == -1) { logger.debug("toType: couldn't identify mainnumber in dptID: {}.", id); return null; } int subNumber = getSubNumber(id); if (subNumber == -1) { logger.debug("toType: couldn't identify sub number in dptID: {}.", id); return null; } /* * Following code section deals with specific mapping of values from KNX to openHAB types were the String * received from the DPTXlator is not sufficient to set the openHAB type or has bugs */ switch (mainNumber) { case 1: DPTXlatorBoolean translatorBoolean = (DPTXlatorBoolean) translator; switch (subNumber) { case 8: return translatorBoolean.getValueBoolean() ? UpDownType.DOWN : UpDownType.UP; case 9: return translatorBoolean.getValueBoolean() ? OpenClosedType.OPEN : OpenClosedType.CLOSED; case 10: return translatorBoolean.getValueBoolean() ? StopMoveType.MOVE : StopMoveType.STOP; case 19: return translatorBoolean.getValueBoolean() ? OpenClosedType.OPEN : OpenClosedType.CLOSED; case 22: return DecimalType.valueOf(translatorBoolean.getValueBoolean() ? "1" : "0"); default: return translatorBoolean.getValueBoolean() ? OnOffType.ON : OnOffType.OFF; } case 2: DPTXlator1BitControlled translator1BitControlled = (DPTXlator1BitControlled) translator; int decValue = (translator1BitControlled.getControlBit() ? 2 : 0) + (translator1BitControlled.getValueBit() ? 1 : 0); return new DecimalType(decValue); case 3: DPTXlator3BitControlled translator3BitControlled = (DPTXlator3BitControlled) translator; if (translator3BitControlled.getStepCode() == 0) { /* * there is no STOP for a IncreaseDecreaseType, so we are just using an INCREASE. * It is up to the binding to recognize that a start/stop-dimming is in progress and * stop the dimming accordingly. */ logger.debug("toType: KNX DPT_Control_Dimming: break received."); return IncreaseDecreaseType.INCREASE; } switch (subNumber) { case 7: return translator3BitControlled.getControlBit() ? IncreaseDecreaseType.INCREASE : IncreaseDecreaseType.DECREASE; case 8: return translator3BitControlled.getControlBit() ? UpDownType.DOWN : UpDownType.UP; } case 14: /* * FIXME: Workaround for a bug in Calimero / Openhab DPTXlator4ByteFloat.makeString(): is using a * locale when * translating a Float to String. It could happen the a ',' is used as separator, such as * 3,14159E20. * Openhab's DecimalType expects this to be in US format and expects '.': 3.14159E20. * There is no issue with DPTXlator2ByteFloat since calimero is using a non-localized translation * there. */ DPTXlator4ByteFloat translator4ByteFloat = (DPTXlator4ByteFloat) translator; Float f = translator4ByteFloat.getValueFloat(); if (Math.abs(f) < 100000) { value = String.valueOf(f); } else { NumberFormat dcf = NumberFormat.getInstance(Locale.US); if (dcf instanceof DecimalFormat) { ((DecimalFormat) dcf).applyPattern("0.#####E0"); } value = dcf.format(f); } break; case 18: DPTXlatorSceneControl translatorSceneControl = (DPTXlatorSceneControl) translator; int decimalValue = translatorSceneControl.getSceneNumber(); if (value.startsWith("learn")) { decimalValue += 0x80; } value = String.valueOf(decimalValue); break; case 19: DPTXlatorDateTime translatorDateTime = (DPTXlatorDateTime) translator; if (translatorDateTime.isFaultyClock()) { // Not supported: faulty clock logger.debug("toType: KNX clock msg ignored: clock faulty bit set, which is not supported"); return null; } else if (!translatorDateTime.isValidField(DPTXlatorDateTime.YEAR) && translatorDateTime.isValidField(DPTXlatorDateTime.DATE)) { // Not supported: "/1/1" (month and day without year) logger.debug( "toType: KNX clock msg ignored: no year, but day and month, which is not supported"); return null; } else if (translatorDateTime.isValidField(DPTXlatorDateTime.YEAR) && !translatorDateTime.isValidField(DPTXlatorDateTime.DATE)) { // Not supported: "1900" (year without month and day) logger.debug( "toType: KNX clock msg ignored: no day and month, but year, which is not supported"); return null; } else if (!translatorDateTime.isValidField(DPTXlatorDateTime.YEAR) && !translatorDateTime.isValidField(DPTXlatorDateTime.DATE) && !translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) { // Not supported: No year, no date and no time logger.debug("toType: KNX clock msg ignored: no day and month or year, which is not supported"); return null; } Calendar cal = Calendar.getInstance(); if (translatorDateTime.isValidField(DPTXlatorDateTime.YEAR) && !translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) { // Pure date format, no time information cal.setTimeInMillis(translatorDateTime.getValueMilliseconds()); value = new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(cal.getTime()); return DateTimeType.valueOf(value); } else if (!translatorDateTime.isValidField(DPTXlatorDateTime.YEAR) && translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) { // Pure time format, no date information cal.clear(); cal.set(Calendar.HOUR_OF_DAY, translatorDateTime.getHour()); cal.set(Calendar.MINUTE, translatorDateTime.getMinute()); cal.set(Calendar.SECOND, translatorDateTime.getSecond()); value = new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(cal.getTime()); return DateTimeType.valueOf(value); } else if (translatorDateTime.isValidField(DPTXlatorDateTime.YEAR) && translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) { // Date format and time information cal.setTimeInMillis(translatorDateTime.getValueMilliseconds()); value = new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(cal.getTime()); return DateTimeType.valueOf(value); } break; } Class<? extends Type> typeClass = toTypeClass(id); if (typeClass == null) { return null; } if (typeClass.equals(PercentType.class)) { return PercentType.valueOf(value.split(" ")[0]); } if (typeClass.equals(DecimalType.class)) { return DecimalType.valueOf(value.split(" ")[0]); } if (typeClass.equals(StringType.class)) { return StringType.valueOf(value); } if (typeClass.equals(DateTimeType.class)) { String date = formatDateTime(value, datapoint.getDPT()); if ((date == null) || (date.isEmpty())) { logger.debug("toType: KNX clock msg ignored: date object null or empty {}.", date); return null; } else { return DateTimeType.valueOf(date); } } if (typeClass.equals(HSBType.class)) { // value has format of "r:<red value> g:<green value> b:<blue value>" int r = Integer.parseInt(value.split(" ")[0].split(":")[1]); int g = Integer.parseInt(value.split(" ")[1].split(":")[1]); int b = Integer.parseInt(value.split(" ")[2].split(":")[1]); Color color = new Color(r, g, b); return new HSBType(color); } } catch (KNXFormatException kfe) { logger.info("Translator couldn't parse data for datapoint type '{}' (KNXFormatException).", datapoint.getDPT()); } catch (KNXIllegalArgumentException kiae) { logger.info("Translator couldn't parse data for datapoint type '{}' (KNXIllegalArgumentException).", datapoint.getDPT()); } catch (KNXException e) { logger.warn("Failed creating a translator for datapoint type '{}'.", datapoint.getDPT(), e); } return null; } /** * Converts a datapoint type id into an openHAB type class * * @param dptId the datapoint type id * @return the openHAB type (command or state) class or {@code null} if the datapoint type id is not supported. */ static public Class<? extends Type> toTypeClass(String dptId) { logger.trace("toTypeClass looking for dptId = " + dptId); return dptTypeMap.get(dptId); } /** * Converts an openHAB type class into a datapoint type id. * * @param typeClass the openHAB type class * @return the datapoint type id */ static public String toDPTid(Class<? extends Type> typeClass) { return defaultDptMap.get(typeClass); } /** * Formats the given <code>value</code> according to the datapoint type * <code>dpt</code> to a String which can be processed by {@link DateTimeType}. * * @param value * @param dpt * * @return a formatted String like </code>yyyy-MM-dd'T'HH:mm:ss</code> which * is target format of the {@link DateTimeType} */ private String formatDateTime(String value, String dpt) { Date date = null; try { if (DPTXlatorDate.DPT_DATE.getID().equals(dpt)) { date = new SimpleDateFormat(DATE_FORMAT).parse(value); } else if (DPTXlatorTime.DPT_TIMEOFDAY.getID().equals(dpt)) { if (value.contains("no-day")) { /* * KNX "no-day" needs special treatment since openHAB's DateTimeType doesn't support "no-day". * Workaround: remove the "no-day" String, parse the remaining time string, which will result in a * date of "1970-01-01". * Replace "no-day" with the current day name */ StringBuffer stb = new StringBuffer(value); int start = stb.indexOf("no-day"); int end = start + "no-day".length(); stb.replace(start, end, String.format(Locale.US, "%1$ta", Calendar.getInstance())); value = stb.toString(); } date = new SimpleDateFormat(TIME_DAY_FORMAT, Locale.US).parse(value); } } catch (ParseException pe) { // do nothing but logging logger.warn("Could not parse '{}' to a valid date", value); } return date != null ? new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(date) : ""; } /** * Formats the given internal <code>dateType</code> to a knx readable String * according to the target datapoint type <code>dpt</code>. * * @param dateType * @param dpt the target datapoint type * * @return a String which contains either an ISO8601 formatted date (yyyy-mm-dd), * a formatted 24-hour clock with the day of week prepended (Mon, 12:00:00) or * a formatted 24-hour clock (12:00:00) * * @throws IllegalArgumentException if none of the datapoint types DPT_DATE or * DPT_TIMEOFDAY has been used. */ static private String formatDateTime(DateTimeType dateType, String dpt) { if (DPTXlatorDate.DPT_DATE.getID().equals(dpt)) { return dateType.format("%tF"); } else if (DPTXlatorTime.DPT_TIMEOFDAY.getID().equals(dpt)) { return dateType.format(Locale.US, "%1$ta, %1$tT"); } else if (DPTXlatorDateTime.DPT_DATE_TIME.getID().equals(dpt)) { return dateType.format(Locale.US, "%tF %1$tT"); } else { throw new IllegalArgumentException("Could not format date to datapoint type '" + dpt + "'"); } } /** * Retrieves sub number from a DTP ID such as "14.001" * * @param dptID String with DPT ID * @return sub number or -1 */ private int getSubNumber(String dptID) { int result = -1; if (dptID == null) { throw new IllegalArgumentException("Parameter dptID cannot be null"); } int dptSepratorPosition = dptID.indexOf('.'); if (dptSepratorPosition > 0) { try { result = Integer.parseInt(dptID.substring(dptSepratorPosition + 1, dptID.length())); } catch (NumberFormatException nfe) { logger.error("toType couldn't identify main and/or sub number in dptID (NumberFormatException): {}", dptID); } catch (IndexOutOfBoundsException ioobe) { logger.error("toType couldn't identify main and/or sub number in dptID (IndexOutOfBoundsException): {}", dptID); } } return result; } /** * Retrieves main number from a DTP ID such as "14.001" * * @param dptID String with DPT ID * @return main number or -1 */ private int getMainNumber(String dptID) { int result = -1; if (dptID == null) { throw new IllegalArgumentException("Parameter dptID cannot be null"); } int dptSepratorPosition = dptID.indexOf('.'); if (dptSepratorPosition > 0) { try { result = Integer.parseInt(dptID.substring(0, dptSepratorPosition)); } catch (NumberFormatException nfe) { logger.error("toType couldn't identify main and/or sub number in dptID (NumberFormatException): {}", dptID); } catch (IndexOutOfBoundsException ioobe) { logger.error("toType couldn't identify main and/or sub number in dptID (IndexOutOfBoundsException): {}", dptID); } } return result; } }