]> git.basschouten.com Git - openhab-addons.git/blob
4e86d9b2b7be6f94085c4ebeb069583c2889b4ea
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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         Date expiryDate = getTokenExpiryDate();
63         if (expiryDate == null) {
64             if (getToken().isEmpty()) {
65                 // We allow a not well formed token (no expiry date) to pass through.
66                 // This allows the user to use this as a password on a profile, with the profile UUID defined in an
67                 // advanced configuration, skipping token validation.
68                 // This behavior would allow the same logic to be used (with profile UUID) as before token validation
69                 // was introduced.
70                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
71                         "@text/offline.configuration-error.tokenEmpty");
72                 return;
73             }
74         } else {
75             Date now = new Date();
76             if (expiryDate.before(now)) {
77                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
78                         "@text/offline.configuration-error.tokenExpired");
79                 return;
80             }
81         }
82
83         String addr = networkAddressService.getPrimaryIpv4HostAddress();
84         addr = (addr == null) ? "unknown" : addr.replace(".", "_");
85         String clientId = addr + "-" + thing.getUID().toString().replace(":", "_");
86         try {
87             nhcComm = new NikoHomeControlCommunication2(this, clientId, scheduler);
88             startCommunication();
89         } catch (CertificateException e) {
90             // this should not happen unless there is a programming error
91             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
92                     "@text/offline.communication-error");
93             return;
94         }
95     }
96
97     @Override
98     protected void updateProperties() {
99         Map<String, String> properties = new HashMap<>();
100
101         NikoHomeControlCommunication2 comm = (NikoHomeControlCommunication2) nhcComm;
102         if (comm != null) {
103             Date expiry = getTokenExpiryDate();
104             if (expiry != null) {
105                 properties.put("tokenExpiryDate", DateFormat.getDateInstance().format(expiry));
106             }
107             String nhcVersion = comm.getSystemInfo().getNhcVersion();
108             if (!nhcVersion.isEmpty()) {
109                 properties.put("nhcVersion", nhcVersion);
110             }
111             String cocoImage = comm.getSystemInfo().getCocoImage();
112             if (!cocoImage.isEmpty()) {
113                 properties.put("cocoImage", cocoImage);
114             }
115             String language = comm.getSystemInfo().getLanguage();
116             if (!language.isEmpty()) {
117                 properties.put("language", language);
118             }
119             String currency = comm.getSystemInfo().getCurrency();
120             if (!currency.isEmpty()) {
121                 properties.put("currency", currency);
122             }
123             String units = comm.getSystemInfo().getUnits();
124             if (!units.isEmpty()) {
125                 properties.put("units", units);
126             }
127             String lastConfig = comm.getSystemInfo().getLastConfig();
128             if (!lastConfig.isEmpty()) {
129                 properties.put("lastConfig", lastConfig);
130             }
131             String electricityTariff = comm.getSystemInfo().getElectricityTariff();
132             if (!electricityTariff.isEmpty()) {
133                 properties.put("electricityTariff", electricityTariff);
134             }
135             String gasTariff = comm.getSystemInfo().getGasTariff();
136             if (!gasTariff.isEmpty()) {
137                 properties.put("gasTariff", gasTariff);
138             }
139             String waterTariff = comm.getSystemInfo().getWaterTariff();
140             if (!waterTariff.isEmpty()) {
141                 properties.put("waterTariff", waterTariff);
142             }
143             String timezone = comm.getTimeInfo().getTimezone();
144             if (!timezone.isEmpty()) {
145                 properties.put("timezone", timezone);
146             }
147             String isDst = comm.getTimeInfo().getIsDst();
148             if (!isDst.isEmpty()) {
149                 properties.put("isDST", isDst);
150             }
151             String services = comm.getServices();
152             if (!services.isEmpty()) {
153                 properties.put("services", services);
154             }
155
156             thing.setProperties(properties);
157         }
158     }
159
160     @Override
161     public String getProfile() {
162         return getConfig().as(NikoHomeControlBridgeConfig2.class).profile;
163     }
164
165     @Override
166     public String getToken() {
167         String token = getConfig().as(NikoHomeControlBridgeConfig2.class).password;
168         if (token.isEmpty()) {
169             logger.debug("no JWT token set.");
170         }
171         return token;
172     }
173
174     /**
175      * Extract the expiry date in the user provided token for the hobby API. Log warnings and errors if the token is
176      * close to expiry or expired.
177      *
178      * @return Hobby API token expiry date, null if no valid token.
179      */
180     private @Nullable Date getTokenExpiryDate() {
181         NhcJwtToken2 jwtToken = null;
182
183         String token = getToken();
184         String[] tokenArray = token.split("\\.");
185
186         if (tokenArray.length == 3) {
187             String tokenPayload = new String(Base64.getDecoder().decode(tokenArray[1]));
188
189             try {
190                 jwtToken = gson.fromJson(tokenPayload, NhcJwtToken2.class);
191             } catch (JsonSyntaxException e) {
192                 logger.debug("unexpected token payload {}", tokenPayload);
193             } catch (NoSuchElementException ignore) {
194                 // Ignore if exp not present in response, this should not happen in token payload response
195                 logger.trace("no expiry date found in payload {}", tokenPayload);
196             }
197         }
198
199         if (jwtToken != null) {
200             Date expiryDate;
201             try {
202                 String expiryEpoch = jwtToken.exp;
203                 long epoch = Long.parseLong(expiryEpoch) * 1000; // convert to milliseconds
204                 expiryDate = new Date(epoch);
205             } catch (NumberFormatException e) {
206                 logger.debug("token expiry not valid {}", jwtToken.exp);
207                 return null;
208             }
209
210             Date now = new Date();
211             if (expiryDate.before(now)) {
212                 logger.warn("hobby API token expired, was valid until {}",
213                         DateFormat.getDateInstance().format(expiryDate));
214             } else {
215                 Calendar c = Calendar.getInstance();
216                 c.setTime(expiryDate);
217                 c.add(Calendar.DATE, -14);
218                 if (c.getTime().before(now)) {
219                     logger.info("hobby API token will expire in less than 14 days, valid until {}",
220                             DateFormat.getDateInstance().format(expiryDate));
221                 }
222             }
223             return expiryDate;
224         }
225
226         return null;
227     }
228 }