added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.nikohomecontrol-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-nikohomecontrol" description="Niko Home Control Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-mqtt</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.nikohomecontrol/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link NikoHomeControlBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlBindingConstants {
public static final String BINDING_ID = "nikohomecontrol";
// List of all Thing Type UIDs
// bridge
public static final ThingTypeUID BRIDGEI_THING_TYPE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID BRIDGEII_THING_TYPE = new ThingTypeUID(BINDING_ID, "bridge2");
// generic thing types
public static final ThingTypeUID THING_TYPE_PUSHBUTTON = new ThingTypeUID(BINDING_ID, "pushButton");
public static final ThingTypeUID THING_TYPE_ON_OFF_LIGHT = new ThingTypeUID(BINDING_ID, "onOff");
public static final ThingTypeUID THING_TYPE_DIMMABLE_LIGHT = new ThingTypeUID(BINDING_ID, "dimmer");
public static final ThingTypeUID THING_TYPE_BLIND = new ThingTypeUID(BINDING_ID, "blind");
public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "thermostat");
public static final ThingTypeUID THING_TYPE_ENERGYMETER = new ThingTypeUID(BINDING_ID, "energyMeter");
// thing type sets
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(BRIDGEI_THING_TYPE, BRIDGEII_THING_TYPE).collect(Collectors.toSet()));
public static final Set<ThingTypeUID> ACTION_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(THING_TYPE_PUSHBUTTON, THING_TYPE_ON_OFF_LIGHT, THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_BLIND)
.collect(Collectors.toSet()));
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_PUSHBUTTON, THING_TYPE_ON_OFF_LIGHT, THING_TYPE_DIMMABLE_LIGHT,
THING_TYPE_BLIND, THING_TYPE_THERMOSTAT, THING_TYPE_ENERGYMETER).collect(Collectors.toSet()));
// List of all Channel ids
public static final String CHANNEL_BUTTON = "button";
public static final String CHANNEL_SWITCH = "switch";
public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_ROLLERSHUTTER = "rollershutter";
public static final String CHANNEL_MEASURED = "measured";
public static final String CHANNEL_SETPOINT = "setpoint";
public static final String CHANNEL_OVERRULETIME = "overruletime";
public static final String CHANNEL_MODE = "mode";
public static final String CHANNEL_DEMAND = "demand";
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_ALARM = "alarm";
public static final String CHANNEL_NOTICE = "notice";
// Bridge config properties
public static final String CONFIG_HOST_NAME = "addr";
public static final String CONFIG_PORT = "port";
public static final String CONFIG_REFRESH = "refresh";
public static final String CONFIG_PROFILE = "profile";
public static final String CONFIG_PASSWORD = "password";
// Thing config properties
public static final String CONFIG_ACTION_ID = "actionId";
public static final String CONFIG_STEP_VALUE = "step";
public static final String CONFIG_THERMOSTAT_ID = "thermostatId";
public static final String CONFIG_OVERRULETIME = "overruleTime";
public static final String CONFIG_ENERGYMETER_ID = "energyMeterId";
}

View File

@@ -0,0 +1,116 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.discovery.NikoHomeControlDiscoveryService;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlActionHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler1;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler2;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlEnergyMeterHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlThermostatHandler;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link NikoHomeControlHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Mark Herwege - Initial Contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.nikohomecontrol")
@NonNullByDefault
public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory {
private @NonNullByDefault({}) NetworkAddressService networkAddressService;
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID) || BRIDGE_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
if (BRIDGE_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
NikoHomeControlBridgeHandler handler;
if (BRIDGEII_THING_TYPE.equals(thing.getThingTypeUID())) {
handler = new NikoHomeControlBridgeHandler2((Bridge) thing, networkAddressService);
} else {
handler = new NikoHomeControlBridgeHandler1((Bridge) thing);
}
registerNikoHomeControlDiscoveryService(handler);
return handler;
} else if (THING_TYPE_THERMOSTAT.equals(thing.getThingTypeUID())) {
return new NikoHomeControlThermostatHandler(thing);
} else if (THING_TYPE_ENERGYMETER.equals(thing.getThingTypeUID())) {
return new NikoHomeControlEnergyMeterHandler(thing);
} else if (ACTION_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
return new NikoHomeControlActionHandler(thing);
}
return null;
}
private synchronized void registerNikoHomeControlDiscoveryService(NikoHomeControlBridgeHandler bridgeHandler) {
NikoHomeControlDiscoveryService nhcDiscoveryService = new NikoHomeControlDiscoveryService(bridgeHandler);
discoveryServiceRegs.put(bridgeHandler.getThing().getUID(), bundleContext
.registerService(DiscoveryService.class.getName(), nhcDiscoveryService, new Hashtable<>()));
nhcDiscoveryService.activate();
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof NikoHomeControlBridgeHandler) {
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(thingHandler.getThing().getUID());
if (serviceReg != null) {
// remove discovery service, if bridge handler is removed
NikoHomeControlDiscoveryService service = (NikoHomeControlDiscoveryService) bundleContext
.getService(serviceReg.getReference());
serviceReg.unregister();
if (service != null) {
service.deactivate();
}
}
}
}
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.discovery;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import java.io.IOException;
import java.net.InetAddress;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlDiscover;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link NikoHomeControlBridgeDiscoveryService} is used to discover a Niko Home Control IP-interface in the local
* network.
*
* @author Mark Herwege - Initial Contribution
*/
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.nikohomecontrol")
@NonNullByDefault
public class NikoHomeControlBridgeDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeDiscoveryService.class);
private volatile @Nullable ScheduledFuture<?> nhcDiscoveryJob;
private @NonNullByDefault({}) NetworkAddressService networkAddressService;
private static final int TIMEOUT = 5;
private static final int REFRESH_INTERVAL = 60;
public NikoHomeControlBridgeDiscoveryService() {
super(NikoHomeControlBindingConstants.BRIDGE_THING_TYPES_UIDS, TIMEOUT);
logger.debug("Niko Home Control: bridge discovery service started");
}
/**
* Discovers devices connected to a Niko Home Control controller
*/
private void discoverBridge() {
try {
String broadcastAddr = networkAddressService.getConfiguredBroadcastAddress();
if (broadcastAddr == null) {
logger.warn("Niko Home Control: discovery not possible, no broadcast address found");
return;
}
logger.debug("Niko Home Control: discovery broadcast on {}", broadcastAddr);
NikoHomeControlDiscover nhcDiscover = new NikoHomeControlDiscover(broadcastAddr);
if (nhcDiscover.isNhcII()) {
addNhcIIBridge(nhcDiscover.getAddr(), nhcDiscover.getNhcBridgeId());
} else {
addNhcIBridge(nhcDiscover.getAddr(), nhcDiscover.getNhcBridgeId());
}
} catch (IOException e) {
logger.debug("Niko Home Control: no bridge found.");
}
}
private void addNhcIBridge(InetAddress addr, String bridgeId) {
logger.debug("Niko Home Control: NHC I bridge found at {}", addr);
String bridgeName = "Niko Home Control Bridge";
ThingUID uid = new ThingUID(BINDING_ID, "bridge", bridgeId);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withLabel(bridgeName)
.withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).build();
thingDiscovered(discoveryResult);
}
private void addNhcIIBridge(InetAddress addr, String bridgeId) {
logger.debug("Niko Home Control: NHC II bridge found at {}", addr);
String bridgeName = "Niko Home Control II Bridge";
ThingUID uid = new ThingUID(BINDING_ID, "bridge2", bridgeId);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withLabel(bridgeName)
.withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).build();
thingDiscovered(discoveryResult);
}
@Override
protected void startScan() {
discoverBridge();
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Niko Home Control: Start background bridge discovery");
ScheduledFuture<?> job = nhcDiscoveryJob;
if (job == null || job.isCancelled()) {
nhcDiscoveryJob = scheduler.scheduleWithFixedDelay(this::discoverBridge, 0, REFRESH_INTERVAL,
TimeUnit.SECONDS);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Niko Home Control: Stop bridge background discovery");
ScheduledFuture<?> job = nhcDiscoveryJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
nhcDiscoveryJob = null;
}
}
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}
}

View File

@@ -0,0 +1,160 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.discovery;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import java.util.Date;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcEnergyMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* If a Niko Home Control bridge is added or if the user scans manually for things this
* {@link NikoHomeControlDiscoveryService} is used to return Niko Home Control Actions as things to the framework.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlDiscoveryService.class);
private static final int TIMEOUT = 5;
private ThingUID bridgeUID;
private NikoHomeControlBridgeHandler handler;
public NikoHomeControlDiscoveryService(NikoHomeControlBridgeHandler handler) {
super(SUPPORTED_THING_TYPES_UIDS, TIMEOUT, false);
logger.debug("Niko Home Control: discovery service {}", handler);
bridgeUID = handler.getThing().getUID();
this.handler = handler;
}
public void activate() {
handler.setNhcDiscovery(this);
}
@Override
public void deactivate() {
removeOlderResults(new Date().getTime());
handler.setNhcDiscovery(null);
}
/**
* Discovers devices connected to a Niko Home Control controller
*/
public void discoverDevices() {
NikoHomeControlCommunication nhcComm = handler.getCommunication();
if ((nhcComm == null) || !nhcComm.communicationActive()) {
logger.warn("Niko Home Control: not connected.");
return;
}
logger.debug("Niko Home Control: getting devices on {}", handler.getThing().getUID().getId());
Map<String, NhcAction> actions = nhcComm.getActions();
actions.forEach((actionId, nhcAction) -> {
String thingName = nhcAction.getName();
String thingLocation = nhcAction.getLocation();
switch (nhcAction.getType()) {
case TRIGGER:
addActionDevice(new ThingUID(THING_TYPE_PUSHBUTTON, handler.getThing().getUID(), actionId),
actionId, thingName, thingLocation);
break;
case RELAY:
addActionDevice(new ThingUID(THING_TYPE_ON_OFF_LIGHT, handler.getThing().getUID(), actionId),
actionId, thingName, thingLocation);
break;
case DIMMER:
addActionDevice(new ThingUID(THING_TYPE_DIMMABLE_LIGHT, handler.getThing().getUID(), actionId),
actionId, thingName, thingLocation);
break;
case ROLLERSHUTTER:
addActionDevice(new ThingUID(THING_TYPE_BLIND, handler.getThing().getUID(), actionId), actionId,
thingName, thingLocation);
break;
default:
logger.debug("Niko Home Control: unrecognized action type {} for {} {}", nhcAction.getType(),
actionId, thingName);
}
});
Map<String, NhcThermostat> thermostats = nhcComm.getThermostats();
thermostats.forEach((thermostatId, nhcThermostat) -> {
String thingName = nhcThermostat.getName();
String thingLocation = nhcThermostat.getLocation();
addThermostatDevice(new ThingUID(THING_TYPE_THERMOSTAT, handler.getThing().getUID(), thermostatId),
thermostatId, thingName, thingLocation);
});
Map<String, NhcEnergyMeter> energyMeters = nhcComm.getEnergyMeters();
energyMeters.forEach((energyMeterId, nhcEnergyMeter) -> {
String thingName = nhcEnergyMeter.getName();
addEnergyMeterDevice(new ThingUID(THING_TYPE_ENERGYMETER, handler.getThing().getUID(), energyMeterId),
energyMeterId, thingName);
});
}
private void addActionDevice(ThingUID uid, String actionId, String thingName, @Nullable String thingLocation) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withLabel(thingName).withProperty(CONFIG_ACTION_ID, actionId);
if (thingLocation != null) {
discoveryResultBuilder.withProperty("Location", thingLocation);
}
thingDiscovered(discoveryResultBuilder.build());
}
private void addThermostatDevice(ThingUID uid, String thermostatId, String thingName,
@Nullable String thingLocation) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withLabel(thingName).withProperty(CONFIG_THERMOSTAT_ID, thermostatId);
if (thingLocation != null) {
discoveryResultBuilder.withProperty("Location", thingLocation);
}
thingDiscovered(discoveryResultBuilder.build());
}
private void addEnergyMeterDevice(ThingUID uid, String energyMeterId, String thingName) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withLabel(thingName).withProperty(CONFIG_ENERGYMETER_ID, energyMeterId);
thingDiscovered(discoveryResultBuilder.build());
}
@Override
protected void startScan() {
discoverDevices();
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
import java.util.List;
/**
* {@link NhcJwtToken2} represents the Niko Home Control II hobby API token payload.
*
* @author Mark Herwege - Initial Contribution
*/
class NhcJwtToken2 {
String sub;
String iat;
String exp;
String aud;
String iss;
String jti;
List<String> role;
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlActionConfig} is the general config class for Niko Home Control Actions.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlActionConfig {
public String actionId;
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlActionDimmerConfig} is the config class for Niko Home Control Dimmer Actions.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlActionDimmerConfig extends NikoHomeControlActionConfig {
public int step;
}

View File

@@ -0,0 +1,321 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.*;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcActionEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAction2;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlActionHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlActionHandler extends BaseThingHandler implements NhcActionEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlActionHandler.class);
private volatile @NonNullByDefault({}) NhcAction nhcAction;
private String actionId = "";
private int stepValue;
public NikoHomeControlActionHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: bridge communication not initialized when trying to execute action command "
+ actionId);
return;
}
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
handleCommandSelection(channelUID, command);
}
});
}
private void handleCommandSelection(ChannelUID channelUID, Command command) {
logger.debug("Niko Home Control: handle command {} for {}", command, channelUID);
if (command == REFRESH) {
actionEvent(nhcAction.getState());
return;
}
switch (channelUID.getId()) {
case CHANNEL_BUTTON:
case CHANNEL_SWITCH:
handleSwitchCommand(command);
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_BRIGHTNESS:
handleBrightnessCommand(command);
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_ROLLERSHUTTER:
handleRollershutterCommand(command);
updateStatus(ThingStatus.ONLINE);
break;
default:
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: channel unknown " + channelUID.getId());
}
}
private void handleSwitchCommand(Command command) {
if (command instanceof OnOffType) {
OnOffType s = (OnOffType) command;
if (OnOffType.OFF.equals(s)) {
nhcAction.execute(NHCOFF);
} else {
nhcAction.execute(NHCON);
}
}
}
private void handleBrightnessCommand(Command command) {
if (command instanceof OnOffType) {
OnOffType s = (OnOffType) command;
if (OnOffType.OFF.equals(s)) {
nhcAction.execute(NHCOFF);
} else {
nhcAction.execute(NHCON);
}
} else if (command instanceof IncreaseDecreaseType) {
IncreaseDecreaseType s = (IncreaseDecreaseType) command;
int currentValue = nhcAction.getState();
int newValue;
if (IncreaseDecreaseType.INCREASE.equals(s)) {
newValue = currentValue + stepValue;
// round down to step multiple
newValue = newValue - newValue % stepValue;
nhcAction.execute(Integer.toString(newValue > 100 ? 100 : newValue));
} else {
newValue = currentValue - stepValue;
// round up to step multiple
newValue = newValue + newValue % stepValue;
if (newValue <= 0) {
nhcAction.execute(NHCOFF);
} else {
nhcAction.execute(Integer.toString(newValue));
}
}
} else if (command instanceof PercentType) {
PercentType p = (PercentType) command;
if (PercentType.ZERO.equals(p)) {
nhcAction.execute(NHCOFF);
} else {
nhcAction.execute(Integer.toString(p.intValue()));
}
}
}
private void handleRollershutterCommand(Command command) {
if (command instanceof UpDownType) {
UpDownType s = (UpDownType) command;
if (UpDownType.UP.equals(s)) {
nhcAction.execute(NHCUP);
} else {
nhcAction.execute(NHCDOWN);
}
} else if (command instanceof StopMoveType) {
nhcAction.execute(NHCSTOP);
} else if (command instanceof PercentType) {
PercentType p = (PercentType) command;
nhcAction.execute(Integer.toString(100 - p.intValue()));
}
}
@Override
public void initialize() {
NikoHomeControlActionConfig config;
if (thing.getThingTypeUID().equals(THING_TYPE_DIMMABLE_LIGHT)) {
config = getConfig().as(NikoHomeControlActionDimmerConfig.class);
stepValue = ((NikoHomeControlActionDimmerConfig) config).step;
} else {
config = getConfig().as(NikoHomeControlActionConfig.class);
}
actionId = config.actionId;
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
return;
}
// We need to do this in a separate thread because we may have to wait for the communication to become active
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: no connection with Niko Home Control, could not initialize action "
+ actionId);
return;
}
nhcAction = nhcComm.getActions().get(actionId);
if (nhcAction == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: actionId does not match an action in the controller " + actionId);
return;
}
nhcAction.setEventHandler(this);
updateProperties();
String actionLocation = nhcAction.getLocation();
if (thing.getLocation() == null) {
thing.setLocation(actionLocation);
}
actionEvent(nhcAction.getState());
logger.debug("Niko Home Control: action initialized {}", actionId);
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
});
}
private void updateProperties() {
Map<String, String> properties = new HashMap<>();
properties.put("type", String.valueOf(nhcAction.getType()));
if (getThing().getThingTypeUID() == THING_TYPE_BLIND) {
properties.put("timeToOpen", String.valueOf(nhcAction.getOpenTime()));
properties.put("timeToClose", String.valueOf(nhcAction.getCloseTime()));
}
if (nhcAction instanceof NhcAction2) {
NhcAction2 action = (NhcAction2) nhcAction;
properties.put("model", action.getModel());
properties.put("technology", action.getTechnology());
}
thing.setProperties(properties);
}
@Override
public void actionEvent(int actionState) {
ActionType actionType = nhcAction.getType();
switch (actionType) {
case TRIGGER:
updateState(CHANNEL_BUTTON, (actionState == 0) ? OnOffType.OFF : OnOffType.ON);
updateStatus(ThingStatus.ONLINE);
case RELAY:
updateState(CHANNEL_SWITCH, (actionState == 0) ? OnOffType.OFF : OnOffType.ON);
updateStatus(ThingStatus.ONLINE);
break;
case DIMMER:
updateState(CHANNEL_BRIGHTNESS, new PercentType(actionState));
updateStatus(ThingStatus.ONLINE);
break;
case ROLLERSHUTTER:
updateState(CHANNEL_ROLLERSHUTTER, new PercentType(actionState));
updateStatus(ThingStatus.ONLINE);
break;
default:
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: unknown action type " + actionType);
}
}
@Override
public void actionRemoved() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: action has been removed from the controller " + actionId);
}
private void restartCommunication(NikoHomeControlCommunication nhcComm) {
// We lost connection but the connection object is there, so was correctly started.
// Try to restart communication.
nhcComm.restartCommunication();
// If still not active, take thing offline and return.
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: communication socket error");
return;
}
// Also put the bridge back online
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler != null) {
nhcBridgeHandler.bridgeOnline();
}
}
private @Nullable NikoHomeControlCommunication getCommunication() {
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for action " + actionId);
return null;
}
NikoHomeControlCommunication nhcComm = nhcBridgeHandler.getCommunication();
return nhcComm;
}
private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
Bridge nhcBridge = getBridge();
if (nhcBridge == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for action " + actionId);
return null;
}
NikoHomeControlBridgeHandler nhcBridgeHandler = (NikoHomeControlBridgeHandler) nhcBridge.getHandler();
return nhcBridgeHandler;
}
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlBridgeConfig} is the general config class for Niko Home Control Bridges.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlBridgeConfig {
public String addr;
public int port;
public int refresh;
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlBridgeConfig2} is the extended config class for Niko Home Control II Bridges.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlBridgeConfig2 extends NikoHomeControlBridgeConfig {
public String profile;
public String password;
}

