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.protocol.nhc2;
15 import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.*;
17 import java.lang.reflect.Type;
18 import java.net.InetAddress;
19 import java.security.cert.CertificateException;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.NoSuchElementException;
23 import java.util.Objects;
24 import java.util.Optional;
25 import java.util.concurrent.CompletableFuture;
26 import java.util.concurrent.CopyOnWriteArrayList;
27 import java.util.concurrent.ExecutionException;
28 import java.util.concurrent.ScheduledExecutorService;
29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.TimeoutException;
31 import java.util.stream.Collectors;
32 import java.util.stream.IntStream;
34 import org.eclipse.jdt.annotation.NonNullByDefault;
35 import org.eclipse.jdt.annotation.Nullable;
36 import org.openhab.binding.nikohomecontrol.internal.protocol.*;
37 import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
38 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcDevice2.NhcProperty;
39 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcMessage2.NhcMessageParam;
40 import org.openhab.core.io.transport.mqtt.MqttConnectionObserver;
41 import org.openhab.core.io.transport.mqtt.MqttConnectionState;
42 import org.openhab.core.io.transport.mqtt.MqttException;
43 import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
47 import com.google.gson.FieldNamingPolicy;
48 import com.google.gson.Gson;
49 import com.google.gson.GsonBuilder;
50 import com.google.gson.JsonSyntaxException;
51 import com.google.gson.reflect.TypeToken;
54 * The {@link NikoHomeControlCommunication2} class is able to do the following tasks with Niko Home Control II
57 * <li>Start and stop MQTT connection with Niko Home Control II Connected Controller.
58 * <li>Read all setup and status information from the Niko Home Control Controller.
59 * <li>Execute Niko Home Control commands.
60 * <li>Listen for events from Niko Home Control.
63 * @author Mark Herwege - Initial Contribution
66 public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
67 implements MqttMessageSubscriber, MqttConnectionObserver {
69 private final Logger logger = LoggerFactory.getLogger(NikoHomeControlCommunication2.class);
71 private final NhcMqttConnection2 mqttConnection;
73 private final List<NhcService2> services = new CopyOnWriteArrayList<>();
75 private volatile String profile = "";
77 private volatile @Nullable NhcSystemInfo2 nhcSystemInfo;
78 private volatile @Nullable NhcTimeInfo2 nhcTimeInfo;
80 private volatile @Nullable CompletableFuture<Boolean> communicationStarted;
82 private ScheduledExecutorService scheduler;
84 private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
87 * Constructor for Niko Home Control communication object, manages communication with
88 * Niko Home Control II Connected Controller.
90 * @throws CertificateException when the SSL context for MQTT communication cannot be created
91 * @throws UnknownHostException when the IP address is not provided
94 public NikoHomeControlCommunication2(NhcControllerEvent handler, String clientId,
95 ScheduledExecutorService scheduler) throws CertificateException {
97 mqttConnection = new NhcMqttConnection2(clientId, this, this);
98 this.scheduler = scheduler;
102 public synchronized void startCommunication() {
103 communicationStarted = new CompletableFuture<>();
105 InetAddress addr = handler.getAddr();
107 logger.warn("Niko Home Control: IP address cannot be empty");
111 String addrString = addr.getHostAddress();
112 int port = handler.getPort();
113 logger.debug("Niko Home Control: initializing for mqtt connection to CoCo on {}:{}", addrString, port);
115 profile = handler.getProfile();
117 String token = handler.getToken();
118 if (token.isEmpty()) {
119 logger.warn("Niko Home Control: JWT token cannot be empty");
125 mqttConnection.startConnection(addrString, port, profile, token);
127 } catch (MqttException e) {
128 logger.warn("Niko Home Control: error in mqtt communication");
134 public synchronized void stopCommunication() {
135 CompletableFuture<Boolean> started = communicationStarted;
136 if (started != null) {
137 started.complete(false);
139 communicationStarted = null;
140 mqttConnection.stopConnection();
144 public boolean communicationActive() {
145 CompletableFuture<Boolean> started = communicationStarted;
146 if (started == null) {
150 // Wait until we received all devices info to confirm we are active.
151 return started.get(5000, TimeUnit.MILLISECONDS);
152 } catch (InterruptedException | ExecutionException | TimeoutException e) {
153 logger.debug("Niko Home Control: exception waiting for connection start");
159 * After setting up the communication with the Niko Home Control Connected Controller, send all initialization
163 private void initialize() throws MqttException {
164 NhcMessage2 message = new NhcMessage2();
166 message.method = "systeminfo.publish";
167 mqttConnection.connectionPublish(profile + "/system/cmd", gson.toJson(message));
169 message.method = "services.list";
170 mqttConnection.connectionPublish(profile + "/authentication/cmd", gson.toJson(message));
172 message.method = "devices.list";
173 mqttConnection.connectionPublish(profile + "/control/devices/cmd", gson.toJson(message));
175 message.method = "notifications.list";
176 mqttConnection.connectionPublish(profile + "/notification/cmd", gson.toJson(message));
179 private void connectionLost() {
180 logger.debug("Niko Home Control: connection lost");
182 handler.controllerOffline();
185 private void systemEvt(String response) {
186 Type messageType = new TypeToken<NhcMessage2>() {
188 List<NhcTimeInfo2> timeInfo = null;
189 List<NhcSystemInfo2> systemInfo = null;
191 NhcMessage2 message = gson.fromJson(response, messageType);
192 List<NhcMessageParam> messageParams = message.params;
193 if (messageParams != null) {
194 timeInfo = messageParams.stream().filter(p -> (p.timeInfo != null)).findFirst().get().timeInfo;
195 systemInfo = messageParams.stream().filter(p -> (p.systemInfo != null)).findFirst().get().systemInfo;
197 } catch (JsonSyntaxException e) {
198 logger.debug("Niko Home Control: unexpected json {}", response);
199 } catch (NoSuchElementException ignore) {
200 // Ignore if timeInfo not present in response, this should not happen in a timeInfo response
202 if (timeInfo != null) {
203 nhcTimeInfo = timeInfo.get(0);
205 if (systemInfo != null) {
206 nhcSystemInfo = systemInfo.get(0);
207 handler.updatePropertiesEvent();
211 private void systeminfoPublishRsp(String response) {
212 Type messageType = new TypeToken<NhcMessage2>() {
214 List<NhcSystemInfo2> systemInfo = null;
216 NhcMessage2 message = gson.fromJson(response, messageType);
217 List<NhcMessageParam> messageParams = message.params;
218 if (messageParams != null) {
219 systemInfo = messageParams.stream().filter(p -> (p.systemInfo != null)).findFirst().get().systemInfo;
221 } catch (JsonSyntaxException e) {
222 logger.debug("Niko Home Control: unexpected json {}", response);
223 } catch (NoSuchElementException ignore) {
224 // Ignore if systemInfo not present in response, this should not happen in a systemInfo response
226 if (systemInfo != null) {
227 nhcSystemInfo = systemInfo.get(0);
231 private void servicesListRsp(String response) {
232 Type messageType = new TypeToken<NhcMessage2>() {
234 List<NhcService2> serviceList = null;
236 NhcMessage2 message = gson.fromJson(response, messageType);
237 List<NhcMessageParam> messageParams = message.params;
238 if (messageParams != null) {
239 serviceList = messageParams.stream().filter(p -> (p.services != null)).findFirst().get().services;
241 } catch (JsonSyntaxException e) {
242 logger.debug("Niko Home Control: unexpected json {}", response);
243 } catch (NoSuchElementException ignore) {
244 // Ignore if services not present in response, this should not happen in a services response
247 if (serviceList != null) {
248 services.addAll(serviceList);
252 private void devicesListRsp(String response) {
253 Type messageType = new TypeToken<NhcMessage2>() {
255 List<NhcDevice2> deviceList = null;
257 NhcMessage2 message = gson.fromJson(response, messageType);
258 List<NhcMessageParam> messageParams = message.params;
259 if (messageParams != null) {
260 deviceList = messageParams.stream().filter(p -> (p.devices != null)).findFirst().get().devices;
262 } catch (JsonSyntaxException e) {
263 logger.debug("Niko Home Control: unexpected json {}", response);
264 } catch (NoSuchElementException ignore) {
265 // Ignore if devices not present in response, this should not happen in a devices response
267 if (deviceList == null) {
271 for (NhcDevice2 device : deviceList) {
276 // Once a devices list response is received, we know the communication is fully started.
277 logger.debug("Niko Home Control: Communication start complete.");
278 handler.controllerOnline();
279 CompletableFuture<Boolean> future = communicationStarted;
280 if (future != null) {
281 future.complete(true);
285 private void devicesEvt(String response) {
286 Type messageType = new TypeToken<NhcMessage2>() {
288 List<NhcDevice2> deviceList = null;
289 String method = null;
291 NhcMessage2 message = gson.fromJson(response, messageType);
292 method = message.method;
293 List<NhcMessageParam> messageParams = message.params;
294 if (messageParams != null) {
295 deviceList = messageParams.stream().filter(p -> (p.devices != null)).findFirst().get().devices;
297 } catch (JsonSyntaxException e) {
298 logger.debug("Niko Home Control: unexpected json {}", response);
299 } catch (NoSuchElementException ignore) {
300 // Ignore if devices not present in response, this should not happen in a devices event
302 if (deviceList == null) {
306 if ("devices.removed".equals(method)) {
307 deviceList.forEach(this::removeDevice);
309 } else if ("devices.added".equals(method)) {
310 deviceList.forEach(this::addDevice);
311 } else if ("devices.changed".equals(method)) {
312 deviceList.forEach(this::removeDevice);
313 deviceList.forEach(this::addDevice);
316 deviceList.forEach(this::updateState);
319 private void notificationEvt(String response) {
320 Type messageType = new TypeToken<NhcMessage2>() {
322 List<NhcNotification2> notificationList = null;
324 NhcMessage2 message = gson.fromJson(response, messageType);
325 List<NhcMessageParam> messageParams = message.params;
326 if (messageParams != null) {
327 notificationList = messageParams.stream().filter(p -> (p.notifications != null)).findFirst()
328 .get().notifications;
330 } catch (JsonSyntaxException e) {
331 logger.debug("Niko Home Control: unexpected json {}", response);
332 } catch (NoSuchElementException ignore) {
333 // Ignore if notifications not present in response, this should not happen in a notifications event
335 logger.debug("Niko Home Control: notifications {}", notificationList);
336 if (notificationList == null) {
340 for (NhcNotification2 notification : notificationList) {
341 if ("new".equals(notification.status)) {
342 String alarmText = notification.text;
343 switch (notification.type) {
345 handler.alarmEvent(alarmText);
348 handler.noticeEvent(alarmText);
351 logger.debug("Niko Home Control: unexpected message type {}", notification.type);
357 private void addDevice(NhcDevice2 device) {
358 String location = null;
359 if (device.parameters != null) {
360 location = device.parameters.stream().map(p -> p.locationName).filter(Objects::nonNull).findFirst()
364 if ("action".equals(device.type)) {
365 if (!actions.containsKey(device.uuid)) {
366 logger.debug("Niko Home Control: adding action device {}, {}", device.uuid, device.name);
368 ActionType actionType;
369 switch (device.model) {
376 case "overallcomfort":
378 actionType = ActionType.TRIGGER;
382 case "switched-generic":
384 actionType = ActionType.RELAY;
387 actionType = ActionType.DIMMER;
389 case "rolldownshutter":
391 case "venetianblind":
393 actionType = ActionType.ROLLERSHUTTER;
396 actionType = ActionType.GENERIC;
397 logger.debug("Niko Home Control: device type {} not recognised, default to GENERIC action",
401 NhcAction2 nhcAction = new NhcAction2(device.uuid, device.name, device.model, device.technology,
402 actionType, location, this);
403 actions.put(device.uuid, nhcAction);
405 } else if ("thermostat".equals(device.type)) {
406 if (!thermostats.containsKey(device.uuid)) {
407 logger.debug("Niko Home Control: adding thermostat device {}, {}", device.uuid, device.name);
409 NhcThermostat2 nhcThermostat = new NhcThermostat2(device.uuid, device.name, device.model,
410 device.technology, location, this);
411 thermostats.put(device.uuid, nhcThermostat);
413 } else if ("centralmeter".equals(device.type)) {
414 if (!energyMeters.containsKey(device.uuid)) {
415 logger.debug("Niko Home Control: adding centralmeter device {}, {}", device.uuid, device.name);
416 NhcEnergyMeter2 nhcEnergyMeter = new NhcEnergyMeter2(device.uuid, device.name, device.model,
417 device.technology, this, scheduler);
418 energyMeters.put(device.uuid, nhcEnergyMeter);
421 logger.debug("Niko Home Control: device type {} not supported for {}, {}", device.type, device.uuid,
426 private void removeDevice(NhcDevice2 device) {
427 if (actions.containsKey(device.uuid)) {
428 actions.get(device.uuid).actionRemoved();
429 actions.remove(device.uuid);
430 } else if (thermostats.containsKey(device.uuid)) {
431 thermostats.get(device.uuid).thermostatRemoved();
432 thermostats.remove(device.uuid);
433 } else if (energyMeters.containsKey(device.uuid)) {
434 energyMeters.get(device.uuid).energyMeterRemoved();
435 energyMeters.remove(device.uuid);
439 private void updateState(NhcDevice2 device) {
440 List<NhcProperty> deviceProperties = device.properties;
441 if (deviceProperties == null) {
442 logger.debug("Cannot Update state for {} as no properties defined in device message", device.uuid);
446 NhcAction action = actions.get(device.uuid);
447 NhcThermostat thermostat = thermostats.get(device.uuid);
448 NhcEnergyMeter energyMeter = energyMeters.get(device.uuid);
450 if (action != null) {
451 updateActionState((NhcAction2) action, deviceProperties);
452 } else if (thermostat != null) {
453 updateThermostatState((NhcThermostat2) thermostat, deviceProperties);
454 } else if (energyMeter != null) {
455 updateEnergyMeterState((NhcEnergyMeter2) energyMeter, deviceProperties);
459 private void updateActionState(NhcAction2 action, List<NhcProperty> deviceProperties) {
460 if (action.getType() == ActionType.ROLLERSHUTTER) {
461 updateRollershutterState(action, deviceProperties);
463 updateLightState(action, deviceProperties);
467 private void updateLightState(NhcAction2 action, List<NhcProperty> deviceProperties) {
468 Optional<NhcProperty> statusProperty = deviceProperties.stream().filter(p -> (p.status != null)).findFirst();
469 Optional<NhcProperty> dimmerProperty = deviceProperties.stream().filter(p -> (p.brightness != null))
471 Optional<NhcProperty> basicStateProperty = deviceProperties.stream().filter(p -> (p.basicState != null))
474 String booleanState = null;
475 if (statusProperty.isPresent()) {
476 booleanState = statusProperty.get().status;
477 } else if (basicStateProperty.isPresent()) {
478 booleanState = basicStateProperty.get().basicState;
481 if (booleanState != null) {
482 if (NHCON.equals(booleanState)) {
483 action.setBooleanState(true);
484 logger.debug("Niko Home Control: setting action {} internally to ON", action.getId());
485 } else if (NHCOFF.equals(booleanState)) {
486 action.setBooleanState(false);
487 logger.debug("Niko Home Control: setting action {} internally to OFF", action.getId());
491 if (dimmerProperty.isPresent()) {
492 String brightness = dimmerProperty.get().brightness;
493 if (brightness != null) {
494 action.setState(Integer.parseInt(brightness));
495 logger.debug("Niko Home Control: setting action {} internally to {}", action.getId(),
496 dimmerProperty.get().brightness);
501 private void updateRollershutterState(NhcAction2 action, List<NhcProperty> deviceProperties) {
502 deviceProperties.stream().map(p -> p.position).filter(Objects::nonNull).findFirst().ifPresent(position -> {
504 action.setState(Integer.parseInt(position));
505 logger.debug("Niko Home Control: setting action {} internally to {}", action.getId(), position);
506 } catch (NumberFormatException e) {
507 logger.trace("Niko Home Control: received empty rollershutter {} position info", action.getId());
512 private void updateThermostatState(NhcThermostat2 thermostat, List<NhcProperty> deviceProperties) {
513 Optional<Boolean> overruleActiveProperty = deviceProperties.stream().map(p -> p.overruleActive)
514 .filter(Objects::nonNull).map(t -> Boolean.parseBoolean(t)).findFirst();
515 Optional<Integer> overruleSetpointProperty = deviceProperties.stream().map(p -> p.overruleSetpoint)
516 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s) * 10) : null)
517 .filter(Objects::nonNull).findFirst();
518 Optional<Integer> overruleTimeProperty = deviceProperties.stream().map(p -> p.overruleTime)
519 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
520 .filter(Objects::nonNull).findFirst();
521 Optional<Integer> setpointTemperatureProperty = deviceProperties.stream().map(p -> p.setpointTemperature)
522 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s) * 10) : null)
523 .filter(Objects::nonNull).findFirst();
524 Optional<Boolean> ecoSaveProperty = deviceProperties.stream().map(p -> p.ecoSave)
525 .map(s -> s != null ? Boolean.parseBoolean(s) : null).filter(Objects::nonNull).findFirst();
526 Optional<Integer> ambientTemperatureProperty = deviceProperties.stream().map(p -> p.ambientTemperature)
527 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s) * 10) : null)
528 .filter(Objects::nonNull).findFirst();
529 Optional<@Nullable String> demandProperty = deviceProperties.stream().map(p -> p.demand)
530 .filter(Objects::nonNull).findFirst();
531 Optional<@Nullable String> operationModeProperty = deviceProperties.stream().map(p -> p.operationMode)
532 .filter(Objects::nonNull).findFirst();
534 String modeString = deviceProperties.stream().map(p -> p.program).filter(Objects::nonNull).findFirst()
536 int mode = IntStream.range(0, THERMOSTATMODES.length).filter(i -> THERMOSTATMODES[i].equals(modeString))
537 .findFirst().orElse(thermostat.getMode());
539 int measured = ambientTemperatureProperty.orElse(thermostat.getMeasured());
540 int setpoint = setpointTemperatureProperty.orElse(thermostat.getSetpoint());
542 int overrule = thermostat.getOverrule();
543 int overruletime = thermostat.getRemainingOverruletime();
544 if (overruleActiveProperty.orElse(false)) {
545 overrule = overruleSetpointProperty.orElse(0);
546 overruletime = overruleTimeProperty.orElse(0);
549 int ecosave = thermostat.getEcosave();
550 if (ecoSaveProperty.orElse(false)) {
554 int demand = thermostat.getDemand();
555 String demandString = demandProperty.orElse(operationModeProperty.orElse(""));
556 demandString = demandString == null ? "" : demandString;
557 switch (demandString) {
570 "Niko Home Control: setting thermostat {} with measured {}, setpoint {}, mode {}, overrule {}, overruletime {}, ecosave {}, demand {}",
571 thermostat.getId(), measured, setpoint, mode, overrule, overruletime, ecosave, demand);
572 thermostat.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
575 private void updateEnergyMeterState(NhcEnergyMeter2 energyMeter, List<NhcProperty> deviceProperties) {
576 deviceProperties.stream().map(p -> p.electricalPower).filter(Objects::nonNull).findFirst()
577 .ifPresent(electricalPower -> {
579 energyMeter.setPower(Integer.parseInt(electricalPower));
580 logger.trace("Niko Home Control: setting energy meter {} power to {}", energyMeter.getId(),
582 } catch (NumberFormatException e) {
583 energyMeter.setPower(null);
584 logger.trace("Niko Home Control: received empty energy meter {} power reading",
585 energyMeter.getId());
591 public void executeAction(String actionId, String value) {
592 NhcMessage2 message = new NhcMessage2();
594 message.method = "devices.control";
595 ArrayList<NhcMessageParam> params = new ArrayList<>();
596 NhcMessageParam param = new NhcMessageParam();
598 message.params = params;
599 ArrayList<NhcDevice2> devices = new ArrayList<>();
600 NhcDevice2 device = new NhcDevice2();
602 param.devices = devices;
603 device.uuid = actionId;
604 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
605 NhcProperty property = new NhcProperty();
606 deviceProperties.add(property);
607 device.properties = deviceProperties;
609 NhcAction2 action = (NhcAction2) actions.get(actionId);
611 switch (action.getType()) {
614 property.basicState = NHCTRIGGERED;
617 property.status = value;
620 if (NHCON.equals(value)) {
621 action.setBooleanState(true); // this will trigger sending the stored brightness value event out
622 property.status = value;
623 } else if (NHCOFF.equals(value)) {
624 property.status = value;
626 // If the light is off, turn the light on before sending the brightness value, needs to happen
627 // in 2 separate messages.
628 if (!action.booleanState()) {
629 executeAction(actionId, NHCON);
631 property.brightness = value;
635 if (NHCSTOP.equals(value)) {
636 property.action = value;
637 } else if (NHCUP.equals(value)) {
638 property.position = "100";
639 } else if (NHCDOWN.equals(value)) {
640 property.position = "0";
642 int position = 100 - Integer.parseInt(value);
643 property.position = String.valueOf(position);
648 String topic = profile + "/control/devices/cmd";
649 String gsonMessage = gson.toJson(message);
650 sendDeviceMessage(topic, gsonMessage);
654 public void executeThermostat(String thermostatId, String mode) {
655 NhcMessage2 message = new NhcMessage2();
657 message.method = "devices.control";
658 ArrayList<NhcMessageParam> params = new ArrayList<>();
659 NhcMessageParam param = new NhcMessageParam();
661 message.params = params;
662 ArrayList<NhcDevice2> devices = new ArrayList<>();
663 NhcDevice2 device = new NhcDevice2();
665 param.devices = devices;
666 device.uuid = thermostatId;
667 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
669 NhcProperty overruleActiveProp = new NhcProperty();
670 deviceProperties.add(overruleActiveProp);
671 overruleActiveProp.overruleActive = "False";
673 NhcProperty program = new NhcProperty();
674 deviceProperties.add(program);
675 program.program = mode;
677 device.properties = deviceProperties;
679 String topic = profile + "/control/devices/cmd";
680 String gsonMessage = gson.toJson(message);
681 sendDeviceMessage(topic, gsonMessage);
685 public void executeThermostat(String thermostatId, int overruleTemp, int overruleTime) {
686 NhcMessage2 message = new NhcMessage2();
688 message.method = "devices.control";
689 ArrayList<NhcMessageParam> params = new ArrayList<>();
690 NhcMessageParam param = new NhcMessageParam();
692 message.params = params;
693 ArrayList<NhcDevice2> devices = new ArrayList<>();
694 NhcDevice2 device = new NhcDevice2();
696 param.devices = devices;
697 device.uuid = thermostatId;
698 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
700 if (overruleTime > 0) {
701 NhcProperty overruleActiveProp = new NhcProperty();
702 overruleActiveProp.overruleActive = "True";
703 deviceProperties.add(overruleActiveProp);
705 NhcProperty overruleSetpointProp = new NhcProperty();
706 overruleSetpointProp.overruleSetpoint = String.valueOf(overruleTemp / 10.0);
707 deviceProperties.add(overruleSetpointProp);
709 NhcProperty overruleTimeProp = new NhcProperty();
710 overruleTimeProp.overruleTime = String.valueOf(overruleTime);
711 deviceProperties.add(overruleTimeProp);
713 NhcProperty overruleActiveProp = new NhcProperty();
714 overruleActiveProp.overruleActive = "False";
715 deviceProperties.add(overruleActiveProp);
717 device.properties = deviceProperties;
719 String topic = profile + "/control/devices/cmd";
720 String gsonMessage = gson.toJson(message);
721 sendDeviceMessage(topic, gsonMessage);
725 public void startEnergyMeter(String energyMeterId) {
726 NhcMessage2 message = new NhcMessage2();
728 message.method = "devices.control";
729 ArrayList<NhcMessageParam> params = new ArrayList<>();
730 NhcMessageParam param = new NhcMessageParam();
732 message.params = params;
733 ArrayList<NhcDevice2> devices = new ArrayList<>();
734 NhcDevice2 device = new NhcDevice2();
736 param.devices = devices;
737 device.uuid = energyMeterId;
738 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
740 NhcProperty reportInstantUsageProp = new NhcProperty();
741 deviceProperties.add(reportInstantUsageProp);
742 reportInstantUsageProp.reportInstantUsage = "True";
743 device.properties = deviceProperties;
745 String topic = profile + "/control/devices/cmd";
746 String gsonMessage = gson.toJson(message);
748 ((NhcEnergyMeter2) energyMeters.get(energyMeterId)).startEnergyMeter(topic, gsonMessage);
752 public void stopEnergyMeter(String energyMeterId) {
753 ((NhcEnergyMeter2) energyMeters.get(energyMeterId)).stopEnergyMeter();
757 * Method called from the {@link NhcEnergyMeter2} object to send message to Niko Home Control.
762 public void executeEnergyMeter(String topic, String gsonMessage) {
763 sendDeviceMessage(topic, gsonMessage);
766 private void sendDeviceMessage(String topic, String gsonMessage) {
768 mqttConnection.connectionPublish(topic, gsonMessage);
770 } catch (MqttException e) {
771 logger.warn("Niko Home Control: sending command failed, trying to restart communication");
772 restartCommunication();
773 // retry sending after restart
775 if (communicationActive()) {
776 mqttConnection.connectionPublish(topic, gsonMessage);
778 logger.warn("Niko Home Control: failed to restart communication");
781 } catch (MqttException e1) {
782 logger.warn("Niko Home Control: error resending device command");
789 public void processMessage(String topic, byte[] payload) {
790 String message = new String(payload);
791 if ((profile + "/system/evt").equals(topic)) {
793 } else if ((profile + "/system/rsp").equals(topic)) {
794 logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
795 systeminfoPublishRsp(message);
796 } else if ((profile + "/notification/evt").equals(topic)) {
797 logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
798 notificationEvt(message);
799 } else if ((profile + "/control/devices/evt").equals(topic)) {
800 logger.trace("Niko Home Control: received topic {}, payload {}", topic, message);
802 } else if ((profile + "/control/devices/rsp").equals(topic)) {
803 logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
804 devicesListRsp(message);
805 } else if ((profile + "/authentication/rsp").equals(topic)) {
806 logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
807 servicesListRsp(message);
808 } else if ((profile + "/control/devices.error").equals(topic)) {
809 logger.warn("Niko Home Control: received error {}", message);
811 logger.trace("Niko Home Control: not acted on received message topic {}, payload {}", topic, message);
816 * @return system info retrieved from Connected Controller
818 public NhcSystemInfo2 getSystemInfo() {
819 NhcSystemInfo2 systemInfo = nhcSystemInfo;
820 if (systemInfo == null) {
821 systemInfo = new NhcSystemInfo2();
827 * @return time info retrieved from Connected Controller
829 public NhcTimeInfo2 getTimeInfo() {
830 NhcTimeInfo2 timeInfo = nhcTimeInfo;
831 if (timeInfo == null) {
832 timeInfo = new NhcTimeInfo2();
838 * @return comma separated list of services retrieved from Connected Controller
840 public String getServices() {
841 return services.stream().map(NhcService2::name).collect(Collectors.joining(", "));
845 public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) {
847 logger.debug("Connection state: {}", state, error);
848 restartCommunication();
849 if (!communicationActive()) {
850 logger.warn("Niko Home Control: failed to restart communication");
854 logger.trace("Connection state: {}", state);