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