]> git.basschouten.com Git - openhab-addons.git/blob
e6687926e96a2c632d2ce04b785bdb954fccc830
[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.nikohomecontrol.internal.handler;
14
15 import java.security.cert.CertificateException;
16 import java.text.DateFormat;
17 import java.util.Base64;
18 import java.util.Calendar;
19 import java.util.Date;
20 import java.util.HashMap;
21 import java.util.Map;
22 import java.util.NoSuchElementException;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NikoHomeControlCommunication2;
27 import org.openhab.core.net.NetworkAddressService;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.ThingStatus;
30 import org.openhab.core.thing.ThingStatusDetail;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import com.google.gson.Gson;
35 import com.google.gson.GsonBuilder;
36 import com.google.gson.JsonSyntaxException;
37
38 /**
39  * {@link NikoHomeControlBridgeHandler2} is the handler for a Niko Home Control II Connected Controller and connects it
40  * to the framework.
41  *
42  * @author Mark Herwege - Initial Contribution
43  */
44 @NonNullByDefault
45 public class NikoHomeControlBridgeHandler2 extends NikoHomeControlBridgeHandler {
46
47     private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler2.class);
48
49     private final Gson gson = new GsonBuilder().create();
50
51     NetworkAddressService networkAddressService;
52
53     public NikoHomeControlBridgeHandler2(Bridge nikoHomeControlBridge, NetworkAddressService networkAddressService) {
54         super(nikoHomeControlBridge);
55         this.networkAddressService = networkAddressService;
56     }
57
58     @Override
59     public void initialize() {
60         logger.debug("initializing NHC II bridge handler");
61
62         setConfig();
63
64         Date expiryDate = getTokenExpiryDate();
65         if (expiryDate == null) {
66             if (getToken().isEmpty()) {
67                 // We allow a not well formed token (no expiry date) to pass through.
68                 // This allows the user to use this as a password on a profile, with the profile UUID defined in an
69                 // advanced configuration, skipping token validation.
70                 // This behavior would allow the same logic to be used (with profile UUID) as before token validation
71                 // was introduced.
72                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Token is empty");
73                 return;
74             }
75         } else {
76             Date now = new Date();
77             if (expiryDate.before(now)) {
78                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
79                         "Hobby api token has expired");
80                 return;
81             }
82         }
83
84         String addr = networkAddressService.getPrimaryIpv4HostAddress();
85         addr = (addr == null) ? "unknown" : addr.replace(".", "_");
86         String clientId = addr + "-" + thing.getUID().toString().replace(":", "_");
87         try {
88             nhcComm = new NikoHomeControlCommunication2(this, clientId, scheduler);
89             startCommunication();
90         } catch (CertificateException e) {
91             // this should not happen unless there is a programming error
92             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
93                     "Not able to set SSL context");
94             return;
95         }
96     }
97
98     @Override
99     protected void updateProperties() {
100         Map<String, String> properties = new HashMap<>();
101
102         NikoHomeControlCommunication2 comm = (NikoHomeControlCommunication2) nhcComm;
103         if (comm != null) {
104             Date expiry = getTokenExpiryDate();
105             if (expiry != null) {
106                 properties.put("tokenExpiryDate", DateFormat.getDateInstance().format(expiry));
107             }
108             String nhcVersion = comm.getSystemInfo().getNhcVersion();
109             if (!nhcVersion.isEmpty()) {
110                 properties.put("nhcVersion", nhcVersion);
111             }
112             String cocoImage = comm.getSystemInfo().getCocoImage();
113             if (!cocoImage.isEmpty()) {
114                 properties.put("cocoImage", cocoImage);
115             }
116             String language = comm.getSystemInfo().getLanguage();
117             if (!language.isEmpty()) {
118                 properties.put("language", language);
119             }
120             String currency = comm.getSystemInfo().getCurrency();
121             if (!currency.isEmpty()) {
122                 properties.put("currency", currency);
123             }
124             String units = comm.getSystemInfo().getUnits();
125             if (!units.isEmpty()) {
126                 properties.put("units", units);
127             }
128             String lastConfig = comm.getSystemInfo().getLastConfig();
129             if (!lastConfig.isEmpty()) {
130                 properties.put("lastConfig", lastConfig);
131             }
132             String electricityTariff = comm.getSystemInfo().getElectricityTariff();
133             if (!electricityTariff.isEmpty()) {
134                 properties.put("electricityTariff", electricityTariff);
135             }
136             String gasTariff = comm.getSystemInfo().getGasTariff();
137             if (!gasTariff.isEmpty()) {
138                 properties.put("gasTariff", gasTariff);
139             }
140             String waterTariff = comm.getSystemInfo().getWaterTariff();
141             if (!waterTariff.isEmpty()) {
142                 properties.put("waterTariff", waterTariff);
143             }
144             String timezone = comm.getTimeInfo().getTimezone();
145             if (!timezone.isEmpty()) {
146                 properties.put("timezone", timezone);
147             }
148             String isDst = comm.getTimeInfo().getIsDst();
149             if (!isDst.isEmpty()) {
150                 properties.put("isDST", isDst);
151             }
152             String services = comm.getServices();
153             if (!services.isEmpty()) {
154                 properties.put("services", services);
155             }
156
157             thing.setProperties(properties);
158         }
159     }
160
161     @Override
162     public String getProfile() {
163         return ((NikoHomeControlBridgeConfig2) config).profile;
164     }
165
166     @Override
167     public String getToken() {
168         String token = ((NikoHomeControlBridgeConfig2) config).password;
169         if (token.isEmpty()) {
170             logger.debug("no JWT token set.");
171         }
172         return token;
173     }
174
175     /**
176      * Extract the expiry date in the user provided token for the hobby API. Log warnings and errors if the token is
177      * close to expiry or expired.
178      *
179      * @return Hobby API token expiry date, null if no valid token.
180      */
181     private @Nullable Date getTokenExpiryDate() {
182         NhcJwtToken2 jwtToken = null;
183
184         String token = getToken();
185         String[] tokenArray = token.split("\\.");
186
187         if (tokenArray.length == 3) {
188             String tokenPayload = new String(Base64.getDecoder().decode(tokenArray[1]));
189
190             try {
191                 jwtToken = gson.fromJson(tokenPayload, NhcJwtToken2.class);
192             } catch (JsonSyntaxException e) {
193                 logger.debug("unexpected token payload {}", tokenPayload);
194             } catch (NoSuchElementException ignore) {
195                 // Ignore if exp not present in response, this should not happen in token payload response
196                 logger.trace("no expiry date found in payload {}", tokenPayload);
197             }
198         }
199
200         if (jwtToken != null) {
201             Date expiryDate;
202             try {
203                 String expiryEpoch = jwtToken.exp;
204                 long epoch = Long.parseLong(expiryEpoch) * 1000; // convert to milliseconds
205                 expiryDate = new Date(epoch);
206             } catch (NumberFormatException e) {
207                 logger.debug("token expiry not valid {}", jwtToken.exp);
208                 return null;
209             }
210
211             Date now = new Date();
212             if (expiryDate.before(now)) {
213                 logger.warn("hobby API token expired, was valid until {}",
214                         DateFormat.getDateInstance().format(expiryDate));
215             } else {
216                 Calendar c = Calendar.getInstance();
217                 c.setTime(expiryDate);
218                 c.add(Calendar.DATE, -14);
219                 if (c.getTime().before(now)) {
220                     logger.info("hobby API token will expire in less than 14 days, valid until {}",
221                             DateFormat.getDateInstance().format(expiryDate));
222                 }
223             }
224             return expiryDate;
225         }
226
227         return null;
228     }
229
230     @Override
231     protected synchronized void setConfig() {
232         config = getConfig().as(NikoHomeControlBridgeConfig2.class);
233     }
234 }