]> git.basschouten.com Git - openhab-addons.git/blob
4f1190a20c3112656127d8d6491b01e8be249378
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.semsportal.internal;
14
15 import java.nio.charset.StandardCharsets;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.concurrent.ScheduledFuture;
19
20 import javax.ws.rs.core.MediaType;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.jetty.client.HttpClient;
25 import org.eclipse.jetty.client.api.ContentResponse;
26 import org.eclipse.jetty.client.api.Request;
27 import org.eclipse.jetty.client.util.StringContentProvider;
28 import org.eclipse.jetty.http.HttpHeader;
29 import org.openhab.binding.semsportal.internal.dto.BaseResponse;
30 import org.openhab.binding.semsportal.internal.dto.LoginRequest;
31 import org.openhab.binding.semsportal.internal.dto.LoginResponse;
32 import org.openhab.binding.semsportal.internal.dto.SEMSToken;
33 import org.openhab.binding.semsportal.internal.dto.Station;
34 import org.openhab.binding.semsportal.internal.dto.StationListRequest;
35 import org.openhab.binding.semsportal.internal.dto.StationListResponse;
36 import org.openhab.binding.semsportal.internal.dto.StationStatus;
37 import org.openhab.binding.semsportal.internal.dto.StatusRequest;
38 import org.openhab.binding.semsportal.internal.dto.StatusResponse;
39 import org.openhab.core.io.net.http.HttpClientFactory;
40 import org.openhab.core.thing.Bridge;
41 import org.openhab.core.thing.ChannelUID;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.binding.BaseBridgeHandler;
45 import org.openhab.core.types.Command;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import com.google.gson.Gson;
50 import com.google.gson.GsonBuilder;
51
52 /**
53  * The {@link PortalHandler} is responsible for handling commands, which are
54  * sent to one of the channels.
55  *
56  * @author Iwan Bron - Initial contribution
57  */
58 @NonNullByDefault
59 public class PortalHandler extends BaseBridgeHandler {
60     private Logger logger = LoggerFactory.getLogger(PortalHandler.class);
61     // the settings that are needed when you do not have avalid session token yet
62     private static final SEMSToken SESSIONLESS_TOKEN = new SEMSToken("v2.1.0", "ios", "en");
63     // the url of the SEMS portal API
64     private static final String BASE_URL = "https://www.semsportal.com/";
65     // url for the login request, to get a valid session token
66     private static final String LOGIN_URL = BASE_URL + "api/v2/Common/CrossLogin";
67     // url to get the status of a specific power station
68     private static final String STATUS_URL = BASE_URL + "api/v2/PowerStation/GetMonitorDetailByPowerstationId";
69     private static final String LIST_URL = BASE_URL + "api/PowerStationMonitor/QueryPowerStationMonitorForApp";
70     // the token holds the credential information for the portal
71     private static final String HTTP_HEADER_TOKEN = "Token";
72
73     // used to parse json from / to the SEMS portal API
74     private final Gson gson;
75     private final HttpClient httpClient;
76
77     // configuration as provided by the openhab framework: initialize with defaults to prevent compiler check errors
78     private SEMSPortalConfiguration config = new SEMSPortalConfiguration();
79     private boolean loggedIn;
80     private SEMSToken sessionToken = SESSIONLESS_TOKEN;// gets the default, it is needed for the login
81     private @Nullable StationStatus currentStatus;
82
83     private @Nullable ScheduledFuture<?> pollingJob;
84
85     public PortalHandler(Bridge bridge, HttpClientFactory httpClientFactory) {
86         super(bridge);
87         httpClient = httpClientFactory.getCommonHttpClient();
88         gson = new GsonBuilder().create();
89     }
90
91     @Override
92     public void handleCommand(ChannelUID channelUID, Command command) {
93         logger.debug("No supported commands. Ignoring command {} for channel {}", command, channelUID);
94         return;
95     }
96
97     @Override
98     public void initialize() {
99         config = getConfigAs(SEMSPortalConfiguration.class);
100         updateStatus(ThingStatus.UNKNOWN);
101
102         scheduler.execute(() -> {
103             try {
104                 login();
105             } catch (Exception e) {
106                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
107                         "Error when loggin in. Check your username and password");
108             }
109         });
110     }
111
112     @Override
113     public void dispose() {
114         ScheduledFuture<?> localPollingJob = pollingJob;
115         if (localPollingJob != null) {
116             localPollingJob.cancel(true);
117         }
118         super.dispose();
119     }
120
121     private void login() {
122         loggedIn = false;
123         String payload = gson.toJson(new LoginRequest(config.username, config.password));
124         String response = sendPost(LOGIN_URL, payload);
125         if (response == null) {
126             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
127                     "Invalid response from SEMS portal");
128             return;
129         }
130         LoginResponse loginResponse = gson.fromJson(response, LoginResponse.class);
131         if (loginResponse == null) {
132             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Check username / password");
133             return;
134         }
135         if (loginResponse.isOk()) {
136             logger.debug("Successfuly logged in to SEMS portal");
137             if (loginResponse.getToken() != null) {
138                 sessionToken = loginResponse.getToken();
139             }
140             loggedIn = true;
141             updateStatus(ThingStatus.ONLINE);
142         } else {
143             updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.CONFIGURATION_ERROR, "Check username / password");
144         }
145     }
146
147     private @Nullable String sendPost(String url, String payload) {
148         try {
149             Request request = httpClient.POST(url).header(HttpHeader.CONTENT_TYPE, MediaType.APPLICATION_JSON)
150                     .header(HTTP_HEADER_TOKEN, gson.toJson(sessionToken))
151                     .content(new StringContentProvider(payload, StandardCharsets.UTF_8.name()),
152                             MediaType.APPLICATION_JSON);
153             request.getHeaders().remove(HttpHeader.ACCEPT_ENCODING);
154             ContentResponse response = request.send();
155             logger.trace("received response: {}", response.getContentAsString());
156             return response.getContentAsString();
157         } catch (Exception e) {
158             logger.debug("{} when posting to url {}", e.getClass().getSimpleName(), url, e);
159         }
160         return null;
161     }
162
163     public boolean isLoggedIn() {
164         return loggedIn;
165     }
166
167     public @Nullable StationStatus getStationStatus(String stationUUID)
168             throws CommunicationException, ConfigurationException {
169         if (!loggedIn) {
170             logger.debug("Not logged in. Not updating.");
171             return null;
172         }
173         String response = sendPost(STATUS_URL, gson.toJson(new StatusRequest(stationUUID)));
174         if (response == null) {
175             throw new CommunicationException("No response received from portal");
176         }
177         BaseResponse semsResponse = gson.fromJson(response, BaseResponse.class);
178         if (semsResponse == null) {
179             throw new CommunicationException("Portal reponse not understood");
180         }
181         if (semsResponse.isOk()) {
182             StatusResponse statusResponse = gson.fromJson(response, StatusResponse.class);
183             if (statusResponse == null) {
184                 throw new CommunicationException("Portal reponse not understood");
185             }
186             currentStatus = statusResponse.getStatus();
187             updateStatus(ThingStatus.ONLINE); // we got a valid response, register as online
188             return currentStatus;
189         } else if (semsResponse.isSessionInvalid()) {
190             logger.debug("Session is invalidated. Attempting new login.");
191             login();
192             return getStationStatus(stationUUID);
193         } else if (semsResponse.isError()) {
194             throw new ConfigurationException(
195                     "ERROR status code received from SEMS portal. Please check your station ID");
196         } else {
197             throw new CommunicationException(String.format("Unknown status code received from SEMS portal: %s - %s",
198                     semsResponse.getCode(), semsResponse.getMsg()));
199         }
200     }
201
202     public long getUpdateInterval() {
203         return config.interval;
204     }
205
206     public List<Station> getAllStations() {
207         String payload = gson.toJson(new StationListRequest());
208         String response = sendPost(LIST_URL, payload);
209         if (response == null) {
210             logger.debug("Received empty list stations response from SEMS portal");
211             return Collections.emptyList();
212         }
213         StationListResponse listResponse = gson.fromJson(response, StationListResponse.class);
214         if (listResponse == null) {
215             logger.debug("Unable to read list stations response from SEMS portal");
216             return Collections.emptyList();
217         }
218         if (listResponse.isOk()) {
219             logger.debug("Received list of {} stations from SEMS portal", listResponse.getStations().size());
220             loggedIn = true;
221             updateStatus(ThingStatus.ONLINE);
222             return listResponse.getStations();
223         } else {
224             logger.debug("Received error response with code {} and message {} from SEMS portal", listResponse.getCode(),
225                     listResponse.getMsg());
226             return Collections.emptyList();
227         }
228     }
229 }