2 * Copyright (c) 2010-2021 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.StateDescriptionFragment;
41 import org.openhab.core.types.StateDescriptionFragmentBuilder;
42 import org.openhab.core.types.StateOption;
43 import org.osgi.service.component.ComponentContext;
44 import org.osgi.service.component.annotations.Activate;
45 import org.osgi.service.component.annotations.Component;
46 import org.osgi.service.component.annotations.Deactivate;
47 import org.osgi.service.component.annotations.Reference;
50 * The {@link DsChannelTypeProvider} implements the {@link ChannelTypeProvider} generates all supported
51 * {@link Channel}'s for digitalSTROM.
53 * @author Michael Ochel - Initial contribution
54 * @author Matthias Siegele - Initial contribution
57 @Component(service = ChannelTypeProvider.class)
58 public class DsChannelTypeProvider extends BaseDsI18n implements ChannelTypeProvider {
60 // channelID building (effect group type + (nothing || SEPERATOR + item type || SEPERATOR + extended item type) e.g.
61 // light_switch, shade or shade_angle
62 // channel effect group type
63 public static final String LIGHT = "light"; // and tag
64 public static final String SHADE = "shade"; // and tag
65 public static final String HEATING = "heating"; // and tag
66 public static final String GENERAL = "general";
67 public static final String SCENE = "scene";
68 // channel extended item type
69 public static final String WIPE = "wipe";
70 public static final String ANGLE = "angle";
71 public static final String STAGE = "stage"; // pre stageses e.g. 2+STAGE_SWITCH
72 public static final String TEMPERATURE_CONTROLLED = "temperature_controlled";
75 public static final String DIMMER = "Dimmer";
76 public static final String SWITCH = "Switch";
77 public static final String ROLLERSHUTTER = "Rollershutter";
78 public static final String STRING = "String";
79 public static final String NUMBER = "Number";
81 public static final String TOTAL_PRE = "total";
82 public static final String BINARY_INPUT_PRE = "binary_input";
83 public static final String OPTION = "opt";
86 public static final String GE = "GE";
87 public static final String GR = "GR";
88 public static final String BL = "BL";
89 public static final String SW = "SW";
90 public static final String DS = "DS";
91 public static final String JOKER = "JOKER";
94 public static final String CATEGORY_BLINDES = "Blinds";
95 public static final String CATEGORY_DIMMABLE_LIGHT = "DimmableLight";
96 public static final String CATEGORY_CARBONE_DIOXIDE = "CarbonDioxide";
97 public static final String CATEGORY_ENERGY = "Energy";
98 public static final String CATEGORY_HUMIDITY = "Humidity";
99 public static final String CATEGORY_BRIGHTNESS = "Brightness";
100 public static final String CATEGORY_LIGHT = "Light";
101 public static final String CATEGORY_PRESSURE = "Pressure";
102 public static final String CATEGORY_SOUND_VOLUME = "SoundVolume";
103 public static final String CATEGORY_TEMPERATURE = "Temperature";
104 public static final String CATEGORY_WIND = "Wind";
105 public static final String CATEGORY_RAIN = "Rain";
106 public static final String CATEGORY_BATTERY = "Battery";
107 public static final String CATEGORY_DOOR = "Door";
108 public static final String CATEGORY_WINDOW = "Window";
109 public static final String CATEGORY_GARAGE_DOOR = "GarageDoor";
110 public static final String CATEGORY_SMOKE = "Smoke";
111 public static final String CATEGORY_ALARM = "Alarm";
112 public static final String CATEGORY_MOTION = "Motion";
115 * Returns the output channel type id as {@link String} for the given {@link FunctionalColorGroupEnum} and
116 * {@link OutputModeEnum} or null, if no channel type exists for the given {@link FunctionalColorGroupEnum} and
117 * {@link OutputModeEnum}.
119 * @param functionalGroup of the {@link Device}
120 * @param outputMode of the {@link Device}
121 * @return the output channel type id or null
123 public static String getOutputChannelTypeID(FunctionalColorGroupEnum functionalGroup, OutputModeEnum outputMode) {
124 if (functionalGroup != null && outputMode != null) {
125 String channelPreID = GENERAL;
126 if (functionalGroup.equals(FunctionalColorGroupEnum.YELLOW)) {
127 channelPreID = LIGHT;
129 if (functionalGroup.equals(FunctionalColorGroupEnum.GREY)) {
130 if (outputMode.equals(OutputModeEnum.POSITION_CON)) {
131 return buildIdentifier(SHADE);
133 if (outputMode.equals(OutputModeEnum.POSITION_CON_US)) {
134 return buildIdentifier(SHADE, ANGLE);
137 if (functionalGroup.equals(FunctionalColorGroupEnum.BLUE)) {
138 channelPreID = HEATING;
139 if (OutputModeEnum.outputModeIsTemperationControlled(outputMode)) {
140 return buildIdentifier(channelPreID, TEMPERATURE_CONTROLLED);
143 if (OutputModeEnum.outputModeIsSwitch(outputMode)) {
144 return buildIdentifier(channelPreID, SWITCH);
146 if (OutputModeEnum.outputModeIsDimmable(outputMode)) {
147 return buildIdentifier(channelPreID, DIMMER);
149 if (!channelPreID.equals(HEATING)) {
150 if (outputMode.equals(OutputModeEnum.COMBINED_2_STAGE_SWITCH)) {
151 return buildIdentifier(channelPreID, "2", STAGE);
153 if (outputMode.equals(OutputModeEnum.COMBINED_3_STAGE_SWITCH)) {
154 return buildIdentifier(channelPreID, "3", STAGE);
161 public static String getMeteringChannelID(MeteringTypeEnum type, MeteringUnitsEnum unit, boolean isTotal) {
163 return buildIdentifier(TOTAL_PRE, type, unit);
165 return buildIdentifier(type, unit);
169 public static MeteringTypeEnum getMeteringType(String channelID) {
170 // check metering channel
171 String[] meteringChannelSplit = channelID.split(SEPERATOR);
172 if (meteringChannelSplit.length > 1) {
175 if (meteringChannelSplit.length == 3) {
179 // check through IllegalArgumentException, if channel is metering
180 return MeteringTypeEnum.valueOf(meteringChannelSplit[0 + offset].toUpperCase());
181 } catch (IllegalArgumentException e) {
188 private static final List<String> SUPPORTED_OUTPUT_CHANNEL_TYPES = new ArrayList<>();
191 * Returns true, if the given channel type id is a output channel.
193 * @param channelTypeID to check
194 * @return true, if channel type id is output channel
196 public static boolean isOutputChannel(String channelTypeID) {
197 return SUPPORTED_OUTPUT_CHANNEL_TYPES.contains(channelTypeID);
202 protected void activate(ComponentContext componentContext) {
203 super.activate(componentContext);
208 protected void deactivate(ComponentContext componentContext) {
209 super.deactivate(componentContext);
214 protected void setTranslationProvider(TranslationProvider translationProvider) {
215 super.setTranslationProvider(translationProvider);
219 protected void unsetTranslationProvider(TranslationProvider translationProvider) {
220 super.unsetTranslationProvider(translationProvider);
224 protected void init() {
225 String channelIDpre = GENERAL;
226 for (short i = 0; i < 3; i++) {
228 channelIDpre = LIGHT;
231 channelIDpre = HEATING;
232 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, TEMPERATURE_CONTROLLED));
234 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, SWITCH));
235 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, DIMMER));
237 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, "2", STAGE));
238 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, "3", STAGE));
241 channelIDpre = SHADE;
242 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(channelIDpre);
243 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, ANGLE));
244 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(SCENE);
247 private String getSensorCategory(SensorEnum sensorType) {
248 switch (sensorType) {
252 case OUTPUT_CURRENT_H:
253 case POWER_CONSUMPTION:
254 return CATEGORY_ENERGY;
256 return CATEGORY_PRESSURE;
258 return CATEGORY_CARBONE_DIOXIDE;
260 return CATEGORY_RAIN;
261 case RELATIVE_HUMIDITY_INDOORS:
262 case RELATIVE_HUMIDITY_OUTDOORS:
263 return CATEGORY_HUMIDITY;
264 case ROOM_TEMPERATURE_CONTROL_VARIABLE:
266 case ROOM_TEMPERATURE_SET_POINT:
268 case TEMPERATURE_INDOORS:
269 case TEMPERATURE_OUTDOORS:
270 return CATEGORY_TEMPERATURE;
273 return CATEGORY_WIND;
274 case SOUND_PRESSURE_LEVEL:
275 return CATEGORY_SOUND_VOLUME;
276 case BRIGHTNESS_INDOORS:
277 case BRIGHTNESS_OUTDOORS:
278 return CATEGORY_BRIGHTNESS;
286 private String getBinaryInputCategory(DeviceBinarayInputEnum binaryInputType) {
287 switch (binaryInputType) {
288 case BATTERY_STATUS_IS_LOW:
289 return CATEGORY_BATTERY;
294 return CATEGORY_BRIGHTNESS;
295 case HEATING_OPERATION_ON_OFF:
296 case CHANGE_OVER_HEATING_COOLING:
297 case TEMPERATION_BELOW_LIMIT:
298 return CATEGORY_TEMPERATURE;
300 return CATEGORY_DOOR;
301 case GARAGE_DOOR_IS_OPEN:
302 return CATEGORY_GARAGE_DOOR;
304 case PRESENCE_IN_DARKNESS:
306 case MOTION_IN_DARKNESS:
307 return CATEGORY_MOTION;
309 return CATEGORY_RAIN;
311 return CATEGORY_SMOKE;
313 case WINDOW_IS_TILTED:
314 return CATEGORY_WINDOW;
315 case WIND_STRENGHT_ABOVE_LIMIT:
316 return CATEGORY_WIND;
318 return CATEGORY_ALARM;
326 private StateDescriptionFragment getSensorStateDescription(SensorEnum sensorType) {
327 // the digitalSTROM resolution for temperature in kelvin is not correct but sensor-events and cached values are
328 // shown in °C so we will use this unit for temperature sensors
329 String unitShortCut = sensorType.getUnitShortcut();
330 if (unitShortCut.equals("%")) {
333 if (sensorType.toString().contains("TEMPERATURE")) {
336 return StateDescriptionFragmentBuilder.create().withPattern(sensorType.getPattern() + " " + unitShortCut)
337 .withReadOnly(true).build();
340 private String getStageChannelOption(String type, String option) {
341 return buildIdentifier(type, STAGE, OPTION, option);
344 private StateDescriptionFragment getStageDescription(String channelID, Locale locale) {
345 if (channelID.contains(STAGE.toLowerCase())) {
346 List<StateOption> stateOptions = new ArrayList<>();
347 if (channelID.contains(LIGHT)) {
348 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF, getText(
349 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF), locale)));
350 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON, getText(
351 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON), locale)));
352 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON, getText(
353 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON), locale)));
354 if (channelID.contains("3")) {
355 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON, getText(
356 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON),
360 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF,
361 getText(getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF),
363 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON, getText(
364 getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON), locale)));
365 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON,
366 getText(getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON),
368 if (channelID.contains("3")) {
369 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON, getText(
370 getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON),
374 return StateDescriptionFragmentBuilder.create().withReadOnly(false).withOptions(stateOptions).build();
376 if (channelID.contains(TEMPERATURE_CONTROLLED)) {
377 return StateDescriptionFragmentBuilder.create().withMinimum(new BigDecimal(0))
378 .withMaximum(new BigDecimal(50)).withStep(new BigDecimal(0.1)).withPattern("%.1f °C")
379 .withReadOnly(false).build();
384 private String getCategory(String channelID) {
385 if (channelID.contains(LIGHT)) {
386 if (channelID.contains(DIMMER.toLowerCase())) {
387 return CATEGORY_DIMMABLE_LIGHT;
389 return CATEGORY_LIGHT;
391 if (channelID.contains(SHADE)) {
392 if (channelID.contains(ANGLE.toLowerCase())) {
393 return CATEGORY_BLINDES;
395 return ROLLERSHUTTER;
397 if (channelID.contains(TEMPERATURE_CONTROLLED)) {
398 return CATEGORY_TEMPERATURE;
403 private Set<String> getTags(String channelID, Locale locale) {
404 if (channelID.contains(LIGHT)) {
405 return new HashSet<>(Arrays.asList(getText(GE, locale), getText(DS, locale), getText(LIGHT, locale)));
407 if (channelID.contains(GENERAL)) {
408 return new HashSet<>(Arrays.asList(getText(SW, locale), getText(DS, locale), getText(JOKER, locale)));
410 if (channelID.contains(SHADE)) {
411 return new HashSet<>(Arrays.asList(getText(GR, locale), getText(DS, locale), getText("SHADE", locale)));
413 if (channelID.contains(SCENE)) {
414 return new HashSet<>(Arrays.asList(getText(SCENE, locale), getText(DS, locale)));
416 if (channelID.contains(HEATING)) {
417 return new HashSet<>(Arrays.asList(getText(BL, locale), getText(DS, locale), getText(HEATING, locale)));
422 private Set<String> getSimpleTags(String channelID, Locale locale) {
423 return new HashSet<>(Arrays.asList(getText(channelID, locale), getText(channelID, locale)));
427 * Returns the supported item type for the given channel type id or null, if the channel type does not exist.
429 * @param channelTypeID of the channel
430 * @return item type or null
432 public static String getItemType(String channelTypeID) {
433 if (channelTypeID != null) {
434 if (stringContains(channelTypeID, STAGE)) {
437 if (stringContains(channelTypeID, SWITCH) || stringContains(channelTypeID, SCENE)
438 || stringContains(channelTypeID, WIPE) || stringContains(channelTypeID, BINARY_INPUT_PRE)) {
441 if (stringContains(channelTypeID, DIMMER) || stringContains(channelTypeID, ANGLE)) {
444 if (stringContains(channelTypeID, TEMPERATURE_CONTROLLED)) {
447 if (channelTypeID.contains(SHADE)) {
448 return ROLLERSHUTTER;
454 private static boolean stringContains(String string, String compare) {
455 return string.toLowerCase().contains(compare.toLowerCase());
459 public Collection<ChannelType> getChannelTypes(Locale locale) {
460 List<ChannelType> channelTypeList = new LinkedList<>();
461 for (String channelTypeId : SUPPORTED_OUTPUT_CHANNEL_TYPES) {
463 getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID, channelTypeId), locale));
465 for (SensorEnum sensorType : SensorEnum.values()) {
466 channelTypeList.add(getChannelType(
467 new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID, buildIdentifier(sensorType)), locale));
469 for (MeteringTypeEnum meteringType : MeteringTypeEnum.values()) {
470 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
471 buildIdentifier(meteringType, MeteringUnitsEnum.WH)), locale));
472 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
473 buildIdentifier(TOTAL_PRE, meteringType, MeteringUnitsEnum.WH)), locale));
475 for (DeviceBinarayInputEnum binaryInput : DeviceBinarayInputEnum.values()) {
476 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
477 buildIdentifier(BINARY_INPUT_PRE, binaryInput)), locale));
479 return channelTypeList;
483 public ChannelType getChannelType(ChannelTypeUID channelTypeUID, Locale locale) {
484 if (channelTypeUID.getBindingId().equals(DigitalSTROMBindingConstants.BINDING_ID)) {
485 String channelID = channelTypeUID.getId();
487 SensorEnum sensorType = SensorEnum.valueOf(channelTypeUID.getId().toUpperCase());
488 return ChannelTypeBuilder.state(channelTypeUID, getLabelText(channelID, locale), NUMBER)
489 .withDescription(getDescText(channelID, locale)).withCategory(getSensorCategory(sensorType))
490 .withTags(getSimpleTags(channelID, locale))
491 .withStateDescriptionFragment(getSensorStateDescription(sensorType)).build();
492 } catch (IllegalArgumentException e) {
493 if (SUPPORTED_OUTPUT_CHANNEL_TYPES.contains(channelID)) {
494 return ChannelTypeBuilder
495 .state(channelTypeUID, getLabelText(channelID, locale), getItemType(channelID))
496 .withDescription(getDescText(channelID, locale)).withCategory(getCategory(channelID))
497 .withTags(getTags(channelID, locale))
498 .withStateDescriptionFragment(getStageDescription(channelID, locale)).build();
500 MeteringTypeEnum meteringType = getMeteringType(channelID);
501 if (meteringType != null) {
502 String pattern = "%.3f kWh";
504 if (MeteringTypeEnum.CONSUMPTION.equals(meteringType)) {
508 return ChannelTypeBuilder.state(channelTypeUID, getLabelText(channelID, locale), NUMBER)
509 .withDescription(getDescText(channelID, locale)).withCategory(CATEGORY_ENERGY)
511 new HashSet<>(Arrays.asList(getLabelText(channelID, locale), getText(DS, locale))))
512 .withStateDescriptionFragment(StateDescriptionFragmentBuilder.create().withPattern(pattern)
513 .withReadOnly(true).build())
517 DeviceBinarayInputEnum binarayInputType = DeviceBinarayInputEnum
518 .valueOf(channelTypeUID.getId().replaceAll(BINARY_INPUT_PRE + SEPERATOR, "").toUpperCase());
519 return ChannelTypeBuilder
520 .state(channelTypeUID, getLabelText(channelID, locale), getItemType(channelID))
521 .withDescription(getDescText(channelID, locale))
522 .withCategory(getBinaryInputCategory(binarayInputType))
523 .withTags(getSimpleTags(channelTypeUID.getId(), locale)).withStateDescriptionFragment(
524 StateDescriptionFragmentBuilder.create().withReadOnly(true).build())
526 } catch (IllegalArgumentException e1) {
535 * Returns the {@link ChannelGroupTypeUID} for the given {@link SensorEnum}.
537 * @param sensorType (must not be null)
538 * @return the channel type uid
540 public static ChannelTypeUID getSensorChannelUID(SensorEnum sensorType) {
541 return new ChannelTypeUID(BINDING_ID, buildIdentifier(sensorType));
545 * Returns the {@link ChannelGroupTypeUID} for the given {@link DeviceBinarayInputEnum}.
547 * @param binaryInputType (must not be null)
548 * @return the channel type uid
550 public static ChannelTypeUID getBinaryInputChannelUID(DeviceBinarayInputEnum binaryInputType) {
551 return new ChannelTypeUID(BINDING_ID, buildIdentifier(BINARY_INPUT_PRE, binaryInputType));