View File

@@ -0,0 +1,280 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.discovery.NikoHomeControlDiscoveryService;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcControllerEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link NikoHomeControlBridgeHandler} is an abstract class representing a handler to all different interfaces to the
* Niko Home Control System. {@link NikoHomeControlBridgeHandler1} or {@link NikoHomeControlBridgeHandler2} should be
* used for the respective
* version of Niko Home Control.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler implements NhcControllerEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler.class);
protected @NonNullByDefault({}) NikoHomeControlBridgeConfig config;
protected @Nullable NikoHomeControlCommunication nhcComm;
private volatile @Nullable ScheduledFuture<?> refreshTimer;
protected volatile @Nullable NikoHomeControlDiscoveryService nhcDiscovery;
public NikoHomeControlBridgeHandler(Bridge nikoHomeControlBridge) {
super(nikoHomeControlBridge);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// There is nothing to handle in the bridge handler
}
/**
* Create communication object to Niko Home Control IP-interface and start communication.
* Trigger discovery when communication setup is successful.
*/
protected void startCommunication() {
NikoHomeControlCommunication comm = nhcComm;
if (comm == null) {
bridgeOffline();
return;
}
scheduler.submit(() -> {
comm.startCommunication();
if (!comm.communicationActive()) {
bridgeOffline();
return;
}
updateProperties();
updateStatus(ThingStatus.ONLINE);
int refreshInterval = config.refresh;
setupRefreshTimer(refreshInterval);
NikoHomeControlDiscoveryService discovery = nhcDiscovery;
if (discovery != null) {
discovery.discoverDevices();
} else {
logger.debug("Niko Home Control: cannot discover devices, discovery service not started");
}
});
}
/**
* Schedule future communication refresh.
*
* @param interval_config Time before refresh in minutes.
*/
private void setupRefreshTimer(int refreshInterval) {
ScheduledFuture<?> timer = refreshTimer;
if (timer != null) {
timer.cancel(true);
refreshTimer = null;
}
if (refreshInterval == 0) {
return;
}
// This timer will restart the bridge connection periodically
logger.debug("Niko Home Control: restart bridge connection every {} min", refreshInterval);
refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
logger.debug("Niko Home Control: restart communication at scheduled time");
NikoHomeControlCommunication comm = nhcComm;
if (comm != null) {
comm.restartCommunication();
if (!comm.communicationActive()) {
bridgeOffline();
return;
}
updateProperties();
updateStatus(ThingStatus.ONLINE);
}
}, refreshInterval, refreshInterval, TimeUnit.MINUTES);
}
/**
* Take bridge offline when error in communication with Niko Home Control IP-interface.
*/
protected void bridgeOffline() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Niko Home Control: error starting bridge connection");
}
/**
* Put bridge online when error in communication resolved.
*/
public void bridgeOnline() {
updateProperties();
updateStatus(ThingStatus.ONLINE);
}
@Override
public void controllerOffline() {
bridgeOffline();
}
@Override
public void controllerOnline() {
bridgeOnline();
int refreshInterval = config.refresh;
if (refreshTimer == null) {
setupRefreshTimer(refreshInterval);
}
}
/**
* Update bridge properties with properties returned from Niko Home Control Controller, so they can be made visible
* in PaperUI.
*/
protected abstract void updateProperties();
@Override
public void dispose() {
ScheduledFuture<?> timer = refreshTimer;
if (timer != null) {
timer.cancel(true);
}
refreshTimer = null;
NikoHomeControlCommunication comm = nhcComm;
if (comm != null) {
comm.stopCommunication();
}
nhcComm = null;
}
@Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
NikoHomeControlCommunication comm = nhcComm;
// if the communication had not been started yet, just dispose and initialize again
if (comm == null) {
super.handleConfigurationUpdate(configurationParameters);
return;
}
Configuration configuration = editConfiguration();
for (Entry<String, Object> configurationParmeter : configurationParameters.entrySet()) {
configuration.put(configurationParmeter.getKey(), configurationParmeter.getValue());
}
updateConfiguration(configuration);
setConfig();
scheduler.submit(() -> {
comm.restartCommunication();
if (!comm.communicationActive()) {
bridgeOffline();
return;
}
updateProperties();
updateStatus(ThingStatus.ONLINE);
int refreshInterval = config.refresh;
setupRefreshTimer(refreshInterval);
});
}
/**
* Set discovery service handler to be able to start discovery after bridge initialization.
*
* @param nhcDiscovery
*/
public void setNhcDiscovery(@Nullable NikoHomeControlDiscoveryService nhcDiscovery) {
this.nhcDiscovery = nhcDiscovery;
}
@Override
public void alarmEvent(String alarmText) {
logger.debug("Niko Home Control: triggering alarm channel with {}", alarmText);
triggerChannel(CHANNEL_ALARM, alarmText);
updateStatus(ThingStatus.ONLINE);
}
@Override
public void noticeEvent(String alarmText) {
logger.debug("Niko Home Control: triggering notice channel with {}", alarmText);
triggerChannel(CHANNEL_NOTICE, alarmText);
updateStatus(ThingStatus.ONLINE);
}
@Override
public void updatePropertiesEvent() {
updateProperties();
}
/**
* Get the Niko Home Control communication object.
*
* @return Niko Home Control communication object
*/
public @Nullable NikoHomeControlCommunication getCommunication() {
return nhcComm;
}
@Override
public @Nullable InetAddress getAddr() {
InetAddress addr = null;
try {
addr = InetAddress.getByName(config.addr);
} catch (UnknownHostException e) {
logger.debug("Niko Home Control: Cannot resolve hostname {} to IP adress", config.addr);
}
return addr;
}
@Override
public int getPort() {
return config.port;
}
protected synchronized void setConfig() {
config = getConfig().as(NikoHomeControlBridgeConfig.class);
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NikoHomeControlCommunication1;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link NikoHomeControlBridgeHandler1} is the handler for a Niko Home Control I IP-interface and connects it to
* the framework.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlBridgeHandler1 extends NikoHomeControlBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler1.class);
public NikoHomeControlBridgeHandler1(Bridge nikoHomeControlBridge) {
super(nikoHomeControlBridge);
}
@Override
public void initialize() {
logger.debug("Niko Home Control: initializing bridge handler");
setConfig();
InetAddress addr = getAddr();
int port = getPort();
logger.debug("Niko Home Control: bridge handler host {}, port {}", addr, port);
if (addr != null) {
nhcComm = new NikoHomeControlCommunication1(this, scheduler);
startCommunication();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Niko Home Control: cannot resolve bridge IP with hostname " + config.addr);
}
}
@Override
protected void updateProperties() {
Map<String, String> properties = new HashMap<>();
NikoHomeControlCommunication1 comm = (NikoHomeControlCommunication1) nhcComm;
if (comm != null) {
properties.put("softwareVersion", comm.getSystemInfo().getSwVersion());
properties.put("apiVersion", comm.getSystemInfo().getApi());
properties.put("language", comm.getSystemInfo().getLanguage());
properties.put("currency", comm.getSystemInfo().getCurrency());
properties.put("units", comm.getSystemInfo().getUnits());
properties.put("tzOffset", comm.getSystemInfo().getTz());
properties.put("dstOffset", comm.getSystemInfo().getDst());
properties.put("configDate", comm.getSystemInfo().getLastConfig());
properties.put("energyEraseDate", comm.getSystemInfo().getLastEnergyErase());
properties.put("connectionStartDate", comm.getSystemInfo().getTime());
thing.setProperties(properties);
}
}
}

View File

@@ -0,0 +1,236 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
import java.security.cert.CertificateException;
import java.text.DateFormat;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NikoHomeControlCommunication2;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
/**
* {@link NikoHomeControlBridgeHandler2} is the handler for a Niko Home Control II Connected Controller and connects it
* to the framework.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlBridgeHandler2 extends NikoHomeControlBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler2.class);
private final Gson gson = new GsonBuilder().create();
NetworkAddressService networkAddressService;
public NikoHomeControlBridgeHandler2(Bridge nikoHomeControlBridge, NetworkAddressService networkAddressService) {
super(nikoHomeControlBridge);
this.networkAddressService = networkAddressService;
}
@Override
public void initialize() {
logger.debug("Niko Home Control: initializing NHC II bridge handler");
setConfig();
Date expiryDate = getTokenExpiryDate();
if (expiryDate == null) {
if (getToken().isEmpty()) {
// We allow a not well formed token (no expiry date) to pass through.
// This allows the user to use this as a password on a profile, with the profile UUID defined in an
// advanced configuration, skipping token validation.
// This behavior would allow the same logic to be used (with profile UUID) as before token validation
// was introduced.
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Niko Home Control: token is empty");
return;
}
} else {
Date now = new Date();
if (expiryDate.before(now)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Niko Home Control: hobby api token has expired");
return;
}
}
String addr = networkAddressService.getPrimaryIpv4HostAddress();
addr = (addr == null) ? "unknown" : addr.replace(".", "_");
String clientId = addr + "-" + thing.getUID().toString().replace(":", "_");
try {
nhcComm = new NikoHomeControlCommunication2(this, clientId, scheduler);
startCommunication();
} catch (CertificateException e) {
// this should not happen unless there is a programming error
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Niko Home Control: not able to set SSL context");
return;
}
}
@Override
protected void updateProperties() {
Map<String, String> properties = new HashMap<>();
NikoHomeControlCommunication2 comm = (NikoHomeControlCommunication2) nhcComm;
if (comm != null) {
Date expiry = getTokenExpiryDate();
if (expiry != null) {
properties.put("tokenExpiryDate", DateFormat.getDateInstance().format(expiry));
}
String nhcVersion = comm.getSystemInfo().getNhcVersion();
if (!nhcVersion.isEmpty()) {
properties.put("nhcVersion", nhcVersion);
}
String cocoImage = comm.getSystemInfo().getCocoImage();
if (!cocoImage.isEmpty()) {
properties.put("cocoImage", cocoImage);
}
String language = comm.getSystemInfo().getLanguage();
if (!language.isEmpty()) {
properties.put("language", language);
}
String currency = comm.getSystemInfo().getCurrency();
if (!currency.isEmpty()) {
properties.put("currency", currency);
}
String units = comm.getSystemInfo().getUnits();
if (!units.isEmpty()) {
properties.put("units", units);
}
String lastConfig = comm.getSystemInfo().getLastConfig();
if (!lastConfig.isEmpty()) {
properties.put("lastConfig", lastConfig);
}
String electricityTariff = comm.getSystemInfo().getElectricityTariff();
if (!electricityTariff.isEmpty()) {
properties.put("electricityTariff", electricityTariff);
}
String gasTariff = comm.getSystemInfo().getGasTariff();
if (!gasTariff.isEmpty()) {
properties.put("gasTariff", gasTariff);
}
String waterTariff = comm.getSystemInfo().getWaterTariff();
if (!waterTariff.isEmpty()) {
properties.put("waterTariff", waterTariff);
}
String timezone = comm.getTimeInfo().getTimezone();
if (!timezone.isEmpty()) {
properties.put("timezone", timezone);
}
String isDst = comm.getTimeInfo().getIsDst();
if (!isDst.isEmpty()) {
properties.put("isDST", isDst);
}
String services = comm.getServices();
if (!services.isEmpty()) {
properties.put("services", services);
}
thing.setProperties(properties);
}
}
@Override
public String getProfile() {
return ((NikoHomeControlBridgeConfig2) config).profile;
}
@Override
public String getToken() {
String token = ((NikoHomeControlBridgeConfig2) config).password;
if ((token == null) || token.isEmpty()) {
logger.debug("Niko Home Control: no JWT token set.");
return "";
}
return token;
}
/**
* Extract the expiry date in the user provided token for the hobby API. Log warnings and errors if the token is
* close to expiry or expired.
*
* @return Hobby API token expiry date, null if no valid token.
*/
private @Nullable Date getTokenExpiryDate() {
NhcJwtToken2 jwtToken = null;
String token = getToken();
String[] tokenArray = token.split("\\.");
if (tokenArray.length == 3) {
String tokenPayload = new String(Base64.getDecoder().decode(tokenArray[1]));
try {
jwtToken = gson.fromJson(tokenPayload, NhcJwtToken2.class);
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected token payload {}", tokenPayload);
} catch (NoSuchElementException ignore) {
// Ignore if exp not present in response, this should not happen in token payload response
logger.trace("Niko Home Control: no expiry date found in payload {}", tokenPayload);
}
}
if (jwtToken != null) {
Date expiryDate;
try {
String expiryEpoch = jwtToken.exp;
long epoch = Long.parseLong(expiryEpoch) * 1000; // convert to milliseconds
expiryDate = new Date(epoch);
} catch (NumberFormatException e) {
logger.debug("Niko Home Control: token expiry not valid {}", jwtToken.exp);
return null;
}
Date now = new Date();
if (expiryDate.before(now)) {
logger.warn("Niko Home Control: hobby API token expired, was valid until {}",
DateFormat.getDateInstance().format(expiryDate));
} else {
Calendar c = Calendar.getInstance();
c.setTime(expiryDate);
c.add(Calendar.DATE, -14);
if (c.getTime().before(now)) {
logger.info("Niko Home Control: hobby API token will expire in less than 14 days, valid until {}",
DateFormat.getDateInstance().format(expiryDate));
}
}
return expiryDate;
}
return null;
}
@Override
protected synchronized void setConfig() {
config = getConfig().as(NikoHomeControlBridgeConfig2.class);
}
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlEnergyMeterConfig} is the config class for Niko Home Control Thermostats.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlEnergyMeterConfig {
public String energyMeterId;
}

View File

@@ -0,0 +1,241 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.CHANNEL_POWER;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcEnergyMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcEnergyMeterEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcEnergyMeter2;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlEnergyMeterHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlEnergyMeterHandler extends BaseThingHandler implements NhcEnergyMeterEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlEnergyMeterHandler.class);
private volatile @NonNullByDefault({}) NhcEnergyMeter nhcEnergyMeter;
private String energyMeterId = "";
public NikoHomeControlEnergyMeterHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command == REFRESH) {
energyMeterEvent(nhcEnergyMeter.getPower());
}
}
@Override
public void initialize() {
NikoHomeControlEnergyMeterConfig config = getConfig().as(NikoHomeControlEnergyMeterConfig.class);
energyMeterId = config.energyMeterId;
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
return;
}
// We need to do this in a separate thread because we may have to wait for the
// communication to become active
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: no connection with Niko Home Control, could not initialize energy meter "
+ energyMeterId);
return;
}
nhcEnergyMeter = nhcComm.getEnergyMeters().get(energyMeterId);
if (nhcEnergyMeter == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: energyMeterId does not match a energy meter in the controller "
+ energyMeterId);
return;
}
nhcEnergyMeter.setEventHandler(this);
updateProperties();
// Subscribing to power readings starts an intensive data flow, therefore only do it when there is an item
// linked to the channel
if (isLinked(CHANNEL_POWER)) {
nhcComm.startEnergyMeter(energyMeterId);
}
logger.debug("Niko Home Control: energy meter intialized {}", energyMeterId);
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
});
}
@Override
public void dispose() {
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm != null) {
nhcComm.stopEnergyMeter(energyMeterId);
}
}
private void updateProperties() {
Map<String, String> properties = new HashMap<>();
if (nhcEnergyMeter instanceof NhcEnergyMeter2) {
NhcEnergyMeter2 energyMeter = (NhcEnergyMeter2) nhcEnergyMeter;
properties.put("model", energyMeter.getModel());
properties.put("technology", energyMeter.getTechnology());
}
thing.setProperties(properties);
}
@Override
public void energyMeterEvent(@Nullable Integer power) {
if (power == null) {
updateState(CHANNEL_POWER, UnDefType.UNDEF);
} else {
updateState(CHANNEL_POWER, new QuantityType<>(power, SmartHomeUnits.WATT));
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void energyMeterRemoved() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: energy meter has been removed from the controller " + energyMeterId);
}
@Override
// Subscribing to power readings starts an intensive data flow, therefore only do it when there is an item linked to
// the channel
public void channelLinked(ChannelUID channelUID) {
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: bridge communication not initialized when trying to start energy meter "
+ energyMeterId);
return;
}
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
nhcComm.startEnergyMeter(energyMeterId);
updateStatus(ThingStatus.ONLINE);
}
});
}
@Override
public void channelUnlinked(ChannelUID channelUID) {
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: bridge communication not initialized when trying to stop energy meter "
+ energyMeterId);
return;
}
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
nhcComm.stopEnergyMeter(energyMeterId);
// as this is momentary power production/consumption, we set it UNDEF as we do not get readings anymore
updateState(CHANNEL_POWER, UnDefType.UNDEF);
updateStatus(ThingStatus.ONLINE);
}
});
}
private void restartCommunication(NikoHomeControlCommunication nhcComm) {
// We lost connection but the connection object is there, so was correctly started.
// Try to restart communication.
nhcComm.restartCommunication();
// If still not active, take thing offline and return.
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: communication socket error");
return;
}
// Also put the bridge back online
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler != null) {
nhcBridgeHandler.bridgeOnline();
}
}
private @Nullable NikoHomeControlCommunication getCommunication() {
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for energy meter " + energyMeterId);
return null;
}
NikoHomeControlCommunication nhcComm = nhcBridgeHandler.getCommunication();
return nhcComm;
}
private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
Bridge nhcBridge = getBridge();
if (nhcBridge == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for energy meter " + energyMeterId);
return null;
}
NikoHomeControlBridgeHandler nhcBridgeHandler = (NikoHomeControlBridgeHandler) nhcBridge.getHandler();
return nhcBridgeHandler;
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlThermostatConfig} is the config class for Niko Home Control Thermostats.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlThermostatConfig {
public String thermostatId;
public int overruleTime;
}

View File

