2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.digitalstrom.internal.providers;
15 import static org.openhab.binding.digitalstrom.internal.DigitalSTROMBindingConstants.BINDING_ID;
17 import java.math.BigDecimal;
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.HashSet;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Locale;
27 import org.openhab.binding.digitalstrom.internal.DigitalSTROMBindingConstants;
28 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.DeviceBinarayInputEnum;
29 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.FunctionalColorGroupEnum;
30 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.MeteringTypeEnum;
31 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.MeteringUnitsEnum;
32 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.OutputModeEnum;
33 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.SensorEnum;
34 import org.openhab.core.i18n.TranslationProvider;
35 import org.openhab.core.thing.type.ChannelGroupTypeUID;
36 import org.openhab.core.thing.type.ChannelType;
37 import org.openhab.core.thing.type.ChannelTypeBuilder;
38 import org.openhab.core.thing.type.ChannelTypeProvider;
39 import org.openhab.core.thing.type.ChannelTypeUID;
40 import org.openhab.core.types.StateDescription;
41 import org.openhab.core.types.StateOption;
42 import org.osgi.service.component.ComponentContext;
43 import org.osgi.service.component.annotations.Activate;
44 import org.osgi.service.component.annotations.Component;
45 import org.osgi.service.component.annotations.Deactivate;
46 import org.osgi.service.component.annotations.Reference;
49 * The {@link DsChannelTypeProvider} implements the {@link ChannelTypeProvider} generates all supported
50 * {@link Channel}'s for digitalSTROM.
52 * @author Michael Ochel - Initial contribution
53 * @author Matthias Siegele - Initial contribution
56 @Component(service = ChannelTypeProvider.class)
57 public class DsChannelTypeProvider extends BaseDsI18n implements ChannelTypeProvider {
59 // channelID building (effect group type + (nothing || SEPERATOR + item type || SEPERATOR + extended item type) e.g.
60 // light_switch, shade or shade_angle
61 // channel effect group type
62 public static final String LIGHT = "light"; // and tag
63 public static final String SHADE = "shade"; // and tag
64 public static final String HEATING = "heating"; // and tag
65 public static final String GENERAL = "general";
66 public static final String SCENE = "scene";
67 // channel extended item type
68 public static final String WIPE = "wipe";
69 public static final String ANGLE = "angle";
70 public static final String STAGE = "stage"; // pre stageses e.g. 2+STAGE_SWITCH
71 public static final String TEMPERATURE_CONTROLLED = "temperature_controlled";
74 public static final String DIMMER = "Dimmer";
75 public static final String SWITCH = "Switch";
76 public static final String ROLLERSHUTTER = "Rollershutter";
77 public static final String STRING = "String";
78 public static final String NUMBER = "Number";
80 public static final String TOTAL_PRE = "total";
81 public static final String BINARY_INPUT_PRE = "binary_input";
82 public static final String OPTION = "opt";
85 public static final String GE = "GE";
86 public static final String GR = "GR";
87 public static final String BL = "BL";
88 public static final String SW = "SW";
89 public static final String DS = "DS";
90 public static final String JOKER = "JOKER";
93 public static final String CATEGORY_BLINDES = "Blinds";
94 public static final String CATEGORY_DIMMABLE_LIGHT = "DimmableLight";
95 public static final String CATEGORY_CARBONE_DIOXIDE = "CarbonDioxide";
96 public static final String CATEGORY_ENERGY = "Energy";
97 public static final String CATEGORY_HUMIDITY = "Humidity";
98 public static final String CATEGORY_BRIGHTNESS = "Brightness";
99 public static final String CATEGORY_LIGHT = "Light";
100 public static final String CATEGORY_PRESSURE = "Pressure";
101 public static final String CATEGORY_SOUND_VOLUME = "SoundVolume";
102 public static final String CATEGORY_TEMPERATURE = "Temperature";
103 public static final String CATEGORY_WIND = "Wind";
104 public static final String CATEGORY_RAIN = "Rain";
105 public static final String CATEGORY_BATTERY = "Battery";
106 public static final String CATEGORY_DOOR = "Door";
107 public static final String CATEGORY_WINDOW = "Window";
108 public static final String CATEGORY_GARAGE_DOOR = "GarageDoor";
109 public static final String CATEGORY_SMOKE = "Smoke";
110 public static final String CATEGORY_ALARM = "Alarm";
111 public static final String CATEGORY_MOTION = "Motion";
114 * Returns the output channel type id as {@link String} for the given {@link FunctionalColorGroupEnum} and
115 * {@link OutputModeEnum} or null, if no channel type exists for the given {@link FunctionalColorGroupEnum} and
116 * {@link OutputModeEnum}.
118 * @param functionalGroup of the {@link Device}
119 * @param outputMode of the {@link Device}
120 * @return the output channel type id or null
122 public static String getOutputChannelTypeID(FunctionalColorGroupEnum functionalGroup, OutputModeEnum outputMode) {
123 if (functionalGroup != null && outputMode != null) {
124 String channelPreID = GENERAL;
125 if (functionalGroup.equals(FunctionalColorGroupEnum.YELLOW)) {
126 channelPreID = LIGHT;
128 if (functionalGroup.equals(FunctionalColorGroupEnum.GREY)) {
129 if (outputMode.equals(OutputModeEnum.POSITION_CON)) {
130 return buildIdentifier(SHADE);
132 if (outputMode.equals(OutputModeEnum.POSITION_CON_US)) {
133 return buildIdentifier(SHADE, ANGLE);
136 if (functionalGroup.equals(FunctionalColorGroupEnum.BLUE)) {
137 channelPreID = HEATING;
138 if (OutputModeEnum.outputModeIsTemperationControlled(outputMode)) {
139 return buildIdentifier(channelPreID, TEMPERATURE_CONTROLLED);
142 if (OutputModeEnum.outputModeIsSwitch(outputMode)) {
143 return buildIdentifier(channelPreID, SWITCH);
145 if (OutputModeEnum.outputModeIsDimmable(outputMode)) {
146 return buildIdentifier(channelPreID, DIMMER);
148 if (!channelPreID.equals(HEATING)) {
149 if (outputMode.equals(OutputModeEnum.COMBINED_2_STAGE_SWITCH)) {
150 return buildIdentifier(channelPreID, "2", STAGE);
152 if (outputMode.equals(OutputModeEnum.COMBINED_3_STAGE_SWITCH)) {
153 return buildIdentifier(channelPreID, "3", STAGE);
160 public static String getMeteringChannelID(MeteringTypeEnum type, MeteringUnitsEnum unit, boolean isTotal) {
162 return buildIdentifier(TOTAL_PRE, type, unit);
164 return buildIdentifier(type, unit);
168 public static MeteringTypeEnum getMeteringType(String channelID) {
169 // check metering channel
170 String[] meteringChannelSplit = channelID.split(SEPERATOR);
171 if (meteringChannelSplit.length > 1) {
174 if (meteringChannelSplit.length == 3) {
178 // check through IllegalArgumentException, if channel is metering
179 return MeteringTypeEnum.valueOf(meteringChannelSplit[0 + offset].toUpperCase());
180 } catch (IllegalArgumentException e) {
187 private static final List<String> SUPPORTED_OUTPUT_CHANNEL_TYPES = new ArrayList<>();
190 * Returns true, if the given channel type id is a output channel.
192 * @param channelTypeID to check
193 * @return true, if channel type id is output channel
195 public static boolean isOutputChannel(String channelTypeID) {
196 return SUPPORTED_OUTPUT_CHANNEL_TYPES.contains(channelTypeID);
201 protected void activate(ComponentContext componentContext) {
202 super.activate(componentContext);
207 protected void deactivate(ComponentContext componentContext) {
208 super.deactivate(componentContext);
213 protected void setTranslationProvider(TranslationProvider translationProvider) {
214 super.setTranslationProvider(translationProvider);
218 protected void unsetTranslationProvider(TranslationProvider translationProvider) {
219 super.unsetTranslationProvider(translationProvider);
223 protected void init() {
224 String channelIDpre = GENERAL;
225 for (short i = 0; i < 3; i++) {
227 channelIDpre = LIGHT;
230 channelIDpre = HEATING;
231 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, TEMPERATURE_CONTROLLED));
233 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, SWITCH));
234 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, DIMMER));
236 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, "2", STAGE));
237 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, "3", STAGE));
240 channelIDpre = SHADE;
241 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(channelIDpre);
242 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, ANGLE));
243 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(SCENE);
246 private String getSensorCategory(SensorEnum sensorType) {
247 switch (sensorType) {
251 case OUTPUT_CURRENT_H:
252 case POWER_CONSUMPTION:
253 return CATEGORY_ENERGY;
255 return CATEGORY_PRESSURE;
257 return CATEGORY_CARBONE_DIOXIDE;
259 return CATEGORY_RAIN;
260 case RELATIVE_HUMIDITY_INDOORS:
261 case RELATIVE_HUMIDITY_OUTDOORS:
262 return CATEGORY_HUMIDITY;
263 case ROOM_TEMPERATURE_CONTROL_VARIABLE:
265 case ROOM_TEMPERATURE_SET_POINT:
267 case TEMPERATURE_INDOORS:
268 case TEMPERATURE_OUTDOORS:
269 return CATEGORY_TEMPERATURE;
272 return CATEGORY_WIND;
273 case SOUND_PRESSURE_LEVEL:
274 return CATEGORY_SOUND_VOLUME;
275 case BRIGHTNESS_INDOORS:
276 case BRIGHTNESS_OUTDOORS:
277 return CATEGORY_BRIGHTNESS;
285 private String getBinaryInputCategory(DeviceBinarayInputEnum binaryInputType) {
286 switch (binaryInputType) {
287 case BATTERY_STATUS_IS_LOW:
288 return CATEGORY_BATTERY;
293 return CATEGORY_BRIGHTNESS;
294 case HEATING_OPERATION_ON_OFF:
295 case CHANGE_OVER_HEATING_COOLING:
296 case TEMPERATION_BELOW_LIMIT:
297 return CATEGORY_TEMPERATURE;
299 return CATEGORY_DOOR;
300 case GARAGE_DOOR_IS_OPEN:
301 return CATEGORY_GARAGE_DOOR;
303 case PRESENCE_IN_DARKNESS:
305 case MOTION_IN_DARKNESS:
306 return CATEGORY_MOTION;
308 return CATEGORY_RAIN;
310 return CATEGORY_SMOKE;
312 case WINDOW_IS_TILTED:
313 return CATEGORY_WINDOW;
314 case WIND_STRENGHT_ABOVE_LIMIT:
315 return CATEGORY_WIND;
317 return CATEGORY_ALARM;
325 private StateDescription getSensorStateDescription(SensorEnum sensorType) {
326 // the digitalSTROM resolution for temperature in kelvin is not correct but sensor-events and cached values are
327 // shown in °C so we will use this unit for temperature sensors
328 String unitShortCut = sensorType.getUnitShortcut();
329 if (unitShortCut.equals("%")) {
332 if (sensorType.toString().contains("TEMPERATURE")) {
335 return new StateDescription(null, null, null, sensorType.getPattern() + " " + unitShortCut, true, null);
338 private String getStageChannelOption(String type, String option) {
339 return buildIdentifier(type, STAGE, OPTION, option);
342 private StateDescription getStageDescription(String channelID, Locale locale) {
343 if (channelID.contains(STAGE.toLowerCase())) {
344 List<StateOption> stateOptions = new ArrayList<>();
345 if (channelID.contains(LIGHT)) {
346 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF, getText(
347 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF), locale)));
348 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON, getText(
349 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON), locale)));
350 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON, getText(
351 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON), locale)));
352 if (channelID.contains("3")) {
353 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON, getText(
354 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON),
358 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF,
359 getText(getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF),
361 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON, getText(
362 getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON), locale)));
363 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON,
364 getText(getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON),
366 if (channelID.contains("3")) {
367 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON, getText(
368 getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON),
372 return new StateDescription(null, null, null, null, false, stateOptions);
374 if (channelID.contains(TEMPERATURE_CONTROLLED)) {
375 return new StateDescription(new BigDecimal(0), new BigDecimal(50), new BigDecimal(0.1), "%.1f °C", false,
381 private String getCategory(String channelID) {
382 if (channelID.contains(LIGHT)) {
383 if (channelID.contains(DIMMER.toLowerCase())) {
384 return CATEGORY_DIMMABLE_LIGHT;
386 return CATEGORY_LIGHT;
388 if (channelID.contains(SHADE)) {
389 if (channelID.contains(ANGLE.toLowerCase())) {
390 return CATEGORY_BLINDES;
392 return ROLLERSHUTTER;
394 if (channelID.contains(TEMPERATURE_CONTROLLED)) {
395 return CATEGORY_TEMPERATURE;
400 private Set<String> getTags(String channelID, Locale locale) {
401 if (channelID.contains(LIGHT)) {
402 return new HashSet<>(Arrays.asList(getText(GE, locale), getText(DS, locale), getText(LIGHT, locale)));
404 if (channelID.contains(GENERAL)) {
405 return new HashSet<>(Arrays.asList(getText(SW, locale), getText(DS, locale), getText(JOKER, locale)));
407 if (channelID.contains(SHADE)) {
408 return new HashSet<>(Arrays.asList(getText(GR, locale), getText(DS, locale), getText("SHADE", locale)));
410 if (channelID.contains(SCENE)) {
411 return new HashSet<>(Arrays.asList(getText(SCENE, locale), getText(DS, locale)));
413 if (channelID.contains(HEATING)) {
414 return new HashSet<>(Arrays.asList(getText(BL, locale), getText(DS, locale), getText(HEATING, locale)));
419 private Set<String> getSimpleTags(String channelID, Locale locale) {
420 return new HashSet<>(Arrays.asList(getText(channelID, locale), getText(channelID, locale)));
424 * Returns the supported item type for the given channel type id or null, if the channel type does not exist.
426 * @param channelTypeID of the channel
427 * @return item type or null
429 public static String getItemType(String channelTypeID) {
430 if (channelTypeID != null) {
431 if (stringContains(channelTypeID, STAGE)) {
434 if (stringContains(channelTypeID, SWITCH) || stringContains(channelTypeID, SCENE)
435 || stringContains(channelTypeID, WIPE) || stringContains(channelTypeID, BINARY_INPUT_PRE)) {
438 if (stringContains(channelTypeID, DIMMER) || stringContains(channelTypeID, ANGLE)) {
441 if (stringContains(channelTypeID, TEMPERATURE_CONTROLLED)) {
444 if (channelTypeID.contains(SHADE)) {
445 return ROLLERSHUTTER;
451 private static boolean stringContains(String string, String compare) {
452 return string.toLowerCase().contains(compare.toLowerCase());
456 public Collection<ChannelType> getChannelTypes(Locale locale) {
457 List<ChannelType> channelTypeList = new LinkedList<>();
458 for (String channelTypeId : SUPPORTED_OUTPUT_CHANNEL_TYPES) {
460 getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID, channelTypeId), locale));
462 for (SensorEnum sensorType : SensorEnum.values()) {
463 channelTypeList.add(getChannelType(
464 new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID, buildIdentifier(sensorType)), locale));
466 for (MeteringTypeEnum meteringType : MeteringTypeEnum.values()) {
467 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
468 buildIdentifier(meteringType, MeteringUnitsEnum.WH)), locale));
469 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
470 buildIdentifier(TOTAL_PRE, meteringType, MeteringUnitsEnum.WH)), locale));
472 for (DeviceBinarayInputEnum binaryInput : DeviceBinarayInputEnum.values()) {
473 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
474 buildIdentifier(BINARY_INPUT_PRE, binaryInput)), locale));
476 return channelTypeList;
480 public ChannelType getChannelType(ChannelTypeUID channelTypeUID, Locale locale) {
481 if (channelTypeUID.getBindingId().equals(DigitalSTROMBindingConstants.BINDING_ID)) {
482 String channelID = channelTypeUID.getId();
484 SensorEnum sensorType = SensorEnum.valueOf(channelTypeUID.getId().toUpperCase());
485 return ChannelTypeBuilder.state(channelTypeUID, getLabelText(channelID, locale), NUMBER)
486 .withDescription(getDescText(channelID, locale)).withCategory(getSensorCategory(sensorType))
487 .withTags(getSimpleTags(channelID, locale))
488 .withStateDescription(getSensorStateDescription(sensorType)).build();
489 } catch (IllegalArgumentException e) {
490 if (SUPPORTED_OUTPUT_CHANNEL_TYPES.contains(channelID)) {
491 return ChannelTypeBuilder
492 .state(channelTypeUID, getLabelText(channelID, locale), getItemType(channelID))
493 .withDescription(getDescText(channelID, locale)).withCategory(getCategory(channelID))
494 .withTags(getTags(channelID, locale))
495 .withStateDescription(getStageDescription(channelID, locale)).build();
497 MeteringTypeEnum meteringType = getMeteringType(channelID);
498 if (meteringType != null) {
499 String pattern = "%.3f kWh";
501 if (MeteringTypeEnum.CONSUMPTION.equals(meteringType)) {
504 return ChannelTypeBuilder.state(channelTypeUID, getLabelText(channelID, locale), NUMBER)
505 .withDescription(getDescText(channelID, locale)).withCategory(CATEGORY_ENERGY)
507 new HashSet<>(Arrays.asList(getLabelText(channelID, locale), getText(DS, locale))))
508 .withStateDescription(new StateDescription(null, null, null, pattern, true, null)).build();
511 DeviceBinarayInputEnum binarayInputType = DeviceBinarayInputEnum
512 .valueOf(channelTypeUID.getId().replaceAll(BINARY_INPUT_PRE + SEPERATOR, "").toUpperCase());
513 return ChannelTypeBuilder
514 .state(channelTypeUID, getLabelText(channelID, locale), getItemType(channelID))
515 .withDescription(getDescText(channelID, locale))
516 .withCategory(getBinaryInputCategory(binarayInputType))
517 .withTags(getSimpleTags(channelTypeUID.getId(), locale))
518 .withStateDescription(new StateDescription(null, null, null, null, true, null)).build();
519 } catch (IllegalArgumentException e1) {
528 * Returns the {@link ChannelGroupTypeUID} for the given {@link SensorEnum}.
530 * @param sensorType (must not be null)
531 * @return the channel type uid
533 public static ChannelTypeUID getSensorChannelUID(SensorEnum sensorType) {
534 return new ChannelTypeUID(BINDING_ID, buildIdentifier(sensorType));
538 * Returns the {@link ChannelGroupTypeUID} for the given {@link DeviceBinarayInputEnum}.
540 * @param binaryInputType (must not be null)
541 * @return the channel type uid
543 public static ChannelTypeUID getBinaryInputChannelUID(DeviceBinarayInputEnum binaryInputType) {
544 return new ChannelTypeUID(BINDING_ID, buildIdentifier(BINARY_INPUT_PRE, binaryInputType));