2 * Copyright (c) 2010-2021 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.nikohomecontrol.internal.handler;
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;
22 import java.util.NoSuchElementException;
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;
34 import com.google.gson.Gson;
35 import com.google.gson.GsonBuilder;
36 import com.google.gson.JsonSyntaxException;
39 * {@link NikoHomeControlBridgeHandler2} is the handler for a Niko Home Control II Connected Controller and connects it
42 * @author Mark Herwege - Initial Contribution
45 public class NikoHomeControlBridgeHandler2 extends NikoHomeControlBridgeHandler {
47 private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler2.class);
49 private final Gson gson = new GsonBuilder().create();
51 NetworkAddressService networkAddressService;
53 public NikoHomeControlBridgeHandler2(Bridge nikoHomeControlBridge, NetworkAddressService networkAddressService) {
54 super(nikoHomeControlBridge);
55 this.networkAddressService = networkAddressService;
59 public void initialize() {
60 logger.debug("initializing NHC II bridge handler");
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
72 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Token is empty");
76 Date now = new Date();
77 if (expiryDate.before(now)) {
78 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
79 "Hobby api token has expired");
84 String addr = networkAddressService.getPrimaryIpv4HostAddress();
85 addr = (addr == null) ? "unknown" : addr.replace(".", "_");
86 String clientId = addr + "-" + thing.getUID().toString().replace(":", "_");
88 nhcComm = new NikoHomeControlCommunication2(this, clientId, scheduler);
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");
99 protected void updateProperties() {
100 Map<String, String> properties = new HashMap<>();
102 NikoHomeControlCommunication2 comm = (NikoHomeControlCommunication2) nhcComm;
104 Date expiry = getTokenExpiryDate();
105 if (expiry != null) {
106 properties.put("tokenExpiryDate", DateFormat.getDateInstance().format(expiry));
108 String nhcVersion = comm.getSystemInfo().getNhcVersion();
109 if (!nhcVersion.isEmpty()) {
110 properties.put("nhcVersion", nhcVersion);
112 String cocoImage = comm.getSystemInfo().getCocoImage();
113 if (!cocoImage.isEmpty()) {
114 properties.put("cocoImage", cocoImage);
116 String language = comm.getSystemInfo().getLanguage();
117 if (!language.isEmpty()) {
118 properties.put("language", language);
120 String currency = comm.getSystemInfo().getCurrency();
121 if (!currency.isEmpty()) {
122 properties.put("currency", currency);
124 String units = comm.getSystemInfo().getUnits();
125 if (!units.isEmpty()) {
126 properties.put("units", units);
128 String lastConfig = comm.getSystemInfo().getLastConfig();
129 if (!lastConfig.isEmpty()) {
130 properties.put("lastConfig", lastConfig);
132 String electricityTariff = comm.getSystemInfo().getElectricityTariff();
133 if (!electricityTariff.isEmpty()) {
134 properties.put("electricityTariff", electricityTariff);
136 String gasTariff = comm.getSystemInfo().getGasTariff();
137 if (!gasTariff.isEmpty()) {
138 properties.put("gasTariff", gasTariff);
140 String waterTariff = comm.getSystemInfo().getWaterTariff();
141 if (!waterTariff.isEmpty()) {
142 properties.put("waterTariff", waterTariff);
144 String timezone = comm.getTimeInfo().getTimezone();
145 if (!timezone.isEmpty()) {
146 properties.put("timezone", timezone);
148 String isDst = comm.getTimeInfo().getIsDst();
149 if (!isDst.isEmpty()) {
150 properties.put("isDST", isDst);
152 String services = comm.getServices();
153 if (!services.isEmpty()) {
154 properties.put("services", services);
157 thing.setProperties(properties);
162 public String getProfile() {
163 return ((NikoHomeControlBridgeConfig2) config).profile;
167 public String getToken() {
168 String token = ((NikoHomeControlBridgeConfig2) config).password;
169 if (token.isEmpty()) {
170 logger.debug("no JWT token set.");
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.
179 * @return Hobby API token expiry date, null if no valid token.
181 private @Nullable Date getTokenExpiryDate() {
182 NhcJwtToken2 jwtToken = null;
184 String token = getToken();
185 String[] tokenArray = token.split("\\.");
187 if (tokenArray.length == 3) {
188 String tokenPayload = new String(Base64.getDecoder().decode(tokenArray[1]));
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);
200 if (jwtToken != null) {
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);
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));
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));
231 protected synchronized void setConfig() {
232 config = getConfig().as(NikoHomeControlBridgeConfig2.class);