@@ -0,0 +1,310 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.handler;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostatEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcThermostat2;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlThermostatHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlThermostatHandler extends BaseThingHandler implements NhcThermostatEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlThermostatHandler.class);
private volatile @NonNullByDefault({}) NhcThermostat nhcThermostat;
private String thermostatId = "";
private int overruleTime;
private volatile @Nullable ScheduledFuture<?> refreshTimer; // used to refresh the remaining overrule time every
// minute
public NikoHomeControlThermostatHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: bridge communication not initialized when trying to execute thermostat command "
+ thermostatId);
return;
}
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
handleCommandSelection(channelUID, command);
}
});
}
private void handleCommandSelection(ChannelUID channelUID, Command command) {
logger.debug("Niko Home Control: handle command {} for {}", command, channelUID);
if (REFRESH.equals(command)) {
thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
nhcThermostat.getOverrule(), nhcThermostat.getDemand());
return;
}
switch (channelUID.getId()) {
case CHANNEL_MEASURED:
case CHANNEL_DEMAND:
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_MODE:
if (command instanceof DecimalType) {
nhcThermostat.executeMode(((DecimalType) command).intValue());
}
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_SETPOINT:
QuantityType<Temperature> setpoint = null;
if (command instanceof QuantityType) {
setpoint = ((QuantityType<Temperature>) command).toUnit(CELSIUS);
// Always set the new setpoint temperature as an overrule
// If no overrule time is given yet, set the overrule time to the configuration parameter
int time = nhcThermostat.getOverruletime();
if (time <= 0) {
time = overruleTime;
}
if (setpoint != null) {
nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
}
}
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_OVERRULETIME:
if (command instanceof DecimalType) {
int overruletime = ((DecimalType) command).intValue();
int overrule = nhcThermostat.getOverrule();
if (overruletime <= 0) {
overruletime = 0;
overrule = 0;
}
nhcThermostat.executeOverrule(overrule, overruletime);
}
updateStatus(ThingStatus.ONLINE);
break;
default:
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: channel unknown " + channelUID.getId());
}
}
@Override
public void initialize() {
NikoHomeControlThermostatConfig config = getConfig().as(NikoHomeControlThermostatConfig.class);
thermostatId = config.thermostatId;
overruleTime = config.overruleTime;
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
return;
}
// We need to do this in a separate thread because we may have to wait for the
// communication to become active
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: no connection with Niko Home Control, could not initialize thermostat "
+ thermostatId);
return;
}
nhcThermostat = nhcComm.getThermostats().get(thermostatId);
if (nhcThermostat == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: thermostatId does not match a thermostat in the controller "
+ thermostatId);
return;
}
nhcThermostat.setEventHandler(this);
updateProperties();
String thermostatLocation = nhcThermostat.getLocation();
if (thing.getLocation() == null) {
thing.setLocation(thermostatLocation);
}
thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
nhcThermostat.getOverrule(), nhcThermostat.getDemand());
logger.debug("Niko Home Control: thermostat intialized {}", thermostatId);
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
});
}
private void updateProperties() {
Map<String, String> properties = new HashMap<>();
if (nhcThermostat instanceof NhcThermostat2) {
NhcThermostat2 thermostat = (NhcThermostat2) nhcThermostat;
properties.put("model", thermostat.getModel());
properties.put("technology", thermostat.getTechnology());
}
thing.setProperties(properties);
}
@Override
public void thermostatEvent(int measured, int setpoint, int mode, int overrule, int demand) {
updateState(CHANNEL_MEASURED, new QuantityType<>(nhcThermostat.getMeasured() / 10.0, CELSIUS));
int overruletime = nhcThermostat.getRemainingOverruletime();
updateState(CHANNEL_OVERRULETIME, new DecimalType(overruletime));
// refresh the remaining time every minute
scheduleRefreshOverruletime(nhcThermostat);
// If there is an overrule temperature set, use this in the setpoint channel, otherwise use the original
// setpoint temperature
if (overruletime == 0) {
updateState(CHANNEL_SETPOINT, new QuantityType<>(setpoint / 10.0, CELSIUS));
} else {
updateState(CHANNEL_SETPOINT, new QuantityType<>(overrule / 10.0, CELSIUS));
}
updateState(CHANNEL_MODE, new DecimalType(mode));
updateState(CHANNEL_DEMAND, new DecimalType(demand));
updateStatus(ThingStatus.ONLINE);
}
/**
* Method to update state of overruletime channel every minute with remaining time.
*
* @param NhcThermostat object
*
*/
private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) {
cancelRefreshTimer();
if (nhcThermostat.getRemainingOverruletime() <= 0) {
return;
}
refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
int remainingTime = nhcThermostat.getRemainingOverruletime();
updateState(CHANNEL_OVERRULETIME, new DecimalType(remainingTime));
if (remainingTime <= 0) {
cancelRefreshTimer();
}
}, 1, 1, TimeUnit.MINUTES);
}
private void cancelRefreshTimer() {
ScheduledFuture<?> timer = refreshTimer;
if (timer != null) {
timer.cancel(true);
}
refreshTimer = null;
}
@Override
public void thermostatRemoved() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: thermostat has been removed from the controller " + thermostatId);
}
private void restartCommunication(NikoHomeControlCommunication nhcComm) {
// We lost connection but the connection object is there, so was correctly started.
// Try to restart communication.
nhcComm.restartCommunication();
// If still not active, take thing offline and return.
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: communication socket error");
return;
}
// Also put the bridge back online
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler != null) {
nhcBridgeHandler.bridgeOnline();
}
}
private @Nullable NikoHomeControlCommunication getCommunication() {
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for thermostat " + thermostatId);
return null;
}
NikoHomeControlCommunication nhcComm = nhcBridgeHandler.getCommunication();
return nhcComm;
}
private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
Bridge nhcBridge = getBridge();
if (nhcBridge == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for thermostat " + thermostatId);
return null;
}
NikoHomeControlBridgeHandler nhcBridgeHandler = (NikoHomeControlBridgeHandler) nhcBridge.getHandler();
return nhcBridgeHandler;
}
}

View File

@@ -0,0 +1,196 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcAction1;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAction2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcAction} class represents the action Niko Home Control communication object. It contains all fields
* representing a Niko Home Control action and has methods to trigger the action in Niko Home Control and receive action
* updates. Specific implementation are {@link NhcAction1} and {@link NhcAction2}.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NhcAction {
private final Logger logger = LoggerFactory.getLogger(NhcAction.class);
protected NikoHomeControlCommunication nhcComm;
protected String id;
protected String name;
protected ActionType type;
protected @Nullable String location;
protected volatile int state;
protected volatile int closeTime = 0;
protected volatile int openTime = 0;
@Nullable
private NhcActionEvent eventHandler;
protected NhcAction(String id, String name, ActionType type, @Nullable String location,
NikoHomeControlCommunication nhcComm) {
this.id = id;
this.name = name;
this.type = type;
this.location = location;
this.nhcComm = nhcComm;
}
/**
* This method should be called when an object implementing the {@NhcActionEvent} interface is initialized.
* It keeps a record of the event handler in that object so it can be updated when the action receives an update
* from the Niko Home Control IP-interface.
*
* @param eventHandler
*/
public void setEventHandler(NhcActionEvent eventHandler) {
this.eventHandler = eventHandler;
}
/**
* Get the id of the action.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Get name of action.
*
* @return action name
*/
public String getName() {
return name;
}
/**
* Get type of action identified.
* <p>
* ActionType can be RELAY (for simple light or socket switch), DIMMER, ROLLERSHUTTER, TRIGGER or GENERIC.
*
* @return {@link ActionType}
*/
public ActionType getType() {
return type;
}
/**
* Get location name of action.
*
* @return location name
*/
public @Nullable String getLocation() {
return location;
}
/**
* Get state of action.
* <p>
* State is a value between 0 and 100 for a dimmer or rollershutter.
* Rollershutter state is 0 for fully closed and 100 for fully open.
* State is 0 or 100 for a switch.
*
* @return action state
*/
public int getState() {
return state;
}
/**
* Set openTime and closeTime for rollershutter action.
* <p>
* Time is in seconds to fully open or close a rollershutter.
*
* @param openTime
* @param closeTime
*/
public void setShutterTimes(int openTime, int closeTime) {
this.openTime = openTime;
this.closeTime = closeTime;
}
/**
* Get openTime of action.
* <p>
* openTime is the time in seconds to fully open a rollershutter.
*
* @return action openTime
*/
public int getOpenTime() {
return openTime;
}
/**
* Get closeTime of action.
* <p>
* closeTime is the time in seconds to fully close a rollershutter.
*
* @return action closeTime
*/
public int getCloseTime() {
return closeTime;
}
protected void updateState() {
updateState(state);
}
protected void updateState(int state) {
NhcActionEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
logger.debug("Niko Home Control: update channel state for {} with {}", id, state);
eventHandler.actionEvent(state);
}
}
/**
* Method called when action is removed from the Niko Home Control Controller.
*/
public void actionRemoved() {
logger.warn("Niko Home Control: action removed {}, {}", id, name);
NhcActionEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.actionRemoved();
}
}
/**
* Sets state of action. This method is implemented in {@link NhcAction1} and {@link NhcAction2}.
*
* @param state - The allowed values depend on the action type.
* switch action: 0 or 100
* dimmer action: between 0 and 100
* rollershutter action: between 0 and 100
*/
public abstract void setState(int state);
/**
* Sends action to Niko Home Control. This method is implemented in {@link NhcAction1} and {@link NhcAction2}.
*
* @param command - The allowed values depend on the action type.
* switch action: On or Off
* dimmer action: between 0 and 100, On or Off
* rollershutter action: between 0 and 100, Up, Down or Stop
*/
public abstract void execute(String command);
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link NhcActionEvent} interface is used to pass action events received from the Niko Home Control controller to
* the consuming client. It is designed to pass events to openHAB handlers that implement this interface. Because of
* the design, the org.openhab.binding.nikohomecontrol.internal.protocol package can be extracted and used independent
* of openHAB.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcActionEvent {
/**
* This method is called when an action event is received from the Niko Home Control controller.
*
* @param state
*/
public void actionEvent(int state);
/**
* Called to indicate the action has been removed from the Niko Home Control controller.
*
*/
public void actionRemoved();
}

View File

@@ -0,0 +1,97 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol;
import java.net.InetAddress;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link NhcControllerEvent} interface is used to get configuration information and to pass alarm or notice events
* received from the Niko Home Control controller to the consuming client. It is designed to pass events to openHAB
* handlers that implement this interface. Because of the design, the
* org.openhab.binding.nikohomecontrol.internal.protocol package can be extracted and used independent of openHAB.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcControllerEvent {
/**
* Get the IP-address of the Niko Home Control IP-interface.
*
* @return the addr
*/
public default @Nullable InetAddress getAddr() {
return null;
}
/**
* Get the listening port of the Niko Home Control IP-interface.
*
* @return the port
*/
public default int getPort() {
return 0;
}
/**
* Get the profile for the hobby API in the Niko Home Control II system.
*
* @return the profile
*/
public default String getProfile() {
return "";
}
/**
* Get the JWT Token of the Niko Home Control II system.
*
* @return the token
*/
public default String getToken() {
return "";
}
/**
* Called to indicate the connection with the Niko Home Control Controller is offline.
*
*/
public void controllerOffline();
/**
* Called to indicate the connection with the Niko Home Control Controller is online.
*
*/
public void controllerOnline();
/**
* This method is called when an alarm event is received from the Niko Home Control controller.
*
* @param alarmText
*/
public void alarmEvent(String alarmText);
/**
* This method is called when a notice event is received from the Niko Home Control controller.
*
* @param alarmText
*/
public void noticeEvent(String noticeText);
/**
* This method is called when properties are updated from from the Niko Home Control controller.
*/
public void updatePropertiesEvent();
}

View File

@@ -0,0 +1,124 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcEnergyMeter2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcEnergyMeter} class represents the energyMeters metering Niko Home Control communication object. It
* contains all
* fields representing a Niko Home Control energyMeters meter and has methods to receive energyMeters usage information.
* A specific
* implementation is {@link NhcEnergyMeter2}.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NhcEnergyMeter {
private final Logger logger = LoggerFactory.getLogger(NhcEnergyMeter.class);
protected NikoHomeControlCommunication nhcComm;
protected String id;
protected String name;
// This can be null as long as we do not receive power readings
protected volatile @Nullable Integer power = null;
private @Nullable NhcEnergyMeterEvent eventHandler;
protected NhcEnergyMeter(String id, String name, NikoHomeControlCommunication nhcComm) {
this.id = id;
this.name = name;
this.nhcComm = nhcComm;
}
/**
* Update all values of the energyMeters meter without touching the energyMeters meter definition (id, name) and
* without changing the ThingHandler callback.
*
* @param power current power consumption/production in W (positive for consumption)
*/
public void updateState(int power) {
NhcEnergyMeterEvent handler = eventHandler;
if (handler != null) {
logger.debug("Niko Home Control: update channel for {}", id);
handler.energyMeterEvent(power);
}
}
/**
* Method called when energyMeters meter is removed from the Niko Home Control Controller.
*/
public void energyMeterRemoved() {
logger.warn("Niko Home Control: action removed {}, {}", id, name);
NhcEnergyMeterEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.energyMeterRemoved();
}
}
/**
* This method should be called when an object implementing the {@NhcEnergyMeterEvent} interface is initialized.
* It keeps a record of the event handler in that object so it can be updated when the action receives an update
* from the Niko Home Control IP-interface.
*
* @param eventHandler
*/
public void setEventHandler(NhcEnergyMeterEvent eventHandler) {
this.eventHandler = eventHandler;
}
/**
* Get the id of the energyMeters meter.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Get name of the energyMeters meter.
*
* @return energyMeters meter name
*/
public String getName() {
return name;
}
/**
* @return the power in W (positive for consumption, negative for production), return null if no reading received
* yet
*/
public @Nullable Integer getPower() {
return power;
}
/**
* @param power the power to set in W (positive for consumption, negative for production), null if an empty reading
* was received
*/
public void setPower(@Nullable Integer power) {
this.power = power;
NhcEnergyMeterEvent handler = eventHandler;
if (handler != null) {
logger.debug("Niko Home Control: update power channel for {} with {}", id, power);
handler.energyMeterEvent(power);
}
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link NhcEnergyMeterEvent} interface is used to pass energyMeters meter events received from the Niko Home
* Control
* controller to the consuming client. It is designed to pass events to openHAB handlers that implement this interface.
* Because of the design, the org.openhab.binding.nikohomecontrol.internal.protocol package can be extracted and used
* independent of openHAB.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcEnergyMeterEvent {
/**
* This method is called when an energyMeters meter event is received from the Niko Home Control controller.
*
* @param power current power consumption/production in W (positive for consumption), null for an empty reading
*/
public void energyMeterEvent(@Nullable Integer power);
/**
* Called to indicate the energyMeters meter has been removed from the Niko Home Control controller.
*
*/
public void energyMeterRemoved();
}

View File

@@ -0,0 +1,319 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcThermostat1;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcThermostat2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcThermostat} class represents the thermostat Niko Home Control communication object. It contains all
* fields representing a Niko Home Control thermostat and has methods to set the thermostat in Niko Home Control and
* receive thermostat updates. Specific implementation are {@link NhcThermostat1} and {@link NhcThermostat2}.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NhcThermostat {
private final Logger logger = LoggerFactory.getLogger(NhcThermostat.class);
protected NikoHomeControlCommunication nhcComm;
protected String id;
protected String name;
protected @Nullable String location;
protected volatile int measured;
protected volatile int setpoint;
protected volatile int mode;
protected volatile int overrule;
protected volatile int overruletime;
protected volatile int ecosave;
protected volatile int demand;
private @Nullable LocalDateTime overruleStart;
private @Nullable NhcThermostatEvent eventHandler;
protected NhcThermostat(String id, String name, @Nullable String location, NikoHomeControlCommunication nhcComm) {
this.id = id;
this.name = name;
this.location = location;
this.nhcComm = nhcComm;
}
/**
* Update all values of the thermostat without touching the thermostat definition (id, name and location) and
* without changing the ThingHandler callback.
*
* @param measured current temperature in 0.1°C multiples
* @param setpoint the setpoint temperature in 0.1°C multiples
* @param mode thermostat mode 0 = day, 1 = night, 2 = eco, 3 = off, 4 = cool, 5 = prog1, 6 = prog2, 7 =
* prog3
* @param overrule the overrule temperature in 0.1°C multiples
* @param overruletime in minutes
* @param ecosave
* @param demand 0 if no demand, > 0 if heating, < 0 if cooling
*/
public void updateState(int measured, int setpoint, int mode, int overrule, int overruletime, int ecosave,
int demand) {
setMeasured(measured);
setSetpoint(setpoint);
setMode(mode);
setOverrule(overrule);
setOverruletime(overruletime);
setEcosave(ecosave);
setDemand(demand);
updateChannels();
}
/**
* Update overrule values of the thermostat without touching the thermostat definition (id, name and location) and
* without changing the ThingHandler callback.
*
* @param overrule the overrule temperature in 0.1°C multiples
* @param overruletime in minutes
*/
public void updateState(int overrule, int overruletime) {
setOverrule(overrule);
setOverruletime(overruletime);
updateChannels();
}
/**
* Update overrule values of the thermostat without touching the thermostat definition (id, name and location) and
* without changing the ThingHandler callback.
*
* @param overrule the overrule temperature in 0.1°C multiples
* @param overruletime in minutes
*/
public void updateState(int mode) {
setMode(mode);
updateChannels();
}
/**
* Method called when thermostat is removed from the Niko Home Control Controller.
*/
public void thermostatRemoved() {
logger.warn("Niko Home Control: action removed {}, {}", id, name);
NhcThermostatEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.thermostatRemoved();
}
}
private void updateChannels() {
NhcThermostatEvent handler = eventHandler;
if (handler != null) {
logger.debug("Niko Home Control: update channels for {}", id);
handler.thermostatEvent(measured, setpoint, mode, overrule, demand);
}
}
/**
* This method should be called when an object implementing the {@NhcThermostatEvent} interface is initialized.
* It keeps a record of the event handler in that object so it can be updated when the action receives an update
* from the Niko Home Control IP-interface.
*
* @param eventHandler
*/
public void setEventHandler(NhcThermostatEvent eventHandler) {
this.eventHandler = eventHandler;
}
/**
* Get the id of the thermostat.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Get name of thermostat.
*
* @return thermostat name
*/
public String getName() {
return name;
}
/**
* Get location name of action.
*
* @return location name
*/
public @Nullable String getLocation() {
return location;
}
/**
* Get measured temperature.
*
* @return measured temperature in 0.1°C multiples
*/
public int getMeasured() {
return measured;
}
private void setMeasured(int measured) {
this.measured = measured;
}
/**
* @return the setpoint temperature in 0.1°C multiples
*/
public int getSetpoint() {
return setpoint;
}
private void setSetpoint(int setpoint) {
this.setpoint = setpoint;
}
/**
* Get the thermostat mode.
*
* @return the mode:
* 0 = day, 1 = night, 2 = eco, 3 = off, 4 = cool, 5 = prog 1, 6 = prog 2, 7 = prog 3
*/
public int getMode() {
return mode;
}
private void setMode(int mode) {
this.mode = mode;
}
/**
* Get the overrule temperature.
*
* @return the overrule temperature in 0.1°C multiples
*/
public int getOverrule() {
if (overrule > 0) {
return overrule;
} else {
return setpoint;
}
}
private void setOverrule(int overrule) {
this.overrule = overrule;
}
/**
* Get the duration for an overrule temperature
*
* @return the overruletime in minutes
*/
public int getOverruletime() {
return overruletime;
}
/**
* Set the duration for an overrule temperature
*
* @param overruletime the overruletime in minutes
*/
private void setOverruletime(int overruletime) {
if (overruletime <= 0) {
stopOverrule();
} else if (overruletime != this.overruletime) {
startOverrule();
}
this.overruletime = overruletime;
}
/**
* @return the ecosave mode
*/
public int getEcosave() {
return ecosave;
}
/**
* @param ecosave the ecosave mode to set
*/
private void setEcosave(int ecosave) {
this.ecosave = ecosave;
}
/**
* @return the heating/cooling demand: 0 if no demand, >0 if heating, <0 if cooling
*/
public int getDemand() {
return demand;
}
/**
* @param demand set the heating/cooling demand
*/
private void setDemand(int demand) {
this.demand = demand;
}
/**
* Sends thermostat mode to Niko Home Control. This method is implemented in {@link NhcThermostat1} and
* {@link NhcThermostat2}.
*
* @param mode
*/
public abstract void executeMode(int mode);
/**
* Sends thermostat setpoint to Niko Home Control. This method is implemented in {@link NhcThermostat1} and
* {@link NhcThermostat2}.
*
* @param overrule temperature to overrule the setpoint in 0.1°C multiples
* @param time time duration in min for overrule
*/
public abstract void executeOverrule(int overrule, int overruletime);
/**
* @return remaining overrule time in minutes
*/
public int getRemainingOverruletime() {
if (overruleStart == null) {
return 0;
} else {
// overruletime time max 23h59min, therefore can safely cast to int
return overruletime - (int) ChronoUnit.MINUTES.between(overruleStart, LocalDateTime.now());
}
}
/**
* Start a new overrule, this method is used to be able to calculate the remaining overrule time
*/
private void startOverrule() {
overruleStart = LocalDateTime.now();
}
/**
* Reset overrule start
*/
private void stopOverrule() {
overruleStart = null;
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link NhcThermostatEvent} interface is used to pass thermostat events received from the Niko Home Control
* controller to
* the consuming client. It is designed to pass events to openHAB handlers that implement this interface. Because of
* the design, the org.openhab.binding.nikohomecontrol.internal.protocol package can be extracted and used independent
* of openHAB.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcThermostatEvent {
/**
* This method is called when thermostat event is received from the Niko Home Control controller.
*
* @param measured current temperature in 0.1°C multiples
* @param setpoint the setpoint temperature in 0.1°C multiples
* @param mode thermostat mode 0 = day, 1 = night, 2 = eco, 3 = off, 4 = cool, 5 = prog1, 6 = prog2, 7 = prog3
* @param overrule the overrule temperature in 0.1°C multiples
* @param demand 0 if no demand, > 0 if heating, < 0 if cooling
*/
public void thermostatEvent(int measured, int setpoint, int mode, int overrule, int demand);
/**
* Called to indicate the thermostat has been removed from the Niko Home Control controller.
*
*/
public void thermostatRemoved();
}

