2 * Copyright (c) 2010-2022 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.api1.Shelly1ApiJsonDTO.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.ShellyApiInterface;
30 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
31 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult;
32 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsLogin;
33 import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO;
34 import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
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;
44 * {@link ShellyManagerActionPage} implements the Shelly Manager's action page
46 * @author Markus Michels - Initial contribution
49 public class ShellyManagerActionPage extends ShellyManagerPage {
50 private final Logger logger = LoggerFactory.getLogger(ShellyManagerActionPage.class);
52 public ShellyManagerActionPage(ConfigurationAdmin configurationAdmin, ShellyTranslationProvider translationProvider,
53 HttpClient httpClient, String localIp, int localPort, ShellyHandlerFactory handlerFactory) {
54 super(configurationAdmin, translationProvider, httpClient, localIp, localPort, handlerFactory);
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);
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);
73 ShellyManagerInterface th = getThingHandler(uid);
75 fillProperties(properties, uid, th);
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);
83 ShellyThingConfiguration config = getThingConfig(th, properties);
84 ShellyDeviceProfile profile = th.getProfile();
85 ShellyApiInterface api = th.getApi();
86 new Shelly1HttpApi(uid, config, httpClient);
90 case ACTION_RES_STATS:
92 message = getMessageP("action.resstats.confirm", MCINFO);
96 if ("yes".equalsIgnoreCase(update)) {
97 message = getMessageP("action.restart.info", MCINFO);
98 actionButtonLabel = "Ok";
99 new Thread(() -> { // schedule asynchronous reboot
102 } catch (ShellyApiException e) {
103 // maybe the device restarts before returning the http response
105 setRestarted(th, uid); // refresh after reboot
107 refreshTimer = profile.isMotion ? 60 : 30;
109 message = getMessageS("action.restart.confirm", MCINFO);
110 actionUrl = buildActionUrl(uid, action);
114 // Get device settings
115 if (config.userId.isEmpty() || config.password.isEmpty()) {
116 message = getMessageP("action.protect.id-missing", MCWARNING);
120 if (!"yes".equalsIgnoreCase(update)) {
121 ShellySettingsLogin status = api.getLoginSettings();
122 message = getMessage("action.protect.status", getBool(status.enabled) ? "enabled" : "disabled",
124 + getMessageP("action.protect.new", MCINFO, config.userId, config.password);
125 actionUrl = buildActionUrl(uid, action);
127 api.setLoginCredentials(config.userId, config.password);
128 message = getMessageP("action.protect.confirm", MCINFO, config.userId, config.password);
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);
140 String peer = getString(profile.settings.coiot.peer);
141 boolean mcast = peer.isEmpty() || SHELLY_COIOT_MCAST.equalsIgnoreCase(peer);
142 String newPeer = mcast ? localIp + ":" + Shelly1CoapJSonDTO.COIOT_PORT : SHELLY_COIOT_MCAST;
143 String displayPeer = mcast ? newPeer : "Multicast";
145 if (profile.isMotion && action.equalsIgnoreCase(ACTION_SETCOIOT_MCAST)) {
146 // feature not available
147 message = getMessageP("coiot.multicast-not-supported", "warning", displayPeer);
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);
157 new Thread(() -> { // schedule asynchronous reboot
159 api.setCoIoTPeer(newPeer);
161 } catch (ShellyApiException e) {
162 // maybe the device restarts before returning the http response
164 setRestarted(th, uid); // refresh after reboot
167 // The device needs a restart after changing the peer mode
168 message = getMessageP("action.restart.info", MCINFO);
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");
180 if (!"yes".equalsIgnoreCase(update)) {
181 message = getMessageP("action.reset.warning", MCWARNING, serviceName);
182 actionUrl = buildActionUrl(uid, action);
184 new Thread(() -> { // schedule asynchronous reboot
187 setRestarted(th, uid);
188 } catch (ShellyApiException e) {
189 // maybe the device restarts before returning the http response
192 message = getMessageP("action.reset.confirm", MCINFO, serviceName);
196 case ACTION_OTACHECK:
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());
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);
213 new Thread(() -> { // schedule asynchronous reboot
215 api.setDebug(enable);
216 } catch (ShellyApiException e) {
217 // maybe the device restarts before returning the http response
221 message = getMessage("action.debug-confirm", enable ? "enabled" : "disabled");
226 if (!"yes".equalsIgnoreCase(update)) {
227 message = getMessage("action.resetsta-info");
228 actionUrl = buildActionUrl(uid, action);
232 message = getMessage("action.resetsta-confirm");
233 } catch (ShellyApiException e) {
234 message = getMessageP("action.resetsta-failed", e.toString());
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);
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());
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);
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());
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());
285 logger.warn("{}: {}", LOG_PREFIX, getMessage("action.unknown", action));
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);
296 th.requestUpdates(1, refreshTimer > 0); // trigger background update
300 html += loadHTML(FOOTER_HTML, properties);
301 return new ShellyMgrResponse(html, HttpStatus.OK_200);
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");
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");
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");
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");
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");
330 boolean set = profile.settings.cloud != null && profile.settings.cloud.enabled;
331 list.put(set ? ACTION_DISCLOUD : ACTION_ENCLOUD, set ? "Disable Cloud" : "Enable Cloud");
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");
340 list.put(ACTION_GETDEB, "Get Debug log");
341 list.put(ACTION_GETDEB1, "Get Debug log1");
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";
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