]> git.basschouten.com Git - openhab-addons.git/blob
7dd2d9c3d37d7c6139948855d5ffb4ba9faacaeb
[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.shelly.internal.provider;
14
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
16 import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.SHELLY_API_INVTEMP;
17 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
18
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.LinkedHashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Optional;
26 import java.util.Set;
27 import java.util.concurrent.CopyOnWriteArrayList;
28
29 import javax.measure.Unit;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
34 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
35 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
36 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDimmer;
37 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsEMeter;
38 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsGlobal;
39 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsMeter;
40 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRelay;
41 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRgbwLight;
42 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
43 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus;
44 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLightChannel;
45 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
46 import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
47 import org.openhab.core.thing.Channel;
48 import org.openhab.core.thing.ChannelUID;
49 import org.openhab.core.thing.Thing;
50 import org.openhab.core.thing.binding.builder.ChannelBuilder;
51 import org.openhab.core.thing.type.ChannelKind;
52 import org.openhab.core.thing.type.ChannelTypeUID;
53 import org.openhab.core.types.StateOption;
54 import org.osgi.service.component.annotations.Activate;
55 import org.osgi.service.component.annotations.Component;
56 import org.osgi.service.component.annotations.Reference;
57
58 /**
59  * The {@link ShellyCHANNEL_DEFINITIONSDTO} defines channel information for dynamically created channels. Those will be
60  * added on the first thing status update
61  *
62  * @author Markus Michels - Initial contribution
63  */
64 @NonNullByDefault
65 @Component(service = ShellyChannelDefinitions.class)
66 public class ShellyChannelDefinitions {
67
68     public static final String ITEMT_STRING = "String";
69     public static final String ITEMT_NUMBER = "Number";
70     public static final String ITEMT_SWITCH = "Switch";
71     public static final String ITEMT_CONTACT = "Contact";
72     public static final String ITEMT_ROLLER = "Rollershutter";
73     public static final String ITEMT_DIMMER = "Dimmer";
74     public static final String ITEMT_LOCATION = "Location";
75     public static final String ITEMT_DATETIME = "DateTime";
76     public static final String ITEMT_TEMP = "Number:Temperature";
77     public static final String ITEMT_LUX = "Number:Illuminance";
78     public static final String ITEMT_POWER = "Number:Power";
79     public static final String ITEMT_ENERGY = "Number:Energy";
80     public static final String ITEMT_VOLT = "Number:ElectricPotential";
81     public static final String ITEMT_AMP = "Number:ElectricPotential";
82     public static final String ITEMT_ANGLE = "Number:Angle";
83     public static final String ITEMT_DISTANCE = "Number:Length";
84     public static final String ITEMT_SPEED = "Number:Speed";
85     public static final String ITEMT_VOLUME = "Number:Volume";
86     public static final String ITEMT_TIME = "Number:Time";
87     public static final String ITEMT_PERCENT = "Number:Dimensionless";
88
89     // shortcuts to avoid line breaks (make code more readable)
90     private static final String CHGR_DEVST = CHANNEL_GROUP_DEV_STATUS;
91     private static final String CHGR_RELAY = CHANNEL_GROUP_RELAY_CONTROL;
92     private static final String CHGR_ROLLER = CHANNEL_GROUP_ROL_CONTROL;
93     private static final String CHGR_LIGHT = CHANNEL_GROUP_LIGHT_CONTROL;
94     private static final String CHGR_LIGHTCH = CHANNEL_GROUP_LIGHT_CHANNEL;
95     private static final String CHGR_STATUS = CHANNEL_GROUP_STATUS;
96     private static final String CHGR_METER = CHANNEL_GROUP_METER;
97     private static final String CHGR_SENSOR = CHANNEL_GROUP_SENSOR;
98     private static final String CHGR_CONTROL = CHANNEL_GROUP_CONTROL;
99     private static final String CHGR_BAT = CHANNEL_GROUP_BATTERY;
100
101     public static final String PREFIX_GROUP = "group-type." + BINDING_ID + ".";
102     public static final String PREFIX_CHANNEL = "channel-type." + BINDING_ID + ".";
103
104     public class OptionEntry {
105         public ChannelTypeUID uid;
106         public String key;
107         public String value;
108
109         public OptionEntry(ChannelTypeUID uid, String key, String value) {
110             this.uid = uid;
111             this.key = key;
112             this.value = value;
113         }
114     }
115
116     private final CopyOnWriteArrayList<OptionEntry> stateOptions = new CopyOnWriteArrayList<>();
117
118     private static final ChannelMap CHANNEL_DEFINITIONS = new ChannelMap();
119
120     @Activate
121     public ShellyChannelDefinitions(@Reference ShellyTranslationProvider translationProvider) {
122         ShellyTranslationProvider m = translationProvider;
123
124         // Device
125         CHANNEL_DEFINITIONS
126                 // Device
127                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_NAME, "deviceName", ITEMT_STRING))
128                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ITEMP, "deviceTemp", ITEMT_TEMP))
129                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_WAKEUP, "sensorWakeup", ITEMT_STRING))
130                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS, "meterAccuWatts", ITEMT_POWER))
131                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL, "meterAccuTotal", ITEMT_POWER))
132                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED, "meterAccuReturned", ITEMT_POWER))
133                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_VOLTAGE, "supplyVoltage", ITEMT_VOLT))
134                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_CHARGER, "charger", ITEMT_SWITCH))
135                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE, "ledStatusDisable", ITEMT_SWITCH))
136                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE, "ledPowerDisable", ITEMT_SWITCH))
137                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST, "selfTest", ITEMT_STRING))
138                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPTIME, "uptime", ITEMT_NUMBER))
139                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT, "heartBeat", ITEMT_DATETIME))
140                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPDATE, "updateAvailable", ITEMT_SWITCH))
141                 .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_CALIBRATED, "calibrated", ITEMT_SWITCH))
142
143                 // Relay
144                 .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_OUTPUT_NAME, "outputName", ITEMT_STRING))
145                 .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_OUTPUT, "system:power", ITEMT_SWITCH))
146                 .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
147                 .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_BUTTON_TRIGGER, "system:button", ITEMT_STRING))
148                 .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
149                 .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
150                 .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_TIMER_AUTOON, "timerAutoOn", ITEMT_TIME))
151                 .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_TIMER_AUTOOFF, "timerAutoOff", ITEMT_TIME))
152                 .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_TIMER_ACTIVE, "timerActive", ITEMT_SWITCH))
153
154                 // Dimmer
155                 .add(new ShellyChannel(m, CHANNEL_GROUP_DIMMER_CONTROL, CHANNEL_BRIGHTNESS, "dimmerBrightness",
156                         ITEMT_DIMMER))
157
158                 // Roller
159                 .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_CONTROL, "rollerShutter", ITEMT_ROLLER))
160                 .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_POS, "rollerPosition", ITEMT_DIMMER))
161                 .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_FAV, "rollerFavorite", ITEMT_NUMBER))
162                 .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE, "rollerState", ITEMT_STRING))
163                 .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STOPR, "rollerStop", ITEMT_STRING))
164                 .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_SAFETY, "rollerSafety", ITEMT_SWITCH))
165                 .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
166                 .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
167                 .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
168                 .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_EVENT_TRIGGER, "system:button", "system:button"))
169
170                 // Bulb/Duo/Vintage
171                 .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
172                 .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_BUTTON_TRIGGER, "system:button", ITEMT_STRING))
173                 .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
174                 .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
175                 .add(new ShellyChannel(m, CHANNEL_GROUP_WHITE_CONTROL, CHANNEL_BRIGHTNESS, "whiteBrightness",
176                         ITEMT_DIMMER))
177                 .add(new ShellyChannel(m, CHANNEL_GROUP_WHITE_CONTROL, CHANNEL_COLOR_TEMP, "whiteTemp", ITEMT_DIMMER))
178
179                 // RGBW2-color
180                 .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_LIGHT_POWER, "system:power", ITEMT_SWITCH))
181                 .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_TIMER_AUTOON, "timerAutoOn", ITEMT_TIME))
182                 .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_TIMER_AUTOOFF, "timerAutoOff", ITEMT_TIME))
183                 .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_TIMER_ACTIVE, "timerActive", ITEMT_SWITCH))
184                 // RGBW2-white
185                 .add(new ShellyChannel(m, CHGR_LIGHTCH, CHANNEL_BRIGHTNESS, "whiteBrightness", ITEMT_DIMMER))
186                 .add(new ShellyChannel(m, CHGR_LIGHTCH, CHANNEL_TIMER_AUTOON, "timerAutoOn", ITEMT_TIME))
187                 .add(new ShellyChannel(m, CHGR_LIGHTCH, CHANNEL_TIMER_AUTOOFF, "timerAutoOff", ITEMT_TIME))
188                 .add(new ShellyChannel(m, CHGR_LIGHTCH, CHANNEL_TIMER_ACTIVE, "timerActive", ITEMT_SWITCH))
189
190                 // Power Meter
191                 .add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_CURRENTWATTS, "meterWatts", ITEMT_POWER))
192                 .add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_TOTALKWH, "meterTotal", ITEMT_ENERGY))
193                 .add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_LASTMIN1, "lastPower1", ITEMT_ENERGY))
194                 .add(new ShellyChannel(m, CHGR_METER, CHANNEL_LAST_UPDATE, "lastUpdate", ITEMT_DATETIME))
195
196                 // EMeter
197                 .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_TOTALRET, "meterReturned", ITEMT_ENERGY))
198                 .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_REACTWATTS, "meterReactive", ITEMT_POWER))
199                 .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_VOLTAGE, "meterVoltage", ITEMT_VOLT))
200                 .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_CURRENT, "meterCurrent", ITEMT_AMP))
201                 .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEMT_NUMBER))
202                 .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_RESETTOTAL, "meterResetTotals", ITEMT_SWITCH))
203
204                 // Sensors
205                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEMT_TEMP))
206                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_HUM, "sensorHumidity", ITEMT_PERCENT))
207                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_LUX, "sensorLux", ITEMT_LUX))
208                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ILLUM, "sensorIllumination", ITEMT_STRING))
209                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VOLTAGE, "sensorADC", ITEMT_VOLT))
210                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_STATE, "sensorContact", ITEMT_CONTACT))
211                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_OPEN, "sensorOpen", ITEMT_CONTACT))
212                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SSTATE, "sensorState", ITEMT_STRING))
213                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TILT, "sensorTilt", ITEMT_ANGLE))
214                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION, "sensorMotion", ITEMT_SWITCH))
215                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION_TS, "motionTimestamp", ITEMT_DATETIME))
216                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION_ACT, "motionActive", ITEMT_SWITCH))
217                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VIBRATION, "vibration", ITEMT_SWITCH))
218                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_FLOOD, "sensorFlood", ITEMT_SWITCH))
219                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SMOKE, "sensorSmoke", ITEMT_SWITCH))
220                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MUTE, "sensorMute", ITEMT_SWITCH))
221                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_PPM, "sensorPPM", ITEMT_NUMBER))
222                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VALVE, "sensorValve", ITEMT_STRING))
223                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ALARM_STATE, "alarmState", ITEMT_STRING))
224                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ERROR, "sensorError", ITEMT_STRING))
225                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_LAST_UPDATE, "lastUpdate", ITEMT_DATETIME))
226                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SLEEPTIME, "sensorSleepTime", ITEMT_NUMBER))
227                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSE_KEY, "senseKey", ITEMT_STRING)) // Sense
228
229                 // Button/ix3
230                 .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
231                 .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
232                 .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
233                 .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_BUTTON_TRIGGER, "system:button", ITEMT_STRING))
234                 .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_LAST_UPDATE, "lastUpdate", ITEMT_DATETIME))
235
236                 // Addon with external sensors
237                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP1, "sensorExtTemp", ITEMT_TEMP))
238                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP2, "sensorExtTemp", ITEMT_TEMP))
239                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP3, "sensorExtTemp", ITEMT_TEMP))
240                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENSOR_HUMIDITY, "sensorExtHum", ITEMT_PERCENT))
241                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENSOR_VOLTAGE, "sensorExtVolt", ITEMT_VOLT))
242                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENSOR_INPUT1, "sensorContact", ITEMT_CONTACT))
243                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENSOR_DIGITALINPUT, "sensorExtDigitalInput",
244                         ITEMT_SWITCH))
245                 .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENSOR_ANALOGINPUT, "sensorExtAnalogInput",
246                         ITEMT_PERCENT))
247
248                 // Battery
249                 .add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LEVEL, "system:battery-level", ITEMT_PERCENT))
250                 .add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LOW, "system:low-battery", ITEMT_SWITCH))
251
252                 // TRV
253                 .add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_POSITION, "sensorPosition", ITEMT_DIMMER))
254                 .add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_MODE, "controlMode", ITEMT_STRING))
255                 .add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_PROFILE, "controlProfile", ITEMT_STRING))
256                 .add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_SETTEMP, "targetTemp", ITEMT_TEMP))
257                 .add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_BCONTROL, "boostControl", ITEMT_SWITCH))
258                 .add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_BTIMER, "boostTimer", ITEMT_TIME))
259                 .add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_SCHEDULE, "controlSchedule", ITEMT_SWITCH));
260     }
261
262     public static @Nullable ShellyChannel getDefinition(String channelName) throws IllegalArgumentException {
263         String group = substringBefore(channelName, "#");
264         String channel = substringAfter(channelName, "#");
265
266         if (group.contains(CHANNEL_GROUP_METER)) {
267             group = CHANNEL_GROUP_METER; // map meter1..n to meter
268         } else if (group.contains(CHANNEL_GROUP_RELAY_CONTROL)) {
269             group = CHANNEL_GROUP_RELAY_CONTROL; // map meter1..n to meter
270         } else if (group.contains(CHANNEL_GROUP_LIGHT_CHANNEL)) {
271             group = CHANNEL_GROUP_LIGHT_CHANNEL;
272         } else if (group.contains(CHANNEL_GROUP_STATUS)) {
273             group = CHANNEL_GROUP_STATUS; // map status1..n to meter
274         }
275
276         if (channel.startsWith(CHANNEL_INPUT)) {
277             channel = CHANNEL_INPUT;
278         } else if (channel.startsWith(CHANNEL_BUTTON_TRIGGER)) {
279             channel = CHANNEL_BUTTON_TRIGGER;
280         } else if (channel.startsWith(CHANNEL_STATUS_EVENTTYPE)) {
281             channel = CHANNEL_STATUS_EVENTTYPE;
282         } else if (channel.startsWith(CHANNEL_STATUS_EVENTCOUNT)) {
283             channel = CHANNEL_STATUS_EVENTCOUNT;
284         }
285
286         String channelId = group + "#" + channel;
287         return CHANNEL_DEFINITIONS.get(channelId);
288     }
289
290     /**
291      * Auto-create relay channels depending on relay type/mode
292      *
293      * @return ArrayList<Channel> of channels to be added to the thing
294      */
295     public static Map<String, Channel> createDeviceChannels(final Thing thing, final ShellyDeviceProfile profile,
296             final ShellySettingsStatus status) {
297         Map<String, Channel> add = new LinkedHashMap<>();
298
299         addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
300
301         if (!profile.isSensor && !profile.isIX && status.temperature != null
302                 && status.temperature != SHELLY_API_INVTEMP) {
303             // Only some devices report the internal device temp
304             addChannel(thing, add, status.tmp != null || status.temperature != null, CHGR_DEVST, CHANNEL_DEVST_ITEMP);
305         }
306         addChannel(thing, add, profile.settings.sleepTime != null, CHGR_SENSOR, CHANNEL_SENSOR_SLEEPTIME);
307
308         // If device has more than 1 meter the channel accumulatedWatts receives the accumulated value
309         boolean accuChannel = (((status.meters != null) && (status.meters.size() > 1) && !profile.isRoller
310                 && !profile.isRGBW2) || ((status.emeters != null && status.emeters.size() > 1)));
311         addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS);
312         addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL);
313         addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED);
314         addChannel(thing, add, status.voltage != null || profile.settings.supplyVoltage != null, CHGR_DEVST,
315                 CHANNEL_DEVST_VOLTAGE);
316         addChannel(thing, add,
317                 profile.status.uptime != null && (!profile.hasBattery || profile.isMotion || profile.isTRV), CHGR_DEVST,
318                 CHANNEL_DEVST_UPTIME);
319         addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPDATE);
320         addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT);
321         addChannel(thing, add, profile.settings.ledPowerDisable != null, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE);
322         addChannel(thing, add, profile.settings.ledStatusDisable != null, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi
323         addChannel(thing, add, profile.settings.calibrated != null, CHGR_DEVST, CHANNEL_DEVST_CALIBRATED);
324
325         return add;
326     }
327
328     /**
329      * Auto-create relay channels depending on relay type/mode
330      *
331      * @return ArrayList<Channel> of channels to be added to the thing
332      */
333     public static Map<String, Channel> createRelayChannels(final Thing thing, final ShellyDeviceProfile profile,
334             final ShellySettingsRelay rstatus, int idx) {
335         Map<String, Channel> add = new LinkedHashMap<>();
336         String group = profile.getControlGroup(idx);
337
338         if (profile.settings.relays != null) {
339             ShellySettingsRelay rs = profile.settings.relays.get(idx);
340             addChannel(thing, add, rs.ison != null, group, CHANNEL_OUTPUT);
341             addChannel(thing, add, rs.name != null, group, CHANNEL_OUTPUT_NAME);
342
343             boolean timer = rs.hasTimer != null || rstatus.hasTimer != null; // Dimmer 1/2 have
344             addChannel(thing, add, timer, group, CHANNEL_TIMER_ACTIVE);
345             addChannel(thing, add, rs.autoOn != null, group, CHANNEL_TIMER_AUTOON);
346             addChannel(thing, add, rs.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
347         }
348
349         // Shelly 1/1PM and Plus 1/1PM Addon
350         if (profile.settings.extSwitch != null && profile.settings.extSwitch.input0 != null
351                 && idx == getInteger(profile.settings.extSwitch.input0.relayNum)) {
352             addChannel(thing, add, true, CHGR_SENSOR, CHANNEL_ESENSOR_INPUT + (idx + 1));
353         }
354         if (profile.status.extTemperature != null) {
355             addChannel(thing, add, profile.status.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP1);
356             addChannel(thing, add, profile.status.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP2);
357             addChannel(thing, add, profile.status.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP3);
358         }
359         addChannel(thing, add, profile.status.extHumidity != null, CHGR_SENSOR, CHANNEL_ESENSOR_HUMIDITY);
360         addChannel(thing, add, profile.status.extVoltage != null, CHGR_SENSOR, CHANNEL_ESENSOR_VOLTAGE);
361         addChannel(thing, add, profile.status.extDigitalInput != null, CHGR_SENSOR, CHANNEL_ESENSOR_DIGITALINPUT);
362         addChannel(thing, add, profile.status.extAnalogInput != null, CHGR_SENSOR, CHANNEL_ESENSOR_ANALOGINPUT);
363
364         return add;
365     }
366
367     public static Map<String, Channel> createDimmerChannels(final Thing thing, final ShellyDeviceProfile profile,
368             final ShellySettingsStatus dstatus, int idx) {
369         Map<String, Channel> add = new LinkedHashMap<>();
370         String group = profile.getControlGroup(idx);
371
372         // Shelly Dimmer has an additional brightness channel
373         addChannel(thing, add, profile.isDimmer, group, CHANNEL_BRIGHTNESS);
374
375         if (profile.settings.dimmers != null) {
376             ShellySettingsDimmer ds = profile.settings.dimmers.get(idx);
377             addChannel(thing, add, ds.autoOn != null, group, CHANNEL_TIMER_AUTOON);
378             addChannel(thing, add, ds.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
379             ShellyShortLightStatus dss = dstatus.dimmers.get(idx);
380             addChannel(thing, add, dss != null && dss.hasTimer != null, group, CHANNEL_TIMER_ACTIVE);
381         }
382         return add;
383     }
384
385     public static Map<String, Channel> createLightChannels(final Thing thing, final ShellyDeviceProfile profile,
386             final ShellyStatusLightChannel status, int idx) {
387         Map<String, Channel> add = new LinkedHashMap<>();
388         String group = profile.getControlGroup(idx);
389
390         if (profile.settings.lights != null) {
391             ShellySettingsRgbwLight light = profile.settings.lights.get(idx);
392             String whiteGroup = profile.isRGBW2 ? group : CHANNEL_GROUP_WHITE_CONTROL;
393             // Create power channel in color mode and brightness channel in white mode
394             addChannel(thing, add, profile.inColor, group, CHANNEL_LIGHT_POWER);
395             addChannel(thing, add, light.autoOn != null, group, CHANNEL_TIMER_AUTOON);
396             addChannel(thing, add, light.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
397             addChannel(thing, add, status.hasTimer != null, group, CHANNEL_TIMER_ACTIVE);
398             addChannel(thing, add, light.brightness != null, whiteGroup, CHANNEL_BRIGHTNESS);
399             addChannel(thing, add, light.temp != null, whiteGroup, CHANNEL_COLOR_TEMP);
400         }
401
402         return add;
403     }
404
405     public static Map<String, Channel> createInputChannels(final Thing thing, final ShellyDeviceProfile profile,
406             final ShellySettingsStatus status) {
407         Map<String, Channel> add = new LinkedHashMap<>();
408         if (status.inputs != null) {
409             // Create channels per input. For devices with more than 1 input (Dimmer, 1L) multiple channel sets are
410             // created by adding the index to the channel name
411             for (int i = 0; i < profile.numInputs; i++) {
412                 String group = profile.getInputGroup(i);
413                 String suffix = profile.getInputSuffix(i); // multi ? String.valueOf(i + 1) : "";
414                 addChannel(thing, add, true, group, CHANNEL_INPUT + suffix);
415                 addChannel(thing, add, true, group,
416                         (!profile.isRoller ? CHANNEL_BUTTON_TRIGGER + suffix : CHANNEL_EVENT_TRIGGER));
417                 if (profile.inButtonMode(i)) {
418                     ShellyInputState input = status.inputs.get(i);
419                     addChannel(thing, add, input.event != null, group, CHANNEL_STATUS_EVENTTYPE + suffix);
420                     addChannel(thing, add, input.eventCount != null, group, CHANNEL_STATUS_EVENTCOUNT + suffix);
421                 }
422             }
423         } else if (status.input != null) {
424             // old RGBW2 firmware
425             String group = profile.getInputGroup(0);
426             addChannel(thing, add, true, group, CHANNEL_INPUT);
427             addChannel(thing, add, true, group, CHANNEL_BUTTON_TRIGGER);
428         }
429         return add;
430     }
431
432     public static Map<String, Channel> createRollerChannels(Thing thing, final ShellyRollerStatus roller) {
433         Map<String, Channel> add = new LinkedHashMap<>();
434         addChannel(thing, add, true, CHGR_ROLLER, CHANNEL_ROL_CONTROL_CONTROL);
435         addChannel(thing, add, true, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE);
436         addChannel(thing, add, true, CHGR_ROLLER, CHANNEL_EVENT_TRIGGER);
437         addChannel(thing, add, roller.currentPos != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_POS);
438         addChannel(thing, add, roller.stopReason != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STOPR);
439         addChannel(thing, add, roller.safetySwitch != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_SAFETY);
440
441         ShellyThingInterface handler = (ShellyThingInterface) thing.getHandler();
442         if (handler != null) {
443             ShellySettingsGlobal settings = handler.getProfile().settings;
444             if (getBool(settings.favoritesEnabled) && settings.favorites != null) {
445                 addChannel(thing, add, roller.currentPos != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_FAV);
446             }
447         }
448         return add;
449     }
450
451     public static Map<String, Channel> createMeterChannels(Thing thing, final ShellySettingsMeter meter, String group) {
452         Map<String, Channel> newChannels = new LinkedHashMap<>();
453         addChannel(thing, newChannels, meter.power != null, group, CHANNEL_METER_CURRENTWATTS);
454         addChannel(thing, newChannels, meter.total != null, group, CHANNEL_METER_TOTALKWH);
455         addChannel(thing, newChannels, meter.counters != null && meter.counters[0] != null, group,
456                 CHANNEL_METER_LASTMIN1);
457         addChannel(thing, newChannels, meter.timestamp != null, group, CHANNEL_LAST_UPDATE);
458         return newChannels;
459     }
460
461     public static Map<String, Channel> createEMeterChannels(final Thing thing, final ShellyDeviceProfile profile,
462             final ShellySettingsEMeter emeter, String group) {
463         Map<String, Channel> newChannels = new LinkedHashMap<>();
464         addChannel(thing, newChannels, emeter.power != null, group, CHANNEL_METER_CURRENTWATTS);
465         addChannel(thing, newChannels, emeter.total != null, group, CHANNEL_METER_TOTALKWH);
466         addChannel(thing, newChannels, emeter.totalReturned != null, group, CHANNEL_EMETER_TOTALRET);
467         addChannel(thing, newChannels, emeter.reactive != null, group, CHANNEL_EMETER_REACTWATTS);
468         addChannel(thing, newChannels, emeter.voltage != null, group, CHANNEL_EMETER_VOLTAGE);
469         addChannel(thing, newChannels, emeter.current != null, group, CHANNEL_EMETER_CURRENT);
470         addChannel(thing, newChannels, emeter.pf != null, group, CHANNEL_EMETER_PFACTOR); // EM has no PF. but power
471         addChannel(thing, newChannels, emeter.total != null && profile.numMeters > 1, group, CHANNEL_EMETER_RESETTOTAL); // 3EM
472         addChannel(thing, newChannels, true, group, CHANNEL_LAST_UPDATE);
473         return newChannels;
474     }
475
476     public static Map<String, Channel> createSensorChannels(final Thing thing, final ShellyDeviceProfile profile,
477             final ShellyStatusSensor sdata) {
478         Map<String, Channel> newChannels = new LinkedHashMap<>();
479
480         // Sensor data
481         addChannel(thing, newChannels, sdata.tmp != null || sdata.thermostats != null, CHANNEL_GROUP_SENSOR,
482                 CHANNEL_SENSOR_TEMP);
483         addChannel(thing, newChannels, sdata.hum != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM);
484         addChannel(thing, newChannels, sdata.lux != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX);
485         addChannel(thing, newChannels, sdata.lux != null && sdata.lux.illumination != null, CHANNEL_GROUP_SENSOR,
486                 CHANNEL_SENSOR_ILLUM);
487         addChannel(thing, newChannels, sdata.flood != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
488         addChannel(thing, newChannels, sdata.smoke != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SMOKE);
489         addChannel(thing, newChannels, sdata.mute != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MUTE);
490         addChannel(thing, newChannels, profile.settings.externalPower != null || sdata.charger != null, CHGR_DEVST,
491                 CHANNEL_DEVST_CHARGER);
492         addChannel(thing, newChannels, sdata.motion != null || (sdata.sensor != null && sdata.sensor.motion != null),
493                 CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION);
494         if (sdata.sensor != null) { // DW, Sense or Motion
495             addChannel(thing, newChannels, sdata.sensor.state != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE); // DW/DW2
496             addChannel(thing, newChannels, sdata.sensor.motionActive != null, CHANNEL_GROUP_SENSOR, // Motion
497                     CHANNEL_SENSOR_MOTION_ACT);
498             addChannel(thing, newChannels, sdata.sensor.motionTimestamp != null, CHANNEL_GROUP_SENSOR, // Motion
499                     CHANNEL_SENSOR_MOTION_TS);
500             addChannel(thing, newChannels, sdata.sensor.vibration != null, CHANNEL_GROUP_SENSOR,
501                     CHANNEL_SENSOR_VIBRATION);
502         }
503         if (sdata.accel != null) { // DW2
504             addChannel(thing, newChannels, sdata.accel.tilt != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT);
505         }
506
507         // Gas
508         if (sdata.gasSensor != null) {
509             addChannel(thing, newChannels, sdata.gasSensor.selfTestState != null, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST);
510             addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
511                     CHANNEL_SENSOR_SSTATE);
512             addChannel(thing, newChannels, sdata.concentration != null && sdata.concentration.ppm != null,
513                     CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM);
514             addChannel(thing, newChannels, sdata.valves != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE);
515             addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
516                     CHANNEL_SENSOR_ALARM_STATE);
517         }
518
519         // Sense
520         addChannel(thing, newChannels, profile.isSense, CHANNEL_GROUP_SENSOR, CHANNEL_SENSE_KEY);
521
522         // UNI
523         addChannel(thing, newChannels, sdata.adcs != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VOLTAGE);
524
525         // TRV
526         if (profile.isTRV) {
527             addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SETTEMP);
528             addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_BCONTROL);
529             addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_BTIMER);
530             addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_POSITION);
531             addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_MODE);
532             addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE);
533             addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SCHEDULE);
534             addChannel(thing, newChannels, true, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE);
535             addChannel(thing, newChannels, true, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_OPEN);
536         }
537
538         // Battery
539         if (sdata.bat != null) {
540             addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL);
541             addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW);
542         }
543
544         addChannel(thing, newChannels, sdata.sensorError != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR);
545         addChannel(thing, newChannels, sdata.actReasons != null, CHGR_DEVST, CHANNEL_DEVST_WAKEUP);
546         addChannel(thing, newChannels, true, profile.isButton ? CHANNEL_GROUP_STATUS : CHANNEL_GROUP_SENSOR,
547                 CHANNEL_LAST_UPDATE);
548         return newChannels;
549     }
550
551     public ChannelTypeUID getChannelTypeUID(String channelId) {
552         ShellyChannel channelDef = getDefinition(channelId);
553         if (channelDef != null) {
554             return new ChannelTypeUID(BINDING_ID, channelDef.typeId);
555         }
556         throw new IllegalArgumentException("Invalid channelId:" + channelId);
557     }
558
559     private static void addChannel(Thing thing, Map<String, Channel> newChannels, boolean supported, String group,
560             String channelName) throws IllegalArgumentException {
561         if (supported) {
562             String channelId = group + "#" + channelName;
563             ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
564             ShellyChannel channelDef = getDefinition(channelId);
565             if (channelDef != null) {
566                 ChannelTypeUID channelTypeUID = channelDef.typeId.contains("system:")
567                         ? new ChannelTypeUID(channelDef.typeId)
568                         : new ChannelTypeUID(BINDING_ID, channelDef.typeId);
569                 ChannelBuilder builder;
570                 if (channelDef.typeId.equalsIgnoreCase("system:button")) {
571                     builder = ChannelBuilder.create(channelUID, null).withKind(ChannelKind.TRIGGER);
572                 } else {
573                     builder = ChannelBuilder.create(channelUID, channelDef.itemType);
574                 }
575                 if (!channelDef.label.isEmpty()) {
576                     char grseq = lastChar(group);
577                     char chseq = lastChar(channelName);
578                     char sequence = isDigit(chseq) ? chseq : grseq;
579                     String label = !isDigit(sequence) ? channelDef.label : channelDef.label + " " + sequence;
580                     builder.withLabel(label);
581                 }
582                 if (!channelDef.description.isEmpty()) {
583                     builder.withDescription(channelDef.description);
584                 }
585                 newChannels.put(channelId, builder.withType(channelTypeUID).build());
586             }
587         }
588     }
589
590     public List<StateOption> getStateOptions(ChannelTypeUID uid) {
591         List<StateOption> options = new ArrayList<>();
592         for (OptionEntry oe : stateOptions) {
593             if (oe.uid.equals(uid)) {
594                 options.add(new StateOption(oe.key, oe.value));
595             }
596         }
597         return options;
598     }
599
600     public void addStateOption(String channelId, String key, String value) {
601         ChannelTypeUID uid = getChannelTypeUID(channelId);
602         stateOptions.addIfAbsent(new OptionEntry(uid, key, value));
603     }
604
605     public void clearStateOptions(String channelId) {
606         ChannelTypeUID uid = getChannelTypeUID(channelId);
607         for (OptionEntry oe : stateOptions) {
608             if (oe.uid.equals(uid)) {
609                 stateOptions.remove(oe);
610             }
611         }
612     }
613
614     public class ShellyChannel {
615         private final ShellyTranslationProvider messages;
616         public String group = "";
617         public String groupLabel = "";
618         public String groupDescription = "";
619
620         public String channel = "";
621         public String label = "";
622         public String description = "";
623         public String itemType = "";
624         public String typeId = "";
625         public String category = "";
626         public Set<String> tags = new HashSet<>();
627         public @Nullable Unit<?> unit;
628         public Optional<Integer> min = Optional.empty();
629         public Optional<Integer> max = Optional.empty();
630         public Optional<Integer> step = Optional.empty();
631         public Optional<String> pattern = Optional.empty();
632
633         public ShellyChannel(ShellyTranslationProvider messages, String group, String channel, String typeId,
634                 String itemType, String... category) {
635             this.messages = messages;
636             this.group = group;
637             this.channel = channel;
638             this.itemType = itemType;
639             this.typeId = typeId;
640
641             groupLabel = getText(PREFIX_GROUP + group + ".label");
642             if (groupLabel.contains(PREFIX_GROUP)) {
643                 groupLabel = "";
644             }
645             groupDescription = getText(PREFIX_GROUP + group + ".description");
646             if (groupDescription.contains(PREFIX_GROUP)) {
647                 groupDescription = "";
648             }
649             label = getText(PREFIX_CHANNEL + typeId.replace(':', '.') + ".label");
650             if (label.contains(PREFIX_CHANNEL)) {
651                 label = "";
652             }
653             description = getText(PREFIX_CHANNEL + typeId + ".description");
654             if (description.contains(PREFIX_CHANNEL)) {
655                 description = "";
656             }
657         }
658
659         public String getChanneId() {
660             return group + "#" + channel;
661         }
662
663         public String getGroupLabel() {
664             return getGroupAttribute("group");
665         }
666
667         public String getGroupDescription() {
668             return getGroupAttribute("group");
669         }
670
671         public String getLabel() {
672             return getChannelAttribute("label");
673         }
674
675         public String getDescription() {
676             return getChannelAttribute("description");
677         }
678
679         public boolean getAdvanced() {
680             String attr = getChannelAttribute("advanced");
681             return attr.isEmpty() ? false : Boolean.valueOf(attr);
682         }
683
684         public boolean getReadyOnly() {
685             String attr = getChannelAttribute("readOnly");
686             return attr.isEmpty() ? false : Boolean.valueOf(attr);
687         }
688
689         public String getCategory() {
690             return getChannelAttribute("category");
691         }
692
693         public String getMin() {
694             return getChannelAttribute("min");
695         }
696
697         public String getMax() {
698             return getChannelAttribute("max");
699         }
700
701         public String getStep() {
702             return getChannelAttribute("step");
703         }
704
705         public String getPattern() {
706             return getChannelAttribute("pattern");
707         }
708
709         public String getGroupAttribute(String attribute) {
710             String key = PREFIX_GROUP + group + "." + attribute;
711             String value = messages.getText(key);
712             return !value.equals(key) ? value : "";
713         }
714
715         public String getChannelAttribute(String attribute) {
716             String key = PREFIX_CHANNEL + channel + "." + attribute;
717             String value = messages.getText(key);
718             return !value.equals(key) ? value : "";
719         }
720
721         private String getText(String key) {
722             return messages.get(key);
723         }
724     }
725
726     public static class ChannelMap {
727         private final Map<String, ShellyChannel> map = new HashMap<>();
728
729         private ChannelMap add(ShellyChannel def) {
730             map.put(def.getChanneId(), def);
731             return this;
732         }
733
734         public ShellyChannel get(String channelName) throws IllegalArgumentException {
735             ShellyChannel def = null;
736             if (channelName.contains("#")) {
737                 def = map.get(channelName);
738                 if (def != null) {
739                     return def;
740                 }
741             }
742             for (HashMap.Entry<String, ShellyChannel> entry : map.entrySet()) {
743                 if (entry.getValue().channel.contains("#" + channelName)) {
744                     def = entry.getValue();
745                     break;
746                 }
747             }
748
749             if (def == null) {
750                 throw new IllegalArgumentException("Channel definition for " + channelName + " not found!");
751             }
752
753             return def;
754         }
755     }
756 }