View File

@@ -0,0 +1,145 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NikoHomeControlCommunication1;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NikoHomeControlCommunication2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlCommunication} class is an abstract class representing the communication objects with the
* Niko Home Control System. {@link NikoHomeControlCommunication1} or {@link NikoHomeControlCommunication2} should be
* used for the respective version of Niko Home Control.
* <ul>
* <li>Start and stop communication with the Niko Home Control System.
* <li>Read all setup and status information from the Niko Home Control Controller.
* <li>Execute Niko Home Control commands.
* <li>Listen to events from Niko Home Control.
* </ul>
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NikoHomeControlCommunication {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlCommunication.class);
protected final Map<String, NhcAction> actions = new ConcurrentHashMap<>();
protected final Map<String, NhcThermostat> thermostats = new ConcurrentHashMap<>();
protected final Map<String, NhcEnergyMeter> energyMeters = new ConcurrentHashMap<>();
protected final NhcControllerEvent handler;
protected NikoHomeControlCommunication(NhcControllerEvent handler) {
this.handler = handler;
}
/**
* Start Communication with Niko Home Control system.
*/
public abstract void startCommunication();
/**
* Stop Communication with Niko Home Control system.
*/
public abstract void stopCommunication();
/**
* Close and restart communication with Niko Home Control system.
*/
public synchronized void restartCommunication() {
stopCommunication();
logger.debug("Niko Home Control: restart communication from thread {}", Thread.currentThread().getId());
startCommunication();
}
/**
* Method to check if communication with Niko Home Control is active.
*
* @return True if active
*/
public abstract boolean communicationActive();
/**
* Return all actions in the Niko Home Control Controller.
*
* @return <code>Map&ltString, {@link NhcAction}></code>
*/
public Map<String, NhcAction> getActions() {
return actions;
}
/**
* Return all thermostats in the Niko Home Control Controller.
*
* @return <code>Map&ltString, {@link NhcThermostat}></code>
*/
public Map<String, NhcThermostat> getThermostats() {
return thermostats;
}
/**
* Return all energyMeters meters in the Niko Home Control Controller.
*
* @return <code>Map&ltString, {@link NhcEnergyMeter}></code>
*/
public Map<String, NhcEnergyMeter> getEnergyMeters() {
return energyMeters;
}
/**
* Execute an action command by sending it to Niko Home Control.
*
* @param actionId
* @param value
*/
public abstract void executeAction(String actionId, String value);
/**
* Execute a thermostat command by sending it to Niko Home Control.
*
* @param thermostatId
* @param mode
*/
public abstract void executeThermostat(String thermostatId, String mode);
/**
* Execute a thermostat command by sending it to Niko Home Control.
*
* @param thermostatId
* @param overruleTemp
* @param overruleTime
*/
public abstract void executeThermostat(String thermostatId, int overruleTemp, int overruleTime);
/**
* Start retrieving energy meter data from Niko Home Control.
*
*/
public void startEnergyMeter(String energyMeterId) {
};
/**
* Stop retrieving energy meter data from Niko Home Control.
*
*/
public void stopEnergyMeter(String energyMeterId) {
};
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link NikoHomeControlConstants} class defines common constants used in the Niko Home Control communication.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlConstants {
// Action types abstracted from NhcI and NhcII action types
public static enum ActionType {
TRIGGER,
RELAY,
DIMMER,
ROLLERSHUTTER,
GENERIC
}
// switch and dimmer constants in the Nhc layer
public static final String NHCON = "On";
public static final String NHCOFF = "Off";
public static final String NHCTRIGGERED = "Triggered";
// rollershutter constants in the Nhc layer
public static final String NHCDOWN = "Down";
public static final String NHCUP = "Up";
public static final String NHCSTOP = "Stop";
// NhcII thermostat modes
public static final String[] THERMOSTATMODES = { "Day", "Night", "Eco", "Off", "Cool", "Prog1", "Prog2", "Prog3" };
}

View File

@@ -0,0 +1,154 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class {@link NikoHomeControlDiscover} is used to get the Niko Home Control IP-interface IP address for bridge
* discovery.
* <p>
* The constructor broadcasts a UDP packet with content 0x44 on port 10000.
* The Niko Home Control IP-interface responds to this UDP packet.
* The IP-address from the Niko Home Control IP-interface is then extracted from the response packet.
* The data content of the response packet is used as a unique identifier for the bridge.
*
* @author Mark Herwege - Initial Contribution
*/
/**
* @author Mark Herwege - Initial Contribution
*
*/
@NonNullByDefault
public final class NikoHomeControlDiscover {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlDiscover.class);
private InetAddress addr;
private String nhcBridgeId = "";
private boolean isNhcII;
/**
* Discover a Niko Home Control IP interface by broadcasting UDP packet 0x44 to port 10000. The IP interface will
* reply. The address of the IP interface is than derived from that response.
*
* @param broadcast Broadcast address of the network
* @throws IOException
*/
public NikoHomeControlDiscover(String broadcast) throws IOException {
final byte[] discoverBuffer = { (byte) 0x44 };
final InetAddress broadcastAddr = InetAddress.getByName(broadcast);
final int broadcastPort = 10000;
DatagramPacket discoveryPacket = new DatagramPacket(discoverBuffer, discoverBuffer.length, broadcastAddr,
broadcastPort);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
try (DatagramSocket datagramSocket = new DatagramSocket(null)) {
datagramSocket.setBroadcast(true);
datagramSocket.setSoTimeout(500);
datagramSocket.send(discoveryPacket);
while (true) {
datagramSocket.receive(packet);
logger.trace("Niko Home Control: bridge discovery response {}",
HexUtils.bytesToHex(Arrays.copyOf(packet.getData(), packet.getLength())));
if (isNhc(packet)) {
break;
}
}
addr = packet.getAddress();
setNhcBridgeId(packet);
setIsNhcII(packet);
logger.debug("Niko Home Control: IP address is {}, unique ID is {}", addr, nhcBridgeId);
}
}
/**
* @return the addr
*/
public InetAddress getAddr() {
return addr;
}
/**
* @return the nhcBridgeId
*/
public String getNhcBridgeId() {
return nhcBridgeId;
}
/**
* Check if the UDP packet comes from a Niko Home Control controller. The response should start with 0x44.
*
* @param packet
* @return true if packet is from a Niko Home Control controller
*/
private boolean isNhc(DatagramPacket packet) {
byte[] packetData = packet.getData();
if ((packet.getLength() > 2) && (packetData[0] == 0x44)) {
return true;
}
return false;
}
/**
* Retrieves a unique ID from the returned datagram packet received after sending the UDP discovery message.
*
* @param packet
*/
private void setNhcBridgeId(DatagramPacket packet) {
byte[] packetData = packet.getData();
int packetLength = packet.getLength();
packetLength = packetLength > 6 ? 6 : packetLength;
StringBuilder sb = new StringBuilder(packetLength);
for (int i = 0; i < packetLength; i++) {
sb.append(String.format("%02x", packetData[i]));
}
nhcBridgeId = sb.toString();
}
/**
* Checks if this is a NHC II Connected Controller
*
* @param packet
*/
private void setIsNhcII(DatagramPacket packet) {
byte[] packetData = packet.getData();
int packetLength = packet.getLength();
// The 16th byte in the packet is 2 for a NHC II Connected Controller
if ((packetLength >= 16) && (packetData[15] >= 2)) {
isNhcII = true;
} else {
isNhcII = false;
}
}
/**
* Test if the installation is a Niko Home Control II installation
*
* @return true if this is a Niko Home Control II installation
*/
public boolean isNhcII() {
return isNhcII;
}
}

View File

