2 * Copyright (c) 2010-2024 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.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;
35 import com.google.gson.Gson;
36 import com.google.gson.GsonBuilder;
37 import com.google.gson.JsonSyntaxException;
40 * {@link NikoHomeControlBridgeHandler2} is the handler for a Niko Home Control II Connected Controller and connects it
43 * @author Mark Herwege - Initial Contribution
46 public class NikoHomeControlBridgeHandler2 extends NikoHomeControlBridgeHandler {
48 private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler2.class);
50 private final Gson gson = new GsonBuilder().create();
52 NetworkAddressService networkAddressService;
54 public NikoHomeControlBridgeHandler2(Bridge nikoHomeControlBridge, NetworkAddressService networkAddressService,
55 TimeZoneProvider timeZoneProvider) {
56 super(nikoHomeControlBridge, timeZoneProvider);
57 this.networkAddressService = networkAddressService;
61 public void initialize() {
62 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,
73 "@text/offline.configuration-error.tokenEmpty");
77 Date now = new Date();
78 if (expiryDate.before(now)) {
79 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
80 "@text/offline.configuration-error.tokenExpired");
85 String addr = networkAddressService.getPrimaryIpv4HostAddress();
86 addr = (addr == null) ? "unknown" : addr.replace(".", "_");
87 String clientId = addr + "-" + thing.getUID().toString().replace(":", "_");
89 nhcComm = new NikoHomeControlCommunication2(this, clientId, scheduler);
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");
100 protected void updateProperties() {
101 Map<String, String> properties = new HashMap<>();
103 NikoHomeControlCommunication2 comm = (NikoHomeControlCommunication2) nhcComm;
105 Date expiry = getTokenExpiryDate();
106 if (expiry != null) {
107 properties.put("tokenExpiryDate", DateFormat.getDateInstance().format(expiry));
109 String nhcVersion = comm.getSystemInfo().getNhcVersion();
110 if (!nhcVersion.isEmpty()) {
111 properties.put("nhcVersion", nhcVersion);
113 String cocoImage = comm.getSystemInfo().getCocoImage();
114 if (!cocoImage.isEmpty()) {
115 properties.put("cocoImage", cocoImage);
117 String language = comm.getSystemInfo().getLanguage();
118 if (!language.isEmpty()) {
119 properties.put("language", language);
121 String currency = comm.getSystemInfo().getCurrency();
122 if (!currency.isEmpty()) {
123 properties.put("currency", currency);
125 String units = comm.getSystemInfo().getUnits();
126 if (!units.isEmpty()) {
127 properties.put("units", units);
129 String lastConfig = comm.getSystemInfo().getLastConfig();
130 if (!lastConfig.isEmpty()) {
131 properties.put("lastConfig", lastConfig);
133 String electricityTariff = comm.getSystemInfo().getElectricityTariff();
134 if (!electricityTariff.isEmpty()) {
135 properties.put("electricityTariff", electricityTariff);
137 String gasTariff = comm.getSystemInfo().getGasTariff();
138 if (!gasTariff.isEmpty()) {
139 properties.put("gasTariff", gasTariff);
141 String waterTariff = comm.getSystemInfo().getWaterTariff();
142 if (!waterTariff.isEmpty()) {
143 properties.put("waterTariff", waterTariff);
145 String timezone = comm.getTimeInfo().getTimezone();
146 if (!timezone.isEmpty()) {
147 properties.put("timezone", timezone);
149 String isDst = comm.getTimeInfo().getIsDst();
150 if (!isDst.isEmpty()) {
151 properties.put("isDST", isDst);
153 String services = comm.getServices();
154 if (!services.isEmpty()) {
155 properties.put("services", services);
158 thing.setProperties(properties);
163 public String getProfile() {
164 return getConfig().as(NikoHomeControlBridgeConfig2.class).profile;
168 public String getToken() {
169 String token = getConfig().as(NikoHomeControlBridgeConfig2.class).password;
170 if (token.isEmpty()) {
171 logger.debug("no JWT token set.");
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.
180 * @return Hobby API token expiry date, null if no valid token.
182 private @Nullable Date getTokenExpiryDate() {
183 NhcJwtToken2 jwtToken = null;
185 String token = getToken();
186 String[] tokenArray = token.split("\\.");
188 if (tokenArray.length == 3) {
189 String tokenPayload = new String(Base64.getDecoder().decode(tokenArray[1]));
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);
201 if (jwtToken != null) {
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);
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));
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));