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