2 * Copyright (c) 2010-2023 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.ApplicationGroup;
29 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.DeviceBinarayInputEnum;
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.OutputChannelEnum;
33 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.OutputModeEnum;
34 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.SensorEnum;
35 import org.openhab.core.i18n.TranslationProvider;
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}
51 * generates all supported {@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 ||
61 // SEPERATOR + extended item type) e.g.
62 // light_switch, shade or shade_angle
63 // channel effect group type
64 public static final String LIGHT = "light"; // and tag
65 public static final String SHADE = "shade"; // and tag
66 public static final String HEATING = "heating"; // and tag
67 public static final String GENERAL = "general";
68 public static final String SCENE = "scene";
69 // channel extended item type
70 public static final String WIPE = "wipe";
71 public static final String ANGLE = "angle";
72 public static final String STAGE = "stage"; // pre stageses e.g. 2+STAGE_SWITCH
73 public static final String TEMPERATURE_CONTROLLED = "temperature_controlled";
76 public static final String DIMMER = "Dimmer";
77 public static final String SWITCH = "Switch";
78 public static final String ROLLERSHUTTER = "Rollershutter";
79 public static final String STRING = "String";
80 public static final String NUMBER = "Number";
82 public static final String TOTAL_PRE = "total";
83 public static final String BINARY_INPUT_PRE = "binary_input";
84 public static final String OPTION = "opt";
87 public static final String GE = "GE";
88 public static final String GR = "GR";
89 public static final String BL = "BL";
90 public static final String SW = "SW";
91 public static final String DS = "DS";
92 public static final String JOKER = "JOKER";
95 public static final String CATEGORY_BLINDES = "Blinds";
96 public static final String CATEGORY_DIMMABLE_LIGHT = "DimmableLight";
97 public static final String CATEGORY_CARBONE_DIOXIDE = "CarbonDioxide";
98 public static final String CATEGORY_ENERGY = "Energy";
99 public static final String CATEGORY_HUMIDITY = "Humidity";
100 public static final String CATEGORY_BRIGHTNESS = "Brightness";
101 public static final String CATEGORY_LIGHT = "Light";
102 public static final String CATEGORY_PRESSURE = "Pressure";
103 public static final String CATEGORY_SOUND_VOLUME = "SoundVolume";
104 public static final String CATEGORY_TEMPERATURE = "Temperature";
105 public static final String CATEGORY_WIND = "Wind";
106 public static final String CATEGORY_RAIN = "Rain";
107 public static final String CATEGORY_BATTERY = "Battery";
108 public static final String CATEGORY_DOOR = "Door";
109 public static final String CATEGORY_WINDOW = "Window";
110 public static final String CATEGORY_GARAGE_DOOR = "GarageDoor";
111 public static final String CATEGORY_SMOKE = "Smoke";
112 public static final String CATEGORY_ALARM = "Alarm";
113 public static final String CATEGORY_MOTION = "Motion";
116 * Returns the output channel type id as {@link String} for the given
117 * {@link ApplicationGroup.Color} and {@link OutputModeEnum} or null, if no
118 * channel type exists for the given {@link ApplicationGroup.Color} and
119 * {@link OutputModeEnum}.
121 * @param functionalGroup of the {@link Device}
122 * @param outputMode of the {@link Device}
123 * @return the output channel type id or null
125 public static String getOutputChannelTypeID(ApplicationGroup.Color functionalGroup, OutputModeEnum outputMode,
126 List<OutputChannelEnum> outputChannels) {
127 if (functionalGroup != null && outputMode != null) {
128 String channelPreID = GENERAL;
130 switch (functionalGroup) {
132 channelPreID = LIGHT;
135 if (outputChannels != null && (outputChannels.contains(OutputChannelEnum.SHADE_OPENING_ANGLE_INDOOR)
136 || outputChannels.contains(OutputChannelEnum.SHADE_OPENING_ANGLE_OUTSIDE))) {
137 return buildIdentifier(SHADE, ANGLE);
139 return buildIdentifier(SHADE);
142 channelPreID = HEATING;
143 if (OutputModeEnum.outputModeIsTemperationControlled(outputMode)) {
144 return buildIdentifier(channelPreID, TEMPERATURE_CONTROLLED);
150 if (OutputModeEnum.outputModeIsSwitch(outputMode)) {
151 return buildIdentifier(channelPreID, SWITCH);
153 if (OutputModeEnum.outputModeIsDimmable(outputMode)) {
154 return buildIdentifier(channelPreID, DIMMER);
156 if (!channelPreID.equals(HEATING)) {
157 if (outputMode.equals(OutputModeEnum.COMBINED_2_STAGE_SWITCH)) {
158 return buildIdentifier(channelPreID, "2", STAGE);
160 if (outputMode.equals(OutputModeEnum.COMBINED_3_STAGE_SWITCH)) {
161 return buildIdentifier(channelPreID, "3", STAGE);
168 public static String getMeteringChannelID(MeteringTypeEnum type, MeteringUnitsEnum unit, boolean isTotal) {
170 return buildIdentifier(TOTAL_PRE, type, unit);
172 return buildIdentifier(type, unit);
176 public static MeteringTypeEnum getMeteringType(String channelID) {
177 // check metering channel
178 String[] meteringChannelSplit = channelID.split(SEPERATOR);
179 if (meteringChannelSplit.length > 1) {
182 if (meteringChannelSplit.length == 3) {
186 // check through IllegalArgumentException, if channel is metering
187 return MeteringTypeEnum.valueOf(meteringChannelSplit[0 + offset].toUpperCase());
188 } catch (IllegalArgumentException e) {
195 private static final List<String> SUPPORTED_OUTPUT_CHANNEL_TYPES = new ArrayList<>();
198 * Returns true, if the given channel type id is an output channel.
200 * @param channelTypeID to check
201 * @return true, if channel type id is output channel
203 public static boolean isOutputChannel(String channelTypeID) {
204 return SUPPORTED_OUTPUT_CHANNEL_TYPES.contains(channelTypeID);
209 protected void activate(ComponentContext componentContext) {
210 super.activate(componentContext);
215 protected void deactivate(ComponentContext componentContext) {
216 super.deactivate(componentContext);
221 protected void setTranslationProvider(TranslationProvider translationProvider) {
222 super.setTranslationProvider(translationProvider);
226 protected void unsetTranslationProvider(TranslationProvider translationProvider) {
227 super.unsetTranslationProvider(translationProvider);
231 protected void init() {
232 String channelIDpre = GENERAL;
233 for (short i = 0; i < 3; i++) {
235 channelIDpre = LIGHT;
238 channelIDpre = HEATING;
239 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, TEMPERATURE_CONTROLLED));
241 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, SWITCH));
242 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, DIMMER));
244 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, "2", STAGE));
245 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, "3", STAGE));
248 channelIDpre = SHADE;
249 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(channelIDpre);
250 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, ANGLE));
251 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(SCENE);
254 private String getSensorCategory(SensorEnum sensorType) {
255 switch (sensorType) {
259 case OUTPUT_CURRENT_H:
260 case POWER_CONSUMPTION:
261 return CATEGORY_ENERGY;
263 return CATEGORY_PRESSURE;
265 return CATEGORY_CARBONE_DIOXIDE;
267 return CATEGORY_RAIN;
268 case RELATIVE_HUMIDITY_INDOORS:
269 case RELATIVE_HUMIDITY_OUTDOORS:
270 return CATEGORY_HUMIDITY;
271 case ROOM_TEMPERATURE_CONTROL_VARIABLE:
273 case ROOM_TEMPERATURE_SET_POINT:
275 case TEMPERATURE_INDOORS:
276 case TEMPERATURE_OUTDOORS:
277 return CATEGORY_TEMPERATURE;
280 return CATEGORY_WIND;
281 case SOUND_PRESSURE_LEVEL:
282 return CATEGORY_SOUND_VOLUME;
283 case BRIGHTNESS_INDOORS:
284 case BRIGHTNESS_OUTDOORS:
285 return CATEGORY_BRIGHTNESS;
293 private String getBinaryInputCategory(DeviceBinarayInputEnum binaryInputType) {
294 switch (binaryInputType) {
295 case BATTERY_STATUS_IS_LOW:
296 return CATEGORY_BATTERY;
301 return CATEGORY_BRIGHTNESS;
302 case HEATING_OPERATION_ON_OFF:
303 case CHANGE_OVER_HEATING_COOLING:
304 case TEMPERATION_BELOW_LIMIT:
305 return CATEGORY_TEMPERATURE;
307 return CATEGORY_DOOR;
308 case GARAGE_DOOR_IS_OPEN:
309 return CATEGORY_GARAGE_DOOR;
311 case PRESENCE_IN_DARKNESS:
313 case MOTION_IN_DARKNESS:
314 return CATEGORY_MOTION;
316 return CATEGORY_RAIN;
318 return CATEGORY_SMOKE;
320 case WINDOW_IS_TILTED:
321 return CATEGORY_WINDOW;
322 case WIND_STRENGHT_ABOVE_LIMIT:
323 return CATEGORY_WIND;
325 return CATEGORY_ALARM;
333 private StateDescriptionFragment getSensorStateDescription(SensorEnum sensorType) {
334 // the digitalSTROM resolution for temperature in kelvin is not correct but
335 // sensor-events and cached values are
336 // shown in °C so we will use this unit for temperature sensors
337 String unitShortCut = sensorType.getUnitShortcut();
338 if ("%".equals(unitShortCut)) {
341 if (sensorType.toString().contains("TEMPERATURE")) {
344 return StateDescriptionFragmentBuilder.create().withPattern(sensorType.getPattern() + " " + unitShortCut)
345 .withReadOnly(true).build();
348 private String getStageChannelOption(String type, String option) {
349 return buildIdentifier(type, STAGE, OPTION, option);
352 private StateDescriptionFragment getStageDescription(String channelID, Locale locale) {
353 if (channelID.contains(STAGE.toLowerCase())) {
354 List<StateOption> stateOptions = new ArrayList<>();
355 if (channelID.contains(LIGHT)) {
356 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF, getText(
357 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF), locale)));
358 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON, getText(
359 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON), locale)));
360 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON, getText(
361 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON), locale)));
362 if (channelID.contains("3")) {
363 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON, getText(
364 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON),
368 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF,
369 getText(getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF),
371 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON, getText(
372 getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON), locale)));
373 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON,
374 getText(getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON),
376 if (channelID.contains("3")) {
377 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON, getText(
378 getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON),
382 return StateDescriptionFragmentBuilder.create().withReadOnly(false).withOptions(stateOptions).build();
384 if (channelID.contains(TEMPERATURE_CONTROLLED)) {
385 return StateDescriptionFragmentBuilder.create().withMinimum(new BigDecimal(0))
386 .withMaximum(new BigDecimal(50)).withStep(new BigDecimal(0.1)).withPattern("%.1f °C")
387 .withReadOnly(false).build();
392 private String getCategory(String channelID) {
393 if (channelID.contains(LIGHT)) {
394 if (channelID.contains(DIMMER.toLowerCase())) {
395 return CATEGORY_DIMMABLE_LIGHT;
397 return CATEGORY_LIGHT;
399 if (channelID.contains(SHADE)) {
400 if (channelID.contains(ANGLE.toLowerCase())) {
401 return CATEGORY_BLINDES;
403 return ROLLERSHUTTER;
405 if (channelID.contains(TEMPERATURE_CONTROLLED)) {
406 return CATEGORY_TEMPERATURE;
411 private Set<String> getTags(String channelID, Locale locale) {
412 if (channelID.contains(LIGHT)) {
413 return new HashSet<>(Arrays.asList(getText(GE, locale), getText(DS, locale), getText(LIGHT, locale)));
415 if (channelID.contains(GENERAL)) {
416 return new HashSet<>(Arrays.asList(getText(SW, locale), getText(DS, locale), getText(JOKER, locale)));
418 if (channelID.contains(SHADE)) {
419 return new HashSet<>(Arrays.asList(getText(GR, locale), getText(DS, locale), getText("SHADE", locale)));
421 if (channelID.contains(SCENE)) {
422 return new HashSet<>(Arrays.asList(getText(SCENE, locale), getText(DS, locale)));
424 if (channelID.contains(HEATING)) {
425 return new HashSet<>(Arrays.asList(getText(BL, locale), getText(DS, locale), getText(HEATING, locale)));
430 private Set<String> getSimpleTags(String channelID, Locale locale) {
431 return new HashSet<>(Arrays.asList(getText(channelID, locale), getText(channelID, locale)));
435 * Returns the supported item type for the given channel type id or null, if the
436 * channel type does not exist.
438 * @param channelTypeID of the channel
439 * @return item type or null
441 public static String getItemType(String channelTypeID) {
442 if (channelTypeID != null) {
443 if (stringContains(channelTypeID, STAGE)) {
446 if (stringContains(channelTypeID, SWITCH) || stringContains(channelTypeID, SCENE)
447 || stringContains(channelTypeID, WIPE) || stringContains(channelTypeID, BINARY_INPUT_PRE)) {
450 if (stringContains(channelTypeID, DIMMER) || stringContains(channelTypeID, ANGLE)) {
453 if (stringContains(channelTypeID, TEMPERATURE_CONTROLLED)) {
456 if (channelTypeID.contains(SHADE)) {
457 return ROLLERSHUTTER;
463 private static boolean stringContains(String string, String compare) {
464 return string.toLowerCase().contains(compare.toLowerCase());
468 public Collection<ChannelType> getChannelTypes(Locale locale) {
469 List<ChannelType> channelTypeList = new LinkedList<>();
470 for (String channelTypeId : SUPPORTED_OUTPUT_CHANNEL_TYPES) {
472 getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID, channelTypeId), locale));
474 for (SensorEnum sensorType : SensorEnum.values()) {
475 channelTypeList.add(getChannelType(
476 new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID, buildIdentifier(sensorType)), locale));
478 for (MeteringTypeEnum meteringType : MeteringTypeEnum.values()) {
479 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
480 buildIdentifier(meteringType, MeteringUnitsEnum.WH)), locale));
481 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
482 buildIdentifier(TOTAL_PRE, meteringType, MeteringUnitsEnum.WH)), locale));
484 for (DeviceBinarayInputEnum binaryInput : DeviceBinarayInputEnum.values()) {
485 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
486 buildIdentifier(BINARY_INPUT_PRE, binaryInput)), locale));
488 return channelTypeList;
492 public ChannelType getChannelType(ChannelTypeUID channelTypeUID, Locale locale) {
493 if (channelTypeUID.getBindingId().equals(DigitalSTROMBindingConstants.BINDING_ID)) {
494 String channelID = channelTypeUID.getId();
496 SensorEnum sensorType = SensorEnum.valueOf(channelTypeUID.getId().toUpperCase());
497 return ChannelTypeBuilder.state(channelTypeUID, getLabelText(channelID, locale), NUMBER)
498 .withDescription(getDescText(channelID, locale)).withCategory(getSensorCategory(sensorType))
499 .withTags(getSimpleTags(channelID, locale))
500 .withStateDescriptionFragment(getSensorStateDescription(sensorType)).build();
501 } catch (IllegalArgumentException e) {
502 if (SUPPORTED_OUTPUT_CHANNEL_TYPES.contains(channelID)) {
503 return ChannelTypeBuilder
504 .state(channelTypeUID, getLabelText(channelID, locale), getItemType(channelID))
505 .withDescription(getDescText(channelID, locale)).withCategory(getCategory(channelID))
506 .withTags(getTags(channelID, locale))
507 .withStateDescriptionFragment(getStageDescription(channelID, locale)).build();
509 MeteringTypeEnum meteringType = getMeteringType(channelID);
510 if (meteringType != null) {
511 String pattern = "%.3f kWh";
513 if (MeteringTypeEnum.CONSUMPTION.equals(meteringType)) {
517 return ChannelTypeBuilder.state(channelTypeUID, getLabelText(channelID, locale), NUMBER)
518 .withDescription(getDescText(channelID, locale)).withCategory(CATEGORY_ENERGY)
520 new HashSet<>(Arrays.asList(getLabelText(channelID, locale), getText(DS, locale))))
521 .withStateDescriptionFragment(StateDescriptionFragmentBuilder.create().withPattern(pattern)
522 .withReadOnly(true).build())
526 DeviceBinarayInputEnum binarayInputType = DeviceBinarayInputEnum
527 .valueOf(channelTypeUID.getId().replaceAll(BINARY_INPUT_PRE + SEPERATOR, "").toUpperCase());
528 return ChannelTypeBuilder
529 .state(channelTypeUID, getLabelText(channelID, locale), getItemType(channelID))
530 .withDescription(getDescText(channelID, locale))
531 .withCategory(getBinaryInputCategory(binarayInputType))
532 .withTags(getSimpleTags(channelTypeUID.getId(), locale)).withStateDescriptionFragment(
533 StateDescriptionFragmentBuilder.create().withReadOnly(true).build())
535 } catch (IllegalArgumentException e1) {
544 * Returns the {@link ChannelGroupTypeUID} for the given {@link SensorEnum}.
546 * @param sensorType (must not be null)
547 * @return the channel type uid
549 public static ChannelTypeUID getSensorChannelUID(SensorEnum sensorType) {
550 return new ChannelTypeUID(BINDING_ID, buildIdentifier(sensorType));
554 * Returns the {@link ChannelGroupTypeUID} for the given
555 * {@link DeviceBinarayInputEnum}.
557 * @param binaryInputType (must not be null)
558 * @return the channel type uid
560 public static ChannelTypeUID getBinaryInputChannelUID(DeviceBinarayInputEnum binaryInputType) {
561 return new ChannelTypeUID(BINDING_ID, buildIdentifier(BINARY_INPUT_PRE, binaryInputType));