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.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.NhcAction;
37 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcControllerEvent;
38 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcEnergyMeter;
39 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
40 import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
41 import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
42 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcDevice2.NhcParameter;
43 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcDevice2.NhcProperty;
44 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcMessage2.NhcMessageParam;
45 import org.openhab.core.io.transport.mqtt.MqttConnectionObserver;
46 import org.openhab.core.io.transport.mqtt.MqttConnectionState;
47 import org.openhab.core.io.transport.mqtt.MqttException;
48 import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
52 import com.google.gson.FieldNamingPolicy;
53 import com.google.gson.Gson;
54 import com.google.gson.GsonBuilder;
55 import com.google.gson.JsonSyntaxException;
56 import com.google.gson.reflect.TypeToken;
59 * The {@link NikoHomeControlCommunication2} class is able to do the following tasks with Niko Home Control II
62 * <li>Start and stop MQTT connection with Niko Home Control II Connected Controller.
63 * <li>Read all setup and status information from the Niko Home Control Controller.
64 * <li>Execute Niko Home Control commands.
65 * <li>Listen for events from Niko Home Control.
68 * @author Mark Herwege - Initial Contribution
71 public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
72 implements MqttMessageSubscriber, MqttConnectionObserver {
74 private final Logger logger = LoggerFactory.getLogger(NikoHomeControlCommunication2.class);
76 private final NhcMqttConnection2 mqttConnection;
78 private final List<NhcService2> services = new CopyOnWriteArrayList<>();
80 private volatile String profile = "";
82 private volatile @Nullable NhcSystemInfo2 nhcSystemInfo;
83 private volatile @Nullable NhcTimeInfo2 nhcTimeInfo;
85 private volatile @Nullable CompletableFuture<Boolean> communicationStarted;
87 private ScheduledExecutorService scheduler;
89 private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
92 * Constructor for Niko Home Control communication object, manages communication with
93 * Niko Home Control II Connected Controller.
95 * @throws CertificateException when the SSL context for MQTT communication cannot be created
96 * @throws UnknownHostException when the IP address is not provided
99 public NikoHomeControlCommunication2(NhcControllerEvent handler, String clientId,
100 ScheduledExecutorService scheduler) throws CertificateException {
102 mqttConnection = new NhcMqttConnection2(clientId, this, this);
103 this.scheduler = scheduler;
107 public synchronized void startCommunication() {
108 communicationStarted = new CompletableFuture<>();
110 InetAddress addr = handler.getAddr();
112 logger.warn("IP address cannot be empty");
116 String addrString = addr.getHostAddress();
117 int port = handler.getPort();
118 logger.debug("initializing for mqtt connection to CoCo on {}:{}", addrString, port);
120 profile = handler.getProfile();
122 String token = handler.getToken();
123 if (token.isEmpty()) {
124 logger.warn("JWT token cannot be empty");
130 mqttConnection.startConnection(addrString, port, profile, token);
132 } catch (MqttException e) {
133 logger.debug("error in mqtt communication");
139 public synchronized void stopCommunication() {
140 CompletableFuture<Boolean> started = communicationStarted;
141 if (started != null) {
142 started.complete(false);
144 communicationStarted = null;
145 mqttConnection.stopConnection();
149 public boolean communicationActive() {
150 CompletableFuture<Boolean> started = communicationStarted;
151 if (started == null) {
155 // Wait until we received all devices info to confirm we are active.
156 return started.get(5000, TimeUnit.MILLISECONDS);
157 } catch (InterruptedException | ExecutionException | TimeoutException e) {
158 logger.debug("exception waiting for connection start");
164 * After setting up the communication with the Niko Home Control Connected Controller, send all initialization
168 private void initialize() throws MqttException {
169 NhcMessage2 message = new NhcMessage2();
171 message.method = "systeminfo.publish";
172 mqttConnection.connectionPublish(profile + "/system/cmd", gson.toJson(message));
174 message.method = "services.list";
175 mqttConnection.connectionPublish(profile + "/authentication/cmd", gson.toJson(message));
177 message.method = "devices.list";
178 mqttConnection.connectionPublish(profile + "/control/devices/cmd", gson.toJson(message));
180 message.method = "notifications.list";
181 mqttConnection.connectionPublish(profile + "/notification/cmd", gson.toJson(message));
184 private void connectionLost(String message) {
185 logger.debug("connection lost");
187 handler.controllerOffline(message);
190 private void systemEvt(String response) {
191 Type messageType = new TypeToken<NhcMessage2>() {
193 List<NhcTimeInfo2> timeInfo = null;
194 List<NhcSystemInfo2> systemInfo = null;
196 NhcMessage2 message = gson.fromJson(response, messageType);
197 List<NhcMessageParam> messageParams = (message != null) ? message.params : null;
198 if (messageParams != null) {
199 timeInfo = messageParams.stream().filter(p -> (p.timeInfo != null)).findFirst().get().timeInfo;
200 systemInfo = messageParams.stream().filter(p -> (p.systemInfo != null)).findFirst().get().systemInfo;
202 } catch (JsonSyntaxException e) {
203 logger.debug("unexpected json {}", response);
204 } catch (NoSuchElementException ignore) {
205 // Ignore if timeInfo not present in response, this should not happen in a timeInfo response
207 if (timeInfo != null) {
208 nhcTimeInfo = timeInfo.get(0);
210 if (systemInfo != null) {
211 nhcSystemInfo = systemInfo.get(0);
212 handler.updatePropertiesEvent();
216 private void systeminfoPublishRsp(String response) {
217 Type messageType = new TypeToken<NhcMessage2>() {
219 List<NhcSystemInfo2> systemInfo = null;
221 NhcMessage2 message = gson.fromJson(response, messageType);
222 List<NhcMessageParam> messageParams = (message != null) ? message.params : null;
223 if (messageParams != null) {
224 systemInfo = messageParams.stream().filter(p -> (p.systemInfo != null)).findFirst().get().systemInfo;
226 } catch (JsonSyntaxException e) {
227 logger.debug("unexpected json {}", response);
228 } catch (NoSuchElementException ignore) {
229 // Ignore if systemInfo not present in response, this should not happen in a systemInfo response
231 if (systemInfo != null) {
232 nhcSystemInfo = systemInfo.get(0);
236 private void servicesListRsp(String response) {
237 Type messageType = new TypeToken<NhcMessage2>() {
239 List<NhcService2> serviceList = null;
241 NhcMessage2 message = gson.fromJson(response, messageType);
242 List<NhcMessageParam> messageParams = (message != null) ? message.params : null;
243 if (messageParams != null) {
244 serviceList = messageParams.stream().filter(p -> (p.services != null)).findFirst().get().services;
246 } catch (JsonSyntaxException e) {
247 logger.debug("unexpected json {}", response);
248 } catch (NoSuchElementException ignore) {
249 // Ignore if services not present in response, this should not happen in a services response
252 if (serviceList != null) {
253 services.addAll(serviceList);
257 private void devicesListRsp(String response) {
258 Type messageType = new TypeToken<NhcMessage2>() {
260 List<NhcDevice2> deviceList = null;
262 NhcMessage2 message = gson.fromJson(response, messageType);
263 List<NhcMessageParam> messageParams = (message != null) ? message.params : null;
264 if (messageParams != null) {
265 deviceList = messageParams.stream().filter(p -> (p.devices != null)).findFirst().get().devices;
267 } catch (JsonSyntaxException e) {
268 logger.debug("unexpected json {}", response);
269 } catch (NoSuchElementException ignore) {
270 // Ignore if devices not present in response, this should not happen in a devices response
272 if (deviceList == null) {
276 for (NhcDevice2 device : deviceList) {
281 // Once a devices list response is received, we know the communication is fully started.
282 logger.debug("Communication start complete.");
283 handler.controllerOnline();
284 CompletableFuture<Boolean> future = communicationStarted;
285 if (future != null) {
286 future.complete(true);
290 private void devicesEvt(String response) {
291 Type messageType = new TypeToken<NhcMessage2>() {
293 List<NhcDevice2> deviceList = null;
294 String method = null;
296 NhcMessage2 message = gson.fromJson(response, messageType);
297 method = (message != null) ? message.method : null;
298 List<NhcMessageParam> messageParams = (message != null) ? message.params : null;
299 if (messageParams != null) {
300 deviceList = messageParams.stream().filter(p -> (p.devices != null)).findFirst().get().devices;
302 } catch (JsonSyntaxException e) {
303 logger.debug("unexpected json {}", response);
304 } catch (NoSuchElementException ignore) {
305 // Ignore if devices not present in response, this should not happen in a devices event
307 if (deviceList == null) {
311 if ("devices.removed".equals(method)) {
312 deviceList.forEach(this::removeDevice);
314 } else if ("devices.added".equals(method)) {
315 deviceList.forEach(this::addDevice);
318 deviceList.forEach(this::updateState);
321 private void notificationEvt(String response) {
322 Type messageType = new TypeToken<NhcMessage2>() {
324 List<NhcNotification2> notificationList = null;
326 NhcMessage2 message = gson.fromJson(response, messageType);
327 List<NhcMessageParam> messageParams = (message != null) ? message.params : null;
328 if (messageParams != null) {
329 notificationList = messageParams.stream().filter(p -> (p.notifications != null)).findFirst()
330 .get().notifications;
332 } catch (JsonSyntaxException e) {
333 logger.debug("unexpected json {}", response);
334 } catch (NoSuchElementException ignore) {
335 // Ignore if notifications not present in response, this should not happen in a notifications event
337 logger.debug("notifications {}", notificationList);
338 if (notificationList == null) {
342 for (NhcNotification2 notification : notificationList) {
343 if ("new".equals(notification.status)) {
344 String alarmText = notification.text;
345 switch (notification.type) {
347 handler.alarmEvent(alarmText);
350 handler.noticeEvent(alarmText);
353 logger.debug("unexpected message type {}", notification.type);
359 private void addDevice(NhcDevice2 device) {
360 String location = null;
361 List<NhcParameter> parameters = device.parameters;
362 if (parameters != null) {
363 location = parameters.stream().map(p -> p.locationName).filter(Objects::nonNull).findFirst().orElse(null);
366 if ("action".equals(device.type) || "virtual".equals(device.type)) {
367 if (!actions.containsKey(device.uuid)) {
368 logger.debug("adding action device {}, {}", device.uuid, device.name);
370 ActionType actionType;
371 switch (device.model) {
378 case "overallcomfort":
380 actionType = ActionType.TRIGGER;
384 case "switched-generic":
387 actionType = ActionType.RELAY;
390 actionType = ActionType.DIMMER;
392 case "rolldownshutter":
394 case "venetianblind":
396 actionType = ActionType.ROLLERSHUTTER;
399 actionType = ActionType.GENERIC;
400 logger.debug("device model {} not recognised, default to GENERIC action", device.model);
403 NhcAction2 nhcAction = new NhcAction2(device.uuid, device.name, device.model, device.technology,
404 actionType, location, this);
405 actions.put(device.uuid, nhcAction);
407 } else if ("thermostat".equals(device.type)) {
408 if (!thermostats.containsKey(device.uuid)) {
409 logger.debug("adding thermostat device {}, {}", device.uuid, device.name);
411 NhcThermostat2 nhcThermostat = new NhcThermostat2(device.uuid, device.name, device.model,
412 device.technology, location, this);
413 thermostats.put(device.uuid, nhcThermostat);
415 } else if ("centralmeter".equals(device.type)) {
416 if (!energyMeters.containsKey(device.uuid)) {
417 logger.debug("adding centralmeter device {}, {}", device.uuid, device.name);
418 NhcEnergyMeter2 nhcEnergyMeter = new NhcEnergyMeter2(device.uuid, device.name, device.model,
419 device.technology, this, scheduler);
420 energyMeters.put(device.uuid, nhcEnergyMeter);
423 logger.debug("device type {} not supported for {}, {}", device.type, device.uuid, device.name);
427 private void removeDevice(NhcDevice2 device) {
428 NhcAction action = actions.get(device.uuid);
429 NhcThermostat thermostat = thermostats.get(device.uuid);
430 NhcEnergyMeter energyMeter = energyMeters.get(device.uuid);
431 if (action != null) {
432 action.actionRemoved();
433 actions.remove(device.uuid);
434 } else if (thermostat != null) {
435 thermostat.thermostatRemoved();
436 thermostats.remove(device.uuid);
437 } else if (energyMeter != null) {
438 energyMeter.energyMeterRemoved();
439 energyMeters.remove(device.uuid);
443 private void updateState(NhcDevice2 device) {
444 List<NhcProperty> deviceProperties = device.properties;
445 if (deviceProperties == null) {
446 logger.debug("Cannot Update state for {} as no properties defined in device message", device.uuid);
450 NhcAction action = actions.get(device.uuid);
451 NhcThermostat thermostat = thermostats.get(device.uuid);
452 NhcEnergyMeter energyMeter = energyMeters.get(device.uuid);
454 if (action != null) {
455 updateActionState((NhcAction2) action, deviceProperties);
456 } else if (thermostat != null) {
457 updateThermostatState((NhcThermostat2) thermostat, deviceProperties);
458 } else if (energyMeter != null) {
459 updateEnergyMeterState((NhcEnergyMeter2) energyMeter, deviceProperties);
463 private void updateActionState(NhcAction2 action, List<NhcProperty> deviceProperties) {
464 if (action.getType() == ActionType.ROLLERSHUTTER) {
465 updateRollershutterState(action, deviceProperties);
467 updateLightState(action, deviceProperties);
471 private void updateLightState(NhcAction2 action, List<NhcProperty> deviceProperties) {
472 Optional<NhcProperty> statusProperty = deviceProperties.stream().filter(p -> (p.status != null)).findFirst();
473 Optional<NhcProperty> dimmerProperty = deviceProperties.stream().filter(p -> (p.brightness != null))
475 Optional<NhcProperty> basicStateProperty = deviceProperties.stream().filter(p -> (p.basicState != null))
478 String booleanState = null;
479 if (statusProperty.isPresent()) {
480 booleanState = statusProperty.get().status;
481 } else if (basicStateProperty.isPresent()) {
482 booleanState = basicStateProperty.get().basicState;
485 if (NHCOFF.equals(booleanState) || NHCFALSE.equals(booleanState)) {
486 action.setBooleanState(false);
487 logger.debug("setting action {} internally to OFF", action.getId());
490 if (dimmerProperty.isPresent()) {
491 String brightness = dimmerProperty.get().brightness;
492 if (brightness != null) {
494 action.setState(Integer.parseInt(brightness));
495 logger.debug("setting action {} internally to {}", action.getId(), dimmerProperty.get().brightness);
496 } catch (NumberFormatException e) {
497 logger.debug("received invalid brightness value {} for dimmer {}", brightness, action.getId());
502 if (NHCON.equals(booleanState) || NHCTRUE.equals(booleanState)) {
503 action.setBooleanState(true);
504 logger.debug("setting action {} internally to ON", action.getId());
508 private void updateRollershutterState(NhcAction2 action, List<NhcProperty> deviceProperties) {
509 deviceProperties.stream().map(p -> p.position).filter(Objects::nonNull).findFirst().ifPresent(position -> {
511 action.setState(Integer.parseInt(position));
512 logger.debug("setting action {} internally to {}", action.getId(), position);
513 } catch (NumberFormatException e) {
514 logger.trace("received empty or invalid rollershutter {} position info {}", action.getId(), position);
519 private void updateThermostatState(NhcThermostat2 thermostat, List<NhcProperty> deviceProperties) {
520 Optional<Boolean> overruleActiveProperty = deviceProperties.stream().map(p -> p.overruleActive)
521 .filter(Objects::nonNull).map(t -> Boolean.parseBoolean(t)).findFirst();
522 Optional<Integer> overruleSetpointProperty = deviceProperties.stream().map(p -> p.overruleSetpoint)
523 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s) * 10) : null)
524 .filter(Objects::nonNull).findFirst();
525 Optional<Integer> overruleTimeProperty = deviceProperties.stream().map(p -> p.overruleTime)
526 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
527 .filter(Objects::nonNull).findFirst();
528 Optional<Integer> setpointTemperatureProperty = deviceProperties.stream().map(p -> p.setpointTemperature)
529 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s) * 10) : null)
530 .filter(Objects::nonNull).findFirst();
531 Optional<Boolean> ecoSaveProperty = deviceProperties.stream().map(p -> p.ecoSave)
532 .map(s -> s != null ? Boolean.parseBoolean(s) : null).filter(Objects::nonNull).findFirst();
533 Optional<Integer> ambientTemperatureProperty = deviceProperties.stream().map(p -> p.ambientTemperature)
534 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s) * 10) : null)
535 .filter(Objects::nonNull).findFirst();
536 Optional<@Nullable String> demandProperty = deviceProperties.stream().map(p -> p.demand)
537 .filter(Objects::nonNull).findFirst();
538 Optional<@Nullable String> operationModeProperty = deviceProperties.stream().map(p -> p.operationMode)
539 .filter(Objects::nonNull).findFirst();
541 String modeString = deviceProperties.stream().map(p -> p.program).filter(Objects::nonNull).findFirst()
543 int mode = IntStream.range(0, THERMOSTATMODES.length).filter(i -> THERMOSTATMODES[i].equals(modeString))
544 .findFirst().orElse(thermostat.getMode());
546 int measured = ambientTemperatureProperty.orElse(thermostat.getMeasured());
547 int setpoint = setpointTemperatureProperty.orElse(thermostat.getSetpoint());
549 int overrule = thermostat.getOverrule();
550 int overruletime = thermostat.getRemainingOverruletime();
551 if (overruleActiveProperty.orElse(false)) {
552 overrule = overruleSetpointProperty.orElse(0);
553 overruletime = overruleTimeProperty.orElse(0);
556 int ecosave = thermostat.getEcosave();
557 if (ecoSaveProperty.orElse(false)) {
561 int demand = thermostat.getDemand();
562 String demandString = demandProperty.orElse(operationModeProperty.orElse(""));
563 demandString = demandString == null ? "" : demandString;
564 switch (demandString) {
577 "Niko Home Control: setting thermostat {} with measured {}, setpoint {}, mode {}, overrule {}, overruletime {}, ecosave {}, demand {}",
578 thermostat.getId(), measured, setpoint, mode, overrule, overruletime, ecosave, demand);
579 thermostat.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
582 private void updateEnergyMeterState(NhcEnergyMeter2 energyMeter, List<NhcProperty> deviceProperties) {
583 deviceProperties.stream().map(p -> p.electricalPower).filter(Objects::nonNull).findFirst()
584 .ifPresent(electricalPower -> {
586 // Sometimes API sends a fractional part, although API should only send whole units in W,
587 // therefore drop fractional part
588 energyMeter.setPower((int) Double.parseDouble(electricalPower));
589 logger.trace("setting energy meter {} power to {}", energyMeter.getId(), electricalPower);
590 } catch (NumberFormatException e) {
591 energyMeter.setPower(null);
592 logger.trace("received empty energy meter {} power reading", energyMeter.getId());
598 public void executeAction(String actionId, String value) {
599 NhcMessage2 message = new NhcMessage2();
601 message.method = "devices.control";
602 ArrayList<NhcMessageParam> params = new ArrayList<>();
603 NhcMessageParam param = new NhcMessageParam();
605 message.params = params;
606 ArrayList<NhcDevice2> devices = new ArrayList<>();
607 NhcDevice2 device = new NhcDevice2();
609 param.devices = devices;
610 device.uuid = actionId;
611 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
612 NhcProperty property = new NhcProperty();
613 deviceProperties.add(property);
614 device.properties = deviceProperties;
616 NhcAction2 action = (NhcAction2) actions.get(actionId);
617 if (action == null) {
621 switch (action.getType()) {
624 property.basicState = NHCTRIGGERED;
627 property.status = value;
630 if (NHCON.equals(value)) {
631 action.setBooleanState(true); // this will trigger sending the stored brightness value event out
632 property.status = value;
633 } else if (NHCOFF.equals(value)) {
634 property.status = value;
637 action.setState(Integer.parseInt(value)); // set cached state to new brightness value to avoid
638 // switching on with old brightness value before
641 } catch (NumberFormatException e) {
642 logger.debug("internal error, trying to set invalid brightness value {} for dimmer {}", value,
647 // If the light is off, turn the light on before sending the brightness value, needs to happen
648 // in 2 separate messages.
649 if (!action.booleanState()) {
650 executeAction(actionId, NHCON);
652 property.brightness = value;
656 if (NHCSTOP.equals(value)) {
657 property.action = value;
658 } else if (NHCUP.equals(value)) {
659 property.position = "100";
660 } else if (NHCDOWN.equals(value)) {
661 property.position = "0";
663 property.position = value;
668 String topic = profile + "/control/devices/cmd";
669 String gsonMessage = gson.toJson(message);
670 sendDeviceMessage(topic, gsonMessage);
674 public void executeThermostat(String thermostatId, String mode) {
675 NhcMessage2 message = new NhcMessage2();
677 message.method = "devices.control";
678 ArrayList<NhcMessageParam> params = new ArrayList<>();
679 NhcMessageParam param = new NhcMessageParam();
681 message.params = params;
682 ArrayList<NhcDevice2> devices = new ArrayList<>();
683 NhcDevice2 device = new NhcDevice2();
685 param.devices = devices;
686 device.uuid = thermostatId;
687 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
689 NhcProperty overruleActiveProp = new NhcProperty();
690 deviceProperties.add(overruleActiveProp);
691 overruleActiveProp.overruleActive = "False";
693 NhcProperty program = new NhcProperty();
694 deviceProperties.add(program);
695 program.program = mode;
697 device.properties = deviceProperties;
699 String topic = profile + "/control/devices/cmd";
700 String gsonMessage = gson.toJson(message);
701 sendDeviceMessage(topic, gsonMessage);
705 public void executeThermostat(String thermostatId, int overruleTemp, int overruleTime) {
706 NhcMessage2 message = new NhcMessage2();
708 message.method = "devices.control";
709 ArrayList<NhcMessageParam> params = new ArrayList<>();
710 NhcMessageParam param = new NhcMessageParam();
712 message.params = params;
713 ArrayList<NhcDevice2> devices = new ArrayList<>();
714 NhcDevice2 device = new NhcDevice2();
716 param.devices = devices;
717 device.uuid = thermostatId;
718 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
720 if (overruleTime > 0) {
721 NhcProperty overruleActiveProp = new NhcProperty();
722 overruleActiveProp.overruleActive = "True";
723 deviceProperties.add(overruleActiveProp);
725 NhcProperty overruleSetpointProp = new NhcProperty();
726 overruleSetpointProp.overruleSetpoint = String.valueOf(overruleTemp / 10.0);
727 deviceProperties.add(overruleSetpointProp);
729 NhcProperty overruleTimeProp = new NhcProperty();
730 overruleTimeProp.overruleTime = String.valueOf(overruleTime);
731 deviceProperties.add(overruleTimeProp);
733 NhcProperty overruleActiveProp = new NhcProperty();
734 overruleActiveProp.overruleActive = "False";
735 deviceProperties.add(overruleActiveProp);
737 device.properties = deviceProperties;
739 String topic = profile + "/control/devices/cmd";
740 String gsonMessage = gson.toJson(message);
741 sendDeviceMessage(topic, gsonMessage);
745 public void startEnergyMeter(String energyMeterId) {
746 NhcMessage2 message = new NhcMessage2();
748 message.method = "devices.control";
749 ArrayList<NhcMessageParam> params = new ArrayList<>();
750 NhcMessageParam param = new NhcMessageParam();
752 message.params = params;
753 ArrayList<NhcDevice2> devices = new ArrayList<>();
754 NhcDevice2 device = new NhcDevice2();
756 param.devices = devices;
757 device.uuid = energyMeterId;
758 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
760 NhcProperty reportInstantUsageProp = new NhcProperty();
761 deviceProperties.add(reportInstantUsageProp);
762 reportInstantUsageProp.reportInstantUsage = "True";
763 device.properties = deviceProperties;
765 String topic = profile + "/control/devices/cmd";
766 String gsonMessage = gson.toJson(message);
768 NhcEnergyMeter2 energyMeter = (NhcEnergyMeter2) energyMeters.get(energyMeterId);
769 if (energyMeter != null) {
770 energyMeter.startEnergyMeter(topic, gsonMessage);
775 public void stopEnergyMeter(String energyMeterId) {
776 NhcEnergyMeter2 energyMeter = (NhcEnergyMeter2) energyMeters.get(energyMeterId);
777 if (energyMeter != null) {
778 energyMeter.stopEnergyMeter();
783 * Method called from the {@link NhcEnergyMeter2} object to send message to Niko Home Control.
788 public void executeEnergyMeter(String topic, String gsonMessage) {
789 sendDeviceMessage(topic, gsonMessage);
792 private void sendDeviceMessage(String topic, String gsonMessage) {
794 mqttConnection.connectionPublish(topic, gsonMessage);
796 } catch (MqttException e) {
797 String message = e.getLocalizedMessage();
799 logger.debug("sending command failed, trying to restart communication");
800 restartCommunication();
801 // retry sending after restart
803 if (communicationActive()) {
804 mqttConnection.connectionPublish(topic, gsonMessage);
806 logger.debug("failed to restart communication");
808 } catch (MqttException e1) {
809 message = e1.getLocalizedMessage();
811 logger.debug("error resending device command");
813 if (!communicationActive()) {
814 message = (message != null) ? message : "@text/offline.communication-error";
815 connectionLost(message);
821 public void processMessage(String topic, byte[] payload) {
822 String message = new String(payload);
823 if ((profile + "/system/evt").equals(topic)) {
825 } else if ((profile + "/system/rsp").equals(topic)) {
826 logger.debug("received topic {}, payload {}", topic, message);
827 systeminfoPublishRsp(message);
828 } else if ((profile + "/notification/evt").equals(topic)) {
829 logger.debug("received topic {}, payload {}", topic, message);
830 notificationEvt(message);
831 } else if ((profile + "/control/devices/evt").equals(topic)) {
832 logger.trace("received topic {}, payload {}", topic, message);
834 } else if ((profile + "/control/devices/rsp").equals(topic)) {
835 logger.debug("received topic {}, payload {}", topic, message);
836 devicesListRsp(message);
837 } else if ((profile + "/authentication/rsp").equals(topic)) {
838 logger.debug("received topic {}, payload {}", topic, message);
839 servicesListRsp(message);
840 } else if ((profile + "/control/devices.error").equals(topic)) {
841 logger.warn("received error {}", message);
843 logger.trace("not acted on received message topic {}, payload {}", topic, message);
848 * @return system info retrieved from Connected Controller
850 public NhcSystemInfo2 getSystemInfo() {
851 NhcSystemInfo2 systemInfo = nhcSystemInfo;
852 if (systemInfo == null) {
853 systemInfo = new NhcSystemInfo2();
859 * @return time info retrieved from Connected Controller
861 public NhcTimeInfo2 getTimeInfo() {
862 NhcTimeInfo2 timeInfo = nhcTimeInfo;
863 if (timeInfo == null) {
864 timeInfo = new NhcTimeInfo2();
870 * @return comma separated list of services retrieved from Connected Controller
872 public String getServices() {
873 return services.stream().map(NhcService2::name).collect(Collectors.joining(", "));
877 public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) {
879 logger.debug("Connection state: {}", state, error);
880 String message = error.getLocalizedMessage();
881 message = (message != null) ? message : "@text/offline.communication-error";
882 if (!MqttConnectionState.CONNECTING.equals(state)) {
883 // This is a connection loss, try to restart
884 restartCommunication();
886 if (!communicationActive()) {
887 connectionLost(message);
890 logger.trace("Connection state: {}", state);