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 ActionType actionType;
368 switch (device.model) {
375 case "overallcomfort":
377 actionType = ActionType.TRIGGER;
381 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("device type {} and model {} not recognised for {}, {}, ignoring", device.type,
398 device.model, device.uuid, device.name);
402 NhcAction nhcAction = actions.get(device.uuid);
403 if (nhcAction != null) {
404 // update name and location so discovery will see updated name and location
405 nhcAction.setName(device.name);
406 nhcAction.setLocation(location);
408 logger.debug("adding action device {} model {}, {}", device.uuid, device.model, device.name);
409 nhcAction = new NhcAction2(device.uuid, device.name, device.type, device.technology, device.model,
410 location, actionType, this);
412 actions.put(device.uuid, nhcAction);
413 } else if ("thermostat".equals(device.type)) {
414 NhcThermostat nhcThermostat = thermostats.get(device.uuid);
415 if (nhcThermostat != null) {
416 nhcThermostat.setName(device.name);
417 nhcThermostat.setLocation(location);
419 logger.debug("adding thermostat device {} model {}, {}", device.uuid, device.model, device.name);
420 nhcThermostat = new NhcThermostat2(device.uuid, device.name, device.type, device.technology,
421 device.model, location, this);
423 thermostats.put(device.uuid, nhcThermostat);
424 } else if ("centralmeter".equals(device.type) || "energyhome".equals(device.type)) {
425 NhcEnergyMeter nhcEnergyMeter = energyMeters.get(device.uuid);
426 if (nhcEnergyMeter != null) {
427 nhcEnergyMeter.setName(device.name);
428 nhcEnergyMeter.setLocation(location);
430 logger.debug("adding energy meter device {} model {}, {}", device.uuid, device.model, device.name);
431 nhcEnergyMeter = new NhcEnergyMeter2(device.uuid, device.name, device.type, device.technology,
432 device.model, location, this, scheduler);
434 energyMeters.put(device.uuid, nhcEnergyMeter);
436 logger.debug("device type {} and model {} not supported for {}, {}", device.type, device.model, device.uuid,
441 private void removeDevice(NhcDevice2 device) {
442 NhcAction action = actions.get(device.uuid);
443 NhcThermostat thermostat = thermostats.get(device.uuid);
444 NhcEnergyMeter energyMeter = energyMeters.get(device.uuid);
445 if (action != null) {
446 action.actionRemoved();
447 actions.remove(device.uuid);
448 } else if (thermostat != null) {
449 thermostat.thermostatRemoved();
450 thermostats.remove(device.uuid);
451 } else if (energyMeter != null) {
452 energyMeter.energyMeterRemoved();
453 energyMeters.remove(device.uuid);
457 private void updateState(NhcDevice2 device) {
458 List<NhcProperty> deviceProperties = device.properties;
459 if (deviceProperties == null) {
460 logger.debug("Cannot Update state for {} as no properties defined in device message", device.uuid);
464 NhcAction action = actions.get(device.uuid);
465 NhcThermostat thermostat = thermostats.get(device.uuid);
466 NhcEnergyMeter energyMeter = energyMeters.get(device.uuid);
468 if (action != null) {
469 updateActionState((NhcAction2) action, deviceProperties);
470 } else if (thermostat != null) {
471 updateThermostatState((NhcThermostat2) thermostat, deviceProperties);
472 } else if (energyMeter != null) {
473 updateEnergyMeterState((NhcEnergyMeter2) energyMeter, deviceProperties);
477 private void updateActionState(NhcAction2 action, List<NhcProperty> deviceProperties) {
478 if (action.getType() == ActionType.ROLLERSHUTTER) {
479 updateRollershutterState(action, deviceProperties);
481 updateLightState(action, deviceProperties);
485 private void updateLightState(NhcAction2 action, List<NhcProperty> deviceProperties) {
486 Optional<NhcProperty> statusProperty = deviceProperties.stream().filter(p -> (p.status != null)).findFirst();
487 Optional<NhcProperty> dimmerProperty = deviceProperties.stream().filter(p -> (p.brightness != null))
489 Optional<NhcProperty> basicStateProperty = deviceProperties.stream().filter(p -> (p.basicState != null))
492 String booleanState = null;
493 if (statusProperty.isPresent()) {
494 booleanState = statusProperty.get().status;
495 } else if (basicStateProperty.isPresent()) {
496 booleanState = basicStateProperty.get().basicState;
499 if (NHCOFF.equals(booleanState) || NHCFALSE.equals(booleanState)) {
500 action.setBooleanState(false);
501 logger.debug("setting action {} internally to OFF", action.getId());
504 if (dimmerProperty.isPresent()) {
505 String brightness = dimmerProperty.get().brightness;
506 if (brightness != null) {
508 action.setState(Integer.parseInt(brightness));
509 logger.debug("setting action {} internally to {}", action.getId(), dimmerProperty.get().brightness);
510 } catch (NumberFormatException e) {
511 logger.debug("received invalid brightness value {} for dimmer {}", brightness, action.getId());
516 if (NHCON.equals(booleanState) || NHCTRUE.equals(booleanState)) {
517 action.setBooleanState(true);
518 logger.debug("setting action {} internally to ON", action.getId());
522 private void updateRollershutterState(NhcAction2 action, List<NhcProperty> deviceProperties) {
523 deviceProperties.stream().map(p -> p.position).filter(Objects::nonNull).findFirst().ifPresent(position -> {
525 action.setState(Integer.parseInt(position));
526 logger.debug("setting action {} internally to {}", action.getId(), position);
527 } catch (NumberFormatException e) {
528 logger.trace("received empty or invalid rollershutter {} position info {}", action.getId(), position);
533 private void updateThermostatState(NhcThermostat2 thermostat, List<NhcProperty> deviceProperties) {
534 Optional<Boolean> overruleActiveProperty = deviceProperties.stream().map(p -> p.overruleActive)
535 .filter(Objects::nonNull).map(t -> Boolean.parseBoolean(t)).findFirst();
536 Optional<Integer> overruleSetpointProperty = deviceProperties.stream().map(p -> p.overruleSetpoint)
537 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s) * 10) : null)
538 .filter(Objects::nonNull).findFirst();
539 Optional<Integer> overruleTimeProperty = deviceProperties.stream().map(p -> p.overruleTime)
540 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
541 .filter(Objects::nonNull).findFirst();
542 Optional<Integer> setpointTemperatureProperty = deviceProperties.stream().map(p -> p.setpointTemperature)
543 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s) * 10) : null)
544 .filter(Objects::nonNull).findFirst();
545 Optional<Boolean> ecoSaveProperty = deviceProperties.stream().map(p -> p.ecoSave)
546 .map(s -> s != null ? Boolean.parseBoolean(s) : null).filter(Objects::nonNull).findFirst();
547 Optional<Integer> ambientTemperatureProperty = deviceProperties.stream().map(p -> p.ambientTemperature)
548 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s) * 10) : null)
549 .filter(Objects::nonNull).findFirst();
550 Optional<@Nullable String> demandProperty = deviceProperties.stream().map(p -> p.demand)
551 .filter(Objects::nonNull).findFirst();
552 Optional<@Nullable String> operationModeProperty = deviceProperties.stream().map(p -> p.operationMode)
553 .filter(Objects::nonNull).findFirst();
555 String modeString = deviceProperties.stream().map(p -> p.program).filter(Objects::nonNull).findFirst()
557 int mode = IntStream.range(0, THERMOSTATMODES.length).filter(i -> THERMOSTATMODES[i].equals(modeString))
558 .findFirst().orElse(thermostat.getMode());
560 int measured = ambientTemperatureProperty.orElse(thermostat.getMeasured());
561 int setpoint = setpointTemperatureProperty.orElse(thermostat.getSetpoint());
563 int overrule = thermostat.getOverrule();
564 int overruletime = thermostat.getRemainingOverruletime();
565 if (overruleActiveProperty.orElse(false)) {
566 overrule = overruleSetpointProperty.orElse(0);
567 overruletime = overruleTimeProperty.orElse(0);
570 int ecosave = thermostat.getEcosave();
571 if (ecoSaveProperty.orElse(false)) {
575 int demand = thermostat.getDemand();
576 String demandString = demandProperty.orElse(operationModeProperty.orElse(""));
577 demandString = demandString == null ? "" : demandString;
578 switch (demandString) {
591 "Niko Home Control: setting thermostat {} with measured {}, setpoint {}, mode {}, overrule {}, overruletime {}, ecosave {}, demand {}",
592 thermostat.getId(), measured, setpoint, mode, overrule, overruletime, ecosave, demand);
593 thermostat.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
596 private void updateEnergyMeterState(NhcEnergyMeter2 energyMeter, List<NhcProperty> deviceProperties) {
598 Optional<Integer> electricalPower = deviceProperties.stream().map(p -> p.electricalPower)
599 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
600 .filter(Objects::nonNull).findFirst();
601 Optional<Integer> powerFromGrid = deviceProperties.stream().map(p -> p.electricalPowerFromGrid)
602 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
603 .filter(Objects::nonNull).findFirst();
604 Optional<Integer> powerToGrid = deviceProperties.stream().map(p -> p.electricalPowerToGrid)
605 .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
606 .filter(Objects::nonNull).findFirst();
607 int power = electricalPower.orElse(powerFromGrid.orElse(0) - powerToGrid.orElse(0));
608 logger.trace("setting energy meter {} power to {}", energyMeter.getId(), electricalPower);
609 energyMeter.setPower(power);
610 } catch (NumberFormatException e) {
611 energyMeter.setPower(null);
612 logger.trace("received empty energy meter {} power reading", energyMeter.getId());
617 public void executeAction(String actionId, String value) {
618 NhcMessage2 message = new NhcMessage2();
620 message.method = "devices.control";
621 ArrayList<NhcMessageParam> params = new ArrayList<>();
622 NhcMessageParam param = new NhcMessageParam();
624 message.params = params;
625 ArrayList<NhcDevice2> devices = new ArrayList<>();
626 NhcDevice2 device = new NhcDevice2();
628 param.devices = devices;
629 device.uuid = actionId;
630 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
631 NhcProperty property = new NhcProperty();
632 deviceProperties.add(property);
633 device.properties = deviceProperties;
635 NhcAction2 action = (NhcAction2) actions.get(actionId);
636 if (action == null) {
640 switch (action.getType()) {
643 property.basicState = NHCTRIGGERED;
646 property.status = value;
649 if (NHCON.equals(value)) {
650 action.setBooleanState(true); // this will trigger sending the stored brightness value event out
651 property.status = value;
652 } else if (NHCOFF.equals(value)) {
653 property.status = value;
656 action.setState(Integer.parseInt(value)); // set cached state to new brightness value to avoid
657 // switching on with old brightness value before
660 } catch (NumberFormatException e) {
661 logger.debug("internal error, trying to set invalid brightness value {} for dimmer {}", value,
666 // If the light is off, turn the light on before sending the brightness value, needs to happen
667 // in 2 separate messages.
668 if (!action.booleanState()) {
669 executeAction(actionId, NHCON);
671 property.brightness = value;
675 if (NHCSTOP.equals(value)) {
676 property.action = value;
677 } else if (NHCUP.equals(value)) {
678 property.position = "100";
679 } else if (NHCDOWN.equals(value)) {
680 property.position = "0";
682 property.position = value;
687 String topic = profile + "/control/devices/cmd";
688 String gsonMessage = gson.toJson(message);
689 sendDeviceMessage(topic, gsonMessage);
693 public void executeThermostat(String thermostatId, String mode) {
694 NhcMessage2 message = new NhcMessage2();
696 message.method = "devices.control";
697 ArrayList<NhcMessageParam> params = new ArrayList<>();
698 NhcMessageParam param = new NhcMessageParam();
700 message.params = params;
701 ArrayList<NhcDevice2> devices = new ArrayList<>();
702 NhcDevice2 device = new NhcDevice2();
704 param.devices = devices;
705 device.uuid = thermostatId;
706 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
708 NhcProperty overruleActiveProp = new NhcProperty();
709 deviceProperties.add(overruleActiveProp);
710 overruleActiveProp.overruleActive = "False";
712 NhcProperty program = new NhcProperty();
713 deviceProperties.add(program);
714 program.program = mode;
716 device.properties = deviceProperties;
718 String topic = profile + "/control/devices/cmd";
719 String gsonMessage = gson.toJson(message);
720 sendDeviceMessage(topic, gsonMessage);
724 public void executeThermostat(String thermostatId, int overruleTemp, int overruleTime) {
725 NhcMessage2 message = new NhcMessage2();
727 message.method = "devices.control";
728 ArrayList<NhcMessageParam> params = new ArrayList<>();
729 NhcMessageParam param = new NhcMessageParam();
731 message.params = params;
732 ArrayList<NhcDevice2> devices = new ArrayList<>();
733 NhcDevice2 device = new NhcDevice2();
735 param.devices = devices;
736 device.uuid = thermostatId;
737 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
739 if (overruleTime > 0) {
740 NhcProperty overruleActiveProp = new NhcProperty();
741 overruleActiveProp.overruleActive = "True";
742 deviceProperties.add(overruleActiveProp);
744 NhcProperty overruleSetpointProp = new NhcProperty();
745 overruleSetpointProp.overruleSetpoint = String.valueOf(overruleTemp / 10.0);
746 deviceProperties.add(overruleSetpointProp);
748 NhcProperty overruleTimeProp = new NhcProperty();
749 overruleTimeProp.overruleTime = String.valueOf(overruleTime);
750 deviceProperties.add(overruleTimeProp);
752 NhcProperty overruleActiveProp = new NhcProperty();
753 overruleActiveProp.overruleActive = "False";
754 deviceProperties.add(overruleActiveProp);
756 device.properties = deviceProperties;
758 String topic = profile + "/control/devices/cmd";
759 String gsonMessage = gson.toJson(message);
760 sendDeviceMessage(topic, gsonMessage);
764 public void startEnergyMeter(String energyMeterId) {
765 NhcMessage2 message = new NhcMessage2();
767 message.method = "devices.control";
768 ArrayList<NhcMessageParam> params = new ArrayList<>();
769 NhcMessageParam param = new NhcMessageParam();
771 message.params = params;
772 ArrayList<NhcDevice2> devices = new ArrayList<>();
773 NhcDevice2 device = new NhcDevice2();
775 param.devices = devices;
776 device.uuid = energyMeterId;
777 ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
779 NhcProperty reportInstantUsageProp = new NhcProperty();
780 deviceProperties.add(reportInstantUsageProp);
781 reportInstantUsageProp.reportInstantUsage = "True";
782 device.properties = deviceProperties;
784 String topic = profile + "/control/devices/cmd";
785 String gsonMessage = gson.toJson(message);
787 NhcEnergyMeter2 energyMeter = (NhcEnergyMeter2) energyMeters.get(energyMeterId);
788 if (energyMeter != null) {
789 energyMeter.startEnergyMeter(topic, gsonMessage);
794 public void stopEnergyMeter(String energyMeterId) {
795 NhcEnergyMeter2 energyMeter = (NhcEnergyMeter2) energyMeters.get(energyMeterId);
796 if (energyMeter != null) {
797 energyMeter.stopEnergyMeter();
802 * Method called from the {@link NhcEnergyMeter2} object to send message to Niko Home Control.
807 public void executeEnergyMeter(String topic, String gsonMessage) {
808 sendDeviceMessage(topic, gsonMessage);
811 private void sendDeviceMessage(String topic, String gsonMessage) {
813 mqttConnection.connectionPublish(topic, gsonMessage);
815 } catch (MqttException e) {
816 String message = e.getLocalizedMessage();
818 logger.debug("sending command failed, trying to restart communication");
819 restartCommunication();
820 // retry sending after restart
822 if (communicationActive()) {
823 mqttConnection.connectionPublish(topic, gsonMessage);
825 logger.debug("failed to restart communication");
827 } catch (MqttException e1) {
828 message = e1.getLocalizedMessage();
830 logger.debug("error resending device command");
832 if (!communicationActive()) {
833 message = (message != null) ? message : "@text/offline.communication-error";
834 connectionLost(message);
840 public void processMessage(String topic, byte[] payload) {
841 String message = new String(payload);
842 if ((profile + "/system/evt").equals(topic)) {
844 } else if ((profile + "/system/rsp").equals(topic)) {
845 logger.debug("received topic {}, payload {}", topic, message);
846 systeminfoPublishRsp(message);
847 } else if ((profile + "/notification/evt").equals(topic)) {
848 logger.debug("received topic {}, payload {}", topic, message);
849 notificationEvt(message);
850 } else if ((profile + "/control/devices/evt").equals(topic)) {
851 logger.trace("received topic {}, payload {}", topic, message);
853 } else if ((profile + "/control/devices/rsp").equals(topic)) {
854 logger.debug("received topic {}, payload {}", topic, message);
855 devicesListRsp(message);
856 } else if ((profile + "/authentication/rsp").equals(topic)) {
857 logger.debug("received topic {}, payload {}", topic, message);
858 servicesListRsp(message);
859 } else if ((profile + "/control/devices.error").equals(topic)) {
860 logger.warn("received error {}", message);
862 logger.trace("not acted on received message topic {}, payload {}", topic, message);
867 * @return system info retrieved from Connected Controller
869 public NhcSystemInfo2 getSystemInfo() {
870 NhcSystemInfo2 systemInfo = nhcSystemInfo;
871 if (systemInfo == null) {
872 systemInfo = new NhcSystemInfo2();
878 * @return time info retrieved from Connected Controller
880 public NhcTimeInfo2 getTimeInfo() {
881 NhcTimeInfo2 timeInfo = nhcTimeInfo;
882 if (timeInfo == null) {
883 timeInfo = new NhcTimeInfo2();
889 * @return comma separated list of services retrieved from Connected Controller
891 public String getServices() {
892 return services.stream().map(NhcService2::name).collect(Collectors.joining(", "));
896 public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) {
898 logger.debug("Connection state: {}, error", state, error);
899 String message = error.getLocalizedMessage();
900 message = (message != null) ? message : "@text/offline.communication-error";
901 if (!MqttConnectionState.CONNECTING.equals(state)) {
902 // This is a connection loss, try to restart
903 restartCommunication();
905 if (!communicationActive()) {
906 connectionLost(message);
909 logger.trace("Connection state: {}", state);