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