2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.shelly.internal.manager;
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.*;
20 import java.util.HashMap;
21 import java.util.LinkedHashMap;
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;
43 * {@link ShellyManagerActionPage} implements the Shelly Manager's action page
45 * @author Markus Michels - Initial contribution
48 public class ShellyManagerActionPage extends ShellyManagerPage {
49 private final Logger logger = LoggerFactory.getLogger(ShellyManagerActionPage.class);
51 public ShellyManagerActionPage(ConfigurationAdmin configurationAdmin, ShellyTranslationProvider translationProvider,
52 HttpClient httpClient, String localIp, int localPort, ShellyHandlerFactory handlerFactory) {
53 super(configurationAdmin, translationProvider, httpClient, localIp, localPort, handlerFactory);
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);
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);
72 ShellyManagerInterface th = getThingHandler(uid);
74 fillProperties(properties, uid, th);
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);
82 ShellyThingConfiguration config = getThingConfig(th, properties);
83 ShellyDeviceProfile profile = th.getProfile();
84 ShellyHttpApi api = th.getApi();
85 new ShellyHttpApi(uid, config, httpClient);
89 case ACTION_RES_STATS:
91 message = getMessageP("action.resstats.confirm", MCINFO);
95 if ("yes".equalsIgnoreCase(update)) {
96 message = getMessageP("action.restart.info", MCINFO);
97 actionButtonLabel = "Ok";
98 new Thread(() -> { // schedule asynchronous reboot
101 } catch (ShellyApiException e) {
102 // maybe the device restarts before returning the http response
104 setRestarted(th, uid); // refresh after reboot
106 refreshTimer = profile.isMotion ? 60 : 30;
108 message = getMessageS("action.restart.confirm", MCINFO);
109 actionUrl = buildActionUrl(uid, action);
113 // Get device settings
114 if (config.userId.isEmpty() || config.password.isEmpty()) {
115 message = getMessageP("action.protect.id-missing", MCWARNING);
119 if (!"yes".equalsIgnoreCase(update)) {
120 ShellySettingsLogin status = api.getLoginSettings();
121 message = getMessage("action.protect.status", getBool(status.enabled) ? "enabled" : "disabled",
123 + getMessageP("action.protect.new", MCINFO, config.userId, config.password);
124 actionUrl = buildActionUrl(uid, action);
126 api.setLoginCredentials(config.userId, config.password);
127 message = getMessageP("action.protect.confirm", MCINFO, config.userId, config.password);
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);
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";
144 if (profile.isMotion && action.equalsIgnoreCase(ACTION_SETCOIOT_MCAST)) {
145 // feature not available
146 message = getMessageP("coiot.multicast-not-supported", "warning", displayPeer);
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);
156 new Thread(() -> { // schedule asynchronous reboot
158 api.setCoIoTPeer(newPeer);
160 } catch (ShellyApiException e) {
161 // maybe the device restarts before returning the http response
163 setRestarted(th, uid); // refresh after reboot
166 // The device needs a restart after changing the peer mode
167 message = getMessageP("action.restart.info", MCINFO);
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");
179 if (!"yes".equalsIgnoreCase(update)) {
180 message = getMessageP("action.reset.warning", MCWARNING, serviceName);
181 actionUrl = buildActionUrl(uid, action);
183 new Thread(() -> { // schedule asynchronous reboot
186 setRestarted(th, uid);
187 } catch (ShellyApiException e) {
188 // maybe the device restarts before returning the http response
191 message = getMessageP("action.reset.confirm", MCINFO, serviceName);
195 case ACTION_OTACHECK:
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());
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);
212 new Thread(() -> { // schedule asynchronous reboot
214 api.setDebug(enable);
215 } catch (ShellyApiException e) {
216 // maybe the device restarts before returning the http response
220 message = getMessage("action.debug-confirm", enable ? "enabled" : "disabled");
225 if (!"yes".equalsIgnoreCase(update)) {
226 message = getMessage("action.resetsta-info");
227 actionUrl = buildActionUrl(uid, action);
231 message = getMessage("action.resetsta-confirm");
232 } catch (ShellyApiException e) {
233 message = getMessageP("action.resetsta-failed", e.toString());
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);
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());
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);
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());
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());
284 logger.warn("{}: {}", LOG_PREFIX, getMessage("action.unknown", action));
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);
295 th.requestUpdates(1, refreshTimer > 0); // trigger background update
299 html += loadHTML(FOOTER_HTML, properties);
300 return new ShellyMgrResponse(html, HttpStatus.OK_200);
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");
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");
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");
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");
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");
329 boolean set = (profile.settings.cloud != null) && profile.settings.cloud.enabled;
330 list.put(set ? ACTION_DISCLOUD : ACTION_ENCLOUD, set ? "Disable Cloud" : "Enable Cloud");
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");
339 list.put(ACTION_GETDEB, "Get Debug log");
340 list.put(ACTION_GETDEB1, "Get Debug log1");
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";
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