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