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.ChannelGroupTypeUID;
37 import org.openhab.core.thing.type.ChannelType;
38 import org.openhab.core.thing.type.ChannelTypeBuilder;
39 import org.openhab.core.thing.type.ChannelTypeProvider;
40 import org.openhab.core.thing.type.ChannelTypeUID;
41 import org.openhab.core.types.StateDescriptionFragment;
42 import org.openhab.core.types.StateDescriptionFragmentBuilder;
43 import org.openhab.core.types.StateOption;
44 import org.osgi.service.component.ComponentContext;
45 import org.osgi.service.component.annotations.Activate;
46 import org.osgi.service.component.annotations.Component;
47 import org.osgi.service.component.annotations.Deactivate;
48 import org.osgi.service.component.annotations.Reference;
51 * The {@link DsChannelTypeProvider} implements the {@link ChannelTypeProvider}
52 * generates all supported {@link Channel}'s for digitalSTROM.
54 * @author Michael Ochel - Initial contribution
55 * @author Matthias Siegele - Initial contribution
58 @Component(service = ChannelTypeProvider.class)
59 public class DsChannelTypeProvider extends BaseDsI18n implements ChannelTypeProvider {
61 // channelID building (effect group type + (nothing || SEPERATOR + item type ||
62 // SEPERATOR + extended item type) e.g.
63 // light_switch, shade or shade_angle
64 // channel effect group type
65 public static final String LIGHT = "light"; // and tag
66 public static final String SHADE = "shade"; // and tag
67 public static final String HEATING = "heating"; // and tag
68 public static final String GENERAL = "general";
69 public static final String SCENE = "scene";
70 // channel extended item type
71 public static final String WIPE = "wipe";
72 public static final String ANGLE = "angle";
73 public static final String STAGE = "stage"; // pre stageses e.g. 2+STAGE_SWITCH
74 public static final String TEMPERATURE_CONTROLLED = "temperature_controlled";
77 public static final String DIMMER = "Dimmer";
78 public static final String SWITCH = "Switch";
79 public static final String ROLLERSHUTTER = "Rollershutter";
80 public static final String STRING = "String";
81 public static final String NUMBER = "Number";
83 public static final String TOTAL_PRE = "total";
84 public static final String BINARY_INPUT_PRE = "binary_input";
85 public static final String OPTION = "opt";
88 public static final String GE = "GE";
89 public static final String GR = "GR";
90 public static final String BL = "BL";
91 public static final String SW = "SW";
92 public static final String DS = "DS";
93 public static final String JOKER = "JOKER";
96 public static final String CATEGORY_BLINDES = "Blinds";
97 public static final String CATEGORY_DIMMABLE_LIGHT = "DimmableLight";
98 public static final String CATEGORY_CARBONE_DIOXIDE = "CarbonDioxide";
99 public static final String CATEGORY_ENERGY = "Energy";
100 public static final String CATEGORY_HUMIDITY = "Humidity";
101 public static final String CATEGORY_BRIGHTNESS = "Brightness";
102 public static final String CATEGORY_LIGHT = "Light";
103 public static final String CATEGORY_PRESSURE = "Pressure";
104 public static final String CATEGORY_SOUND_VOLUME = "SoundVolume";
105 public static final String CATEGORY_TEMPERATURE = "Temperature";
106 public static final String CATEGORY_WIND = "Wind";
107 public static final String CATEGORY_RAIN = "Rain";
108 public static final String CATEGORY_BATTERY = "Battery";
109 public static final String CATEGORY_DOOR = "Door";
110 public static final String CATEGORY_WINDOW = "Window";
111 public static final String CATEGORY_GARAGE_DOOR = "GarageDoor";
112 public static final String CATEGORY_SMOKE = "Smoke";
113 public static final String CATEGORY_ALARM = "Alarm";
114 public static final String CATEGORY_MOTION = "Motion";
117 * Returns the output channel type id as {@link String} for the given
118 * {@link ApplicationGroup.Color} and {@link OutputModeEnum} or null, if no
119 * channel type exists for the given {@link ApplicationGroup.Color} and
120 * {@link OutputModeEnum}.
122 * @param functionalGroup of the {@link Device}
123 * @param outputMode of the {@link Device}
124 * @return the output channel type id or null
126 public static String getOutputChannelTypeID(ApplicationGroup.Color functionalGroup, OutputModeEnum outputMode,
127 List<OutputChannelEnum> outputChannels) {
128 if (functionalGroup != null && outputMode != null) {
129 String channelPreID = GENERAL;
131 switch (functionalGroup) {
133 channelPreID = LIGHT;
136 if (outputChannels != null && (outputChannels.contains(OutputChannelEnum.SHADE_OPENING_ANGLE_INDOOR)
137 || outputChannels.contains(OutputChannelEnum.SHADE_OPENING_ANGLE_OUTSIDE))) {
138 return buildIdentifier(SHADE, ANGLE);
140 return buildIdentifier(SHADE);
143 channelPreID = HEATING;
144 if (OutputModeEnum.outputModeIsTemperationControlled(outputMode)) {
145 return buildIdentifier(channelPreID, TEMPERATURE_CONTROLLED);
151 if (OutputModeEnum.outputModeIsSwitch(outputMode)) {
152 return buildIdentifier(channelPreID, SWITCH);
154 if (OutputModeEnum.outputModeIsDimmable(outputMode)) {
155 return buildIdentifier(channelPreID, DIMMER);
157 if (!channelPreID.equals(HEATING)) {
158 if (outputMode.equals(OutputModeEnum.COMBINED_2_STAGE_SWITCH)) {
159 return buildIdentifier(channelPreID, "2", STAGE);
161 if (outputMode.equals(OutputModeEnum.COMBINED_3_STAGE_SWITCH)) {
162 return buildIdentifier(channelPreID, "3", STAGE);
169 public static String getMeteringChannelID(MeteringTypeEnum type, MeteringUnitsEnum unit, boolean isTotal) {
171 return buildIdentifier(TOTAL_PRE, type, unit);
173 return buildIdentifier(type, unit);
177 public static MeteringTypeEnum getMeteringType(String channelID) {
178 // check metering channel
179 String[] meteringChannelSplit = channelID.split(SEPERATOR);
180 if (meteringChannelSplit.length > 1) {
183 if (meteringChannelSplit.length == 3) {
187 // check through IllegalArgumentException, if channel is metering
188 return MeteringTypeEnum.valueOf(meteringChannelSplit[0 + offset].toUpperCase());
189 } catch (IllegalArgumentException e) {
196 private static final List<String> SUPPORTED_OUTPUT_CHANNEL_TYPES = new ArrayList<>();
199 * Returns true, if the given channel type id is an output channel.
201 * @param channelTypeID to check
202 * @return true, if channel type id is output channel
204 public static boolean isOutputChannel(String channelTypeID) {
205 return SUPPORTED_OUTPUT_CHANNEL_TYPES.contains(channelTypeID);
210 protected void activate(ComponentContext componentContext) {
211 super.activate(componentContext);
216 protected void deactivate(ComponentContext componentContext) {
217 super.deactivate(componentContext);
222 protected void setTranslationProvider(TranslationProvider translationProvider) {
223 super.setTranslationProvider(translationProvider);
227 protected void unsetTranslationProvider(TranslationProvider translationProvider) {
228 super.unsetTranslationProvider(translationProvider);
232 protected void init() {
233 String channelIDpre = GENERAL;
234 for (short i = 0; i < 3; i++) {
236 channelIDpre = LIGHT;
239 channelIDpre = HEATING;
240 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, TEMPERATURE_CONTROLLED));
242 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, SWITCH));
243 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, DIMMER));
245 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, "2", STAGE));
246 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, "3", STAGE));
249 channelIDpre = SHADE;
250 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(channelIDpre);
251 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(buildIdentifier(channelIDpre, ANGLE));
252 SUPPORTED_OUTPUT_CHANNEL_TYPES.add(SCENE);
255 private String getSensorCategory(SensorEnum sensorType) {
256 switch (sensorType) {
260 case OUTPUT_CURRENT_H:
261 case POWER_CONSUMPTION:
262 return CATEGORY_ENERGY;
264 return CATEGORY_PRESSURE;
266 return CATEGORY_CARBONE_DIOXIDE;
268 return CATEGORY_RAIN;
269 case RELATIVE_HUMIDITY_INDOORS:
270 case RELATIVE_HUMIDITY_OUTDOORS:
271 return CATEGORY_HUMIDITY;
272 case ROOM_TEMPERATURE_CONTROL_VARIABLE:
274 case ROOM_TEMPERATURE_SET_POINT:
276 case TEMPERATURE_INDOORS:
277 case TEMPERATURE_OUTDOORS:
278 return CATEGORY_TEMPERATURE;
281 return CATEGORY_WIND;
282 case SOUND_PRESSURE_LEVEL:
283 return CATEGORY_SOUND_VOLUME;
284 case BRIGHTNESS_INDOORS:
285 case BRIGHTNESS_OUTDOORS:
286 return CATEGORY_BRIGHTNESS;
294 private String getBinaryInputCategory(DeviceBinarayInputEnum binaryInputType) {
295 switch (binaryInputType) {
296 case BATTERY_STATUS_IS_LOW:
297 return CATEGORY_BATTERY;
302 return CATEGORY_BRIGHTNESS;
303 case HEATING_OPERATION_ON_OFF:
304 case CHANGE_OVER_HEATING_COOLING:
305 case TEMPERATION_BELOW_LIMIT:
306 return CATEGORY_TEMPERATURE;
308 return CATEGORY_DOOR;
309 case GARAGE_DOOR_IS_OPEN:
310 return CATEGORY_GARAGE_DOOR;
312 case PRESENCE_IN_DARKNESS:
314 case MOTION_IN_DARKNESS:
315 return CATEGORY_MOTION;
317 return CATEGORY_RAIN;
319 return CATEGORY_SMOKE;
321 case WINDOW_IS_TILTED:
322 return CATEGORY_WINDOW;
323 case WIND_STRENGHT_ABOVE_LIMIT:
324 return CATEGORY_WIND;
326 return CATEGORY_ALARM;
334 private StateDescriptionFragment getSensorStateDescription(SensorEnum sensorType) {
335 // the digitalSTROM resolution for temperature in kelvin is not correct but
336 // sensor-events and cached values are
337 // shown in °C so we will use this unit for temperature sensors
338 String unitShortCut = sensorType.getUnitShortcut();
339 if (unitShortCut.equals("%")) {
342 if (sensorType.toString().contains("TEMPERATURE")) {
345 return StateDescriptionFragmentBuilder.create().withPattern(sensorType.getPattern() + " " + unitShortCut)
346 .withReadOnly(true).build();
349 private String getStageChannelOption(String type, String option) {
350 return buildIdentifier(type, STAGE, OPTION, option);
353 private StateDescriptionFragment getStageDescription(String channelID, Locale locale) {
354 if (channelID.contains(STAGE.toLowerCase())) {
355 List<StateOption> stateOptions = new ArrayList<>();
356 if (channelID.contains(LIGHT)) {
357 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF, getText(
358 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF), locale)));
359 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON, getText(
360 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON), locale)));
361 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON, getText(
362 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON), locale)));
363 if (channelID.contains("3")) {
364 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON, getText(
365 getStageChannelOption(LIGHT, DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON),
369 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF,
370 getText(getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_OFF),
372 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON, getText(
373 getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_BOTH_ON), locale)));
374 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON,
375 getText(getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_FIRST_ON),
377 if (channelID.contains("3")) {
378 stateOptions.add(new StateOption(DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON, getText(
379 getStageChannelOption(GENERAL, DigitalSTROMBindingConstants.OPTION_COMBINED_SECOND_ON),
383 return StateDescriptionFragmentBuilder.create().withReadOnly(false).withOptions(stateOptions).build();
385 if (channelID.contains(TEMPERATURE_CONTROLLED)) {
386 return StateDescriptionFragmentBuilder.create().withMinimum(new BigDecimal(0))
387 .withMaximum(new BigDecimal(50)).withStep(new BigDecimal(0.1)).withPattern("%.1f °C")
388 .withReadOnly(false).build();
393 private String getCategory(String channelID) {
394 if (channelID.contains(LIGHT)) {
395 if (channelID.contains(DIMMER.toLowerCase())) {
396 return CATEGORY_DIMMABLE_LIGHT;
398 return CATEGORY_LIGHT;
400 if (channelID.contains(SHADE)) {
401 if (channelID.contains(ANGLE.toLowerCase())) {
402 return CATEGORY_BLINDES;
404 return ROLLERSHUTTER;
406 if (channelID.contains(TEMPERATURE_CONTROLLED)) {
407 return CATEGORY_TEMPERATURE;
412 private Set<String> getTags(String channelID, Locale locale) {
413 if (channelID.contains(LIGHT)) {
414 return new HashSet<>(Arrays.asList(getText(GE, locale), getText(DS, locale), getText(LIGHT, locale)));
416 if (channelID.contains(GENERAL)) {
417 return new HashSet<>(Arrays.asList(getText(SW, locale), getText(DS, locale), getText(JOKER, locale)));
419 if (channelID.contains(SHADE)) {
420 return new HashSet<>(Arrays.asList(getText(GR, locale), getText(DS, locale), getText("SHADE", locale)));
422 if (channelID.contains(SCENE)) {
423 return new HashSet<>(Arrays.asList(getText(SCENE, locale), getText(DS, locale)));
425 if (channelID.contains(HEATING)) {
426 return new HashSet<>(Arrays.asList(getText(BL, locale), getText(DS, locale), getText(HEATING, locale)));
431 private Set<String> getSimpleTags(String channelID, Locale locale) {
432 return new HashSet<>(Arrays.asList(getText(channelID, locale), getText(channelID, locale)));
436 * Returns the supported item type for the given channel type id or null, if the
437 * channel type does not exist.
439 * @param channelTypeID of the channel
440 * @return item type or null
442 public static String getItemType(String channelTypeID) {
443 if (channelTypeID != null) {
444 if (stringContains(channelTypeID, STAGE)) {
447 if (stringContains(channelTypeID, SWITCH) || stringContains(channelTypeID, SCENE)
448 || stringContains(channelTypeID, WIPE) || stringContains(channelTypeID, BINARY_INPUT_PRE)) {
451 if (stringContains(channelTypeID, DIMMER) || stringContains(channelTypeID, ANGLE)) {
454 if (stringContains(channelTypeID, TEMPERATURE_CONTROLLED)) {
457 if (channelTypeID.contains(SHADE)) {
458 return ROLLERSHUTTER;
464 private static boolean stringContains(String string, String compare) {
465 return string.toLowerCase().contains(compare.toLowerCase());
469 public Collection<ChannelType> getChannelTypes(Locale locale) {
470 List<ChannelType> channelTypeList = new LinkedList<>();
471 for (String channelTypeId : SUPPORTED_OUTPUT_CHANNEL_TYPES) {
473 getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID, channelTypeId), locale));
475 for (SensorEnum sensorType : SensorEnum.values()) {
476 channelTypeList.add(getChannelType(
477 new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID, buildIdentifier(sensorType)), locale));
479 for (MeteringTypeEnum meteringType : MeteringTypeEnum.values()) {
480 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
481 buildIdentifier(meteringType, MeteringUnitsEnum.WH)), locale));
482 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
483 buildIdentifier(TOTAL_PRE, meteringType, MeteringUnitsEnum.WH)), locale));
485 for (DeviceBinarayInputEnum binaryInput : DeviceBinarayInputEnum.values()) {
486 channelTypeList.add(getChannelType(new ChannelTypeUID(DigitalSTROMBindingConstants.BINDING_ID,
487 buildIdentifier(BINARY_INPUT_PRE, binaryInput)), locale));
489 return channelTypeList;
493 public ChannelType getChannelType(ChannelTypeUID channelTypeUID, Locale locale) {
494 if (channelTypeUID.getBindingId().equals(DigitalSTROMBindingConstants.BINDING_ID)) {
495 String channelID = channelTypeUID.getId();
497 SensorEnum sensorType = SensorEnum.valueOf(channelTypeUID.getId().toUpperCase());
498 return ChannelTypeBuilder.state(channelTypeUID, getLabelText(channelID, locale), NUMBER)
499 .withDescription(getDescText(channelID, locale)).withCategory(getSensorCategory(sensorType))
500 .withTags(getSimpleTags(channelID, locale))
501 .withStateDescriptionFragment(getSensorStateDescription(sensorType)).build();
502 } catch (IllegalArgumentException e) {
503 if (SUPPORTED_OUTPUT_CHANNEL_TYPES.contains(channelID)) {
504 return ChannelTypeBuilder
505 .state(channelTypeUID, getLabelText(channelID, locale), getItemType(channelID))
506 .withDescription(getDescText(channelID, locale)).withCategory(getCategory(channelID))
507 .withTags(getTags(channelID, locale))
508 .withStateDescriptionFragment(getStageDescription(channelID, locale)).build();
510 MeteringTypeEnum meteringType = getMeteringType(channelID);
511 if (meteringType != null) {
512 String pattern = "%.3f kWh";
514 if (MeteringTypeEnum.CONSUMPTION.equals(meteringType)) {
518 return ChannelTypeBuilder.state(channelTypeUID, getLabelText(channelID, locale), NUMBER)
519 .withDescription(getDescText(channelID, locale)).withCategory(CATEGORY_ENERGY)
521 new HashSet<>(Arrays.asList(getLabelText(channelID, locale), getText(DS, locale))))
522 .withStateDescriptionFragment(StateDescriptionFragmentBuilder.create().withPattern(pattern)
523 .withReadOnly(true).build())
527 DeviceBinarayInputEnum binarayInputType = DeviceBinarayInputEnum
528 .valueOf(channelTypeUID.getId().replaceAll(BINARY_INPUT_PRE + SEPERATOR, "").toUpperCase());
529 return ChannelTypeBuilder
530 .state(channelTypeUID, getLabelText(channelID, locale), getItemType(channelID))
531 .withDescription(getDescText(channelID, locale))
532 .withCategory(getBinaryInputCategory(binarayInputType))
533 .withTags(getSimpleTags(channelTypeUID.getId(), locale)).withStateDescriptionFragment(
534 StateDescriptionFragmentBuilder.create().withReadOnly(true).build())
536 } catch (IllegalArgumentException e1) {
545 * Returns the {@link ChannelGroupTypeUID} for the given {@link SensorEnum}.
547 * @param sensorType (must not be null)
548 * @return the channel type uid
550 public static ChannelTypeUID getSensorChannelUID(SensorEnum sensorType) {
551 return new ChannelTypeUID(BINDING_ID, buildIdentifier(sensorType));
555 * Returns the {@link ChannelGroupTypeUID} for the given
556 * {@link DeviceBinarayInputEnum}.
558 * @param binaryInputType (must not be null)
559 * @return the channel type uid
561 public static ChannelTypeUID getBinaryInputChannelUID(DeviceBinarayInputEnum binaryInputType) {
562 return new ChannelTypeUID(BINDING_ID, buildIdentifier(BINARY_INPUT_PRE, binaryInputType));