]> git.basschouten.com Git - openhab-addons.git/blob
e13df68b42c050626f37d0cde92b17ab4b66c57c
[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.manager;
14
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.PROPERTY_SERVICE_NAME;
16 import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.SHELLY_COIOT_MCAST;
17 import static org.openhab.binding.shelly.internal.manager.ShellyManagerConstants.*;
18 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
19
20 import java.util.HashMap;
21 import java.util.LinkedHashMap;
22 import java.util.Map;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.eclipse.jetty.http.HttpStatus;
27 import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
28 import org.openhab.binding.shelly.internal.api.ShellyApiException;
29 import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
30 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyOtaCheckResult;
31 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsLogin;
32 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
33 import org.openhab.binding.shelly.internal.api.ShellyHttpApi;
34 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO;
35 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
36 import org.openhab.binding.shelly.internal.handler.ShellyManagerInterface;
37 import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.osgi.service.cm.ConfigurationAdmin;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * {@link ShellyManagerActionPage} implements the Shelly Manager's action page
45  *
46  * @author Markus Michels - Initial contribution
47  */
48 @NonNullByDefault
49 public class ShellyManagerActionPage extends ShellyManagerPage {
50     private final Logger logger = LoggerFactory.getLogger(ShellyManagerActionPage.class);
51
52     public ShellyManagerActionPage(ConfigurationAdmin configurationAdmin, ShellyTranslationProvider translationProvider,
53             HttpClient httpClient, String localIp, int localPort, ShellyHandlerFactory handlerFactory) {
54         super(configurationAdmin, translationProvider, httpClient, localIp, localPort, handlerFactory);
55     }
56
57     @Override
58     public ShellyMgrResponse generateContent(String path, Map<String, String[]> parameters) throws ShellyApiException {
59         String action = getUrlParm(parameters, URLPARM_ACTION);
60         String uid = getUrlParm(parameters, URLPARM_UID);
61         String update = getUrlParm(parameters, URLPARM_UPDATE);
62         if (uid.isEmpty() || action.isEmpty()) {
63             return new ShellyMgrResponse("Invalid URL parameters: " + parameters.toString(),
64                     HttpStatus.BAD_REQUEST_400);
65         }
66
67         Map<String, String> properties = new HashMap<>();
68         properties.put(ATTRIBUTE_METATAG, "");
69         properties.put(ATTRIBUTE_CSS_HEADER, "");
70         properties.put(ATTRIBUTE_CSS_FOOTER, "");
71         String html = loadHTML(HEADER_HTML, properties);
72
73         ShellyManagerInterface th = getThingHandler(uid);
74         if (th != null) {
75             fillProperties(properties, uid, th);
76
77             Map<String, String> actions = getActions(th.getProfile());
78             String actionUrl = SHELLY_MGR_OVERVIEW_URI;
79             String actionButtonLabel = "OK"; // Default
80             String serviceName = getValue(properties, PROPERTY_SERVICE_NAME);
81             String message = "";
82
83             ShellyThingConfiguration config = getThingConfig(th, properties);
84             ShellyDeviceProfile profile = th.getProfile();
85             ShellyApiInterface api = th.getApi();
86             new ShellyHttpApi(uid, config, httpClient);
87
88             int refreshTimer = 0;
89             switch (action) {
90                 case ACTION_RES_STATS:
91                     th.resetStats();
92                     message = getMessageP("action.resstats.confirm", MCINFO);
93                     refreshTimer = 3;
94                     break;
95                 case ACTION_RESTART:
96                     if ("yes".equalsIgnoreCase(update)) {
97                         message = getMessageP("action.restart.info", MCINFO);
98                         actionButtonLabel = "Ok";
99                         new Thread(() -> { // schedule asynchronous reboot
100                             try {
101                                 api.deviceReboot();
102                             } catch (ShellyApiException e) {
103                                 // maybe the device restarts before returning the http response
104                             }
105                             setRestarted(th, uid); // refresh after reboot
106                         }).start();
107                         refreshTimer = profile.isMotion ? 60 : 30;
108                     } else {
109                         message = getMessageS("action.restart.confirm", MCINFO);
110                         actionUrl = buildActionUrl(uid, action);
111                     }
112                     break;
113                 case ACTION_PROTECT:
114                     // Get device settings
115                     if (config.userId.isEmpty() || config.password.isEmpty()) {
116                         message = getMessageP("action.protect.id-missing", MCWARNING);
117                         break;
118                     }
119
120                     if (!"yes".equalsIgnoreCase(update)) {
121                         ShellySettingsLogin status = api.getLoginSettings();
122                         message = getMessage("action.protect.status", getBool(status.enabled) ? "enabled" : "disabled",
123                                 status.username)
124                                 + getMessageP("action.protect.new", MCINFO, config.userId, config.password);
125                         actionUrl = buildActionUrl(uid, action);
126                     } else {
127                         api.setLoginCredentials(config.userId, config.password);
128                         message = getMessageP("action.protect.confirm", MCINFO, config.userId, config.password);
129                         refreshTimer = 3;
130                     }
131                     break;
132                 case ACTION_SETCOIOT_MCAST:
133                 case ACTION_SETCOIOT_PEER:
134                     if ((profile.settings.coiot == null) || (profile.settings.coiot.peer == null)) {
135                         // feature not available
136                         message = getMessage("coiot.mode-not-suppored", MCWARNING, action);
137                         break;
138                     }
139
140                     String peer = getString(profile.settings.coiot.peer);
141                     boolean mcast = peer.isEmpty() || SHELLY_COIOT_MCAST.equalsIgnoreCase(peer);
142                     String newPeer = mcast ? localIp + ":" + ShellyCoapJSonDTO.COIOT_PORT : SHELLY_COIOT_MCAST;
143                     String displayPeer = mcast ? newPeer : "Multicast";
144
145                     if (profile.isMotion && action.equalsIgnoreCase(ACTION_SETCOIOT_MCAST)) {
146                         // feature not available
147                         message = getMessageP("coiot.multicast-not-supported", "warning", displayPeer);
148                         break;
149                     }
150
151                     if (!"yes".equalsIgnoreCase(update)) {
152                         message = getMessageP("coiot.current-peer", MCMESSAGE, mcast ? "Multicast" : peer)
153                                 + getMessageP("coiot.new-peer", MCINFO, displayPeer)
154                                 + getMessageP(mcast ? "coiot.mode-peer" : "coiot.mode-mcast", MCMESSAGE);
155                         actionUrl = buildActionUrl(uid, action);
156                     } else {
157                         new Thread(() -> { // schedule asynchronous reboot
158                             try {
159                                 api.setCoIoTPeer(newPeer);
160                                 api.deviceReboot();
161                             } catch (ShellyApiException e) {
162                                 // maybe the device restarts before returning the http response
163                             }
164                             setRestarted(th, uid); // refresh after reboot
165                         }).start();
166
167                         // The device needs a restart after changing the peer mode
168                         message = getMessageP("action.restart.info", MCINFO);
169                         refreshTimer = 30;
170                     }
171                     break;
172                 case ACTION_ENCLOUD:
173                 case ACTION_DISCLOUD:
174                     boolean enabled = action.equals(ACTION_ENCLOUD);
175                     api.setCloud(enabled);
176                     message = getMessageP("action.setcloud.config", MCINFO, enabled ? "enabled" : "disabled");
177                     refreshTimer = 20;
178                     break;
179                 case ACTION_RESET:
180                     if (!"yes".equalsIgnoreCase(update)) {
181                         message = getMessageP("action.reset.warning", MCWARNING, serviceName);
182                         actionUrl = buildActionUrl(uid, action);
183                     } else {
184                         new Thread(() -> { // schedule asynchronous reboot
185                             try {
186                                 api.factoryReset();
187                                 setRestarted(th, uid);
188                             } catch (ShellyApiException e) {
189                                 // maybe the device restarts before returning the http response
190                             }
191                         }).start();
192                         message = getMessageP("action.reset.confirm", MCINFO, serviceName);
193                         refreshTimer = 5;
194                     }
195                     break;
196                 case ACTION_OTACHECK:
197                     try {
198                         ShellyOtaCheckResult result = api.checkForUpdate();
199                         message = getMessage("action.checkupd." + result.status);
200                     } catch (ShellyApiException e) {
201                         // maybe the device restarts before returning the http response
202                         message = getMessageP("action.checkupd.failed", e.toString());
203                     }
204                     refreshTimer = 3;
205                     break;
206                 case ACTION_ENDEBUG:
207                 case ACTION_DISDEBUG:
208                     boolean enable = ACTION_ENDEBUG.equalsIgnoreCase(action);
209                     if (!"yes".equalsIgnoreCase(update)) {
210                         message = getMessage(enable ? "action.debug-enable" : "action.debug-disable");
211                         actionUrl = buildActionUrl(uid, action);
212                     } else {
213                         new Thread(() -> { // schedule asynchronous reboot
214                             try {
215                                 api.setDebug(enable);
216                             } catch (ShellyApiException e) {
217                                 // maybe the device restarts before returning the http response
218                             }
219                         }).start();
220
221                         message = getMessage("action.debug-confirm", enable ? "enabled" : "disabled");
222                         refreshTimer = 3;
223                     }
224                     break;
225                 case ACTION_RESSTA:
226                     if (!"yes".equalsIgnoreCase(update)) {
227                         message = getMessage("action.resetsta-info");
228                         actionUrl = buildActionUrl(uid, action);
229                     } else {
230                         try {
231                             api.resetStaCache();
232                             message = getMessage("action.resetsta-confirm");
233                         } catch (ShellyApiException e) {
234                             message = getMessageP("action.resetsta-failed", e.toString());
235                         }
236                         refreshTimer = 10;
237                     }
238                     break;
239                 case ACTION_ENWIFIREC:
240                 case ACTION_DISWIFIREC:
241                     enable = ACTION_ENWIFIREC.equalsIgnoreCase(action);
242                     if (!"yes".equalsIgnoreCase(update)) {
243                         message = getMessage(enable ? "action.setwifirec-enable" : "action.setwifirec-disable");
244                         actionUrl = buildActionUrl(uid, action);
245                     } else {
246                         try {
247                             api.setWiFiRecovery(enable);
248                             message = getMessage("action.setwifirec-confirm", enable ? "enabled" : "disabled");
249                         } catch (ShellyApiException e) {
250                             message = getMessage("action.setwifirec-failed", e.toString());
251                         }
252                         refreshTimer = 3;
253                     }
254                     break;
255
256                 case ACTION_ENAPROAMING:
257                 case ACTION_DISAPROAMING:
258                     enable = ACTION_ENAPROAMING.equalsIgnoreCase(action);
259                     if (!"yes".equalsIgnoreCase(update)) {
260                         message = getMessage(enable ? "action.aproaming-enable" : "action.aproaming-disable");
261                         actionUrl = buildActionUrl(uid, action);
262                     } else {
263                         try {
264                             api.setApRoaming(enable);
265                             message = getMessage("action.aproaming-confirm", enable ? "enabled" : "disabled");
266                         } catch (ShellyApiException e) {
267                             message = getMessage("action.aproaming-failed", e.toString());
268                         }
269                         refreshTimer = 3;
270                     }
271                     break;
272
273                 case ACTION_GETDEB:
274                 case ACTION_GETDEB1:
275                     try {
276                         message = api.getDebugLog(ACTION_GETDEB.equalsIgnoreCase(action) ? "log" : "log1");
277                         message = message.replaceAll("[\r]", "").replaceAll("[\r\n]", "<br>");
278                     } catch (ShellyApiException e) {
279                         message = getMessage("action.getdebug-failed", e.toString());
280                     }
281                     break;
282                 case ACTION_NONE:
283                     break;
284                 default:
285                     logger.warn("{}: {}", LOG_PREFIX, getMessage("action.unknown", action));
286             }
287
288             properties.put(ATTRIBUTE_ACTION, getString(actions.get(action))); // get description for command
289             properties.put(ATTRIBUTE_ACTION_BUTTON, actionButtonLabel);
290             properties.put(ATTRIBUTE_ACTION_URL, actionUrl);
291             message = fillAttributes(message, properties);
292             properties.put(ATTRIBUTE_MESSAGE, message);
293             properties.put(ATTRIBUTE_REFRESH, String.valueOf(refreshTimer));
294             html += loadHTML(ACTION_HTML, properties);
295
296             th.requestUpdates(1, refreshTimer > 0); // trigger background update
297         }
298
299         properties.clear();
300         html += loadHTML(FOOTER_HTML, properties);
301         return new ShellyMgrResponse(html, HttpStatus.OK_200);
302     }
303
304     public static Map<String, String> getActions(ShellyDeviceProfile profile) {
305         Map<String, String> list = new LinkedHashMap<>();
306         list.put(ACTION_RES_STATS, "Reset Statistics");
307         list.put(ACTION_RESTART, "Reboot Device");
308         list.put(ACTION_PROTECT, "Protect Device");
309
310         if ((profile.settings.coiot != null) && (profile.settings.coiot.peer != null) && !profile.isMotion) {
311             boolean mcast = profile.settings.coiot.peer.isEmpty()
312                     || SHELLY_COIOT_MCAST.equalsIgnoreCase(profile.settings.coiot.peer);
313             list.put(mcast ? ACTION_SETCOIOT_PEER : ACTION_SETCOIOT_MCAST,
314                     mcast ? "Set CoIoT Peer Mode" : "Set CoIoT Multicast Mode");
315         }
316         if (profile.isSensor && !profile.isMotion && (profile.settings.wifiSta != null)
317                 && profile.settings.wifiSta.enabled) {
318             // FW 1.10+: Reset STA list, force WiFi rescan and connect to stringest AP
319             list.put(ACTION_RESSTA, "Reconnect WiFi");
320         }
321         if (profile.settings.apRoaming != null) {
322             list.put(!profile.settings.apRoaming.enabled ? ACTION_ENAPROAMING : ACTION_DISAPROAMING,
323                     !profile.settings.apRoaming.enabled ? "Enable WiFi Roaming" : "Disable WiFi Roaming");
324         }
325         if (profile.settings.wifiRecoveryReboot != null) {
326             list.put(!profile.settings.wifiRecoveryReboot ? ACTION_ENWIFIREC : ACTION_DISWIFIREC,
327                     !profile.settings.wifiRecoveryReboot ? "Enable WiFi Recovery" : "Disable WiFi Recovery");
328         }
329
330         boolean set = profile.settings.cloud != null && profile.settings.cloud.enabled;
331         list.put(set ? ACTION_DISCLOUD : ACTION_ENCLOUD, set ? "Disable Cloud" : "Enable Cloud");
332
333         list.put(ACTION_RESET, "-Factory Reset");
334         if (profile.extFeatures) {
335             list.put(ACTION_OTACHECK, "Check for Update");
336             boolean debug_enable = getBool(profile.settings.debugEnable);
337             list.put(!debug_enable ? ACTION_ENDEBUG : ACTION_DISDEBUG,
338                     !debug_enable ? "Enable Debug" : "Disable Debug");
339             if (debug_enable) {
340                 list.put(ACTION_GETDEB, "Get Debug log");
341                 list.put(ACTION_GETDEB1, "Get Debug log1");
342             }
343         }
344
345         return list;
346     }
347
348     private String buildActionUrl(String uid, String action) {
349         return SHELLY_MGR_ACTION_URI + "?" + URLPARM_ACTION + "=" + action + "&" + URLPARM_UID + "=" + urlEncode(uid)
350                 + "&" + URLPARM_UPDATE + "=yes";
351     }
352
353     private void setRestarted(ShellyManagerInterface th, String uid) {
354         th.setThingOffline(ThingStatusDetail.GONE, "offline.status-error-restarted");
355         scheduleUpdate(th, uid + "_upgrade", 25); // wait 25s before refresh
356     }
357 }