@@ -0,0 +1,304 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc1;
import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcAction1} class represents the action Niko Home Control I communication object. It contains all fields
* representing a Niko Home Control action and has methods to trigger the action in Niko Home Control and receive action
* updates.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcAction1 extends NhcAction {
private final Logger logger = LoggerFactory.getLogger(NhcAction1.class);
@FunctionalInterface
private interface Action {
void execute();
}
private ScheduledExecutorService scheduler;
private volatile @Nullable Action rollershutterTask;
private volatile @Nullable ScheduledFuture<?> rollershutterStopTask;
private volatile @Nullable ScheduledFuture<?> rollershutterMovingFlagTask;
private volatile boolean filterEvent = false; // flag to filter first event from rollershutter on percent move to
// avoid wrong position update
private volatile boolean rollershutterMoving = false; // flag to indicate if rollershutter is currently moving
private volatile boolean waitForEvent = false; // flag to wait for position update rollershutter before doing next
// move
NhcAction1(String id, String name, ActionType type, @Nullable String location, NikoHomeControlCommunication nhcComm,
ScheduledExecutorService scheduler) {
super(id, name, type, location, nhcComm);
this.scheduler = scheduler;
}
/**
* Sets state of action. This is the version for Niko Home Control I.
*
* @param newState - The allowed values depend on the action type.
* switch action: 0 or 100
* dimmer action: between 0 and 100
* rollershutter action: between 0 and 100
*/
@Override
public void setState(int newState) {
if (getType() == ActionType.ROLLERSHUTTER) {
if (filterEvent) {
filterEvent = false;
logger.debug("Niko Home Control: filtered event {} for {}", newState, id);
return;
}
cancelRollershutterStop();
if (((newState == 0) || (newState == 100)) && (newState != state)) {
long duration = rollershutterMoveTime(state, newState);
setRollershutterMovingTrue(duration);
} else {
setRollershutterMovingFalse();
}
}
if (waitForEvent) {
logger.debug("Niko Home Control: received requested rollershutter {} position event {}", id, newState);
executeRollershutterTask();
} else {
state = newState;
updateState();
}
}
/**
* Sends action to Niko Home Control. This version is used for Niko Home Control I.
*
* @param command - The allowed values depend on the action type.
*/
@Override
public void execute(String command) {
logger.debug("Niko Home Control: execute action {} of type {} for {}", command, type, id);
String value = "";
switch (getType()) {
case GENERIC:
case TRIGGER:
case RELAY:
if (command.equals(NHCON)) {
value = "100";
} else {
value = "0";
}
nhcComm.executeAction(id, value);
break;
case DIMMER:
if (command.equals(NHCON)) {
value = "254";
} else if (command.equals(NHCOFF)) {
value = "255";
} else {
value = command;
}
nhcComm.executeAction(id, value);
break;
case ROLLERSHUTTER:
executeRollershutter(command);
break;
}
}
private void executeRollershutter(String command) {
if (logger.isTraceEnabled()) {
logger.trace("handleRollerShutterCommand: rollershutter {} command {}", id, command);
logger.trace("handleRollerShutterCommand: rollershutter {}, current position {}", id, state);
}
// first stop all current movement of rollershutter and wait until exact position is known
if (rollershutterMoving) {
if (logger.isTraceEnabled()) {
logger.trace("handleRollerShutterCommand: rollershutter {} moving, therefore stop", id);
}
rollershutterPositionStop();
}
// task to be executed once exact position received from Niko Home Control
rollershutterTask = () -> {
if (logger.isTraceEnabled()) {
logger.trace("handleRollerShutterCommand: rollershutter {} task running", id);
}
int currentValue = state;
if (command.equals(NHCDOWN)) {
executeRollershutterDown();
} else if (command.equals(NHCUP)) {
executeRollershutterUp();
} else if (command.equals(NHCSTOP)) {
executeRollershutterStop();
} else {
int newValue = 100 - Integer.parseInt(command);
if (logger.isTraceEnabled()) {
logger.trace("handleRollerShutterCommand: rollershutter {} percent command, current {}, new {}", id,
currentValue, newValue);
}
if (currentValue == newValue) {
return;
}
if ((newValue > 0) && (newValue < 100)) {
scheduleRollershutterStop(currentValue, newValue);
}
if (newValue < currentValue) {
executeRollershutterDown();
} else if (newValue > currentValue) {
executeRollershutterUp();
}
}
};
// execute immediately if not waiting for exact position
if (!waitForEvent) {
if (logger.isTraceEnabled()) {
logger.trace("handleRollerShutterCommand: rollershutter {} task executing immediately", id);
}
executeRollershutterTask();
}
}
private void executeRollershutterStop() {
nhcComm.executeAction(id, "253");
}
private void executeRollershutterDown() {
nhcComm.executeAction(id, "254");
}
private void executeRollershutterUp() {
nhcComm.executeAction(id, "255");
}
/**
* Method used to stop rollershutter when moving. This will then result in an exact position to be received, so next
* percentage movements could be done accurately.
*/
private void rollershutterPositionStop() {
if (logger.isTraceEnabled()) {
logger.trace("rollershutterPositionStop: rollershutter {} executing", id);
}
cancelRollershutterStop();
rollershutterTask = null;
filterEvent = false;
waitForEvent = true;
executeRollershutterStop();
}
private void executeRollershutterTask() {
if (logger.isTraceEnabled()) {
logger.trace("executeRollershutterTask: rollershutter {} task triggered", id);
}
waitForEvent = false;
Action action = rollershutterTask;
if (action != null) {
action.execute();
rollershutterTask = null;
}
}
/**
* Method used to schedule a rollershutter stop when moving. This allows stopping the rollershutter at a percent
* position.
*
* @param currentValue current percent position
* @param newValue new percent position
*
*/
private void scheduleRollershutterStop(int currentValue, int newValue) {
// filter first event for a rollershutter coming from Niko Home Control if moving to an intermediate
// position to avoid updating state to full open or full close
filterEvent = true;
long duration = rollershutterMoveTime(currentValue, newValue);
setRollershutterMovingTrue(duration);
if (logger.isTraceEnabled()) {
logger.trace("scheduleRollershutterStop: schedule rollershutter {} stop in {}ms", id, duration);
}
rollershutterStopTask = scheduler.schedule(() -> {
logger.trace("scheduleRollershutterStop: run rollershutter {} stop", id);
executeRollershutterStop();
}, duration, TimeUnit.MILLISECONDS);
}
private void cancelRollershutterStop() {
ScheduledFuture<?> stopTask = rollershutterStopTask;
if (stopTask != null) {
if (logger.isTraceEnabled()) {
logger.trace("cancelRollershutterStop: cancel rollershutter {} stop", id);
}
stopTask.cancel(true);
}
rollershutterStopTask = null;
filterEvent = false;
}
private void setRollershutterMovingTrue(long duration) {
if (logger.isTraceEnabled()) {
logger.trace("setRollershutterMovingTrue: rollershutter {} moving", id);
}
rollershutterMoving = true;
rollershutterMovingFlagTask = scheduler.schedule(() -> {
if (logger.isTraceEnabled()) {
logger.trace("setRollershutterMovingTrue: rollershutter {} stopped moving", id);
}
rollershutterMoving = false;
}, duration, TimeUnit.MILLISECONDS);
}
private void setRollershutterMovingFalse() {
if (logger.isTraceEnabled()) {
logger.trace("setRollershutterMovingFalse: rollershutter {} not moving", id);
}
rollershutterMoving = false;
ScheduledFuture<?> future = rollershutterMovingFlagTask;
if (future != null) {
future.cancel(true);
rollershutterMovingFlagTask = null;
}
}
private long rollershutterMoveTime(int currentValue, int newValue) {
int totalTime = (newValue > currentValue) ? getOpenTime() : getCloseTime();
long duration = Math.abs(newValue - currentValue) * totalTime * 10;
if (logger.isTraceEnabled()) {
logger.trace("rollershutterMoveTime: rollershutter {} move time {}", id, duration);
}
return duration;
}
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc1;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link NhcLocation1} class represents the location Niko Home Control communication object. It contains all fields
* representing a Niko Home Control location.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public final class NhcLocation1 {
private final String name;
public NhcLocation1(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc1;
/**
* Class {@link NhcMessageBase1} used as base class for output from gson for cmd or event feedback from Niko Home
* Control. This class only contains the common base fields required for the deserializer
* {@link NikoHomeControlMessageDeserializer1} to select the specific formats implemented in {@link NhcMessageMap1},
* {@link NhcMessageListMap1}, {@link NhcMessageCmd1}.
* <p>
*
* @author Mark Herwege - Initial Contribution
*/
abstract class NhcMessageBase1 {
private String cmd;
private String event;
String getCmd() {
return cmd;
}
void setCmd(String cmd) {
this.cmd = cmd;
}
String getEvent() {
return event;
}
void setEvent(String event) {
this.event = event;
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc1;
/**
* Class {@link NhcMessageCmd1} used as input to gson to send commands to Niko Home Control. Extends
* {@link NhcMessageBase1}.
* <p>
* Example: <code>{"cmd":"executeactions","id":1,"value1":0}</code>
*
* @author Mark Herwege - Initial Contribution
*/
@SuppressWarnings("unused")
class NhcMessageCmd1 extends NhcMessageBase1 {
private int id;
private int value1;
private int value2;
private int value3;
private int mode;
private int overrule;
private String overruletime;
NhcMessageCmd1(String cmd) {
super.setCmd(cmd);
}
NhcMessageCmd1(String cmd, int id) {
this(cmd);
this.id = id;
}
NhcMessageCmd1(String cmd, int id, int value1) {
this(cmd, id);
this.value1 = value1;
}
NhcMessageCmd1(String cmd, int id, int value1, int value2, int value3) {
this(cmd, id, value1);
this.value2 = value2;
this.value3 = value3;
}
NhcMessageCmd1 withMode(int mode) {
this.mode = mode;
return this;
}
NhcMessageCmd1 withOverrule(int overrule) {
this.overrule = overrule;
return this;
}
NhcMessageCmd1 withOverruletime(String overruletime) {
this.overruletime = overruletime;
return this;
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc1;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Class {@link NhcMessageListMap1} used as output from gson for cmd or event feedback from Niko Home Control where the
* data part is enclosed by [] and contains a list of json strings. Extends {@link NhcMessageBase1}.
* <p>
* Example: <code>{"cmd":"listactions","data":[{"id":1,"name":"Garage","type":1,"location":1,"value1":0},
* {"id":25,"name":"Frontdoor","type":2,"location":2,"value1":0}]}</code>
*
* @author Mark Herwege - Initial Contribution
*/
class NhcMessageListMap1 extends NhcMessageBase1 {
private List<Map<String, String>> data = new ArrayList<>();
List<Map<String, String>> getData() {
return data;
}
void setData(List<Map<String, String>> data) {
this.data = data;
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc1;
import java.util.HashMap;
import java.util.Map;
/**
* Class {@link NhcMessageMap1} used as output from gson for cmd or event feedback from Niko Home Control where the
* data part is a simple json string. Extends {@link NhcMessageBase1}.
* <p>
* Example: <code>{"cmd":"executeactions", "data":{"error":0}}</code>
*
* @author Mark Herwege - Initial Contribution
*/
class NhcMessageMap1 extends NhcMessageBase1 {
private Map<String, String> data = new HashMap<>();
Map<String, String> getData() {
return data;
}
void setData(Map<String, String> data) {
this.data = data;
}
}

View File

@@ -0,0 +1,116 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc1;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link NhcSystemInfo1} class represents the systeminfo Niko Home Control communication object. It contains all
* Niko Home Control system data received from the Niko Home Control controller when initializing the connection.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public final class NhcSystemInfo1 {
private String swVersion = "";
private String api = "";
private String time = "";
private String language = "";
private String currency = "";
private String units = "";
private String dst = "";
private String tz = "";
private String lastEnergyErase = "";
private String lastConfig = "";
public String getSwVersion() {
return swVersion;
}
void setSwVersion(String swVersion) {
this.swVersion = swVersion;
}
public String getApi() {
return api;
}
void setApi(String api) {
this.api = api;
}
public String getTime() {
return time;
}
void setTime(String time) {
this.time = time;
}
public String getLanguage() {
return language;
}
void setLanguage(String language) {
this.language = language;
}
public String getCurrency() {
return currency;
}
void setCurrency(String currency) {
this.currency = currency;
}
public String getUnits() {
return units;
}
void setUnits(String units) {
this.units = units;
}
public String getDst() {
return dst;
}
void setDst(String dst) {
this.dst = dst;
}
public String getTz() {
return tz;
}
void setTz(String tz) {
this.tz = tz;
}
public String getLastEnergyErase() {
return lastEnergyErase;
}
void setLastEnergyErase(String lastEnergyErase) {
this.lastEnergyErase = lastEnergyErase;
}
public String getLastConfig() {
return lastConfig;
}
void setLastConfig(String lastConfig) {
this.lastConfig = lastConfig;
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc1;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcThermostat1} class represents the thermostat Niko Home Control I communication object. It contains all
* fields representing a Niko Home Control thermostat and has methods to set the thermostat in Niko Home Control and
* receive thermostat updates.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcThermostat1 extends NhcThermostat {
private final Logger logger = LoggerFactory.getLogger(NhcThermostat1.class);
NhcThermostat1(String id, String name, @Nullable String location, NikoHomeControlCommunication nhcComm) {
super(id, name, location, nhcComm);
}
/**
* Sends thermostat mode to Niko Home Control.
*
* @param mode
*/
@Override
public void executeMode(int mode) {
logger.debug("Niko Home Control: execute thermostat mode {} for {}", mode, id);
nhcComm.executeThermostat(id, Integer.toString(mode));
}
/**
* Sends thermostat setpoint to Niko Home Control.
*
* @param overrule temperature to overrule the setpoint in 0.1°C multiples
* @param time time duration in min for overrule
*/
@Override
public void executeOverrule(int overrule, int overruletime) {
logger.debug("Niko Home Control: execute thermostat overrule {} during {} min for {}", overrule, overruletime,
id);
nhcComm.executeThermostat(id, overrule, overruletime);
}
}

View File

@@ -0,0 +1,538 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc1;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcControllerEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
/**
* The {@link NikoHomeControlCommunication1} class is able to do the following tasks with Niko Home Control I
* systems:
* <ul>
* <li>Start and stop TCP socket connection with Niko Home Control IP-interface.
* <li>Read all setup and status information from the Niko Home Control Controller.
* <li>Execute Niko Home Control commands.
* <li>Listen to events from Niko Home Control.
* </ul>
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication {
private Logger logger = LoggerFactory.getLogger(NikoHomeControlCommunication1.class);
private final NhcSystemInfo1 systemInfo = new NhcSystemInfo1();
private final Map<String, NhcLocation1> locations = new ConcurrentHashMap<>();
private @Nullable Socket nhcSocket;
private @Nullable PrintWriter nhcOut;
private @Nullable BufferedReader nhcIn;
private volatile boolean listenerStopped;
private volatile boolean nhcEventsRunning;
private ScheduledExecutorService scheduler;
// We keep only 2 gson adapters used to serialize and deserialize all messages sent and received
protected final Gson gsonOut = new Gson();
protected Gson gsonIn;
/**
* Constructor for Niko Home Control I communication object, manages communication with
* Niko Home Control IP-interface.
*
*/
public NikoHomeControlCommunication1(NhcControllerEvent handler, ScheduledExecutorService scheduler) {
super(handler);
this.scheduler = scheduler;
// When we set up this object, we want to get the proper gson adapter set up once
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(NhcMessageBase1.class, new NikoHomeControlMessageDeserializer1());
gsonIn = gsonBuilder.create();
}
@Override
public synchronized void startCommunication() {
try {
for (int i = 1; nhcEventsRunning && (i <= 5); i++) {
// the events listener thread did not finish yet, so wait max 5000ms before restarting
Thread.sleep(1000);
}
if (nhcEventsRunning) {
logger.debug("Niko Home Control: starting but previous connection still active after 5000ms");
throw new IOException();
}
InetAddress addr = handler.getAddr();
int port = handler.getPort();
Socket socket = new Socket(addr, port);
nhcSocket = socket;
nhcOut = new PrintWriter(socket.getOutputStream(), true);
nhcIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
logger.debug("Niko Home Control: connected via local port {}", socket.getLocalPort());
// initialize all info in local fields
initialize();
// Start Niko Home Control event listener. This listener will act on all messages coming from
// IP-interface.
(new Thread(this::runNhcEvents)).start();
} catch (IOException | InterruptedException e) {
logger.warn("Niko Home Control: error initializing communication");
stopCommunication();
handler.controllerOffline();
}
}
/**
* Cleanup socket when the communication with Niko Home Control IP-interface is closed.
*
*/
@Override
public synchronized void stopCommunication() {
listenerStopped = true;
Socket socket = nhcSocket;
if (socket != null) {
try {
socket.close();
} catch (IOException ignore) {
// ignore IO Error when trying to close the socket if the intention is to close it anyway
}
}
nhcSocket = null;
logger.debug("Niko Home Control: communication stopped");
}
@Override
public boolean communicationActive() {
return (nhcSocket != null);
}
/**
* Method that handles inbound communication from Niko Home Control, to be called on a separate thread.
* <p>
* The thread listens to the TCP socket opened at instantiation of the {@link NikoHomeControlCommunication} class
* and interprets all inbound json messages. It triggers state updates for active channels linked to the Niko Home
* Control actions. It is started after initialization of the communication.
*
*/
private void runNhcEvents() {
String nhcMessage;
logger.debug("Niko Home Control: listening for events");
listenerStopped = false;
nhcEventsRunning = true;
try {
while (!listenerStopped & (nhcIn != null) & ((nhcMessage = nhcIn.readLine()) != null)) {
readMessage(nhcMessage);
}
} catch (IOException e) {
if (!listenerStopped) {
nhcEventsRunning = false;
// this is a socket error, not a communication stop triggered from outside this runnable
logger.warn("Niko Home Control: IO error in listener");
// the IO has stopped working, so we need to close cleanly and try to restart
restartCommunication();
return;
}
} finally {
nhcEventsRunning = false;
}
nhcEventsRunning = false;
// this is a stop from outside the runnable, so just log it and stop
logger.debug("Niko Home Control: event listener thread stopped");
}
/**
* After setting up the communication with the Niko Home Control IP-interface, send all initialization messages.
* <p>
* Only at first initialization, also set the return values. Otherwise use the runnable to get updated values.
* While communication is set up for thermostats, tariff data and alarms, only info from locations and actions
* is used beyond this point in openHAB. All other elements are for future extensions.
*
* @throws IOException
*/
private void initialize() throws IOException {
sendAndReadMessage("systeminfo");
sendAndReadMessage("startevents");
sendAndReadMessage("listlocations");
sendAndReadMessage("listactions");
sendAndReadMessage("listthermostat");
sendAndReadMessage("listthermostatHVAC");
sendAndReadMessage("readtariffdata");
sendAndReadMessage("getalarms");
}
@SuppressWarnings("null")
private void sendAndReadMessage(String command) throws IOException {
sendMessage(new NhcMessageCmd1(command));
readMessage(nhcIn.readLine());
}
/**
* Called by other methods to send json cmd to Niko Home Control.
*
* @param nhcMessage
*/
@SuppressWarnings("null")
private synchronized void sendMessage(Object nhcMessage) {
String json = gsonOut.toJson(nhcMessage);
logger.debug("Niko Home Control: send json {}", json);
nhcOut.println(json);
if (nhcOut.checkError()) {
logger.warn("Niko Home Control: error sending message, trying to restart communication");
restartCommunication();
// retry sending after restart
logger.debug("Niko Home Control: resend json {}", json);
nhcOut.println(json);
if (nhcOut.checkError()) {
logger.warn("Niko Home Control: error resending message");
handler.controllerOffline();
}
}
}
/**
* Method that interprets all feedback from Niko Home Control and calls appropriate handling methods.
*
* @param nhcMessage message read from Niko Home Control.
*/
private void readMessage(@Nullable String nhcMessage) {
logger.debug("Niko Home Control: received json {}", nhcMessage);
try {
NhcMessageBase1 nhcMessageGson = gsonIn.fromJson(nhcMessage, NhcMessageBase1.class);
String cmd = nhcMessageGson.getCmd();
String event = nhcMessageGson.getEvent();
if ("systeminfo".equals(cmd)) {
cmdSystemInfo(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("startevents".equals(cmd)) {
cmdStartEvents(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("listlocations".equals(cmd)) {
cmdListLocations(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("listactions".equals(cmd)) {
cmdListActions(((NhcMessageListMap1) nhcMessageGson).getData());
} else if (("listthermostat").equals(cmd)) {
cmdListThermostat(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("executeactions".equals(cmd)) {
cmdExecuteActions(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("executethermostat".equals(cmd)) {
cmdExecuteThermostat(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("listactions".equals(event)) {
eventListActions(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("listthermostat".equals(event)) {
eventListThermostat(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("getalarms".equals(event)) {
eventGetAlarms(((NhcMessageMap1) nhcMessageGson).getData());
} else {
logger.debug("Niko Home Control: not acted on json {}", nhcMessage);
}
} catch (JsonParseException e) {
logger.debug("Niko Home Control: not acted on unsupported json {}", nhcMessage);
}
}
private synchronized void cmdSystemInfo(Map<String, String> data) {
logger.debug("Niko Home Control: systeminfo");
if (data.containsKey("swversion")) {
systemInfo.setSwVersion(data.get("swversion"));
}
if (data.containsKey("api")) {
systemInfo.setApi(data.get("api"));
}
if (data.containsKey("time")) {
systemInfo.setTime(data.get("time"));
}
if (data.containsKey("language")) {
systemInfo.setLanguage(data.get("language"));
}
if (data.containsKey("currency")) {
systemInfo.setCurrency(data.get("currency"));
}
if (data.containsKey("units")) {
systemInfo.setUnits(data.get("units"));
}
if (data.containsKey("DST")) {
systemInfo.setDst(data.get("DST"));
}
if (data.containsKey("TZ")) {
systemInfo.setTz(data.get("TZ"));
}
if (data.containsKey("lastenergyerase")) {
systemInfo.setLastEnergyErase(data.get("lastenergyerase"));
}
if (data.containsKey("lastconfig")) {
systemInfo.setLastConfig(data.get("lastconfig"));
}
}
/**
* Return the object with system info as read from the Niko Home Control controller.
*
* @return the systemInfo
*/
public synchronized NhcSystemInfo1 getSystemInfo() {
return systemInfo;
}
private void cmdStartEvents(Map<String, String> data) {
int errorCode = Integer.parseInt(data.get("error"));
if (errorCode == 0) {
logger.debug("Niko Home Control: start events success");
} else {
logger.warn("Niko Home Control: error code {} returned on start events", errorCode);
}
}
private void cmdListLocations(List<Map<String, String>> data) {
logger.debug("Niko Home Control: list locations");
locations.clear();
for (Map<String, String> location : data) {
String id = location.get("id");
String name = location.get("name");
NhcLocation1 nhcLocation1 = new NhcLocation1(name);
locations.put(id, nhcLocation1);
}
}
private void cmdListActions(List<Map<String, String>> data) {
logger.debug("Niko Home Control: list actions");
for (Map<String, String> action : data) {
String id = action.get("id");
int state = Integer.parseInt(action.get("value1"));
String value2 = action.get("value2");
int closeTime = ((value2 == null) || value2.isEmpty() ? 0 : Integer.parseInt(value2));
String value3 = action.get("value3");
int openTime = ((value3 == null) || value3.isEmpty() ? 0 : Integer.parseInt(value3));
if (!actions.containsKey(id)) {
// Initial instantiation of NhcAction class for action object
String name = action.get("name");
String type = action.get("type");
ActionType actionType = ActionType.GENERIC;
switch (type) {
case "0":
actionType = ActionType.TRIGGER;
break;
case "1":
actionType = ActionType.RELAY;
break;
case "2":
actionType = ActionType.DIMMER;
break;
case "4":
case "5":
actionType = ActionType.ROLLERSHUTTER;
break;
default:
logger.debug("Niko Home Control: unknown action type {} for action {}", type, id);
continue;
}
String locationId = action.get("location");
String location = "";
if (!locationId.isEmpty()) {
location = locations.get(locationId).getName();
}
NhcAction nhcAction = new NhcAction1(id, name, actionType, location, this, scheduler);
if (actionType == ActionType.ROLLERSHUTTER) {
nhcAction.setShutterTimes(openTime, closeTime);
}
nhcAction.setState(state);
actions.put(id, nhcAction);
} else {
// Action object already exists, so only update state.
// If we would re-instantiate action, we would lose pointer back from action to thing handler that was
// set in thing handler initialize().
actions.get(id).setState(state);
}
}
}
private void cmdListThermostat(List<Map<String, String>> data) {
logger.debug("Niko Home Control: list thermostats");
for (Map<String, String> thermostat : data) {
String id = thermostat.get("id");
int measured = Integer.parseInt(thermostat.get("measured"));
int setpoint = Integer.parseInt(thermostat.get("setpoint"));
int mode = Integer.parseInt(thermostat.get("mode"));
int overrule = Integer.parseInt(thermostat.get("overrule"));
// overruletime received in "HH:MM" format
String[] overruletimeStrings = thermostat.get("overruletime").split(":");
int overruletime = 0;
if (overruletimeStrings.length == 2) {
overruletime = Integer.parseInt(overruletimeStrings[0]) * 60 + Integer.parseInt(overruletimeStrings[1]);
}
int ecosave = Integer.parseInt(thermostat.get("ecosave"));
// For parity with NHC II, assume heating/cooling if thermostat is on and setpoint different from measured
int demand = (mode != 3) ? (setpoint > measured ? 1 : (setpoint < measured ? -1 : 0)) : 0;
if (!thermostats.containsKey(id)) {
// Initial instantiation of NhcThermostat class for thermostat object
String name = thermostat.get("name");
String locationId = thermostat.get("location");
String location = "";
if (!locationId.isEmpty()) {
location = locations.get(locationId).getName();
}
NhcThermostat nhcThermostat = new NhcThermostat1(id, name, location, this);
nhcThermostat.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
thermostats.put(id, nhcThermostat);
} else {
// Thermostat object already exists, so only update state.
// If we would re-instantiate thermostat, we would lose pointer back from thermostat to thing handler
// that was set in thing handler initialize().
thermostats.get(id).updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
}
}
}
private void cmdExecuteActions(Map<String, String> data) {
int errorCode = Integer.parseInt(data.get("error"));
if (errorCode == 0) {
logger.debug("Niko Home Control: execute action success");
} else {
logger.warn("Niko Home Control: error code {} returned on command execution", errorCode);
}
}
private void cmdExecuteThermostat(Map<String, String> data) {
int errorCode = Integer.parseInt(data.get("error"));
if (errorCode == 0) {
logger.debug("Niko Home Control: execute thermostats success");
} else {
logger.warn("Niko Home Control: error code {} returned on command execution", errorCode);
}
}
private void eventListActions(List<Map<String, String>> data) {
for (Map<String, String> action : data) {
String id = action.get("id");
if (!actions.containsKey(id)) {
logger.warn("Niko Home Control: action in controller not known {}", id);
return;
}
int state = Integer.parseInt(action.get("value1"));
logger.debug("Niko Home Control: event execute action {} with state {}", id, state);
actions.get(id).setState(state);
}
}
private void eventListThermostat(List<Map<String, String>> data) {
for (Map<String, String> thermostat : data) {
String id = thermostat.get("id");
if (!thermostats.containsKey(id)) {
logger.warn("Niko Home Control: thermostat in controller not known {}", id);
return;
}
int measured = Integer.parseInt(thermostat.get("measured"));
int setpoint = Integer.parseInt(thermostat.get("setpoint"));
int mode = Integer.parseInt(thermostat.get("mode"));
int overrule = Integer.parseInt(thermostat.get("overrule"));
// overruletime received in "HH:MM" format
String[] overruletimeStrings = thermostat.get("overruletime").split(":");
int overruletime = 0;
if (overruletimeStrings.length == 2) {
overruletime = Integer.parseInt(overruletimeStrings[0]) * 60 + Integer.parseInt(overruletimeStrings[1]);
}
int ecosave = Integer.parseInt(thermostat.get("ecosave"));
int demand = (mode != 3) ? (setpoint > measured ? 1 : (setpoint < measured ? -1 : 0)) : 0;
logger.debug(
"Niko Home Control: event execute thermostat {} with measured {}, setpoint {}, mode {}, overrule {}, overruletime {}, ecosave {}, demand {}",
id, measured, setpoint, mode, overrule, overruletime, ecosave, demand);
thermostats.get(id).updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
}
}
private void eventGetAlarms(Map<String, String> data) {
int type = Integer.parseInt(data.get("type"));
String alarmText = data.get("text");
switch (type) {
case 0:
logger.debug("Niko Home Control: alarm - {}", alarmText);
handler.alarmEvent(alarmText);
break;
case 1:
logger.debug("Niko Home Control: notice - {}", alarmText);
handler.noticeEvent(alarmText);
break;
default:
logger.debug("Niko Home Control: unexpected message type {}", type);
}
}
@Override
public void executeAction(String actionId, String value) {
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("executeactions", Integer.parseInt(actionId),
Integer.parseInt(value));
sendMessage(nhcCmd);
}
@Override
public void executeThermostat(String thermostatId, String mode) {
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("executethermostat", Integer.parseInt(thermostatId))
.withMode(Integer.parseInt(mode));
sendMessage(nhcCmd);
}
@Override
public void executeThermostat(String thermostatId, int overruleTemp, int overruleTime) {
String overruletimeString = String.format("%1$02d:%2$02d", overruleTime / 60, overruleTime % 60);
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("executethermostat", Integer.parseInt(thermostatId))
.withOverrule(overruleTemp).withOverruletime(overruletimeString);
sendMessage(nhcCmd);
}
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc1;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
* Class {@link NikoHomeControlMessageDeserializer1} deserializes all json messages from Niko Home Control. Various json
* message formats are supported. The format is selected based on the content of the cmd and event json objects.
*
* @author Mark Herwege - Initial Contribution
*
*/
class NikoHomeControlMessageDeserializer1 implements JsonDeserializer<NhcMessageBase1> {
@Override
public NhcMessageBase1 deserialize(final JsonElement json, final Type typeOfT,
final JsonDeserializationContext context) throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
try {
String cmd = null;
String event = null;
if (jsonObject.has("cmd")) {
cmd = jsonObject.get("cmd").getAsString();
}
if (jsonObject.has("event")) {
event = jsonObject.get("event").getAsString();
}
JsonElement jsonData = null;
if (jsonObject.has("data")) {
jsonData = jsonObject.get("data");
}
NhcMessageBase1 message = null;
if (jsonData != null) {
if (jsonData.isJsonObject()) {
message = new NhcMessageMap1();
Map<String, String> data = new HashMap<>();
for (Entry<String, JsonElement> entry : jsonData.getAsJsonObject().entrySet()) {
data.put(entry.getKey(), entry.getValue().getAsString());
}
((NhcMessageMap1) message).setData(data);
} else if (jsonData.isJsonArray()) {
JsonArray jsonDataArray = jsonData.getAsJsonArray();
message = new NhcMessageListMap1();
List<Map<String, String>> dataList = new ArrayList<>();
for (int i = 0; i < jsonDataArray.size(); i++) {
JsonObject jsonDataObject = jsonDataArray.get(i).getAsJsonObject();
Map<String, String> data = new HashMap<>();
for (Entry<String, JsonElement> entry : jsonDataObject.entrySet()) {
data.put(entry.getKey(), entry.getValue().getAsString());
}
dataList.add(data);
}
((NhcMessageListMap1) message).setData(dataList);
}
}
if (message != null) {
message.setCmd(cmd);
message.setEvent(event);
} else {
throw new JsonParseException("Unexpected Json type");
}
return message;
} catch (IllegalStateException | ClassCastException e) {
throw new JsonParseException("Unexpected Json type");
}
}
}

View File

@@ -0,0 +1,136 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcAction2} class represents the action Niko Home Control II communication object. It contains all fields
* representing a Niko Home Control action and has methods to trigger the action in Niko Home Control and receive action
* updates.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcAction2 extends NhcAction {
private final Logger logger = LoggerFactory.getLogger(NhcAction2.class);
private volatile boolean booleanState;
private String model;
private String technology;
NhcAction2(String id, String name, String model, String technology, ActionType type, @Nullable String location,
NikoHomeControlCommunication nhcComm) {
super(id, name, type, location, nhcComm);
this.model = model;
this.technology = technology;
}
/**
* Get on/off state of action.
* <p>
* true for on, false for off
*
* @return action on/off state
*/
boolean booleanState() {
return booleanState;
}
@Override
public int getState() {
return booleanState ? state : 0;
}
/**
* Sets on/off state of action.
*
* @param state - boolean false for on, true for off
*/
public void setBooleanState(boolean state) {
booleanState = state;
if (getType().equals(ActionType.DIMMER)) {
if (booleanState) {
// only send stored brightness value if on
updateState();
} else {
updateState(0);
}
} else {
if (booleanState) {
this.state = 100;
updateState(100);
} else {
this.state = 0;
updateState(0);
}
}
}
/**
* Sets state of action. This version is used for Niko Home Control II.
*
* @param state - The allowed values depend on the action type.
* switch action: 0 or 100
* dimmer action: between 0 and 100
* rollershutter action: between 0 and 100
*/
@Override
public void setState(int state) {
this.state = state;
if (getType().equals(ActionType.DIMMER)) { // for dimmers, only send the update to the event
// handler if on
if (booleanState) {
updateState();
}
} else {
updateState();
}
}
/**
* Sends action to Niko Home Control. This version is used for Niko Home Control II, that has extra status options.
*
* @param command - The allowed values depend on the action type.
* switch action: On or Off
* dimmer action: between 0 and 100, On or Off
* rollershutter action: between 0 and 100, Up, Down or Stop
*/
@Override
public void execute(String command) {
logger.debug("Niko Home Control: execute action {} of type {} for {}", command, type, id);
nhcComm.executeAction(id, command);
}
/**
* @return model as returned from Niko Home Control
*/
public String getModel() {
return model;
}
/**
* @return technology as returned from Niko Home Control
*/
public String getTechnology() {
return technology;
}
}

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* {@link NhcDevice2} represents a Niko Home Control II device. It is used when parsing the device response json and
* when creating the state update json to send to the Connected Controller.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
class NhcDevice2 {
static class NhcProperty {
// fields for lights
@Nullable
String status;
@Nullable
String brightness;
@Nullable
String aligned;
@Nullable
String basicState;
// fields for motors
@Nullable
String action;
@Nullable
String position;
@Nullable
String moving;
// fields for thermostats and hvac
@Nullable
String setpointTemperature;
@Nullable
String program;
@Nullable
String overruleActive;
@Nullable
String overruleSetpoint;
@Nullable
String overruleTime;
@Nullable
String ecoSave;
@Nullable
String demand;
@Nullable
String operationMode;
@Nullable
String ambientTemperature;
@Nullable
String protectMode;
@Nullable
String thermostatOn;
@Nullable
String hvacOn;
// fields for fans and ventilation
@Nullable
String fanSpeed;
// fields for electricity metering
@Nullable
String electricalEnergy;
@Nullable
String electricalPower;
@Nullable
String reportInstantUsage;
// fields for access control
@Nullable
String doorlock;
}
static class NhcTrait {
@Nullable
String macAddress;
// fields for energyMeters metering
@Nullable
String channel;
@Nullable
String meterType;
}
static class NhcParameter {
@Nullable
String locationId;
@Nullable
String locationName;
@Nullable
String locationIcon;
// fields for electricity metering
@Nullable
String flow;
@Nullable
String segment;
@Nullable
String clampType;
@Nullable
String shortName;
}
String name = "";
String uuid = "";
String technology = "";
String identifier = "";
String model = "";
String type = "";
String online = "";
@Nullable
List<NhcProperty> properties;
@Nullable
List<NhcTrait> traits;
@Nullable
List<NhcParameter> parameters;
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcEnergyMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
/**
* The {@link NhcEnergyMeter} class represents the energyMeters metering Niko Home Control communication object. It
* contains all fields representing a Niko Home Control energyMeters meter and has methods to receive energyMeters usage
* information.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcEnergyMeter2 extends NhcEnergyMeter {
private ScheduledExecutorService scheduler;
private volatile @Nullable ScheduledFuture<?> restartTimer;
private String model;
private String technology;
protected NhcEnergyMeter2(String id, String name, String model, String technology,
NikoHomeControlCommunication nhcComm, ScheduledExecutorService scheduler) {
super(id, name, nhcComm);
this.model = model;
this.technology = technology;
this.scheduler = scheduler;
}
/**
* Start the flow from energy information from the energy meter. The Niko Home Control energy meter will send power
* information every 2s for 30s. This method will retrigger every 25s to make sure the information continues
* flowing. If the information is no longer required, make sure to use the {@link stopEnergyMeter} method to stop
* the flow of information.
*
* @param topic topic the start event will have to be sent to every 25s
* @param gsonMessage content of message
*/
public void startEnergyMeter(String topic, String gsonMessage) {
stopEnergyMeter();
restartTimer = scheduler.scheduleWithFixedDelay(() -> {
((NikoHomeControlCommunication2) nhcComm).executeEnergyMeter(topic, gsonMessage);
}, 0, 25, TimeUnit.SECONDS);
}
/**
* Cancel receiving energy information from the controller. We therefore stop the automatic retriggering of the
* subscription, see {@link startEnergyMeter}.
*/
public void stopEnergyMeter() {
ScheduledFuture<?> timer = restartTimer;
if (timer != null) {
timer.cancel(true);
restartTimer = null;
}
}
/**
* @return model as returned from Niko Home Control
*/
public String getModel() {
return model;
}
/**
* @return technology as returned from Niko Home Control
*/
public String getTechnology() {
return technology;
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* {@link NhcMessage2} represents a Niko Home Control II message. It is used when sending messages to the Connected
* Controller or when parsing the message response json.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
class NhcMessage2 {
static class NhcMessageParam {
@Nullable
List<NhcSystemInfo2> systemInfo;
@Nullable
List<NhcService2> services;
@Nullable
List<NhcDevice2> devices;
@Nullable
List<NhcNotification2> notifications;
@Nullable
List<NhcTimeInfo2> timeInfo;
}
@Nullable
String method;
String errCode = "";
String errMessage = "";
@Nullable
List<NhcMessageParam> params;
}

View File

@@ -0,0 +1,247 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.io.transport.mqtt.MqttActionCallback;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.io.transport.mqtt.MqttConnectionObserver;
import org.openhab.core.io.transport.mqtt.MqttConnectionState;
import org.openhab.core.io.transport.mqtt.MqttException;
import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link NhcMqttConnection2} manages the MQTT connection to the Connected Controller. It allows receiving state
* information about specific devices and sending updates to specific devices.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcMqttConnection2 implements MqttActionCallback {
private final Logger logger = LoggerFactory.getLogger(NhcMqttConnection2.class);
private volatile @Nullable MqttBrokerConnection mqttConnection;
private volatile @Nullable CompletableFuture<Boolean> subscribedFuture;
private volatile @Nullable CompletableFuture<Boolean> stoppedFuture;
private MqttMessageSubscriber messageSubscriber;
private MqttConnectionObserver connectionObserver;
private TrustManager trustManagers[];
private String clientId;
private volatile String cocoAddress = "";
private volatile int port;
private volatile String profile = "";
private volatile String token = "";
NhcMqttConnection2(String clientId, MqttMessageSubscriber messageSubscriber,
MqttConnectionObserver connectionObserver) throws CertificateException {
trustManagers = getTrustManagers();
this.clientId = clientId;
this.messageSubscriber = messageSubscriber;
this.connectionObserver = connectionObserver;
}
private TrustManager[] getTrustManagers() throws CertificateException {
ResourceBundle certificatesBundle = ResourceBundle.getBundle("nikohomecontrol/certificates");
try {
// Load server public certificates into key store
CertificateFactory cf = CertificateFactory.getInstance("X509");
InputStream certificateStream;
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
for (String certName : certificatesBundle.keySet()) {
certificateStream = new ByteArrayInputStream(
certificatesBundle.getString(certName).getBytes(StandardCharsets.UTF_8));
X509Certificate certificate = (X509Certificate) cf.generateCertificate(certificateStream);
keyStore.setCertificateEntry(certName, certificate);
}
ResourceBundle.clearCache();
// Create trust managers used to validate server
TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmFactory.init(keyStore);
return tmFactory.getTrustManagers();
} catch (CertificateException | KeyStoreException | NoSuchAlgorithmException | IOException e) {
logger.warn("Niko Home Control: error with SSL context creation: {} ", e.getMessage());
throw new CertificateException("SSL context creation exception", e);
} finally {
ResourceBundle.clearCache();
}
}
/**
* Start a secure MQTT connection and subscribe to all topics.
*
* @param subscriber MqttMessageSubscriber that will handle received messages
* @param cocoAddress IP Address of the Niko Connected Controller
* @param port Port for MQTT communication with the Niko Connected Controller
* @param token JWT token for the hobby profile
* @throws MqttException
*/
synchronized void startConnection(String cocoAddress, int port, String profile, String token) throws MqttException {
CompletableFuture<Boolean> future = stoppedFuture;
if (future != null) {
try {
future.get(5000, TimeUnit.MILLISECONDS);
logger.debug("Niko Home Control: finished stopping connection");
} catch (InterruptedException | ExecutionException | TimeoutException ignore) {
logger.debug("Niko Home Control: error stopping connection");
}
stoppedFuture = null;
}
logger.debug("Niko Home Control: starting connection...");
this.cocoAddress = cocoAddress;
this.port = port;
this.profile = profile;
this.token = token;
MqttBrokerConnection connection = createMqttConnection();
connection.addConnectionObserver(connectionObserver);
mqttConnection = connection;
try {
if (connection.start().get(5000, TimeUnit.MILLISECONDS)) {
if (subscribedFuture == null) {
subscribedFuture = connection.subscribe("#", messageSubscriber);
}
} else {
logger.debug("Niko Home Control: error connecting");
throw new MqttException("Connection execution exception");
}
} catch (InterruptedException e) {
logger.debug("Niko Home Control: connection interrupted exception");
throw new MqttException("Connection interrupted exception");
} catch (ExecutionException e) {
logger.debug("Niko Home Control: connection execution exception", e.getCause());
throw new MqttException("Connection execution exception");
} catch (TimeoutException e) {
logger.debug("Niko Home Control: connection timeout exception");
throw new MqttException("Connection timeout exception");
}
}
private MqttBrokerConnection createMqttConnection() throws MqttException {
MqttBrokerConnection connection = new MqttBrokerConnection(cocoAddress, port, true, clientId);
connection.setTrustManagers(trustManagers);
connection.setCredentials(profile, token);
connection.setQos(1);
return connection;
}
/**
* Stop the MQTT connection.
*/
void stopConnection() {
logger.debug("Niko Home Control: stopping connection...");
MqttBrokerConnection connection = mqttConnection;
if (connection != null) {
connection.removeConnectionObserver(connectionObserver);
}
stoppedFuture = stopConnection(connection);
mqttConnection = null;
CompletableFuture<Boolean> future = subscribedFuture;
if (future != null) {
future.complete(false);
subscribedFuture = null;
}
}
private CompletableFuture<Boolean> stopConnection(@Nullable MqttBrokerConnection connection) {
if (connection != null) {
return connection.stop();
} else {
return CompletableFuture.completedFuture(true);
}
}
/**
* @return true if connection established and subscribed to all topics
*/
private boolean isConnected() {
MqttBrokerConnection connection = mqttConnection;
CompletableFuture<Boolean> future = subscribedFuture;
if (connection != null) {
try {
if ((future != null) && future.get(5000, TimeUnit.MILLISECONDS)) {
MqttConnectionState state = connection.connectionState();
logger.debug("Niko Home Control: connection state {} for {}", state, connection.getClientId());
return state == MqttConnectionState.CONNECTED;
}
} catch (InterruptedException | ExecutionException | TimeoutException e) {
return false;
}
}
return false;
}
/**
* Publish a message on the general connection.
*
* @param topic
* @param payload
* @throws MqttException
*/
void connectionPublish(String topic, String payload) throws MqttException {
MqttBrokerConnection connection = mqttConnection;
if (connection == null) {
logger.debug("Niko Home Control: cannot publish, no connection");
throw new MqttException("No connection exception");
}
if (isConnected()) {
logger.debug("Niko Home Control: publish {}, {}", topic, payload);
connection.publish(topic, payload.getBytes());
} else {
logger.debug("Niko Home Control: cannot publish, not subscribed to connection messages");
}
}
@Override
public void onSuccess(String topic) {
logger.debug("Niko Home Control: publish succeeded {}", topic);
}
@Override
public void onFailure(String topic, Throwable error) {
logger.debug("Niko Home Control: publish failed {}, {}", topic, error.getMessage(), error);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* {@link NhcNotification2} represents a Niko Home Control II notification. It is used when parsing the notification
* response json.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
class NhcNotification2 {
String status = "";
String type = "";
String timeOccured = "";
String uuid = "";
String text = "";
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* {@link NhcService2} represents a Niko Home Control II service. It is used when parsing the service response json.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
class NhcService2 {
String name = "";
String name() {
return name;
}
}

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import java.util.ArrayList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* {@link NhcSystemInfo2} represents Niko Home Control II system info. It is used when parsing the systeminfo response
* json.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcSystemInfo2 {
static class NhcSwVersion {
String nhcVersion = "";
String cocoImage = "";
}
String lastConfig = "";
String waterTariff = "";
String electricityTariff = "";
String gasTariff = "";
String currency = "";
String units = "";
String language = "";
@SerializedName(value = "SWversions")
ArrayList<NhcSwVersion> swVersions = new ArrayList<>();
/**
* @return the NhcVersion
*/
public String getNhcVersion() {
return swVersions.stream().map(p -> p.nhcVersion).filter(v -> !v.isEmpty()).findFirst().orElse("");
}
/**
* @return the CocoImage version
*/
public String getCocoImage() {
return swVersions.stream().map(p -> p.cocoImage).filter(v -> !v.isEmpty()).findFirst().orElse("");
}
/**
* @return the lastConfig
*/
public String getLastConfig() {
return lastConfig;
}
/**
* @return the waterTariff
*/
public String getWaterTariff() {
return waterTariff;
}
/**
* @return the electricityTariff
*/
public String getElectricityTariff() {
return electricityTariff;
}
/**
* @return the gasTariff
*/
public String getGasTariff() {
return gasTariff;
}
/**
* @return the currency
*/
public String getCurrency() {
return currency;
}
/**
* @return the units
*/
public String getUnits() {
return units;
}
/**
* @return the language
*/
public String getLanguage() {
return language;
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.THERMOSTATMODES;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcThermostat2} class represents the thermostat Niko Home Control II communication object. It contains all
* fields representing a Niko Home Control thermostat and has methods to set the thermostat in Niko Home Control and
* receive thermostat updates.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcThermostat2 extends NhcThermostat {
private final Logger logger = LoggerFactory.getLogger(NhcThermostat2.class);
private String model;
private String technology;
protected NhcThermostat2(String id, String name, String model, String technology, @Nullable String location,
NikoHomeControlCommunication nhcComm) {
super(id, name, location, nhcComm);
this.model = model;
this.technology = technology;
}
@Override
public void executeMode(int mode) {
logger.debug("Niko Home Control: execute thermostat mode {} for {}", mode, id);
String program = THERMOSTATMODES[mode];
nhcComm.executeThermostat(id, program);
}
@Override
public void executeOverrule(int overrule, int overruletime) {
logger.debug("Niko Home Control: execute thermostat overrule {} during {} min for {}", overrule, overruletime,
id);
nhcComm.executeThermostat(id, overrule, overruletime);
}
/**
* @return model as returned from Niko Home Control
*/
public String getModel() {
return model;
}
/**
* @return technology as returned from Niko Home Control
*/
public String getTechnology() {
return technology;
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* {@link NhcTimeInfo2} represents Niko Home Control II timeinfo. It is used when parsing the timeinfo response json.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcTimeInfo2 {
@SerializedName(value = "GMTOffset")
String gmtOffset = "";
String timezone = "";
String isDST = "";
@SerializedName(value = "UTCTime")
String utcTime = "";
/**
* @return the gMTOffset
*/
public String getGMTOffset() {
return gmtOffset;
}
/**
* @return the timeZone
*/
public String getTimezone() {
return timezone;
}
/**
* @return the isDST
*/
public String getIsDst() {
return isDST;
}
/**
* @return the uTCTime
*/
public String getUTCTime() {
return utcTime;
}
}

View File

@@ -0,0 +1,847 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.protocol.nhc2;
import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.*;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcControllerEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcDevice2.NhcProperty;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcMessage2.NhcMessageParam;
import org.openhab.core.io.transport.mqtt.MqttConnectionObserver;
import org.openhab.core.io.transport.mqtt.MqttConnectionState;
import org.openhab.core.io.transport.mqtt.MqttException;
import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
/**
* The {@link NikoHomeControlCommunication2} class is able to do the following tasks with Niko Home Control II
* systems:
* <ul>
* <li>Start and stop MQTT connection with Niko Home Control II Connected Controller.
* <li>Read all setup and status information from the Niko Home Control Controller.
* <li>Execute Niko Home Control commands.
* <li>Listen for events from Niko Home Control.
* </ul>
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
implements MqttMessageSubscriber, MqttConnectionObserver {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlCommunication2.class);
private final NhcMqttConnection2 mqttConnection;
private final List<NhcService2> services = new CopyOnWriteArrayList<>();
private volatile String profile = "";
private volatile @Nullable NhcSystemInfo2 nhcSystemInfo;
private volatile @Nullable NhcTimeInfo2 nhcTimeInfo;
private volatile @Nullable CompletableFuture<Boolean> communicationStarted;
private ScheduledExecutorService scheduler;
private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
/**
* Constructor for Niko Home Control communication object, manages communication with
* Niko Home Control II Connected Controller.
*
* @throws CertificateException when the SSL context for MQTT communication cannot be created
* @throws UnknownHostException when the IP address is not provided
*
*/
public NikoHomeControlCommunication2(NhcControllerEvent handler, String clientId,
ScheduledExecutorService scheduler) throws CertificateException {
super(handler);
mqttConnection = new NhcMqttConnection2(clientId, this, this);
this.scheduler = scheduler;
}
@Override
public synchronized void startCommunication() {
communicationStarted = new CompletableFuture<>();
InetAddress addr = handler.getAddr();
if (addr == null) {
logger.warn("Niko Home Control: IP address cannot be empty");
stopCommunication();
return;
}
String addrString = addr.getHostAddress();
int port = handler.getPort();
logger.debug("Niko Home Control: initializing for mqtt connection to CoCo on {}:{}", addrString, port);
profile = handler.getProfile();
String token = handler.getToken();
if (token.isEmpty()) {
logger.warn("Niko Home Control: JWT token cannot be empty");
stopCommunication();
return;
}
try {
mqttConnection.startConnection(addrString, port, profile, token);
initialize();
} catch (MqttException e) {
logger.warn("Niko Home Control: error in mqtt communication");
stopCommunication();
}
}
@Override
public synchronized void stopCommunication() {
CompletableFuture<Boolean> started = communicationStarted;
if (started != null) {
started.complete(false);
}
communicationStarted = null;
mqttConnection.stopConnection();
}
@Override
public boolean communicationActive() {
CompletableFuture<Boolean> started = communicationStarted;
if (started == null) {
return false;
}
try {
// Wait until we received all devices info to confirm we are active.
return started.get(5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
logger.debug("Niko Home Control: exception waiting for connection start");
return false;
}
}
/**
* After setting up the communication with the Niko Home Control Connected Controller, send all initialization
* messages.
*
*/
private void initialize() throws MqttException {
NhcMessage2 message = new NhcMessage2();
message.method = "systeminfo.publish";
mqttConnection.connectionPublish(profile + "/system/cmd", gson.toJson(message));
message.method = "services.list";
mqttConnection.connectionPublish(profile + "/authentication/cmd", gson.toJson(message));
message.method = "devices.list";
mqttConnection.connectionPublish(profile + "/control/devices/cmd", gson.toJson(message));
message.method = "notifications.list";
mqttConnection.connectionPublish(profile + "/notification/cmd", gson.toJson(message));
}
private void connectionLost() {
logger.debug("Niko Home Control: connection lost");
stopCommunication();
handler.controllerOffline();
}
private void systemEvt(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcTimeInfo2> timeInfo = null;
List<NhcSystemInfo2> systemInfo = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
timeInfo = messageParams.stream().filter(p -> (p.timeInfo != null)).findFirst().get().timeInfo;
systemInfo = messageParams.stream().filter(p -> (p.systemInfo != null)).findFirst().get().systemInfo;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if timeInfo not present in response, this should not happen in a timeInfo response
}
if (timeInfo != null) {
nhcTimeInfo = timeInfo.get(0);
}
if (systemInfo != null) {
nhcSystemInfo = systemInfo.get(0);
handler.updatePropertiesEvent();
}
}
private void systeminfoPublishRsp(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcSystemInfo2> systemInfo = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
systemInfo = messageParams.stream().filter(p -> (p.systemInfo != null)).findFirst().get().systemInfo;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if systemInfo not present in response, this should not happen in a systemInfo response
}
if (systemInfo != null) {
nhcSystemInfo = systemInfo.get(0);
}
}
private void servicesListRsp(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcService2> serviceList = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
serviceList = messageParams.stream().filter(p -> (p.services != null)).findFirst().get().services;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if services not present in response, this should not happen in a services response
}
services.clear();
if (serviceList != null) {
services.addAll(serviceList);
}
}
private void devicesListRsp(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcDevice2> deviceList = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
deviceList = messageParams.stream().filter(p -> (p.devices != null)).findFirst().get().devices;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if devices not present in response, this should not happen in a devices response
}
if (deviceList == null) {
return;
}
for (NhcDevice2 device : deviceList) {
addDevice(device);
updateState(device);
}
// Once a devices list response is received, we know the communication is fully started.
logger.debug("Niko Home Control: Communication start complete.");
handler.controllerOnline();
CompletableFuture<Boolean> future = communicationStarted;
if (future != null) {
future.complete(true);
}
}
private void devicesEvt(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcDevice2> deviceList = null;
String method = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
method = message.method;
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
deviceList = messageParams.stream().filter(p -> (p.devices != null)).findFirst().get().devices;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if devices not present in response, this should not happen in a devices event
}
if (deviceList == null) {
return;
}
if ("devices.removed".equals(method)) {
deviceList.forEach(this::removeDevice);
return;
} else if ("devices.added".equals(method)) {
deviceList.forEach(this::addDevice);
} else if ("devices.changed".contentEquals(method)) {
deviceList.forEach(this::removeDevice);
deviceList.forEach(this::addDevice);
}
deviceList.forEach(this::updateState);
}
private void notificationEvt(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcNotification2> notificationList = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
notificationList = messageParams.stream().filter(p -> (p.notifications != null)).findFirst()
.get().notifications;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if notifications not present in response, this should not happen in a notifications event
}
logger.debug("Niko Home Control: notifications {}", notificationList);
if (notificationList == null) {
return;
}
for (NhcNotification2 notification : notificationList) {
if ("new".equals(notification.status)) {
String alarmText = notification.text;
switch (notification.type) {
case "alarm":
handler.alarmEvent(alarmText);
break;
case "notification":
handler.noticeEvent(alarmText);
break;
default:
logger.debug("Niko Home Control: unexpected message type {}", notification.type);
}
}
}
}
private void addDevice(NhcDevice2 device) {
String location = null;
if (device.parameters != null) {
location = device.parameters.stream().map(p -> p.locationName).filter(Objects::nonNull).findFirst()
.orElse(null);
}
if ("action".equals(device.type)) {
if (!actions.containsKey(device.uuid)) {
logger.debug("Niko Home Control: adding action device {}, {}", device.uuid, device.name);
ActionType actionType;
switch (device.model) {
case "generic":
case "pir":
case "simulation":
case "comfort":
case "alarms":
case "alloff":
case "overallcomfort":
case "garagedoor":
actionType = ActionType.TRIGGER;
break;
case "light":
case "socket":
case "switched-generic":
case "switched-fan":
actionType = ActionType.RELAY;
break;
case "dimmer":
actionType = ActionType.DIMMER;
break;
case "rolldownshutter":
case "sunblind":
case "venetianblind":
case "gate":
actionType = ActionType.ROLLERSHUTTER;
break;
default:
actionType = ActionType.GENERIC;
logger.debug("Niko Home Control: device type {} not recognised, default to GENERIC action",
device.type);
}
NhcAction2 nhcAction = new NhcAction2(device.uuid, device.name, device.model, device.technology,
actionType, location, this);
actions.put(device.uuid, nhcAction);
}
} else if ("thermostat".equals(device.type)) {
if (!thermostats.containsKey(device.uuid)) {
logger.debug("Niko Home Control: adding thermostat device {}, {}", device.uuid, device.name);
NhcThermostat2 nhcThermostat = new NhcThermostat2(device.uuid, device.name, device.model,
device.technology, location, this);
thermostats.put(device.uuid, nhcThermostat);
}
} else if ("centralmeter".equals(device.type)) {
if (!energyMeters.containsKey(device.uuid)) {
logger.debug("Niko Home Control: adding centralmeter device {}, {}", device.uuid, device.name);
NhcEnergyMeter2 nhcEnergyMeter = new NhcEnergyMeter2(device.uuid, device.name, device.model,
device.technology, this, scheduler);
energyMeters.put(device.uuid, nhcEnergyMeter);
}
} else {
logger.debug("Niko Home Control: device type {} not supported for {}, {}", device.type, device.uuid,
device.name);
}
}
private void removeDevice(NhcDevice2 device) {
if (actions.containsKey(device.uuid)) {
actions.get(device.uuid).actionRemoved();
actions.remove(device.uuid);
} else if (thermostats.containsKey(device.uuid)) {
thermostats.get(device.uuid).thermostatRemoved();
thermostats.remove(device.uuid);
} else if (energyMeters.containsKey(device.uuid)) {
energyMeters.get(device.uuid).energyMeterRemoved();
energyMeters.remove(device.uuid);
}
}
private void updateState(NhcDevice2 device) {
List<NhcProperty> deviceProperties = device.properties;
if (deviceProperties == null) {
logger.debug("Cannot Update state for {} as no properties defined in device message", device.uuid);
return;
}
if (actions.containsKey(device.uuid)) {
updateActionState((NhcAction2) actions.get(device.uuid), deviceProperties);
} else if (thermostats.containsKey(device.uuid)) {
updateThermostatState((NhcThermostat2) thermostats.get(device.uuid), deviceProperties);
} else if (energyMeters.containsKey(device.uuid)) {
updateEnergyMeterState((NhcEnergyMeter2) energyMeters.get(device.uuid), deviceProperties);
}
}
private void updateActionState(NhcAction2 action, List<NhcProperty> deviceProperties) {
if (action.getType() == ActionType.ROLLERSHUTTER) {
updateRollershutterState(action, deviceProperties);
} else {
updateLightState(action, deviceProperties);
}
}
private void updateLightState(NhcAction2 action, List<NhcProperty> deviceProperties) {
Optional<NhcProperty> statusProperty = deviceProperties.stream().filter(p -> (p.status != null)).findFirst();
Optional<NhcProperty> dimmerProperty = deviceProperties.stream().filter(p -> (p.brightness != null))
.findFirst();
Optional<NhcProperty> basicStateProperty = deviceProperties.stream().filter(p -> (p.basicState != null))
.findFirst();
String booleanState = null;
if (statusProperty.isPresent()) {
booleanState = statusProperty.get().status;
} else if (basicStateProperty.isPresent()) {
booleanState = basicStateProperty.get().basicState;
}
if (booleanState != null) {
if (NHCON.equals(booleanState)) {
action.setBooleanState(true);
logger.debug("Niko Home Control: setting action {} internally to ON", action.getId());
} else if (NHCOFF.equals(booleanState)) {
action.setBooleanState(false);
logger.debug("Niko Home Control: setting action {} internally to OFF", action.getId());
}
}
if (dimmerProperty.isPresent()) {
action.setState(Integer.parseInt(dimmerProperty.get().brightness));
logger.debug("Niko Home Control: setting action {} internally to {}", action.getId(),
dimmerProperty.get().brightness);
}
}
private void updateRollershutterState(NhcAction2 action, List<NhcProperty> deviceProperties) {
deviceProperties.stream().map(p -> p.position).filter(Objects::nonNull).findFirst().ifPresent(position -> {
try {
action.setState(Integer.parseInt(position));
logger.debug("Niko Home Control: setting action {} internally to {}", action.getId(), position);
} catch (NumberFormatException e) {
logger.trace("Niko Home Control: received empty rollershutter {} position info", action.getId());
}
});
}
private void updateThermostatState(NhcThermostat2 thermostat, List<NhcProperty> deviceProperties) {
Optional<Boolean> overruleActiveProperty = deviceProperties.stream().map(p -> p.overruleActive)
.filter(Objects::nonNull).map(t -> Boolean.parseBoolean(t)).findFirst();
Optional<Integer> overruleSetpointProperty = deviceProperties.stream().map(p -> p.overruleSetpoint)
.filter(s -> !((s == null) || s.isEmpty())).map(t -> Math.round(Float.parseFloat(t) * 10)).findFirst();
Optional<Integer> overruleTimeProperty = deviceProperties.stream().map(p -> p.overruleTime)
.filter(s -> !((s == null) || s.isEmpty())).map(t -> Math.round(Float.parseFloat(t))).findFirst();
Optional<Integer> setpointTemperatureProperty = deviceProperties.stream().map(p -> p.setpointTemperature)
.filter(s -> !((s == null) || s.isEmpty())).map(t -> Math.round(Float.parseFloat(t) * 10)).findFirst();
Optional<Boolean> ecoSaveProperty = deviceProperties.stream().map(p -> p.ecoSave).filter(Objects::nonNull)
.map(t -> Boolean.parseBoolean(t)).findFirst();
Optional<Integer> ambientTemperatureProperty = deviceProperties.stream().map(p -> p.ambientTemperature)
.filter(s -> !(s == null || s.isEmpty())).map(t -> Math.round(Float.parseFloat(t) * 10)).findFirst();
Optional<@Nullable String> demandProperty = deviceProperties.stream().map(p -> p.demand)
.filter(Objects::nonNull).findFirst();
Optional<@Nullable String> operationModeProperty = deviceProperties.stream().map(p -> p.operationMode)
.filter(Objects::nonNull).findFirst();
String modeString = deviceProperties.stream().map(p -> p.program).filter(Objects::nonNull).findFirst()
.orElse("");
int mode = IntStream.range(0, THERMOSTATMODES.length).filter(i -> THERMOSTATMODES[i].equals(modeString))
.findFirst().orElse(thermostat.getMode());
int measured = ambientTemperatureProperty.orElse(thermostat.getMeasured());
int setpoint = setpointTemperatureProperty.orElse(thermostat.getSetpoint());
int overrule = thermostat.getOverrule();
int overruletime = thermostat.getRemainingOverruletime();
if (overruleActiveProperty.orElse(false)) {
overrule = overruleSetpointProperty.orElse(0);
overruletime = overruleTimeProperty.orElse(0);
}
int ecosave = thermostat.getEcosave();
if (ecoSaveProperty.orElse(false)) {
ecosave = 1;
}
int demand = thermostat.getDemand();
String demandString = demandProperty.orElse(operationModeProperty.orElse(""));
demandString = demandString == null ? "" : demandString;
switch (demandString) {
case "None":
demand = 0;
break;
case "Heating":
demand = 1;
break;
case "Cooling":
demand = -1;
break;
}
logger.debug(
"Niko Home Control: setting thermostat {} with measured {}, setpoint {}, mode {}, overrule {}, overruletime {}, ecosave {}, demand {}",
thermostat.getId(), measured, setpoint, mode, overrule, overruletime, ecosave, demand);
thermostat.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
}
private void updateEnergyMeterState(NhcEnergyMeter2 energyMeter, List<NhcProperty> deviceProperties) {
deviceProperties.stream().map(p -> p.electricalPower).filter(Objects::nonNull).findFirst()
.ifPresent(electricalPower -> {
try {
energyMeter.setPower(Integer.parseInt(electricalPower));
logger.trace("Niko Home Control: setting energy meter {} power to {}", energyMeter.getId(),
electricalPower);
} catch (NumberFormatException e) {
energyMeter.setPower(null);
logger.trace("Niko Home Control: received empty energy meter {} power reading",
energyMeter.getId());
}
});
}
@Override
public void executeAction(String actionId, String value) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
ArrayList<NhcMessageParam> params = new ArrayList<>();
NhcMessageParam param = new NhcMessageParam();
params.add(param);
message.params = params;
ArrayList<NhcDevice2> devices = new ArrayList<>();
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = actionId;
ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
NhcProperty property = new NhcProperty();
deviceProperties.add(property);
device.properties = deviceProperties;
NhcAction2 action = (NhcAction2) actions.get(actionId);
switch (action.getType()) {
case GENERIC:
case TRIGGER:
property.basicState = NHCTRIGGERED;
break;
case RELAY:
property.status = value;
break;
case DIMMER:
if (NHCON.equals(value)) {
action.setBooleanState(true); // this will trigger sending the stored brightness value event out
property.status = value;
} else if (NHCOFF.equals(value)) {
property.status = value;
} else {
// If the light is off, turn the light on before sending the brightness value, needs to happen
// in 2 separate messages.
if (!action.booleanState()) {
executeAction(actionId, NHCON);
}
property.brightness = value;
}
break;
case ROLLERSHUTTER:
if (NHCSTOP.equals(value)) {
property.action = value;
} else if (NHCUP.equals(value)) {
property.position = "100";
} else if (NHCDOWN.equals(value)) {
property.position = "0";
} else {
int position = 100 - Integer.parseInt(value);
property.position = String.valueOf(position);
}
break;
}
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
sendDeviceMessage(topic, gsonMessage);
}
@Override
public void executeThermostat(String thermostatId, String mode) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
ArrayList<NhcMessageParam> params = new ArrayList<>();
NhcMessageParam param = new NhcMessageParam();
params.add(param);
message.params = params;
ArrayList<NhcDevice2> devices = new ArrayList<>();
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = thermostatId;
ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
NhcProperty overruleActiveProp = new NhcProperty();
deviceProperties.add(overruleActiveProp);
overruleActiveProp.overruleActive = "False";
NhcProperty program = new NhcProperty();
deviceProperties.add(program);
program.program = mode;
device.properties = deviceProperties;
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
sendDeviceMessage(topic, gsonMessage);
}
@Override
public void executeThermostat(String thermostatId, int overruleTemp, int overruleTime) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
ArrayList<NhcMessageParam> params = new ArrayList<>();
NhcMessageParam param = new NhcMessageParam();
params.add(param);
message.params = params;
ArrayList<NhcDevice2> devices = new ArrayList<>();
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = thermostatId;
ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
if (overruleTime > 0) {
NhcProperty overruleActiveProp = new NhcProperty();
overruleActiveProp.overruleActive = "True";
deviceProperties.add(overruleActiveProp);
NhcProperty overruleSetpointProp = new NhcProperty();
overruleSetpointProp.overruleSetpoint = String.valueOf(overruleTemp / 10.0);
deviceProperties.add(overruleSetpointProp);
NhcProperty overruleTimeProp = new NhcProperty();
overruleTimeProp.overruleTime = String.valueOf(overruleTime);
deviceProperties.add(overruleTimeProp);
} else {
NhcProperty overruleActiveProp = new NhcProperty();
overruleActiveProp.overruleActive = "False";
deviceProperties.add(overruleActiveProp);
}
device.properties = deviceProperties;
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
sendDeviceMessage(topic, gsonMessage);
}
@Override
public void startEnergyMeter(String energyMeterId) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
ArrayList<NhcMessageParam> params = new ArrayList<>();
NhcMessageParam param = new NhcMessageParam();
params.add(param);
message.params = params;
ArrayList<NhcDevice2> devices = new ArrayList<>();
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = energyMeterId;
ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
NhcProperty reportInstantUsageProp = new NhcProperty();
deviceProperties.add(reportInstantUsageProp);
reportInstantUsageProp.reportInstantUsage = "True";
device.properties = deviceProperties;
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
((NhcEnergyMeter2) energyMeters.get(energyMeterId)).startEnergyMeter(topic, gsonMessage);
}
@Override
public void stopEnergyMeter(String energyMeterId) {
((NhcEnergyMeter2) energyMeters.get(energyMeterId)).stopEnergyMeter();
}
/**
* Method called from the {@link NhcEnergyMeter2} object to send message to Niko Home Control.
*
* @param topic
* @param gsonMessage
*/
public void executeEnergyMeter(String topic, String gsonMessage) {
sendDeviceMessage(topic, gsonMessage);
}
private void sendDeviceMessage(String topic, String gsonMessage) {
try {
mqttConnection.connectionPublish(topic, gsonMessage);
} catch (MqttException e) {
logger.warn("Niko Home Control: sending command failed, trying to restart communication");
restartCommunication();
// retry sending after restart
try {
if (communicationActive()) {
mqttConnection.connectionPublish(topic, gsonMessage);
} else {
logger.warn("Niko Home Control: failed to restart communication");
connectionLost();
}
} catch (MqttException e1) {
logger.warn("Niko Home Control: error resending device command");
connectionLost();
}
}
}
@Override
public void processMessage(String topic, byte[] payload) {
String message = new String(payload);
if ((profile + "/system/evt").equals(topic)) {
systemEvt(message);
} else if ((profile + "/system/rsp").equals(topic)) {
logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
systeminfoPublishRsp(message);
} else if ((profile + "/notification/evt").equals(topic)) {
logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
notificationEvt(message);
} else if ((profile + "/control/devices/evt").equals(topic)) {
logger.trace("Niko Home Control: received topic {}, payload {}", topic, message);
devicesEvt(message);
} else if ((profile + "/control/devices/rsp").equals(topic)) {
logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
devicesListRsp(message);
} else if ((profile + "/authentication/rsp").equals(topic)) {
logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
servicesListRsp(message);
} else if ((profile + "/control/devices.error").equals(topic)) {
logger.warn("Niko Home Control: received error {}", message);
} else {
logger.trace("Niko Home Control: not acted on received message topic {}, payload {}", topic, message);
}
}
/**
* @return system info retrieved from Connected Controller
*/
public NhcSystemInfo2 getSystemInfo() {
NhcSystemInfo2 systemInfo = nhcSystemInfo;
if (systemInfo == null) {
systemInfo = new NhcSystemInfo2();
}
return systemInfo;
}
/**
* @return time info retrieved from Connected Controller
*/
public NhcTimeInfo2 getTimeInfo() {
NhcTimeInfo2 timeInfo = nhcTimeInfo;
if (timeInfo == null) {
timeInfo = new NhcTimeInfo2();
}
return timeInfo;
}
/**
* @return comma separated list of services retrieved from Connected Controller
*/
public String getServices() {
return services.stream().map(NhcService2::name).collect(Collectors.joining(", "));
}
@Override
public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) {
if (error != null) {
logger.debug("Connection state: {}", state, error);
restartCommunication();
if (!communicationActive()) {
logger.warn("Niko Home Control: failed to restart communication");
connectionLost();
}
} else {
logger.trace("Connection state: {}", state);
}
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="nikohomecontrol" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Niko Home Control Binding</name>
<description>This is the binding for the Niko Home Control system</description>
<author>Mark Herwege</author>
</binding:binding>

View File

@@ -0,0 +1,281 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="nikohomecontrol"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="bridge">
<label>Niko Home Control I Bridge</label>
<description>This bridge represents a Niko Home Control I IP-interface</description>
<channels>
<channel id="alarm" typeId="alarm"/>
<channel id="notice" typeId="notice"/>
</channels>
<config-description>
<parameter name="addr" type="text" required="true">
<label>IP or Host Name</label>
<description>IP Address of Niko Home Control IP-interface</description>
<advanced>false</advanced>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer">
<label>Bridge Port</label>
<description>Port to communicate with Niko Home Control IP-interface, default 8000</description>
<default>8000</default>
<advanced>true</advanced>
</parameter>
<parameter name="refresh" type="integer">
<label>Refresh Interval</label>
<description>Refresh interval for connection with Niko Home Control IP-interface (min), default 300. If set to 0 or
left empty, no refresh will be scheduled</description>
<default>300</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<bridge-type id="bridge2">
<label>Niko Home Control II Bridge</label>
<description>This bridge represents a Niko Home Control II Connected Controller</description>
<channels>
<channel id="alarm" typeId="alarm"/>
<channel id="notice" typeId="notice"/>
</channels>
<config-description>
<parameter name="addr" type="text" required="true">
<label>IP or Host Name</label>
<description>IP Address of Connected Controller</description>
<advanced>false</advanced>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer">
<label>Bridge Port</label>
<description>Port for secure MQTT communication with Connected Controller, default 8884</description>
<default>8884</default>
<advanced>true</advanced>
</parameter>
<parameter name="profile" type="text">
<label>Profile</label>
<description>Profile used in Niko Home Control II for hobby API</description>
<default>hobby</default>
<advanced>true</advanced>
</parameter>
<parameter name="password" type="text" required="true">
<label>API Token</label>
<description>Token for Niko Home Control II hobby API, should not be empty. This token will have to be renewed after
expiration (1 year after creation)</description>
<context>password</context>
</parameter>
<parameter name="refresh" type="integer">
<label>Refresh Interval</label>
<description>Refresh interval for connection with Connected Controller (min), default 300. If set to 0 or left
empty, no refresh will be scheduled</description>
<default>300</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<thing-type id="pushButton">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Pushbutton</label>
<description>Pushbutton type action in Niko Home Control</description>
<channels>
<channel id="button" typeId="button"/>
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>Action ID</label>
<description>Niko Home Control action ID</description>
<advanced>false</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="onOff">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Switch</label>
<description>On/Off type action in Niko Home Control</description>
<channels>
<channel id="switch" typeId="system.power"/>
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>Action ID</label>
<description>Niko Home Control action ID</description>
<advanced>false</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="dimmer">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Dimmer</label>
<description>Dimmer type action in Niko Home Control</description>
<channels>
<channel id="brightness" typeId="system.brightness"/>
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>Action ID</label>
<description>Niko Home Control action ID</description>
<advanced>false</advanced>
</parameter>
<parameter name="step" type="integer" required="true">
<label>Step Value</label>
<description>Step value used for increase/decrease of dimmer brightness, default 10%</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="blind">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Shutter</label>
<description>Rollershutter type action in Niko Home Control</description>
<channels>
<channel id="rollershutter" typeId="rollershutter"/>
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>Action ID</label>
<description>Niko Home Control action ID</description>
<advanced>false</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="thermostat">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Thermostat</label>
<description>Thermostat in the Niko Home Control system</description>
<channels>
<channel id="measured" typeId="measured"/>
<channel id="mode" typeId="mode"/>
<channel id="setpoint" typeId="setpoint"/>
<channel id="overruletime" typeId="overruletime"/>
</channels>
<config-description>
<parameter name="thermostatId" type="text" required="true">
<label>Thermostat ID</label>
<description>Niko Home Control Thermostat ID</description>
<advanced>false</advanced>
</parameter>
<parameter name="overruleTime" type="integer">
<label>Overrule Time</label>
<description>Default overrule duration in minutes when an overrule temperature is set without providing overrule
time, 60 minutes by default</description>
<default>60</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="energyMeter">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Energy Meter</label>
<description>Energy meter in the Niko Home Control system</description>
<channels>
<channel id="power" typeId="power"/>
</channels>
<config-description>
<parameter name="energyMeterId" type="text" required="true">
<label>Energy Meter ID</label>
<description>Niko Home Control Energy Meter ID</description>
<advanced>false</advanced>
</parameter>
</config-description>
</thing-type>
<channel-type id="button">
<item-type>Switch</item-type>
<label>Button</label>
<description>Pushbutton control for action in Niko Home Control</description>
<category>Switch</category>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="rollershutter">
<item-type>Rollershutter</item-type>
<label>Rollershutter</label>
<description>Rollershutter control for rollershutter action in Niko Home Control</description>
<category>Blinds</category>
</channel-type>
<channel-type id="measured">
<item-type>Number:Temperature</item-type>
<label>Measured</label>
<description>Temperature measured by thermostat</description>
<category>Temperature</category>
<tags>
<tag>CurrentTemperature</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="setpoint">
<item-type>Number:Temperature</item-type>
<label>Setpoint</label>
<description>Setpoint temperature of thermostat</description>
<category>Temperature</category>
<tags>
<tag>TargetTemperature</tag>
</tags>
<state min="0" max="100" step="0.5" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="overruletime">
<item-type>Number</item-type>
<label>Overrule Time</label>
<description>Time duration for overruling thermostat target temperature in min.</description>
<category>Number</category>
<state min="0" max="1440" step="5"/>
</channel-type>
<channel-type id="mode">
<item-type>Number</item-type>
<label>Mode</label>
<description>Thermostat mode</description>
<category>Number</category>
<state>
<options>
<option value="0">day</option>
<option value="1">night</option>
<option value="2">eco</option>
<option value="3">off</option>
<option value="4">cool</option>
<option value="5">prog 1</option>
<option value="6">prog 2</option>
<option value="7">prog 3</option>
</options>
</state>
</channel-type>
<channel-type id="power">
<item-type>Number:Power</item-type>
<label>Power</label>
<description>Momentary power consumption/production (positive is consumption)</description>
<category>Number</category>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="alarm">
<kind>trigger</kind>
<label>Alarm</label>
<description>Alarm from Niko Home Control</description>
</channel-type>
<channel-type id="notice">
<kind>trigger</kind>
<label>Notice</label>
<description>Notice from Niko Home Control</description>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,69 @@
newintermediate-certificate = -----BEGIN CERTIFICATE-----\r\n\
MIIF7jCCA9agAwIBAgICEA4wDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAkJF\
MRgwFgYDVQQIDA9Pb3N0LVZsYWFuZGVyZW4xFTATBgNVBAcMDFNpbnQtTmlrbGFh\
czENMAsGA1UECgwETmlrbzEVMBMGA1UECwwMSG9tZSBDb250cm9sMRswGQYJKoZI\
hvcNAQkBFgxpbmZvQG5pa28uYmUwHhcNNzAwMTAxMDAwMDAwWhcNMzcwMTAxMDAw\
MDAwWjCBiTELMAkGA1UEBhMCQkUxGDAWBgNVBAgMD09vc3QtVmxhYW5kZXJlbjEN\
MAsGA1UECgwETmlrbzEVMBMGA1UECwwMSG9tZSBDb250cm9sMR0wGwYDVQQDDBRO\
aWtvIEludGVybWVkaWF0ZSBDQTEbMBkGCSqGSIb3DQEJARYMaW5mb0BuaWtvLmJl\
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuSDk7ob45c78+b/SSfMl\
TOY82nJ/RQjNrIFRTdMUwrt18GMz2TDXJnaz+N5bkxC4L2CkPWZE3eOr3l10al+r\
hZ+m55AhZZcoHHN9vFIul8pw86mVAY8uxr3pM72/270L9yJ+Ra8d+qwM6+L8zWUc\
S/RoGokyutkzfuC20tC1u8IOsUgNHuHwh2dWA0OrI+GWZ6k+Mr/Ojsj7YL5xIrOK\
eZHIN0jy6/hSnWDN1GTxIKpiKCOoFUGAj5Wwpf3Z3mpmSIvAG048fczX2ZdcjCcg\
Iaiw5yeK77G5iMYtzPxJwZRKBVfo+Kf0sPn7QSOJwMJZ8KRgO1KAysuCtspUsemg\
mA0I0pzXOwFJI5dIquMj/2vO+JFB+T8XeoPdeaOc9RJA5Wj2ENIjHTu/W86ElJwU\
8Aw3Z6Gc63mto4FGkM7kN7VQyQVX7EbTmuMC5gHDltrYpsnlKz2d0pShBg++x6IY\
Hd321i8HGqg7NyfG6jZpISQSKKzPZKG++9l2/w7eQ8qJYpGZ6zqiUphygKdx9q2s\
sP8AUbKYZzRBK0u4XDwtJtYAaNw5arKGH4qLHn+EEYTruC1fo9SAGqkPoACd0Oze\
3w8tjsHwwzD8NXJzEpnUyjDmtvi1VfUzKlc82CrNW6iePzR0lGzEQtVBI4rfqbfJ\
RvQ9Hq9HaCrX1P6M5s/ZfisCAwEAAaNmMGQwHQYDVR0OBBYEFHoJvtyYZ7/j4nDe\
kGT2q+xKCWE/MB8GA1UdIwQYMBaAFOa0NGf2t36uYioWVapmm073eJBZMBIGA1Ud\
EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IC\
AQBsl6Y9A5qhTMQHL+Z7S0+t1xjRuzYtzgddEGIWK9nsx1RmH6NDv4KoziaUX3Tx\
YMlOFqx6Q+P0Tyl+x+wbPC3stVa8h4hmr5mOd1ph15hecAK0VbcWeRfMAEqg9o47\
6MheSjXwwIp/oRx3YCX3MhiBaWj1IgLElOA99Xtlksk6VsR6QVoSmTKUNDR0T3TF\
AKq6AH+IIa4wsMlXdkK7LQFGnArmYwXuTyVpDoaYbYP9F5sXslfa294oqPp0kfUl\
niyzX0jLYKAL7CqEBzMXVtLPo2Be6X6uagBIz6MV8s1FGmETf++pWKsuvR9EOoh8\
Cm0xozW9WlPm0dBeMyT991QqDkfaMyOtFT6KZwkD3HxAiTBOZ1LI/P00kaPjpJwt\
+8OKGjqQcXBn6p4ZxF6AmZ9fMCWkYyG37HwSeQYJM/zqrbP+Opfl6dgGJ+Qa5P6k\
1f8YzBkE1gG1V9YcAAWOGPMOgqBE0V0uZfPVctp4wcC4WBqti4pYC28+iHdewQzl\
9LB6RwIJmWNrhRLY+fdutV8NgTVb44vtkaQ+ewyc8y01Fk/G0HXarPt3UYgO6oqa\
FpEU/wi2o9qMVgvHmkXdR1yQLSYZs2R/yzE1KDUSOmxa5T+XFfW7KQ07fhwk27Gk\
y7Ob3mU1LT25MO7yLXUjGqNj9k9aa5FLUTyoh1JGGM64Zw==\r\n\
-----END CERTIFICATE-----\r\n
newca-certificate = -----BEGIN CERTIFICATE-----\r\n\
MIIF6jCCA9KgAwIBAgIJANTA8rXGnhG7MA0GCSqGSIb3DQEBCwUAMIGBMQswCQYD\
VQQGEwJCRTEYMBYGA1UECAwPT29zdC1WbGFhbmRlcmVuMRUwEwYDVQQHDAxTaW50\
LU5pa2xhYXMxDTALBgNVBAoMBE5pa28xFTATBgNVBAsMDEhvbWUgQ29udHJvbDEb\
MBkGCSqGSIb3DQEJARYMaW5mb0BuaWtvLmJlMB4XDTcwMDEwMTAwMDAwNVoXDTM3\
MDEyOTAwMDAwNVowgYExCzAJBgNVBAYTAkJFMRgwFgYDVQQIDA9Pb3N0LVZsYWFu\
ZGVyZW4xFTATBgNVBAcMDFNpbnQtTmlrbGFhczENMAsGA1UECgwETmlrbzEVMBMG\
A1UECwwMSG9tZSBDb250cm9sMRswGQYJKoZIhvcNAQkBFgxpbmZvQG5pa28uYmUw\
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpKNKKHC0fCND19D96G78G\
Zdj+OGLvy/DRJswbLepG8cPedqEZwXjn762fvLdTlcTX/ohkeG4QPb1mxPzjpEgl\
M5aNmp2rmlAFVtLWILQx7mWir5FjG5eTyYi2fbYHnPQpx8XuVk2INENd85818R4j\
RfouYLaZWSd8wc7LP20N0rVtjg5RJ/zAkQ6A7KzdgeOkKhn07wSGBWu9vDw7gCdL\
+Oyeo4LQmABXB7up8nIDCl+o23QL4/aSzdrS5cBCXoPWwto7OiXw0RRcEbpumQyW\
mTGS8jT2FCUNAIWAxC3pKEIXbzf03pLo7EMfFcmjsLDcvcnkB+EJX0fuATwl5CLz\
SneUFY7MNTpv9xgZFX83LhoiFbycZwzWEUr/Q0pmHYZdmezm84+W6EA3E9qH+oR8\
V09bwEMAMSQpbebEB8JmvvwykQHxowkpnV01bmimBEOaquAmyfiW3YSO90vJu+kg\
Zrkihc0AEMFcDbLRCEKvx/u6Hs2xMmVPz0W9mPW37t5zKOV0vcrHmFgMp+9EyDAQ\
vfNofLx790lD1LFp3qvD/H0+IbydQoEc7Q1/tTQDjL45TLNXwwBWQVQLIEQY5sqN\
n8p2ita3MPpSnu5XU93pBcns8jUNlc6/wFIMSBDWK40RiJKzTsr/2jTGVqZX8PXA\
rDnIoa0Eapt0nq87qnkQzQIDAQABo2MwYTAdBgNVHQ4EFgQU5rQ0Z/a3fq5iKhZV\
qmabTvd4kFkwHwYDVR0jBBgwFoAU5rQ0Z/a3fq5iKhZVqmabTvd4kFkwDwYDVR0T\
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAFLw\
6lbxex6hElSrqOZljoFQzQg78dmtUFm4BgKu5EAqn1Ug/OHOKk8LBbNMf2X0Y+4i\
SO4g8yO/C9M/YPxjnM5As1ouTu7UzQL4hEJynyHoPuCBD8nZxuaKeGMX7nQYm7GD\
0iaL0iP9gFwv/A2O/isQUB/sTAclhm1zKAw4f/SaBq8t22wf59e8na0xIfHui0PD\
s8PfRbC4xIOKMxHkHFv+DHeMGjCbR4x20RV/z4JNx1ALEBGo6Oh7Dph/maAQWbje\
x9BCstNR3V1Bhx9rUe7BjIMyJUGEItpZXG+N+qnQr2K7xDdloJl4X0flIa74sdUE\
K4s0X7p+JixLMSxbu5oS6W+d3g6EG0ZgEUwwwc98D1fsm1ziNqwcnYMkI6P2601G\
kEaK/54kYqCxvw6fu5+PNmsDD8ptdazoO3/UOxWvspI1U3drcpnaEHuNclEF7WeL\
yqTfi+8UiL9xJgq9ivjKjZdchkdaD2THgrnzs0XxLbZnwAPeh3cHooUJQkInmKp3\
O05Gv0rnSr29bH8vh/sy4/yJJCUd036pF9C8mPHAYsvNDVGaGYVmNt5P28z3PO16\
YKNJCOJ0x333F6PJaqWAQQP9bGMuJThX8ZQ9Fd8KMXVUfFVKICEkb4erWpL2RIz3\
9JFSC56ZtXv2losfASTyXJwCpyib7FcTZ1rJze+l\r\n\
-----END CERTIFICATE-----\r\n