]> git.basschouten.com Git - openhab-addons.git/blob
bc2b18e31512a90692d854c9b51700d60abed06c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.knx.internal.dpt;
14
15 import java.math.BigDecimal;
16 import java.math.RoundingMode;
17 import java.text.DecimalFormat;
18 import java.text.NumberFormat;
19 import java.text.ParseException;
20 import java.text.SimpleDateFormat;
21 import java.util.Arrays;
22 import java.util.Calendar;
23 import java.util.Date;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.knx.internal.KNXTypeMapper;
32 import org.openhab.core.library.types.DateTimeType;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.HSBType;
35 import org.openhab.core.library.types.IncreaseDecreaseType;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.library.types.OpenClosedType;
38 import org.openhab.core.library.types.PercentType;
39 import org.openhab.core.library.types.QuantityType;
40 import org.openhab.core.library.types.StopMoveType;
41 import org.openhab.core.library.types.StringType;
42 import org.openhab.core.library.types.UpDownType;
43 import org.openhab.core.types.Type;
44 import org.openhab.core.types.UnDefType;
45 import org.osgi.service.component.annotations.Component;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import tuwien.auto.calimero.KNXException;
50 import tuwien.auto.calimero.KNXFormatException;
51 import tuwien.auto.calimero.KNXIllegalArgumentException;
52 import tuwien.auto.calimero.datapoint.Datapoint;
53 import tuwien.auto.calimero.dptxlator.DPT;
54 import tuwien.auto.calimero.dptxlator.DPTXlator;
55 import tuwien.auto.calimero.dptxlator.DPTXlator1BitControlled;
56 import tuwien.auto.calimero.dptxlator.DPTXlator2ByteFloat;
57 import tuwien.auto.calimero.dptxlator.DPTXlator2ByteUnsigned;
58 import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled;
59 import tuwien.auto.calimero.dptxlator.DPTXlator4ByteFloat;
60 import tuwien.auto.calimero.dptxlator.DPTXlator4ByteSigned;
61 import tuwien.auto.calimero.dptxlator.DPTXlator4ByteUnsigned;
62 import tuwien.auto.calimero.dptxlator.DPTXlator64BitSigned;
63 import tuwien.auto.calimero.dptxlator.DPTXlator8BitSigned;
64 import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
65 import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
66 import tuwien.auto.calimero.dptxlator.DPTXlatorDate;
67 import tuwien.auto.calimero.dptxlator.DPTXlatorDateTime;
68 import tuwien.auto.calimero.dptxlator.DPTXlatorRGB;
69 import tuwien.auto.calimero.dptxlator.DPTXlatorSceneControl;
70 import tuwien.auto.calimero.dptxlator.DPTXlatorSceneNumber;
71 import tuwien.auto.calimero.dptxlator.DPTXlatorString;
72 import tuwien.auto.calimero.dptxlator.DPTXlatorTime;
73 import tuwien.auto.calimero.dptxlator.DPTXlatorUtf8;
74 import tuwien.auto.calimero.dptxlator.TranslatorTypes;
75
76 /**
77  * This class provides type mapping between all openHAB core types and KNX data point types.
78  *
79  * Each 'MainType' delivered from calimero, has a default mapping
80  * for all it's children to an openHAB Typeclass.
81  * All these 'MainType' mapping's are put into 'dptMainTypeMap'.
82  *
83  * Default 'MainType' mapping's we can override by a specific mapping.
84  * All specific mapping's are put into 'dptTypeMap'.
85  *
86  * If for a 'MainType' there is currently no specific mapping registered,
87  * you can find a commented example line, with it's correct 'DPTXlator' class.
88  *
89  * @author Kai Kreuzer - initial contribution
90  * @author Volker Daube - improvements
91  * @author Jan N. Klug - improvements
92  * @author Helmut Lehmeyer - Java8, generic DPT Mapper
93  */
94 @NonNullByDefault
95 @Component
96 public class KNXCoreTypeMapper implements KNXTypeMapper {
97
98     private final Logger logger = LoggerFactory.getLogger(KNXCoreTypeMapper.class);
99
100     private static final String TIME_DAY_FORMAT = new String("EEE, HH:mm:ss");
101     private static final String TIME_FORMAT = new String("HH:mm:ss");
102     private static final String DATE_FORMAT = new String("yyyy-MM-dd");
103
104     /**
105      * stores the openHAB type class for (supported) KNX datapoint types in a generic way.
106      * dptTypeMap stores more specific type class and exceptions.
107      */
108     private final Map<Integer, Class<? extends Type>> dptMainTypeMap;
109
110     /** stores the openHAB type class for all (supported) KNX datapoint types */
111     private final Map<String, Class<? extends Type>> dptTypeMap;
112
113     /** stores the default KNX DPT to use for each openHAB type */
114     private final Map<Class<? extends Type>, String> defaultDptMap;
115
116     public KNXCoreTypeMapper() {
117         @SuppressWarnings("unused")
118         final List<Class<?>> xlators = Arrays.<Class<?>> asList(DPTXlator1BitControlled.class,
119                 DPTXlator2ByteFloat.class, DPTXlator2ByteUnsigned.class, DPTXlator3BitControlled.class,
120                 DPTXlator4ByteFloat.class, DPTXlator4ByteSigned.class, DPTXlator4ByteUnsigned.class,
121                 DPTXlator64BitSigned.class, DPTXlator8BitSigned.class, DPTXlator8BitUnsigned.class,
122                 DPTXlatorBoolean.class, DPTXlatorDate.class, DPTXlatorDateTime.class, DPTXlatorRGB.class,
123                 DPTXlatorSceneControl.class, DPTXlatorSceneNumber.class, DPTXlatorString.class, DPTXlatorTime.class,
124                 DPTXlatorUtf8.class);
125
126         dptTypeMap = new HashMap<>();
127         dptMainTypeMap = new HashMap<>();
128
129         /**
130          * MainType: 1
131          * 1.000: General bool
132          * 1.001: DPT_Switch values: 0 = off 1 = on
133          * 1.002: DPT_Bool values: 0 = false 1 = true
134          * 1.003: DPT_Enable values: 0 = disable 1 = enable
135          * 1.004: DPT_Ramp values: 0 = no ramp 1 = ramp
136          * 1.005: DPT_Alarm values: 0 = no alarm 1 = alarm
137          * 1.006: DPT_BinaryValue values: 0 = low 1 = high
138          * 1.007: DPT_Step values: 0 = decrease 1 = increase
139          * 1.008: DPT_UpDown values: 0 = up 1 = down
140          * 1.009: DPT_OpenClose values: 0 = open 1 = close
141          * 1.010: DPT_Start values: 0 = stop 1 = start
142          * 1.011: DPT_State values: 0 = inactive 1 = active
143          * 1.012: DPT_Invert values: 0 = not inverted 1 = inverted
144          * 1.013: DPT_DimSendStyle values: 0 = start/stop 1 = cyclic
145          * 1.014: DPT_InputSource values: 0 = fixed 1 = calculated
146          * 1.015: DPT_Reset values: 0 = no action 1 = reset
147          * 1.016: DPT_Ack values: 0 = no action 1 = acknowledge
148          * 1.017: DPT_Trigger values: 0 = trigger 1 = trigger
149          * 1.018: DPT_Occupancy values: 0 = not occupied 1 = occupied
150          * 1.019: DPT_Window_Door values: 0 = closed 1 = open
151          * 1.021: DPT_LogicalFunction values: 0 = OR 1 = AND
152          * 1.022: DPT_Scene_AB values: 0 = scene A 1 = scene B
153          * 1.023: DPT_ShutterBlinds_Mode values: 0 = only move up/down 1 = move up/down + step-stop
154          * 1.100: DPT_Heat/Cool values: 0 = cooling 1 = heating
155          */
156         dptMainTypeMap.put(1, OnOffType.class);
157         /** Exceptions Datapoint Types "B1", Main number 1 */
158         dptTypeMap.put(DPTXlatorBoolean.DPT_UPDOWN.getID(), UpDownType.class);
159         dptTypeMap.put(DPTXlatorBoolean.DPT_OPENCLOSE.getID(), OpenClosedType.class);
160         dptTypeMap.put(DPTXlatorBoolean.DPT_START.getID(), StopMoveType.class);
161         dptTypeMap.put(DPTXlatorBoolean.DPT_WINDOW_DOOR.getID(), OpenClosedType.class);
162         dptTypeMap.put(DPTXlatorBoolean.DPT_SCENE_AB.getID(), DecimalType.class);
163
164         /**
165          * MainType: 2
166          * 2.001: DPT_Switch_Control values: 0 = off 1 = on
167          * 2.002: DPT_Bool_Control values: 0 = false 1 = true
168          * 2.003: DPT_Enable_Control values: 0 = disable 1 = enable
169          * 2.004: DPT_Ramp_Control values: 0 = no ramp 1 = ramp
170          * 2.005: DPT_Alarm_Control values: 0 = no alarm 1 = alarm
171          * 2.006: DPT_BinaryValue_Control values: 0 = low 1 = high
172          * 2.007: DPT_Step_Control values: 0 = decrease 1 = increase
173          * 2.008: DPT_Direction1_Control values: 0 = up 1 = down
174          * 2.009: DPT_Direction2_Control values: 0 = open 1 = close
175          * 2.010: DPT_Start_Control values: 0 = stop 1 = start
176          * 2.011: DPT_State_Control values: 0 = inactive 1 = active
177          * 2.012: DPT_Invert_Control values: 0 = not inverted 1 = inverted
178          */
179         dptMainTypeMap.put(2, DecimalType.class);
180         /** Exceptions Datapoint Types "B2", Main number 2 */
181         // Example: dptTypeMap.put(DPTXlator1BitControlled.DPT_SWITCH_CONTROL.getID(), DecimalType.class);
182
183         /**
184          * MainType: 3
185          * 3.007: DPT_Control_Dimming values: 0 = decrease 1 = increase
186          * 3.008: DPT_Control_Blinds values: 0 = up 1 = down
187          */
188         dptMainTypeMap.put(3, IncreaseDecreaseType.class);
189         /** Exceptions Datapoint Types "B1U3", Main number 3 */
190         dptTypeMap.put(DPTXlator3BitControlled.DPT_CONTROL_BLINDS.getID(), UpDownType.class);
191
192         /**
193          * MainType: 4
194          * 4.001: DPT_Char_ASCII
195          * 4.002: DPT_Char_8859_1
196          */
197         dptMainTypeMap.put(4, StringType.class);
198
199         /**
200          * MainType: 5
201          * 5.000: General byte
202          * 5.001: DPT_Scaling values: 0...100 %
203          * 5.003: DPT_Angle values: 0...360 °
204          * 5.004: DPT_Percent_U8 (8 Bit) values: 0...255 %
205          * 5.005: DPT_DecimalFactor values: 0...255 ratio
206          * 5.006: DPT_Tariff values: 0...254
207          * 5.010: DPT_Value_1_Ucount Unsigned count values: 0...255 counter pulses
208          */
209         dptMainTypeMap.put(5, DecimalType.class);
210         /** Exceptions Types "8-Bit Unsigned Value", Main number 5 */
211         dptTypeMap.put(DPTXlator8BitUnsigned.DPT_SCALING.getID(), PercentType.class);
212         dptTypeMap.put(DPTXlator8BitUnsigned.DPT_PERCENT_U8.getID(), PercentType.class);
213
214         /**
215          * MainType: 6
216          * 6.001: DPT_Percent_V8 (8 Bit) values: -128...127 %
217          * 6.010: DPT_Value_1_Count values: signed -128...127 counter pulses
218          * 6.020: DPT_Status_Mode3 with mode values: 0/0/0/0/0 0...1/1/1/1/1 2
219          */
220         dptMainTypeMap.put(6, DecimalType.class);
221         /** Exceptions Datapoint Types "8-Bit Signed Value", Main number 6 */
222         dptTypeMap.put(DPTXlator8BitSigned.DPT_PERCENT_V8.getID(), PercentType.class);
223         dptTypeMap.put(DPTXlator8BitSigned.DPT_STATUS_MODE3.getID(), StringType.class);
224
225         /**
226          * MainType: 7
227          * 7.000: General unsigned integer
228          * 7.001: DPT_Value_2_Ucount values: 0...65535 pulses
229          * 7.002: DPT_TimePeriodMsec values: 0...65535 res 1 ms
230          * 7.003: DPT_TimePeriod10MSec values: 0...655350 res 10 ms
231          * 7.004: DPT_TimePeriod100MSec values: 0...6553500 res 100 ms
232          * 7.005: DPT_TimePeriodSec values: 0...65535 s
233          * 7.006: DPT_TimePeriodMin values: 0...65535 min
234          * 7.007: DPT_TimePeriodHrs values: 0...65535 h
235          * 7.010: DPT_PropDataType values: 0...65535
236          * 7.011: DPT_Length_mm values: 0...65535 mm
237          * 7.012: DPT_UElCurrentmA values: 0...65535 mA
238          * 7.013: DPT_Brightness values: 0...65535 lx
239          * 7.600: DPT_Colour_Temperature values: 0...65535 K, 2000K 3000K 5000K 8000K
240          */
241         dptMainTypeMap.put(7, DecimalType.class);
242         /** Exceptions Datapoint Types "2-Octet Unsigned Value", Main number 7 */
243         dptTypeMap.put(DPTXlator2ByteFloat.DPT_HUMIDITY.getID(), PercentType.class);
244
245         /**
246          * MainType: 8
247          * 8.000: General integer
248          * 8.001: DPT_Value_2_Count
249          * 8.002: DPT_DeltaTimeMsec
250          * 8.003: DPT_DeltaTime10MSec
251          * 8.004: DPT_DeltaTime100MSec
252          * 8.005: DPT_DeltaTimeSec
253          * 8.006: DPT_DeltaTimeMin
254          * 8.007: DPT_DeltaTimeHrs
255          * 8.010: DPT_Percent_V16
256          * 8.011: DPT_Rotation_Angle
257          * 8.012: DPT_Length_m
258          */
259         dptMainTypeMap.put(8, DecimalType.class);
260
261         /**
262          * MainType: 9
263          * 9.000: General float
264          * 9.001: DPT_Value_Temp values: -273...+670760 °C
265          * 9.002: DPT_Value_Tempd values: -670760...+670760 K
266          * 9.003: DPT_Value_Tempa values: -670760...+670760 K/h
267          * 9.004: DPT_Value_Lux values: 0...+670760 lx
268          * 9.005: DPT_Value_Wsp values: 0...+670760 m/s
269          * 9.006: DPT_Value_Pres values: 0...+670760 Pa
270          * 9.007: DPT_Value_Humidity values: 0...+670760 %
271          * 9.008: DPT_Value_AirQuality values: 0...+670760 ppm
272          * 9.010: DPT_Value_Time1 values: -670760...+670760 s
273          * 9.011: DPT_Value_Time2 values: -670760...+670760 ms
274          * 9.020: DPT_Value_Volt values: -670760...+670760 mV
275          * 9.021: DPT_Value_Curr values: -670760...+670760 mA
276          * 9.022: DPT_PowerDensity values: -670760...+670760 W/m²
277          * 9.023: DPT_KelvinPerPercent values: -670760...+670760 K/%
278          * 9.024: DPT_Power values: -670760...+670760 kW
279          * 9.025: DPT_Value_Volume_Flow values: -670760...+670760 l/h
280          * 9.026: DPT_Rain_Amount values: -671088.64...670760.96 l/m²
281          * 9.027: DPT_Value_Temp_F values: -459.6...670760.96 °F
282          * 9.028: DPT_Value_Wsp_kmh values: 0...670760.96 km/h
283          * 9.029: DPT_Value_Absolute_Humidity: 0...670760 g/m³
284          * 9.030: DPT_Concentration_μgm3: 0...670760 µg/m³
285          */
286         dptMainTypeMap.put(9, DecimalType.class);
287         /** Exceptions Datapoint Types "2-Octet Float Value", Main number 9 */
288         dptTypeMap.put(DPTXlator2ByteFloat.DPT_HUMIDITY.getID(), PercentType.class);
289
290         /**
291          * MainType: 10
292          * 10.001: DPT_TimeOfDay values: 1 = Monday...7 = Sunday, 0 = no-day, 00:00:00 Sun, 23:59:59 dow, hh:mm:ss
293          */
294         dptMainTypeMap.put(10, DateTimeType.class);
295         /** Exceptions Datapoint Types "Time", Main number 10 */
296         // Example: dptTypeMap.put(DPTXlatorTime.DPT_TIMEOFDAY.getID(), DateTimeType.class);
297
298         /**
299          * MainType: 11
300          * 11.001: DPT_Date values: 1990-01-01...2089-12-31, yyyy-mm-dd
301          */
302         dptMainTypeMap.put(11, DateTimeType.class);
303         /** Exceptions Datapoint Types “Date”", Main number 11 */
304         // Example: dptTypeMap.put(DPTXlatorDate.DPT_DATE.getID(), DateTimeType.class);
305
306         /**
307          * MainType: 12
308          * 12.000: General unsigned long
309          * 12.001: DPT_Value_4_Ucount values: 0...4294967295 counter pulses
310          * 12.100: DPT_LongTimePeriod_Sec values: 0...4294967295 s
311          * 12.101: DPT_LongTimePeriod_Min values: 0...4294967295 min
312          * 12.102: DPT_LongTimePeriod_Hrs values: 0...4294967295 h
313          * 12.1200: DPT_VolumeLiquid_Litre values: 0..4294967295 l
314          * 12.1201: DPT_Volume_m3 values: 0..4294967295 m3
315          */
316         dptMainTypeMap.put(12, DecimalType.class);
317         /** Exceptions Datapoint Types "4-Octet Unsigned Value", Main number 12 */
318         // Example: dptTypeMap.put(DPTXlator4ByteUnsigned.DPT_VALUE_4_UCOUNT.getID(), DecimalType.class);
319
320         /**
321          * MainType: 13
322          * 13.000: General long
323          * 13.001: DPT_Value_4_Count values: -2147483648...2147483647 counter pulses
324          * 13.002: DPT_FlowRate_m3h values: -2147483648...2147483647 m3/h
325          * 13.010: DPT_ActiveEnergy values: -2147483648...2147483647 Wh
326          * 13.011: DPT_ApparantEnergy values: -2147483648...2147483647 VAh
327          * 13.012: DPT_ReactiveEnergy values: -2147483648...2147483647 VARh
328          * 13.013: DPT_ActiveEnergy_kWh values: -2147483648...2147483647 kWh
329          * 13.014: DPT_ApparantEnergy_kVAh values: -2147483648...2147483647 kVAh
330          * 13.015: DPT_ReactiveEnergy_kVARh values: -2147483648...2147483647 kVAR
331          * 13.016: DPT_ActiveEnergy_MWh4 values: -2147483648...2147483647 MWh
332          * 13.100: DPT_LongDeltaTimeSec values: -2147483648...2147483647 s
333          * 13.1200: DPT_DeltaVolumeLiquid_Litre values: -2147483648...2147483647 l
334          * 13.1201: DPT_DeltaVolume_m3 values: -2147483648...2147483647 m³
335          */
336         dptMainTypeMap.put(13, DecimalType.class);
337         /** Exceptions Datapoint Types "4-Octet Signed Value", Main number 13 */
338         // Example: dptTypeMap.put(DPTXlator4ByteSigned.DPT_COUNT.getID(), DecimalType.class);
339
340         /**
341          * MainType: 14, Range: [-3.40282347e+38f...3.40282347e+38f]
342          * 14.000: Acceleration, values: ms⁻²
343          * 14.001: Acceleration, angular, values: rad s⁻²
344          * 14.002: Activation energy, values: J/mol
345          * 14.003: Activity, values: s⁻¹
346          * 14.004: Mol, values: mol
347          * 14.005: Amplitude, values:
348          * 14.006: Angle, values: rad
349          * 14.007: Angle, values: °
350          * 14.008: Momentum, values: Js
351          * 14.009: Angular velocity, values: rad/s
352          * 14.010: Area, values: m²
353          * 14.011: Capacitance, values: F
354          * 14.012: Charge density (surface), values: C m⁻²
355          * 14.013: Charge density (volume), values: C m⁻³
356          * 14.014: Compressibility, values: m²/N
357          * 14.015: Conductance, values: Ω⁻¹
358          * 14.016: Conductivity, electrical, values: Ω⁻¹m⁻¹
359          * 14.017: Density, values: kg m⁻³
360          * 14.018: Electric charge, values: C
361          * 14.019: Electric current, values: A
362          * 14.020: Electric current density, values: A m⁻²
363          * 14.021: Electric dipole moment, values: Cm
364          * 14.022: Electric displacement, values: C m⁻²
365          * 14.023: Electric field strength, values: V/m
366          * 14.024: Electric flux, values: Vm
367          * 14.025: Electric flux density, values: C m⁻²
368          * 14.026: Electric polarization, values: C m⁻²
369          * 14.027: Electric potential, values: V
370          * 14.028: Electric potential difference, values: V
371          * 14.029: Electromagnetic moment, values: A m²
372          * 14.030: Electromotive force, values: V
373          * 14.031: Energy, values: J
374          * 14.032: Force, values: N
375          * 14.033: Frequency, values: Hz
376          * 14.034: Frequency, angular, values: rad/s
377          * 14.035: Heat capacity, values: J/K
378          * 14.036: Heat flow rate, values: W
379          * 14.037: Heat quantity, values: J
380          * 14.038: Impedance, values: Ω
381          * 14.039: Length, values: m
382          * 14.040: Quantity of Light, values: J
383          * 14.041: Luminance, values: cd m⁻²
384          * 14.042: Luminous flux, values: lm
385          * 14.043: Luminous intensity, values: cd
386          * 14.044: Magnetic field strength, values: A/m
387          * 14.045: Magnetic flux, values: Wb
388          * 14.046: Magnetic flux density, values: T
389          * 14.047: Magnetic moment, values: A m²
390          * 14.048: Magnetic polarization, values: T
391          * 14.049: Magnetization, values: A/m
392          * 14.050: Magneto motive force, values: A
393          * 14.051: Mass, values: kg
394          * 14.052: Mass flux, values: kg/s
395          * 14.053: Momentum, values: N/s
396          * 14.054: Phase angle, radiant, values: rad
397          * 14.055: Phase angle, degree, values: °
398          * 14.056: Power, values: W
399          * 14.057: Power factor, values:
400          * 14.058: Pressure, values: Pa
401          * 14.059: Reactance, values: Ω
402          * 14.060: Resistance, values: Ω
403          * 14.061: Resistivity, values: Ωm
404          * 14.062: Self inductance, values: H
405          * 14.063: Solid angle, values: sr
406          * 14.064: Sound intensity, values: W m⁻²
407          * 14.065: Speed, values: m/s
408          * 14.066: Stress, values: Pa
409          * 14.067: Surface tension, values: N/m
410          * 14.068: Temperature in Celsius Degree, values: °C
411          * 14.069: Temperature, absolute, values: K
412          * 14.070: Temperature difference, values: K
413          * 14.071: Thermal capacity, values: J/K
414          * 14.072: Thermal conductivity, values: W/m K⁻¹
415          * 14.073: Thermoelectric power, values: V/K
416          * 14.074: Time, values: s
417          * 14.075: Torque, values: Nm
418          * 14.076: Volume, values: m³
419          * 14.077: Volume flux, values: m³/s
420          * 14.078: Weight, values: N
421          * 14.079: Work, values: J
422          * 14.080: apparent power: VA
423          */
424         dptMainTypeMap.put(14, DecimalType.class);
425         /** Exceptions Datapoint Types "4-Octet Float Value", Main number 14 */
426         // Example: dptTypeMap.put(DPTXlator4ByteFloat.DPT_ACCELERATION_ANGULAR.getID(), DecimalType.class);
427
428         /**
429          * MainType: 16
430          * 16.000: ASCII string
431          * 16.001: ISO-8859-1 string (Latin 1)
432          */
433         dptMainTypeMap.put(16, StringType.class);
434         /** Exceptions Datapoint Types "String", Main number 16 */
435         dptTypeMap.put(DPTXlatorString.DPT_STRING_8859_1.getID(), StringType.class);
436         dptTypeMap.put(DPTXlatorString.DPT_STRING_ASCII.getID(), StringType.class);
437
438         /**
439          * MainType: 17
440          * 17.001: Scene Number, values: 0...63
441          */
442         dptMainTypeMap.put(17, DecimalType.class);
443         /** Exceptions Datapoint Types "Scene Number", Main number 17 */
444         // Example: dptTypeMap.put(DPTXlatorSceneNumber.DPT_SCENE_NUMBER.getID(), DecimalType.class);
445
446         /**
447          * MainType: 18
448          * 18.001: Scene Control, values: 0...63, 0 = activate, 1 = learn
449          */
450         dptMainTypeMap.put(18, DecimalType.class);
451         /** Exceptions Datapoint Types "Scene Control", Main number 18 */
452         // Example: dptTypeMap.put(DPTXlatorSceneControl.DPT_SCENE_CONTROL.getID(), DecimalType.class);
453
454         /**
455          * MainType: 19
456          * 19.001: Date with time, values: 0 = 1900, 255 = 2155, 01/01 00:00:00, 12/31 24:00:00 yr/mth/day hr:min:sec
457          */
458         dptMainTypeMap.put(19, DateTimeType.class);
459         /** Exceptions Datapoint Types "DateTime", Main number 19 */
460         // Example: dptTypeMap.put(DPTXlatorDateTime.DPT_DATE_TIME.getID(), DateTimeType.class);
461
462         /**
463          * MainType: 20
464          * 20.001: System Clock Mode, enumeration [0..2]
465          * 20.002: Building Mode, enumeration [0..2]
466          * 20.003: Occupancy Mode, enumeration [0..2]
467          * 20.004: Priority, enumeration [0..3]
468          * 20.005: Light Application Mode, enumeration [0..2]
469          * 20.006: Application Area, enumeration [0..14]
470          * 20.007: Alarm Class Type, enumeration [0..3]
471          * 20.008: PSU Mode, enumeration [0..2]
472          * 20.011: Error Class System, enumeration [0..18]
473          * 20.012: Error Class HVAC, enumeration [0..4]
474          * 20.013: Time Delay, enumeration [0..25]
475          * 20.014: Beaufort Wind Force Scale, enumeration [0..12]
476          * 20.017: Sensor Select, enumeration [0..4]
477          * 20.020: Actuator Connect Type, enumeration [1..2]
478          * 20.100: Fuel Type, enumeration [0..3]
479          * 20.101: Burner Type, enumeration [0..3]
480          * 20.102: HVAC Mode, enumeration [0..4]
481          * 20.103: DHW Mode, enumeration [0..4]
482          * 20.104: Load Priority, enumeration [0..2]
483          * 20.105: HVAC Control Mode, enumeration [0..20]
484          * 20.106: HVAC Emergency Mode, enumeration [0..5]
485          * 20.107: Changeover Mode, enumeration [0..2]
486          * 20.108: Valve Mode, enumeration [1..5]
487          * 20.109: Damper Mode, enumeration [1..4]
488          * 20.110: Heater Mode, enumeration [1..3]
489          * 20.111: Fan Mode, enumeration [0..2]
490          * 20.112: Master/Slave Mode, enumeration [0..2]
491          * 20.113: Status Room Setpoint, enumeration [0..2]
492          * 20.114: Metering Device Type, enumeration [0..41/255]
493          * 20.120: Air Damper Actuator Type, enumeration [1..2]
494          * 20.121: Backup Mode, enumeration [0..1]
495          * 20.122: Start Synchronization, enumeration [0..2]
496          * 20.600: Behavior Lock/Unlock, enumeration [0..6]
497          * 20.601: Behavior Bus Power Up/Down, enumeration [0..4]
498          * 20.602: DALI Fade Time, enumeration [0..15]
499          * 20.603: Blinking Mode, enumeration [0..2]
500          * 20.604: Light Control Mode, enumeration [0..1]
501          * 20.605: Switch PB Model, enumeration [1..2]
502          * 20.606: PB Action, enumeration [0..3]
503          * 20.607: Dimm PB Model, enumeration [1..4]
504          * 20.608: Switch On Mode, enumeration [0..2]
505          * 20.609: Load Type Set, enumeration [0..2]
506          * 20.610: Load Type Detected, enumeration [0..3]
507          * 20.801: SAB Except Behavior, enumeration [0..4]
508          * 20.802: SAB Behavior Lock/Unlock, enumeration [0..6]
509          * 20.803: SSSB Mode, enumeration [1..4]
510          * 20.804: Blinds Control Mode, enumeration [0..1]
511          * 20.1000: Comm Mode, enumeration [0..255]
512          * 20.1001: Additional Info Type, enumeration [0..7]
513          * 20.1002: RF Mode Select, enumeration [0..2]
514          * 20.1003: RF Filter Select, enumeration [0..3]
515          * 20.1200: M-Bus Breaker/Valve State, enumeration [0..255]
516          * 20.1202: Gas Measurement Condition, enumeration [0..3]
517          *
518          */
519         dptMainTypeMap.put(20, StringType.class);
520         /** Exceptions Datapoint Types, Main number 20 */
521         // Example since calimero 2.4: dptTypeMap.put(DPTXlator8BitEnum.DptSystemClockMode.getID(), StringType.class);
522
523         /**
524          * MainType: 21
525          * 21.001: General Status, values: 0...31
526          * 21.002: Device Control, values: 0...7
527          * 21.100: Forcing Signal, values: 0...255
528          * 21.101: Forcing Signal Cool, values: 0...1
529          * 21.102: Room Heating Controller Status, values: 0...255
530          * 21.103: Solar Dhw Controller Status, values: 0...7
531          * 21.104: Fuel Type Set, values: 0...7
532          * 21.105: Room Cooling Controller Status, values: 0...1
533          * 21.106: Ventilation Controller Status, values: 0...15
534          * 21.601: Light Actuator Error Info, values: 0...127
535          * 21.1000: R F Comm Mode Info, values: 0...7
536          * 21.1001: R F Filter Modes, values: 0...7
537          * 21.1010: Channel Activation State, values: 0...255
538          */
539         dptMainTypeMap.put(21, StringType.class);
540         /** Exceptions Datapoint Types, Main number 21 */
541         // Example since calimero 2.4: dptTypeMap.put(DptXlator8BitSet.DptGeneralStatus.getID(), StringType.class);
542
543         /**
544          * MainType: 28
545          * 28.001: UTF-8
546          */
547         dptMainTypeMap.put(28, StringType.class);
548         /** Exceptions Datapoint Types "String" UTF-8, Main number 28 */
549         // Example: dptTypeMap.put(DPTXlatorUtf8.DPT_UTF8.getID(), StringType.class);
550
551         /**
552          * MainType: 29
553          * 29.010: Active Energy, values: -9223372036854775808...9223372036854775807 Wh
554          * 29.011: Apparent energy, values: -9223372036854775808...9223372036854775807 VAh
555          * 29.012: Reactive energy, values: -9223372036854775808...9223372036854775807 VARh
556          */
557         dptMainTypeMap.put(29, DecimalType.class);
558         /** Exceptions Datapoint Types "64-Bit Signed Value", Main number 29 */
559         // Example: dptTypeMap.put(DPTXlator64BitSigned.DPT_ACTIVE_ENERGY.getID(), DecimalType.class);
560
561         /**
562          * MainType: 229
563          * 229.001: Metering Value, values: -2147483648...2147483647
564          */
565         dptMainTypeMap.put(229, DecimalType.class);
566         /** Exceptions Datapoint Types "4-Octet Signed Value", Main number 229 */
567         // Example: dptTypeMap.put(DptXlatorMeteringValue.DptMeteringValue.getID(), DecimalType.class);
568
569         /**
570          * MainType: 232, 3 bytes
571          * 232.600: DPT_Colour_RGB, values: 0 0 0...255 255 255, r g b
572          */
573         dptMainTypeMap.put(232, HSBType.class);
574         /** Exceptions Datapoint Types "RGB Color", Main number 232 */
575         // Example: dptTypeMap.put(DPTXlatorRGB.DPT_RGB.getID(), HSBType.class);
576
577         defaultDptMap = new HashMap<>();
578         defaultDptMap.put(OnOffType.class, DPTXlatorBoolean.DPT_SWITCH.getID());
579         defaultDptMap.put(UpDownType.class, DPTXlatorBoolean.DPT_UPDOWN.getID());
580         defaultDptMap.put(StopMoveType.class, DPTXlatorBoolean.DPT_START.getID());
581         defaultDptMap.put(OpenClosedType.class, DPTXlatorBoolean.DPT_WINDOW_DOOR.getID());
582         defaultDptMap.put(IncreaseDecreaseType.class, DPTXlator3BitControlled.DPT_CONTROL_DIMMING.getID());
583         defaultDptMap.put(PercentType.class, DPTXlator8BitUnsigned.DPT_SCALING.getID());
584         defaultDptMap.put(DecimalType.class, DPTXlator2ByteFloat.DPT_TEMPERATURE.getID());
585         defaultDptMap.put(DateTimeType.class, DPTXlatorTime.DPT_TIMEOFDAY.getID());
586         defaultDptMap.put(StringType.class, DPTXlatorString.DPT_STRING_8859_1.getID());
587         defaultDptMap.put(HSBType.class, DPTXlatorRGB.DPT_RGB.getID());
588     }
589
590     /*
591      * This function computes the target unit for type conversion from OH quantity type to DPT types.
592      * Calimero library provides units which can be used for most of the DPTs. There are some deviations
593      * from the OH unit scheme which are handled.
594      */
595     private String quantityTypeToDPTValue(QuantityType<?> qt, int mainNumber, int subNumber, String dpUnit)
596             throws KNXException {
597         String targetOhUnit = dpUnit;
598         double scaleFactor = 1.0;
599         switch (mainNumber) {
600             case 7:
601                 switch (subNumber) {
602                     case 3:
603                     case 4:
604                         targetOhUnit = "ms";
605                         break;
606                 }
607                 break;
608             case 9:
609                 switch (subNumber) {
610                     // special case: temperature deltas specified in different units
611                     // ignore the offset, but run a conversion to handle prefixes like mK
612                     // scaleFactor is needed to properly handle °F
613                     case 2: {
614                         final String unit = qt.getUnit().toString();
615                         // find out if the unit is based on °C or K, getSystemUnit() does not help here as it always
616                         // gives "K"
617                         if (unit.contains("°C")) {
618                             targetOhUnit = "°C";
619                         } else if (unit.contains("°F")) {
620                             targetOhUnit = "°F";
621                             scaleFactor = 5.0 / 9.0;
622                         } else if (unit.contains("K")) {
623                             targetOhUnit = "K";
624                         } else {
625                             targetOhUnit = "";
626                         }
627                         break;
628                     }
629                     case 3: {
630                         final String unit = qt.getUnit().toString();
631                         if (unit.contains("°C")) {
632                             targetOhUnit = "°C/h";
633                         } else if (unit.contains("°F")) {
634                             targetOhUnit = "°F/h";
635                             scaleFactor = 5.0 / 9.0;
636                         } else if (unit.contains("K")) {
637                             targetOhUnit = "K/h";
638                         } else {
639                             targetOhUnit = "";
640                         }
641                         break;
642                     }
643                     case 23: {
644                         final String unit = qt.getUnit().toString();
645                         if (unit.contains("°C")) {
646                             targetOhUnit = "°C/%";
647                         } else if (unit.contains("°F")) {
648                             targetOhUnit = "°F/%";
649                             scaleFactor = 5.0 / 9.0;
650                         } else if (unit.contains("K")) {
651                             targetOhUnit = "K/%";
652                         } else {
653                             targetOhUnit = "";
654                         }
655                         break;
656                     }
657                 }
658                 break;
659             case 12:
660                 switch (subNumber) {
661                     case 1200:
662                         // Calimero uses "litre"
663                         targetOhUnit = "l";
664                         break;
665                 }
666                 break;
667             case 13:
668                 switch (subNumber) {
669                     case 12:
670                     case 15:
671                         // Calimero uses VARh, OH uses varh
672                         targetOhUnit = targetOhUnit.replace("VARh", "varh");
673                         break;
674                     case 14:
675                         // OH does not accept kVAh, only VAh
676                         targetOhUnit = targetOhUnit.replace("kVAh", "VAh");
677                         scaleFactor = 1.0 / 1000.0;
678                         break;
679                 }
680                 break;
681
682             case 14:
683                 targetOhUnit = targetOhUnit.replace("Ω\u207B¹", "S");
684                 // Calimero uses a special unicode character to specify units like m*s^-2
685                 // this needs to be rewritten to m/s²
686                 final int posMinus = targetOhUnit.indexOf("\u207B");
687                 if (posMinus > 0) {
688                     targetOhUnit = targetOhUnit.substring(0, posMinus - 1) + "/" + targetOhUnit.charAt(posMinus - 1)
689                             + targetOhUnit.substring(posMinus + 1);
690                 }
691                 switch (subNumber) {
692                     case 8:
693                         // OH does not support unut Js, need to expand
694                         targetOhUnit = "J*s";
695                         break;
696                     case 21:
697                         targetOhUnit = "C*m";
698                         break;
699                     case 24:
700                         targetOhUnit = "C";
701                         break;
702                     case 29:
703                     case 47:
704                         targetOhUnit = "A*m²";
705                         break;
706                     case 40:
707                         if (qt.getUnit().toString().contains("J")) {
708                             targetOhUnit = "J";
709                         } else {
710                             targetOhUnit = "lm*s";
711                         }
712                         break;
713                     case 61:
714                         targetOhUnit = "Ohm*m";
715                         break;
716                     case 75:
717                         targetOhUnit = "N*m";
718                         break;
719                 }
720                 break;
721             case 29:
722                 switch (subNumber) {
723                     case 12:
724                         // Calimero uses VARh, OH uses varh
725                         targetOhUnit = targetOhUnit.replace("VARh", "varh");
726                         break;
727                 }
728                 break;
729         }
730         // replace e.g. m3 by m³
731         targetOhUnit = targetOhUnit.replace("3", "³").replace("2", "²");
732
733         final QuantityType<?> result = qt.toUnit(targetOhUnit);
734         if (result == null) {
735             throw new KNXException("incompatible types: " + qt.getUnit().toString() + ", " + targetOhUnit);
736         }
737         return String.valueOf(result.doubleValue() * scaleFactor);
738     }
739
740     @Override
741     public @Nullable String toDPTValue(Type type, @Nullable String dptID) {
742         DPT dpt;
743         int mainNumber = getMainNumber(dptID);
744         if (mainNumber == -1) {
745             logger.error("toDPTValue couldn't identify mainnumber in dptID: {}", dptID);
746             return null;
747         }
748         int subNumber = getSubNumber(dptID);
749         if (subNumber == -1) {
750             logger.debug("toType: couldn't identify sub number in dptID: {}.", dptID);
751             return null;
752         }
753
754         try {
755             DPTXlator translator = TranslatorTypes.createTranslator(mainNumber, dptID);
756             dpt = translator.getType();
757         } catch (KNXException e) {
758             return null;
759         }
760
761         try {
762             // check for HSBType first, because it extends PercentType as well
763             if (type instanceof HSBType) {
764                 switch (mainNumber) {
765                     case 5:
766                         switch (subNumber) {
767                             case 3: // * 5.003: Angle, values: 0...360 °
768                                 return ((HSBType) type).getHue().toString();
769                             case 1: // * 5.001: Scaling, values: 0...100 %
770                             default:
771                                 return ((HSBType) type).getBrightness().toString();
772                         }
773                     case 232:
774                         switch (subNumber) {
775                             case 600: // 232.600
776                                 HSBType hc = ((HSBType) type);
777                                 return "r:" + convertPercentToByte(hc.getRed()) + " g:"
778                                         + convertPercentToByte(hc.getGreen()) + " b:"
779                                         + convertPercentToByte(hc.getBlue());
780                         }
781                     default:
782                         HSBType hc = ((HSBType) type);
783                         return "r:" + hc.getRed().intValue() + " g:" + hc.getGreen().intValue() + " b:"
784                                 + hc.getBlue().intValue();
785                 }
786             } else if (type instanceof OnOffType) {
787                 return type.equals(OnOffType.OFF) ? dpt.getLowerValue() : dpt.getUpperValue();
788             } else if (type instanceof UpDownType) {
789                 return type.equals(UpDownType.UP) ? dpt.getLowerValue() : dpt.getUpperValue();
790             } else if (type instanceof IncreaseDecreaseType) {
791                 DPT valueDPT = ((DPTXlator3BitControlled.DPT3BitControlled) dpt).getControlDPT();
792                 return type.equals(IncreaseDecreaseType.DECREASE) ? valueDPT.getLowerValue() + " 5"
793                         : valueDPT.getUpperValue() + " 5";
794             } else if (type instanceof OpenClosedType) {
795                 return type.equals(OpenClosedType.CLOSED) ? dpt.getLowerValue() : dpt.getUpperValue();
796             } else if (type instanceof StopMoveType) {
797                 return type.equals(StopMoveType.STOP) ? dpt.getLowerValue() : dpt.getUpperValue();
798             } else if (type instanceof PercentType) {
799                 return String.valueOf(((DecimalType) type).intValue());
800             } else if (type instanceof DecimalType) {
801                 switch (mainNumber) {
802                     case 2:
803                         DPT valueDPT = ((DPTXlator1BitControlled.DPT1BitControlled) dpt).getValueDPT();
804                         switch (((DecimalType) type).intValue()) {
805                             case 0:
806                                 return "0 " + valueDPT.getLowerValue();
807                             case 1:
808                                 return "0 " + valueDPT.getUpperValue();
809                             case 2:
810                                 return "1 " + valueDPT.getLowerValue();
811                             default:
812                                 return "1 " + valueDPT.getUpperValue();
813                         }
814                     case 18:
815                         int intVal = ((DecimalType) type).intValue();
816                         if (intVal > 63) {
817                             return "learn " + (intVal - 0x80);
818                         } else {
819                             return "activate " + intVal;
820                         }
821                     default:
822                         return ((DecimalType) type).toBigDecimal().stripTrailingZeros().toPlainString();
823                 }
824             } else if (type instanceof StringType) {
825                 return type.toString();
826             } else if (type instanceof DateTimeType) {
827                 return formatDateTime((DateTimeType) type, dptID);
828             } else if (type instanceof QuantityType) {
829                 final QuantityType<?> qt = (QuantityType<?>) type;
830                 return quantityTypeToDPTValue(qt, mainNumber, subNumber, dpt.getUnit());
831             }
832         } catch (Exception e) {
833             logger.warn("An exception occurred converting type {} to dpt id {}: error message={}", type, dptID,
834                     e.getMessage());
835             return null;
836         }
837
838         logger.debug("toDPTValue: Couldn't convert type {} to dpt id {} (no mapping).", type, dptID);
839
840         return null;
841     }
842
843     @Override
844     public @Nullable Type toType(Datapoint datapoint, byte[] data) {
845         try {
846             DPTXlator translator = TranslatorTypes.createTranslator(datapoint.getMainNumber(), datapoint.getDPT());
847             translator.setData(data);
848             String value = translator.getValue();
849
850             String id = translator.getType().getID();
851             logger.trace("toType datapoint DPT = {}", datapoint.getDPT());
852
853             int mainNumber = getMainNumber(id);
854             if (mainNumber == -1) {
855                 logger.debug("toType: couldn't identify mainnumber in dptID: {}.", id);
856                 return null;
857             }
858             int subNumber = getSubNumber(id);
859             if (subNumber == -1) {
860                 logger.debug("toType: couldn't identify sub number in dptID: {}.", id);
861                 return null;
862             }
863             /*
864              * Following code section deals with specific mapping of values from KNX to openHAB types were the String
865              * received from the DPTXlator is not sufficient to set the openHAB type or has bugs
866              */
867             switch (mainNumber) {
868                 case 1:
869                     DPTXlatorBoolean translatorBoolean = (DPTXlatorBoolean) translator;
870                     switch (subNumber) {
871                         case 8:
872                             return translatorBoolean.getValueBoolean() ? UpDownType.DOWN : UpDownType.UP;
873                         case 9:
874                             return translatorBoolean.getValueBoolean() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
875                         case 10:
876                             return translatorBoolean.getValueBoolean() ? StopMoveType.MOVE : StopMoveType.STOP;
877                         case 19:
878                             return translatorBoolean.getValueBoolean() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
879                         case 22:
880                             return DecimalType.valueOf(translatorBoolean.getValueBoolean() ? "1" : "0");
881                         default:
882                             return translatorBoolean.getValueBoolean() ? OnOffType.ON : OnOffType.OFF;
883                     }
884                 case 2:
885                     DPTXlator1BitControlled translator1BitControlled = (DPTXlator1BitControlled) translator;
886                     int decValue = (translator1BitControlled.getControlBit() ? 2 : 0)
887                             + (translator1BitControlled.getValueBit() ? 1 : 0);
888                     return new DecimalType(decValue);
889                 case 3:
890                     DPTXlator3BitControlled translator3BitControlled = (DPTXlator3BitControlled) translator;
891                     if (translator3BitControlled.getStepCode() == 0) {
892                         logger.debug("toType: KNX DPT_Control_Dimming: break received.");
893                         return UnDefType.UNDEF;
894                     }
895                     switch (subNumber) {
896                         case 7:
897                             return translator3BitControlled.getControlBit() ? IncreaseDecreaseType.INCREASE
898                                     : IncreaseDecreaseType.DECREASE;
899                         case 8:
900                             return translator3BitControlled.getControlBit() ? UpDownType.DOWN : UpDownType.UP;
901                     }
902                 case 14:
903                     /*
904                      * FIXME: Workaround for a bug in Calimero / Openhab DPTXlator4ByteFloat.makeString(): is using a
905                      * locale when
906                      * translating a Float to String. It could happen the a ',' is used as separator, such as
907                      * 3,14159E20.
908                      * Openhab's DecimalType expects this to be in US format and expects '.': 3.14159E20.
909                      * There is no issue with DPTXlator2ByteFloat since calimero is using a non-localized translation
910                      * there.
911                      */
912                     DPTXlator4ByteFloat translator4ByteFloat = (DPTXlator4ByteFloat) translator;
913                     Float f = translator4ByteFloat.getValueFloat();
914                     if (Math.abs(f) < 100000) {
915                         value = String.valueOf(f);
916                     } else {
917                         NumberFormat dcf = NumberFormat.getInstance(Locale.US);
918                         if (dcf instanceof DecimalFormat) {
919                             ((DecimalFormat) dcf).applyPattern("0.#####E0");
920                         }
921                         value = dcf.format(f);
922                     }
923                     break;
924                 case 18:
925                     DPTXlatorSceneControl translatorSceneControl = (DPTXlatorSceneControl) translator;
926                     int decimalValue = translatorSceneControl.getSceneNumber();
927                     if (value.startsWith("learn")) {
928                         decimalValue += 0x80;
929                     }
930                     value = String.valueOf(decimalValue);
931
932                     break;
933                 case 19:
934                     DPTXlatorDateTime translatorDateTime = (DPTXlatorDateTime) translator;
935                     if (translatorDateTime.isFaultyClock()) {
936                         // Not supported: faulty clock
937                         logger.debug("toType: KNX clock msg ignored: clock faulty bit set, which is not supported");
938                         return null;
939                     } else if (!translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
940                             && translatorDateTime.isValidField(DPTXlatorDateTime.DATE)) {
941                         // Not supported: "/1/1" (month and day without year)
942                         logger.debug(
943                                 "toType: KNX clock msg ignored: no year, but day and month, which is not supported");
944                         return null;
945                     } else if (translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
946                             && !translatorDateTime.isValidField(DPTXlatorDateTime.DATE)) {
947                         // Not supported: "1900" (year without month and day)
948                         logger.debug(
949                                 "toType: KNX clock msg ignored: no day and month, but year, which is not supported");
950                         return null;
951                     } else if (!translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
952                             && !translatorDateTime.isValidField(DPTXlatorDateTime.DATE)
953                             && !translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) {
954                         // Not supported: No year, no date and no time
955                         logger.debug("toType: KNX clock msg ignored: no day and month or year, which is not supported");
956                         return null;
957                     }
958
959                     Calendar cal = Calendar.getInstance();
960                     if (translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
961                             && !translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) {
962                         // Pure date format, no time information
963                         cal.setTimeInMillis(translatorDateTime.getValueMilliseconds());
964                         value = new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(cal.getTime());
965                         return DateTimeType.valueOf(value);
966                     } else if (!translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
967                             && translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) {
968                         // Pure time format, no date information
969                         cal.clear();
970                         cal.set(Calendar.HOUR_OF_DAY, translatorDateTime.getHour());
971                         cal.set(Calendar.MINUTE, translatorDateTime.getMinute());
972                         cal.set(Calendar.SECOND, translatorDateTime.getSecond());
973                         value = new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(cal.getTime());
974                         return DateTimeType.valueOf(value);
975                     } else if (translatorDateTime.isValidField(DPTXlatorDateTime.YEAR)
976                             && translatorDateTime.isValidField(DPTXlatorDateTime.TIME)) {
977                         // Date format and time information
978                         cal.setTimeInMillis(translatorDateTime.getValueMilliseconds());
979                         value = new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(cal.getTime());
980                         return DateTimeType.valueOf(value);
981                     }
982                     break;
983             }
984
985             Class<? extends Type> typeClass = toTypeClass(id);
986             if (typeClass == null) {
987                 return null;
988             }
989
990             if (typeClass.equals(PercentType.class)) {
991                 return new PercentType(BigDecimal.valueOf(Math.round(translator.getNumericValue())));
992             }
993             if (typeClass.equals(DecimalType.class)) {
994                 return new DecimalType(translator.getNumericValue());
995             }
996             if (typeClass.equals(StringType.class)) {
997                 return StringType.valueOf(value);
998             }
999
1000             if (typeClass.equals(DateTimeType.class)) {
1001                 String date = formatDateTime(value, datapoint.getDPT());
1002                 if (date.isEmpty()) {
1003                     logger.debug("toType: KNX clock msg ignored: date object null or empty {}.", date);
1004                     return null;
1005                 } else {
1006                     return DateTimeType.valueOf(date);
1007                 }
1008             }
1009
1010             if (typeClass.equals(HSBType.class)) {
1011                 // value has format of "r:<red value> g:<green value> b:<blue value>"
1012                 int r = Integer.parseInt(value.split(" ")[0].split(":")[1]);
1013                 int g = Integer.parseInt(value.split(" ")[1].split(":")[1]);
1014                 int b = Integer.parseInt(value.split(" ")[2].split(":")[1]);
1015
1016                 return HSBType.fromRGB(r, g, b);
1017             }
1018
1019         } catch (KNXFormatException kfe) {
1020             logger.info("Translator couldn't parse data for datapoint type '{}' (KNXFormatException).",
1021                     datapoint.getDPT());
1022         } catch (KNXIllegalArgumentException kiae) {
1023             logger.info("Translator couldn't parse data for datapoint type '{}' (KNXIllegalArgumentException).",
1024                     datapoint.getDPT());
1025         } catch (KNXException e) {
1026             logger.warn("Failed creating a translator for datapoint type '{}'.", datapoint.getDPT(), e);
1027         }
1028
1029         return null;
1030     }
1031
1032     /**
1033      * Converts a datapoint type id into an openHAB type class
1034      *
1035      * @param dptId the datapoint type id
1036      * @return the openHAB type (command or state) class or {@code null} if the datapoint type id is not supported.
1037      */
1038     @Override
1039     public @Nullable Class<? extends Type> toTypeClass(@Nullable String dptId) {
1040         @Nullable
1041         Class<? extends Type> ohClass = dptTypeMap.get(dptId);
1042         if (ohClass == null) {
1043             int mainNumber = getMainNumber(dptId);
1044             if (mainNumber == -1) {
1045                 logger.debug("Couldn't convert KNX datapoint type id into openHAB type class for dptId: {}.", dptId);
1046                 return null;
1047             }
1048             ohClass = dptMainTypeMap.get(mainNumber);
1049         }
1050         return ohClass;
1051     }
1052
1053     /**
1054      * Converts an openHAB type class into a datapoint type id.
1055      *
1056      * @param typeClass the openHAB type class
1057      * @return the datapoint type id
1058      */
1059     public @Nullable String toDPTid(Class<? extends Type> typeClass) {
1060         return defaultDptMap.get(typeClass);
1061     }
1062
1063     /**
1064      * Formats the given <code>value</code> according to the datapoint type
1065      * <code>dpt</code> to a String which can be processed by {@link DateTimeType}.
1066      *
1067      * @param value
1068      * @param dpt
1069      *
1070      * @return a formatted String like </code>yyyy-MM-dd'T'HH:mm:ss</code> which
1071      *         is target format of the {@link DateTimeType}
1072      */
1073     private String formatDateTime(String value, String dpt) {
1074         Date date = null;
1075
1076         try {
1077             if (DPTXlatorDate.DPT_DATE.getID().equals(dpt)) {
1078                 date = new SimpleDateFormat(DATE_FORMAT).parse(value);
1079             } else if (DPTXlatorTime.DPT_TIMEOFDAY.getID().equals(dpt)) {
1080                 if (value.contains("no-day")) {
1081                     /*
1082                      * KNX "no-day" needs special treatment since openHAB's DateTimeType doesn't support "no-day".
1083                      * Workaround: remove the "no-day" String, parse the remaining time string, which will result in a
1084                      * date of "1970-01-01".
1085                      * Replace "no-day" with the current day name
1086                      */
1087                     StringBuffer stb = new StringBuffer(value);
1088                     int start = stb.indexOf("no-day");
1089                     int end = start + "no-day".length();
1090                     stb.replace(start, end, String.format(Locale.US, "%1$ta", Calendar.getInstance()));
1091                     value = stb.toString();
1092                 }
1093                 try {
1094                     date = new SimpleDateFormat(TIME_DAY_FORMAT, Locale.US).parse(value);
1095                 } catch (ParseException pe) {
1096                     date = new SimpleDateFormat(TIME_FORMAT, Locale.US).parse(value);
1097                 }
1098             }
1099         } catch (ParseException pe) {
1100             // do nothing but logging
1101             logger.warn("Could not parse '{}' to a valid date", value);
1102         }
1103
1104         return date != null ? new SimpleDateFormat(DateTimeType.DATE_PATTERN).format(date) : "";
1105     }
1106
1107     /**
1108      * Formats the given internal <code>dateType</code> to a knx readable String
1109      * according to the target datapoint type <code>dpt</code>.
1110      *
1111      * @param dateType
1112      * @param dpt the target datapoint type
1113      *
1114      * @return a String which contains either an ISO8601 formatted date (yyyy-mm-dd),
1115      *         a formatted 24-hour clock with the day of week prepended (Mon, 12:00:00) or
1116      *         a formatted 24-hour clock (12:00:00)
1117      *
1118      * @throws IllegalArgumentException if none of the datapoint types DPT_DATE or
1119      *             DPT_TIMEOFDAY has been used.
1120      */
1121     private static String formatDateTime(DateTimeType dateType, @Nullable String dpt) {
1122         if (DPTXlatorDate.DPT_DATE.getID().equals(dpt)) {
1123             return dateType.format("%tF");
1124         } else if (DPTXlatorTime.DPT_TIMEOFDAY.getID().equals(dpt)) {
1125             return dateType.format(Locale.US, "%1$ta, %1$tT");
1126         } else if (DPTXlatorDateTime.DPT_DATE_TIME.getID().equals(dpt)) {
1127             return dateType.format(Locale.US, "%tF %1$tT");
1128         } else {
1129             throw new IllegalArgumentException("Could not format date to datapoint type '" + dpt + "'");
1130         }
1131     }
1132
1133     /**
1134      * Retrieves sub number from a DTP ID such as "14.001"
1135      *
1136      * @param dptID String with DPT ID
1137      * @return sub number or -1
1138      */
1139     private int getSubNumber(@Nullable String dptID) {
1140         int result = -1;
1141         if (dptID == null) {
1142             throw new IllegalArgumentException("Parameter dptID cannot be null");
1143         }
1144
1145         int dptSepratorPosition = dptID.indexOf('.');
1146         if (dptSepratorPosition > 0) {
1147             try {
1148                 result = Integer.parseInt(dptID.substring(dptSepratorPosition + 1, dptID.length()));
1149             } catch (NumberFormatException nfe) {
1150                 logger.error("toType couldn't identify main and/or sub number in dptID (NumberFormatException): {}",
1151                         dptID);
1152             } catch (IndexOutOfBoundsException ioobe) {
1153                 logger.error("toType couldn't identify main and/or sub number in dptID (IndexOutOfBoundsException): {}",
1154                         dptID);
1155             }
1156         }
1157         return result;
1158     }
1159
1160     /**
1161      * Retrieves main number from a DTP ID such as "14.001"
1162      *
1163      * @param dptID String with DPT ID
1164      * @return main number or -1
1165      */
1166     private int getMainNumber(@Nullable String dptID) {
1167         int result = -1;
1168         if (dptID == null) {
1169             throw new IllegalArgumentException("Parameter dptID cannot be null");
1170         }
1171
1172         int dptSepratorPosition = dptID.indexOf('.');
1173         if (dptSepratorPosition > 0) {
1174             try {
1175                 result = Integer.parseInt(dptID.substring(0, dptSepratorPosition));
1176             } catch (NumberFormatException nfe) {
1177                 logger.error("toType couldn't identify main and/or sub number in dptID (NumberFormatException): {}",
1178                         dptID);
1179             } catch (IndexOutOfBoundsException ioobe) {
1180                 logger.error("toType couldn't identify main and/or sub number in dptID (IndexOutOfBoundsException): {}",
1181                         dptID);
1182             }
1183         }
1184         return result;
1185     }
1186
1187     /**
1188      * convert 0...100% to 1 byte 0..255
1189      *
1190      * @param percent
1191      * @return int 0..255
1192      */
1193     private int convertPercentToByte(PercentType percent) {
1194         return percent.toBigDecimal().multiply(BigDecimal.valueOf(255))
1195                 .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP).intValue();
1196     }
1197 }