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