2 * Copyright (c) 2010-2022 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");
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
70 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
71 "@text/offline.configuration-error.tokenEmpty");
75 Date now = new Date();
76 if (expiryDate.before(now)) {
77 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
78 "@text/offline.configuration-error.tokenExpired");
83 String addr = networkAddressService.getPrimaryIpv4HostAddress();
84 addr = (addr == null) ? "unknown" : addr.replace(".", "_");
85 String clientId = addr + "-" + thing.getUID().toString().replace(":", "_");
87 nhcComm = new NikoHomeControlCommunication2(this, clientId, scheduler);
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");
98 protected void updateProperties() {
99 Map<String, String> properties = new HashMap<>();
101 NikoHomeControlCommunication2 comm = (NikoHomeControlCommunication2) nhcComm;
103 Date expiry = getTokenExpiryDate();
104 if (expiry != null) {
105 properties.put("tokenExpiryDate", DateFormat.getDateInstance().format(expiry));
107 String nhcVersion = comm.getSystemInfo().getNhcVersion();
108 if (!nhcVersion.isEmpty()) {
109 properties.put("nhcVersion", nhcVersion);
111 String cocoImage = comm.getSystemInfo().getCocoImage();
112 if (!cocoImage.isEmpty()) {
113 properties.put("cocoImage", cocoImage);
115 String language = comm.getSystemInfo().getLanguage();
116 if (!language.isEmpty()) {
117 properties.put("language", language);
119 String currency = comm.getSystemInfo().getCurrency();
120 if (!currency.isEmpty()) {
121 properties.put("currency", currency);
123 String units = comm.getSystemInfo().getUnits();
124 if (!units.isEmpty()) {
125 properties.put("units", units);
127 String lastConfig = comm.getSystemInfo().getLastConfig();
128 if (!lastConfig.isEmpty()) {
129 properties.put("lastConfig", lastConfig);
131 String electricityTariff = comm.getSystemInfo().getElectricityTariff();
132 if (!electricityTariff.isEmpty()) {
133 properties.put("electricityTariff", electricityTariff);
135 String gasTariff = comm.getSystemInfo().getGasTariff();
136 if (!gasTariff.isEmpty()) {
137 properties.put("gasTariff", gasTariff);
139 String waterTariff = comm.getSystemInfo().getWaterTariff();
140 if (!waterTariff.isEmpty()) {
141 properties.put("waterTariff", waterTariff);
143 String timezone = comm.getTimeInfo().getTimezone();
144 if (!timezone.isEmpty()) {
145 properties.put("timezone", timezone);
147 String isDst = comm.getTimeInfo().getIsDst();
148 if (!isDst.isEmpty()) {
149 properties.put("isDST", isDst);
151 String services = comm.getServices();
152 if (!services.isEmpty()) {
153 properties.put("services", services);
156 thing.setProperties(properties);
161 public String getProfile() {
162 return getConfig().as(NikoHomeControlBridgeConfig2.class).profile;
166 public String getToken() {
167 String token = getConfig().as(NikoHomeControlBridgeConfig2.class).password;
168 if (token.isEmpty()) {
169 logger.debug("no JWT token set.");
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.
178 * @return Hobby API token expiry date, null if no valid token.
180 private @Nullable Date getTokenExpiryDate() {
181 NhcJwtToken2 jwtToken = null;
183 String token = getToken();
184 String[] tokenArray = token.split("\\.");
186 if (tokenArray.length == 3) {
187 String tokenPayload = new String(Base64.getDecoder().decode(tokenArray[1]));
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);
199 if (jwtToken != null) {
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);
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));
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));