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