[nikohomecontrol] Discovery improvements (#12855)

* Add discovery representation properties
* Recognized device types improvements
* Move discovery to thingHandlerService
* Discover multiple bridges in network
* Made device property names constants

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
This commit is contained in:
Mark Herwege 2022-06-03 20:08:08 +02:00 committed by GitHub
parent 43e44ea39a
commit 651fa295e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 486 additions and 322 deletions

View File

@ -90,4 +90,9 @@ public class NikoHomeControlBindingConstants {
public static final String CONFIG_OVERRULETIME = "overruleTime"; public static final String CONFIG_OVERRULETIME = "overruleTime";
public static final String CONFIG_ENERGYMETER_ID = "energyMeterId"; public static final String CONFIG_ENERGYMETER_ID = "energyMeterId";
// Thing properties
public static final String PROPERTY_DEVICE_TYPE = "deviceType";
public static final String PROPERTY_DEVICE_TECHNOLOGY = "deviceTechnology";
public static final String PROPERTY_DEVICE_MODEL = "deviceModel";
} }

View File

@ -14,29 +14,20 @@ package org.openhab.binding.nikohomecontrol.internal;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.NikoHomeControlActionHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler1; import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler1;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler2; import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler2;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlEnergyMeterHandler; import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlEnergyMeterHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlThermostatHandler; 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.net.NetworkAddressService;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID; 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.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory; 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.Component;
import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.Reference;
@ -47,14 +38,12 @@ import org.osgi.service.component.annotations.Reference;
* @author Mark Herwege - Initial Contribution * @author Mark Herwege - Initial Contribution
*/ */
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.nikohomecontrol")
@NonNullByDefault @NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.nikohomecontrol")
public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory { public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory {
private @NonNullByDefault({}) NetworkAddressService networkAddressService; private @NonNullByDefault({}) NetworkAddressService networkAddressService;
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Override @Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) { public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID) || BRIDGE_THING_TYPES_UIDS.contains(thingTypeUID); return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID) || BRIDGE_THING_TYPES_UIDS.contains(thingTypeUID);
@ -63,14 +52,11 @@ public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory {
@Override @Override
protected @Nullable ThingHandler createHandler(Thing thing) { protected @Nullable ThingHandler createHandler(Thing thing) {
if (BRIDGE_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) { if (BRIDGE_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
NikoHomeControlBridgeHandler handler;
if (BRIDGEII_THING_TYPE.equals(thing.getThingTypeUID())) { if (BRIDGEII_THING_TYPE.equals(thing.getThingTypeUID())) {
handler = new NikoHomeControlBridgeHandler2((Bridge) thing, networkAddressService); return new NikoHomeControlBridgeHandler2((Bridge) thing, networkAddressService);
} else { } else {
handler = new NikoHomeControlBridgeHandler1((Bridge) thing); return new NikoHomeControlBridgeHandler1((Bridge) thing);
} }
registerNikoHomeControlDiscoveryService(handler);
return handler;
} else if (THING_TYPE_THERMOSTAT.equals(thing.getThingTypeUID())) { } else if (THING_TYPE_THERMOSTAT.equals(thing.getThingTypeUID())) {
return new NikoHomeControlThermostatHandler(thing); return new NikoHomeControlThermostatHandler(thing);
} else if (THING_TYPE_ENERGYMETER.equals(thing.getThingTypeUID())) { } else if (THING_TYPE_ENERGYMETER.equals(thing.getThingTypeUID())) {
@ -82,29 +68,6 @@ public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory {
return null; 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 @Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) { protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService; this.networkAddressService = networkAddressService;

View File

@ -40,8 +40,8 @@ import org.slf4j.LoggerFactory;
* *
* @author Mark Herwege - Initial Contribution * @author Mark Herwege - Initial Contribution
*/ */
@Component(service = DiscoveryService.class, configurationPid = "discovery.nikohomecontrol")
@NonNullByDefault @NonNullByDefault
@Component(service = DiscoveryService.class, configurationPid = "discovery.nikohomecontrol")
public class NikoHomeControlBridgeDiscoveryService extends AbstractDiscoveryService { public class NikoHomeControlBridgeDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeDiscoveryService.class); private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeDiscoveryService.class);
@ -50,11 +50,11 @@ public class NikoHomeControlBridgeDiscoveryService extends AbstractDiscoveryServ
private @NonNullByDefault({}) NetworkAddressService networkAddressService; private @NonNullByDefault({}) NetworkAddressService networkAddressService;
private static final int TIMEOUT = 5; private static final int TIMOUT_S = 5;
private static final int REFRESH_INTERVAL = 60; private static final int REFRESH_INTERVAL_S = 60;
public NikoHomeControlBridgeDiscoveryService() { public NikoHomeControlBridgeDiscoveryService() {
super(NikoHomeControlBindingConstants.BRIDGE_THING_TYPES_UIDS, TIMEOUT); super(NikoHomeControlBindingConstants.BRIDGE_THING_TYPES_UIDS, TIMOUT_S);
logger.debug("bridge discovery service started"); logger.debug("bridge discovery service started");
} }
@ -70,13 +70,18 @@ public class NikoHomeControlBridgeDiscoveryService extends AbstractDiscoveryServ
} }
logger.debug("discovery broadcast on {}", broadcastAddr); logger.debug("discovery broadcast on {}", broadcastAddr);
NikoHomeControlDiscover nhcDiscover = new NikoHomeControlDiscover(broadcastAddr); NikoHomeControlDiscover nhcDiscover = new NikoHomeControlDiscover(broadcastAddr);
if (nhcDiscover.isNhcII()) { for (String nhcController : nhcDiscover.getNhcBridgeIds()) {
addNhcIIBridge(nhcDiscover.getAddr(), nhcDiscover.getNhcBridgeId()); InetAddress addr = nhcDiscover.getAddr(nhcController);
} else { if (addr != null) {
addNhcIBridge(nhcDiscover.getAddr(), nhcDiscover.getNhcBridgeId()); if (nhcDiscover.isNhcII(nhcController)) {
addNhcIIBridge(addr, nhcController);
} else {
addNhcIBridge(addr, nhcController);
}
}
} }
} catch (IOException e) { } catch (IOException e) {
logger.debug("no bridge found."); logger.debug("bridge discovery IO exception");
} }
} }
@ -87,7 +92,8 @@ public class NikoHomeControlBridgeDiscoveryService extends AbstractDiscoveryServ
ThingUID uid = new ThingUID(BINDING_ID, "bridge", bridgeId); ThingUID uid = new ThingUID(BINDING_ID, "bridge", bridgeId);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withLabel(bridgeName) DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withLabel(bridgeName)
.withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).build(); .withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).withRepresentationProperty(CONFIG_HOST_NAME)
.build();
thingDiscovered(discoveryResult); thingDiscovered(discoveryResult);
} }
@ -98,7 +104,8 @@ public class NikoHomeControlBridgeDiscoveryService extends AbstractDiscoveryServ
ThingUID uid = new ThingUID(BINDING_ID, "bridge2", bridgeId); ThingUID uid = new ThingUID(BINDING_ID, "bridge2", bridgeId);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withLabel(bridgeName) DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withLabel(bridgeName)
.withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).build(); .withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).withRepresentationProperty(CONFIG_HOST_NAME)
.build();
thingDiscovered(discoveryResult); thingDiscovered(discoveryResult);
} }
@ -115,10 +122,10 @@ public class NikoHomeControlBridgeDiscoveryService extends AbstractDiscoveryServ
@Override @Override
protected void startBackgroundDiscovery() { protected void startBackgroundDiscovery() {
logger.debug("Start background bridge discovery"); logger.debug("Start bridge background discovery");
ScheduledFuture<?> job = nhcDiscoveryJob; ScheduledFuture<?> job = nhcDiscoveryJob;
if (job == null || job.isCancelled()) { if (job == null || job.isCancelled()) {
nhcDiscoveryJob = scheduler.scheduleWithFixedDelay(this::discoverBridge, 0, REFRESH_INTERVAL, nhcDiscoveryJob = scheduler.scheduleWithFixedDelay(this::discoverBridge, 0, REFRESH_INTERVAL_S,
TimeUnit.SECONDS); TimeUnit.SECONDS);
} }
} }

View File

@ -14,8 +14,10 @@ package org.openhab.binding.nikohomecontrol.internal.discovery;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*; import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import java.util.Date; import java.time.Instant;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -27,6 +29,8 @@ import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlComm
import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -37,43 +41,52 @@ import org.slf4j.LoggerFactory;
* @author Mark Herwege - Initial Contribution * @author Mark Herwege - Initial Contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class NikoHomeControlDiscoveryService extends AbstractDiscoveryService { public class NikoHomeControlDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlDiscoveryService.class); private final Logger logger = LoggerFactory.getLogger(NikoHomeControlDiscoveryService.class);
private static final int TIMEOUT = 5; private volatile @Nullable ScheduledFuture<?> nhcDiscoveryJob;
private ThingUID bridgeUID; private static final int TIMEOUT_S = 5;
private NikoHomeControlBridgeHandler handler; private static final int INITIAL_DELAY_S = 5; // initial delay for polling to allow time for initial request to NHC
// controller to complete
private static final int REFRESH_INTERVAL_S = 60;
public NikoHomeControlDiscoveryService(NikoHomeControlBridgeHandler handler) { private @Nullable ThingUID bridgeUID;
super(SUPPORTED_THING_TYPES_UIDS, TIMEOUT, false); private @Nullable NikoHomeControlBridgeHandler handler;
logger.debug("discovery service {}", handler);
bridgeUID = handler.getThing().getUID(); public NikoHomeControlDiscoveryService() {
this.handler = handler; super(SUPPORTED_THING_TYPES_UIDS, TIMEOUT_S, true);
logger.debug("device discovery service started");
} }
@Override
public void activate() { public void activate() {
handler.setNhcDiscovery(this); startBackgroundDiscovery();
} }
@Override @Override
public void deactivate() { public void deactivate() {
removeOlderResults(new Date().getTime()); removeOlderResults(Instant.now().toEpochMilli());
handler.setNhcDiscovery(null); super.deactivate();
} }
/** /**
* Discovers devices connected to a Niko Home Control controller * Discovers devices connected to a Niko Home Control controller
*/ */
public void discoverDevices() { public void discoverDevices() {
NikoHomeControlCommunication nhcComm = handler.getCommunication(); NikoHomeControlBridgeHandler bridgeHandler = handler;
if (bridgeHandler == null) {
return;
}
NikoHomeControlCommunication nhcComm = bridgeHandler.getCommunication();
if ((nhcComm == null) || !nhcComm.communicationActive()) { if ((nhcComm == null) || !nhcComm.communicationActive()) {
logger.warn("not connected"); logger.warn("not connected");
return; return;
} }
logger.debug("getting devices on {}", handler.getThing().getUID().getId()); logger.debug("getting devices on {}", bridgeHandler.getThing().getUID().getId());
Map<String, NhcAction> actions = nhcComm.getActions(); Map<String, NhcAction> actions = nhcComm.getActions();
@ -83,20 +96,21 @@ public class NikoHomeControlDiscoveryService extends AbstractDiscoveryService {
switch (nhcAction.getType()) { switch (nhcAction.getType()) {
case TRIGGER: case TRIGGER:
addActionDevice(new ThingUID(THING_TYPE_PUSHBUTTON, handler.getThing().getUID(), actionId), addActionDevice(new ThingUID(THING_TYPE_PUSHBUTTON, bridgeHandler.getThing().getUID(), actionId),
actionId, thingName, thingLocation); actionId, thingName, thingLocation);
break; break;
case RELAY: case RELAY:
addActionDevice(new ThingUID(THING_TYPE_ON_OFF_LIGHT, handler.getThing().getUID(), actionId), addActionDevice(new ThingUID(THING_TYPE_ON_OFF_LIGHT, bridgeHandler.getThing().getUID(), actionId),
actionId, thingName, thingLocation); actionId, thingName, thingLocation);
break; break;
case DIMMER: case DIMMER:
addActionDevice(new ThingUID(THING_TYPE_DIMMABLE_LIGHT, handler.getThing().getUID(), actionId), addActionDevice(
new ThingUID(THING_TYPE_DIMMABLE_LIGHT, bridgeHandler.getThing().getUID(), actionId),
actionId, thingName, thingLocation); actionId, thingName, thingLocation);
break; break;
case ROLLERSHUTTER: case ROLLERSHUTTER:
addActionDevice(new ThingUID(THING_TYPE_BLIND, handler.getThing().getUID(), actionId), actionId, addActionDevice(new ThingUID(THING_TYPE_BLIND, bridgeHandler.getThing().getUID(), actionId),
thingName, thingLocation); actionId, thingName, thingLocation);
break; break;
default: default:
logger.debug("unrecognized action type {} for {} {}", nhcAction.getType(), actionId, thingName); logger.debug("unrecognized action type {} for {} {}", nhcAction.getType(), actionId, thingName);
@ -108,7 +122,7 @@ public class NikoHomeControlDiscoveryService extends AbstractDiscoveryService {
thermostats.forEach((thermostatId, nhcThermostat) -> { thermostats.forEach((thermostatId, nhcThermostat) -> {
String thingName = nhcThermostat.getName(); String thingName = nhcThermostat.getName();
String thingLocation = nhcThermostat.getLocation(); String thingLocation = nhcThermostat.getLocation();
addThermostatDevice(new ThingUID(THING_TYPE_THERMOSTAT, handler.getThing().getUID(), thermostatId), addThermostatDevice(new ThingUID(THING_TYPE_THERMOSTAT, bridgeHandler.getThing().getUID(), thermostatId),
thermostatId, thingName, thingLocation); thermostatId, thingName, thingLocation);
}); });
@ -116,14 +130,16 @@ public class NikoHomeControlDiscoveryService extends AbstractDiscoveryService {
energyMeters.forEach((energyMeterId, nhcEnergyMeter) -> { energyMeters.forEach((energyMeterId, nhcEnergyMeter) -> {
String thingName = nhcEnergyMeter.getName(); String thingName = nhcEnergyMeter.getName();
addEnergyMeterDevice(new ThingUID(THING_TYPE_ENERGYMETER, handler.getThing().getUID(), energyMeterId), String thingLocation = nhcEnergyMeter.getLocation();
energyMeterId, thingName); addEnergyMeterDevice(new ThingUID(THING_TYPE_ENERGYMETER, bridgeHandler.getThing().getUID(), energyMeterId),
energyMeterId, thingName, thingLocation);
}); });
} }
private void addActionDevice(ThingUID uid, String actionId, String thingName, @Nullable String thingLocation) { private void addActionDevice(ThingUID uid, String actionId, String thingName, @Nullable String thingLocation) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID) DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withLabel(thingName).withProperty(CONFIG_ACTION_ID, actionId); .withLabel(thingName).withProperty(CONFIG_ACTION_ID, actionId)
.withRepresentationProperty(CONFIG_ACTION_ID);
if (thingLocation != null) { if (thingLocation != null) {
discoveryResultBuilder.withProperty("Location", thingLocation); discoveryResultBuilder.withProperty("Location", thingLocation);
} }
@ -133,16 +149,22 @@ public class NikoHomeControlDiscoveryService extends AbstractDiscoveryService {
private void addThermostatDevice(ThingUID uid, String thermostatId, String thingName, private void addThermostatDevice(ThingUID uid, String thermostatId, String thingName,
@Nullable String thingLocation) { @Nullable String thingLocation) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID) DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withLabel(thingName).withProperty(CONFIG_THERMOSTAT_ID, thermostatId); .withLabel(thingName).withProperty(CONFIG_THERMOSTAT_ID, thermostatId)
.withRepresentationProperty(CONFIG_THERMOSTAT_ID);
if (thingLocation != null) { if (thingLocation != null) {
discoveryResultBuilder.withProperty("Location", thingLocation); discoveryResultBuilder.withProperty("Location", thingLocation);
} }
thingDiscovered(discoveryResultBuilder.build()); thingDiscovered(discoveryResultBuilder.build());
} }
private void addEnergyMeterDevice(ThingUID uid, String energyMeterId, String thingName) { private void addEnergyMeterDevice(ThingUID uid, String energyMeterId, String thingName,
@Nullable String thingLocation) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID) DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withLabel(thingName).withProperty(CONFIG_ENERGYMETER_ID, energyMeterId); .withLabel(thingName).withProperty(CONFIG_ENERGYMETER_ID, energyMeterId)
.withRepresentationProperty(CONFIG_ENERGYMETER_ID);
if (thingLocation != null) {
discoveryResultBuilder.withProperty("Location", thingLocation);
}
thingDiscovered(discoveryResultBuilder.build()); thingDiscovered(discoveryResultBuilder.build());
} }
@ -156,4 +178,37 @@ public class NikoHomeControlDiscoveryService extends AbstractDiscoveryService {
super.stopScan(); super.stopScan();
removeOlderResults(getTimestampOfLastScan()); removeOlderResults(getTimestampOfLastScan());
} }
@Override
protected void startBackgroundDiscovery() {
logger.debug("Start device background discovery");
ScheduledFuture<?> job = nhcDiscoveryJob;
if (job == null || job.isCancelled()) {
nhcDiscoveryJob = scheduler.scheduleWithFixedDelay(this::discoverDevices, INITIAL_DELAY_S,
REFRESH_INTERVAL_S, TimeUnit.SECONDS);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stop device background discovery");
ScheduledFuture<?> job = nhcDiscoveryJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
nhcDiscoveryJob = null;
}
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof NikoHomeControlBridgeHandler) {
this.handler = (NikoHomeControlBridgeHandler) handler;
bridgeUID = handler.getThing().getUID();
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}
} }

View File

@ -238,7 +238,7 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
nhcAction.setEventHandler(this); nhcAction.setEventHandler(this);
updateProperties(); updateProperties(nhcAction);
String actionLocation = nhcAction.getLocation(); String actionLocation = nhcAction.getLocation();
if (thing.getLocation() == null) { if (thing.getLocation() == null) {
@ -260,14 +260,9 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
}); });
} }
private void updateProperties() { private void updateProperties(NhcAction nhcAction) {
NhcAction nhcAction = this.nhcAction;
if (nhcAction == null) {
logger.debug("action with ID {} not initialized", actionId);
return;
}
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
properties.put("type", String.valueOf(nhcAction.getType())); properties.put("type", String.valueOf(nhcAction.getType()));
if (getThing().getThingTypeUID() == THING_TYPE_BLIND) { if (getThing().getThingTypeUID() == THING_TYPE_BLIND) {
properties.put("timeToOpen", String.valueOf(nhcAction.getOpenTime())); properties.put("timeToOpen", String.valueOf(nhcAction.getOpenTime()));
@ -276,8 +271,9 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
if (nhcAction instanceof NhcAction2) { if (nhcAction instanceof NhcAction2) {
NhcAction2 action = (NhcAction2) nhcAction; NhcAction2 action = (NhcAction2) nhcAction;
properties.put("model", action.getModel()); properties.put(PROPERTY_DEVICE_TYPE, action.getDeviceType());
properties.put("technology", action.getTechnology()); properties.put(PROPERTY_DEVICE_TECHNOLOGY, action.getDeviceTechnology());
properties.put(PROPERTY_DEVICE_MODEL, action.getDeviceModel());
} }
thing.setProperties(properties); thing.setProperties(properties);

View File

@ -16,8 +16,10 @@ import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindin
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -32,6 +34,7 @@ import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -39,8 +42,7 @@ import org.slf4j.LoggerFactory;
/** /**
* {@link NikoHomeControlBridgeHandler} is an abstract class representing a handler to all different interfaces to the * {@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 * Niko Home Control System. {@link NikoHomeControlBridgeHandler1} or {@link NikoHomeControlBridgeHandler2} should be
* used for the respective * used for the respective version of Niko Home Control.
* version of Niko Home Control.
* *
* @author Mark Herwege - Initial Contribution * @author Mark Herwege - Initial Contribution
*/ */
@ -49,14 +51,10 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler.class); private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler.class);
protected @NonNullByDefault({}) NikoHomeControlBridgeConfig config;
protected @Nullable NikoHomeControlCommunication nhcComm; protected @Nullable NikoHomeControlCommunication nhcComm;
private volatile @Nullable ScheduledFuture<?> refreshTimer; private volatile @Nullable ScheduledFuture<?> refreshTimer;
protected volatile @Nullable NikoHomeControlDiscoveryService nhcDiscovery;
public NikoHomeControlBridgeHandler(Bridge nikoHomeControlBridge) { public NikoHomeControlBridgeHandler(Bridge nikoHomeControlBridge) {
super(nikoHomeControlBridge); super(nikoHomeControlBridge);
} }
@ -90,22 +88,15 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
int refreshInterval = config.refresh; int refreshInterval = getConfig().as(NikoHomeControlBridgeConfig.class).refresh;
setupRefreshTimer(refreshInterval); setupRefreshTimer(refreshInterval);
NikoHomeControlDiscoveryService discovery = nhcDiscovery;
if (discovery != null) {
discovery.discoverDevices();
} else {
logger.debug("cannot discover devices, discovery service not started");
}
}); });
} }
/** /**
* Schedule future communication refresh. * Schedule future communication refresh.
* *
* @param interval_config Time before refresh in minutes. * @param refreshInterval Time before refresh in minutes.
*/ */
private void setupRefreshTimer(int refreshInterval) { private void setupRefreshTimer(int refreshInterval) {
ScheduledFuture<?> timer = refreshTimer; ScheduledFuture<?> timer = refreshTimer;
@ -163,7 +154,7 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
public void controllerOnline() { public void controllerOnline() {
bridgeOnline(); bridgeOnline();
int refreshInterval = config.refresh; int refreshInterval = getConfig().as(NikoHomeControlBridgeConfig.class).refresh;
if (refreshTimer == null) { if (refreshTimer == null) {
setupRefreshTimer(refreshInterval); setupRefreshTimer(refreshInterval);
} }
@ -199,13 +190,11 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
} }
Configuration configuration = editConfiguration(); Configuration configuration = editConfiguration();
for (Entry<String, Object> configurationParmeter : configurationParameters.entrySet()) { for (Entry<String, Object> configurationParameter : configurationParameters.entrySet()) {
configuration.put(configurationParmeter.getKey(), configurationParmeter.getValue()); configuration.put(configurationParameter.getKey(), configurationParameter.getValue());
} }
updateConfiguration(configuration); updateConfiguration(configuration);
setConfig();
scheduler.submit(() -> { scheduler.submit(() -> {
comm.restartCommunication(); comm.restartCommunication();
if (!comm.communicationActive()) { if (!comm.communicationActive()) {
@ -217,20 +206,11 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
int refreshInterval = config.refresh; int refreshInterval = getConfig().as(NikoHomeControlBridgeConfig.class).refresh;
setupRefreshTimer(refreshInterval); 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 @Override
public void alarmEvent(String alarmText) { public void alarmEvent(String alarmText) {
logger.debug("triggering alarm channel with {}", alarmText); logger.debug("triggering alarm channel with {}", alarmText);
@ -262,6 +242,7 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
@Override @Override
public @Nullable InetAddress getAddr() { public @Nullable InetAddress getAddr() {
InetAddress addr = null; InetAddress addr = null;
NikoHomeControlBridgeConfig config = getConfig().as(NikoHomeControlBridgeConfig.class);
try { try {
addr = InetAddress.getByName(config.addr); addr = InetAddress.getByName(config.addr);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
@ -272,10 +253,11 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
@Override @Override
public int getPort() { public int getPort() {
return config.port; return getConfig().as(NikoHomeControlBridgeConfig.class).port;
} }
protected synchronized void setConfig() { @Override
config = getConfig().as(NikoHomeControlBridgeConfig.class); public Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(NikoHomeControlDiscoveryService.class);
} }
} }

View File

@ -45,7 +45,6 @@ public class NikoHomeControlBridgeHandler1 extends NikoHomeControlBridgeHandler
public void initialize() { public void initialize() {
logger.debug("initializing bridge handler"); logger.debug("initializing bridge handler");
setConfig();
InetAddress addr = getAddr(); InetAddress addr = getAddr();
int port = getPort(); int port = getPort();

View File

@ -59,8 +59,6 @@ public class NikoHomeControlBridgeHandler2 extends NikoHomeControlBridgeHandler
public void initialize() { public void initialize() {
logger.debug("initializing NHC II bridge handler"); logger.debug("initializing NHC II bridge handler");
setConfig();
Date expiryDate = getTokenExpiryDate(); Date expiryDate = getTokenExpiryDate();
if (expiryDate == null) { if (expiryDate == null) {
if (getToken().isEmpty()) { if (getToken().isEmpty()) {
@ -161,12 +159,12 @@ public class NikoHomeControlBridgeHandler2 extends NikoHomeControlBridgeHandler
@Override @Override
public String getProfile() { public String getProfile() {
return ((NikoHomeControlBridgeConfig2) config).profile; return getConfig().as(NikoHomeControlBridgeConfig2.class).profile;
} }
@Override @Override
public String getToken() { public String getToken() {
String token = ((NikoHomeControlBridgeConfig2) config).password; String token = getConfig().as(NikoHomeControlBridgeConfig2.class).password;
if (token.isEmpty()) { if (token.isEmpty()) {
logger.debug("no JWT token set."); logger.debug("no JWT token set.");
} }
@ -227,9 +225,4 @@ public class NikoHomeControlBridgeHandler2 extends NikoHomeControlBridgeHandler
return null; return null;
} }
@Override
protected synchronized void setConfig() {
config = getConfig().as(NikoHomeControlBridgeConfig2.class);
}
} }

View File

@ -12,7 +12,7 @@
*/ */
package org.openhab.binding.nikohomecontrol.internal.handler; package org.openhab.binding.nikohomecontrol.internal.handler;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.CHANNEL_POWER; import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import static org.openhab.core.types.RefreshType.REFRESH; import static org.openhab.core.types.RefreshType.REFRESH;
import java.util.HashMap; import java.util.HashMap;
@ -102,7 +102,12 @@ public class NikoHomeControlEnergyMeterHandler extends BaseThingHandler implemen
nhcEnergyMeter.setEventHandler(this); nhcEnergyMeter.setEventHandler(this);
updateProperties(); updateProperties(nhcEnergyMeter);
String location = nhcEnergyMeter.getLocation();
if (thing.getLocation() == null) {
thing.setLocation(location);
}
// Subscribing to power readings starts an intensive data flow, therefore only do it when there is an item // Subscribing to power readings starts an intensive data flow, therefore only do it when there is an item
// linked to the channel // linked to the channel
@ -132,13 +137,14 @@ public class NikoHomeControlEnergyMeterHandler extends BaseThingHandler implemen
} }
} }
private void updateProperties() { private void updateProperties(NhcEnergyMeter nhcEnergyMeter) {
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
if (nhcEnergyMeter instanceof NhcEnergyMeter2) { if (nhcEnergyMeter instanceof NhcEnergyMeter2) {
NhcEnergyMeter2 energyMeter = (NhcEnergyMeter2) nhcEnergyMeter; NhcEnergyMeter2 energyMeter = (NhcEnergyMeter2) nhcEnergyMeter;
properties.put("model", energyMeter.getModel()); properties.put(PROPERTY_DEVICE_TYPE, energyMeter.getDeviceType());
properties.put("technology", energyMeter.getTechnology()); properties.put(PROPERTY_DEVICE_TECHNOLOGY, energyMeter.getDeviceTechnology());
properties.put(PROPERTY_DEVICE_MODEL, energyMeter.getDeviceModel());
} }
thing.setProperties(properties); thing.setProperties(properties);

View File

@ -181,7 +181,7 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
nhcThermostat.setEventHandler(this); nhcThermostat.setEventHandler(this);
updateProperties(); updateProperties(nhcThermostat);
String thermostatLocation = nhcThermostat.getLocation(); String thermostatLocation = nhcThermostat.getLocation();
if (thing.getLocation() == null) { if (thing.getLocation() == null) {
@ -204,13 +204,14 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
}); });
} }
private void updateProperties() { private void updateProperties(NhcThermostat nhcThermostat) {
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
if (nhcThermostat instanceof NhcThermostat2) { if (nhcThermostat instanceof NhcThermostat2) {
NhcThermostat2 thermostat = (NhcThermostat2) nhcThermostat; NhcThermostat2 thermostat = (NhcThermostat2) nhcThermostat;
properties.put("model", thermostat.getModel()); properties.put(PROPERTY_DEVICE_TYPE, thermostat.getDeviceType());
properties.put("technology", thermostat.getTechnology()); properties.put(PROPERTY_DEVICE_TECHNOLOGY, thermostat.getDeviceTechnology());
properties.put(PROPERTY_DEVICE_MODEL, thermostat.getDeviceModel());
} }
thing.setProperties(properties); thing.setProperties(properties);

View File

@ -66,9 +66,9 @@ public abstract class NhcAction {
} }
/** /**
* Get the id of the action. * Get id of action.
* *
* @return the id * @return id
*/ */
public String getId() { public String getId() {
return id; return id;
@ -83,6 +83,15 @@ public abstract class NhcAction {
return name; return name;
} }
/**
* Set name of action.
*
* @param name action name
*/
public void setName(String name) {
this.name = name;
}
/** /**
* Get type of action identified. * Get type of action identified.
* <p> * <p>
@ -103,6 +112,15 @@ public abstract class NhcAction {
return location; return location;
} }
/**
* Set location name of action.
*
* @param location action location name
*/
public void setLocation(@Nullable String location) {
this.location = location;
}
/** /**
* Get state of action. * Get state of action.
* <p> * <p>

View File

@ -36,14 +36,16 @@ public abstract class NhcEnergyMeter {
protected String id; protected String id;
protected String name; protected String name;
protected @Nullable String location;
// This can be null as long as we do not receive power readings // This can be null as long as we do not receive power readings
protected volatile @Nullable Integer power = null; protected volatile @Nullable Integer power = null;
private @Nullable NhcEnergyMeterEvent eventHandler; private @Nullable NhcEnergyMeterEvent eventHandler;
protected NhcEnergyMeter(String id, String name, NikoHomeControlCommunication nhcComm) { protected NhcEnergyMeter(String id, String name, @Nullable String location, NikoHomeControlCommunication nhcComm) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.location = location;
this.nhcComm = nhcComm; this.nhcComm = nhcComm;
} }
@ -84,23 +86,50 @@ public abstract class NhcEnergyMeter {
} }
/** /**
* Get the id of the energyMeters meter. * Get id of meter.
* *
* @return the id * @return id
*/ */
public String getId() { public String getId() {
return id; return id;
} }
/** /**
* Get name of the energyMeters meter. * Get name of meter.
* *
* @return energyMeters meter name * @return energyMeter name
*/ */
public String getName() { public String getName() {
return name; return name;
} }
/**
* Set name of meter.
*
* @param name meter name
*/
public void setName(String name) {
this.name = name;
}
/**
* Get location name of meter.
*
* @return location energyMeter location
*/
public @Nullable String getLocation() {
return location;
}
/**
* Set location name of meter.
*
* @param location meter location name
*/
public void setLocation(@Nullable String location) {
this.location = location;
}
/** /**
* @return the power in W (positive for consumption, negative for production), return null if no reading received * @return the power in W (positive for consumption, negative for production), return null if no reading received
* yet * yet

View File

@ -142,9 +142,9 @@ public abstract class NhcThermostat {
} }
/** /**
* Get the id of the thermostat. * Get id of the thermostat.
* *
* @return the id * @return id
*/ */
public String getId() { public String getId() {
return id; return id;
@ -160,7 +160,16 @@ public abstract class NhcThermostat {
} }
/** /**
* Get location name of action. * Set name of thermostat.
*
* @param name thermostat name
*/
public void setName(String name) {
this.name = name;
}
/**
* Get location name of thermostat.
* *
* @return location name * @return location name
*/ */
@ -168,6 +177,15 @@ public abstract class NhcThermostat {
return location; return location;
} }
/**
* Set location name of thermostat.
*
* @param location thermostat location name
*/
public void setLocation(@Nullable String location) {
this.location = location;
}
/** /**
* Get measured temperature. * Get measured temperature.
* *

View File

@ -16,9 +16,15 @@ import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.util.HexUtils; import org.openhab.core.util.HexUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -43,13 +49,13 @@ public final class NikoHomeControlDiscover {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlDiscover.class); private final Logger logger = LoggerFactory.getLogger(NikoHomeControlDiscover.class);
private InetAddress addr; private List<String> nhcBridgeIds = new ArrayList<>();
private String nhcBridgeId = ""; private Map<String, InetAddress> addr = new HashMap<>();
private boolean isNhcII; private Map<String, Boolean> isNhcII = new HashMap<>();
/** /**
* Discover a Niko Home Control IP interface by broadcasting UDP packet 0x44 to port 10000. The IP interface will * Discover the list of Niko Home Control IP interfaces by broadcasting UDP packet 0x44 to port 10000. The IP
* reply. The address of the IP interface is than derived from that response. * interface will reply. The address of the IP interface is than derived from that response.
* *
* @param broadcast Broadcast address of the network * @param broadcast Broadcast address of the network
* @throws IOException * @throws IOException
@ -68,33 +74,37 @@ public final class NikoHomeControlDiscover {
datagramSocket.setBroadcast(true); datagramSocket.setBroadcast(true);
datagramSocket.setSoTimeout(500); datagramSocket.setSoTimeout(500);
datagramSocket.send(discoveryPacket); datagramSocket.send(discoveryPacket);
while (true) { try {
datagramSocket.receive(packet); while (true) {
logger.trace("bridge discovery response {}", datagramSocket.receive(packet);
HexUtils.bytesToHex(Arrays.copyOf(packet.getData(), packet.getLength()))); logger.trace("bridge discovery response {}",
if (isNhc(packet)) { HexUtils.bytesToHex(Arrays.copyOf(packet.getData(), packet.getLength())));
break; if (isNhcController(packet)) {
String bridgeId = setNhcBridgeId(packet);
setIsNhcII(bridgeId, packet);
setAddr(bridgeId, packet);
logger.debug("IP address is {}, unique ID is {}", addr, bridgeId);
}
} }
} catch (SocketTimeoutException e) {
// all received, continue
} }
addr = packet.getAddress();
setNhcBridgeId(packet);
setIsNhcII(packet);
logger.debug("IP address is {}, unique ID is {}", addr, nhcBridgeId);
} }
} }
/** /**
* @return the addr * @return the discovered nhcBridgeIds
*/ */
public InetAddress getAddr() { public List<String> getNhcBridgeIds() {
return addr; return nhcBridgeIds;
} }
/** /**
* @return the nhcBridgeId * @param bridgeId discovered bridgeId
* @return the addr, null if not in the list of discovered bridgeId's
*/ */
public String getNhcBridgeId() { public @Nullable InetAddress getAddr(String bridgeId) {
return nhcBridgeId; return addr.get(bridgeId);
} }
/** /**
@ -103,9 +113,15 @@ public final class NikoHomeControlDiscover {
* @param packet * @param packet
* @return true if packet is from a Niko Home Control controller * @return true if packet is from a Niko Home Control controller
*/ */
private boolean isNhc(DatagramPacket packet) { private boolean isNhcController(DatagramPacket packet) {
byte[] packetData = packet.getData(); byte[] packetData = packet.getData();
return ((packet.getLength() > 2) && (packetData[0] == 0x44)); boolean isNhc = (packet.getLength() > 2) && (packetData[0] == 0x44);
// filter response from Gen1 touchscreens
boolean isController = isNhc && (packetData[1] == 0x3b) || (packetData[1] == 0x0c) || (packetData[1] == 0x0e);
if (!isController) {
logger.trace("not a NHC controller");
}
return isController;
} }
/** /**
@ -113,7 +129,7 @@ public final class NikoHomeControlDiscover {
* *
* @param packet * @param packet
*/ */
private void setNhcBridgeId(DatagramPacket packet) { private String setNhcBridgeId(DatagramPacket packet) {
byte[] packetData = packet.getData(); byte[] packetData = packet.getData();
int packetLength = packet.getLength(); int packetLength = packet.getLength();
packetLength = packetLength > 6 ? 6 : packetLength; packetLength = packetLength > 6 ? 6 : packetLength;
@ -121,31 +137,45 @@ public final class NikoHomeControlDiscover {
for (int i = 0; i < packetLength; i++) { for (int i = 0; i < packetLength; i++) {
sb.append(String.format("%02x", packetData[i])); sb.append(String.format("%02x", packetData[i]));
} }
nhcBridgeId = sb.toString(); String bridgeId = sb.toString();
nhcBridgeIds.add(bridgeId);
return bridgeId;
} }
/** /**
* Checks if this is a NHC II Connected Controller * Checks if this is a NHC II Connected Controller
* *
* @param bridgeId
* @param packet * @param packet
*/ */
private void setIsNhcII(DatagramPacket packet) { private void setIsNhcII(String bridgeId, DatagramPacket packet) {
byte[] packetData = packet.getData(); byte[] packetData = packet.getData();
int packetLength = packet.getLength(); int packetLength = packet.getLength();
// The 16th byte in the packet is 2 for a NHC II Connected Controller // The 16th byte in the packet is 2 for a NHC II Connected Controller
if ((packetLength >= 16) && (packetData[15] >= 2)) { if ((packetLength >= 16) && (packetData[15] >= 2)) {
isNhcII = true; isNhcII.put(bridgeId, true);
} else { } else {
isNhcII = false; isNhcII.put(bridgeId, false);
} }
} }
/**
* Sets the IP address retrieved from the packet response
*
* @param bridgeId
* @param packet
*/
private void setAddr(String bridgeId, DatagramPacket packet) {
addr.put(bridgeId, packet.getAddress());
}
/** /**
* Test if the installation is a Niko Home Control II installation * Test if the installation is a Niko Home Control II installation
* *
* @param bridgeId
* @return true if this is a Niko Home Control II installation * @return true if this is a Niko Home Control II installation
*/ */
public boolean isNhcII() { public boolean isNhcII(String bridgeId) {
return isNhcII; return isNhcII.getOrDefault(bridgeId, false);
} }
} }

View File

@ -364,38 +364,38 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
String value3 = action.get("value3"); String value3 = action.get("value3");
int openTime = ((value3 == null) || value3.isEmpty() ? 0 : Integer.parseInt(value3)); int openTime = ((value3 == null) || value3.isEmpty() ? 0 : Integer.parseInt(value3));
String name = action.get("name");
if (name == null) {
logger.debug("name not found in action {}", action);
continue;
}
String type = Optional.ofNullable(action.get("type")).orElse("");
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("unknown action type {} for action {}", type, id);
continue;
}
String locationId = action.get("location");
String location = "";
if (locationId != null && !locationId.isEmpty()) {
location = locations.getOrDefault(locationId, new NhcLocation1("")).getName();
}
if (!actions.containsKey(id)) { if (!actions.containsKey(id)) {
// Initial instantiation of NhcAction class for action object // Initial instantiation of NhcAction class for action object
String name = action.get("name");
if (name == null) {
logger.debug("name not found in action {}", action);
continue;
}
String type = Optional.ofNullable(action.get("type")).orElse("");
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("unknown action type {} for action {}", type, id);
continue;
}
String locationId = action.get("location");
String location = "";
if (locationId != null && !locationId.isEmpty()) {
location = locations.getOrDefault(locationId, new NhcLocation1("")).getName();
}
NhcAction nhcAction = new NhcAction1(id, name, actionType, location, this, scheduler); NhcAction nhcAction = new NhcAction1(id, name, actionType, location, this, scheduler);
if (actionType == ActionType.ROLLERSHUTTER) { if (actionType == ActionType.ROLLERSHUTTER) {
nhcAction.setShutterTimes(openTime, closeTime); nhcAction.setShutterTimes(openTime, closeTime);
@ -403,11 +403,13 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
nhcAction.setState(state); nhcAction.setState(state);
actions.put(id, nhcAction); actions.put(id, nhcAction);
} else { } else {
// Action object already exists, so only update state. // Action object already exists, so only update state, name and location.
// If we would re-instantiate action, we would lose pointer back from action to thing handler that was // If we would re-instantiate action, we would lose pointer back from action to thing handler that was
// set in thing handler initialize(). // set in thing handler initialize().
NhcAction nhcAction = actions.get(id); NhcAction nhcAction = actions.get(id);
if (nhcAction != null) { if (nhcAction != null) {
nhcAction.setName(name);
nhcAction.setLocation(location);
nhcAction.setState(state); nhcAction.setState(state);
} }
} }
@ -452,23 +454,25 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
// measured // measured
int demand = (mode != 3) ? (setpoint > measured ? 1 : (setpoint < measured ? -1 : 0)) : 0; int demand = (mode != 3) ? (setpoint > measured ? 1 : (setpoint < measured ? -1 : 0)) : 0;
String name = thermostat.get("name");
String locationId = thermostat.get("location");
NhcLocation1 nhcLocation = null;
if (!((locationId == null) || locationId.isEmpty())) {
nhcLocation = locations.get(locationId);
}
String location = (nhcLocation != null) ? nhcLocation.getName() : null;
NhcThermostat t = thermostats.computeIfAbsent(id, i -> { NhcThermostat t = thermostats.computeIfAbsent(id, i -> {
// Initial instantiation of NhcThermostat class for thermostat object // Initial instantiation of NhcThermostat class for thermostat object
String name = thermostat.get("name");
String locationId = thermostat.get("location");
String location = "";
if (!((locationId == null) || locationId.isEmpty())) {
NhcLocation1 nhcLocation = locations.get(locationId);
if (nhcLocation != null) {
location = nhcLocation.getName();
}
}
if (name != null) { if (name != null) {
return new NhcThermostat1(i, name, location, this); return new NhcThermostat1(i, name, location, this);
} }
throw new IllegalArgumentException(); throw new IllegalArgumentException();
}); });
if (t != null) { if (t != null) {
if (name != null) {
t.setName(name);
}
t.setLocation(location);
t.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand); t.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {

View File

@ -35,14 +35,16 @@ public class NhcAction2 extends NhcAction {
private final Logger logger = LoggerFactory.getLogger(NhcAction2.class); private final Logger logger = LoggerFactory.getLogger(NhcAction2.class);
private volatile boolean booleanState; private volatile boolean booleanState;
private String model; private String deviceType;
private String technology; private String deviceTechnology;
private String deviceModel;
NhcAction2(String id, String name, String model, String technology, ActionType type, @Nullable String location, NhcAction2(String id, String name, String deviceType, String deviceTechnology, String deviceModel,
NikoHomeControlCommunication nhcComm) { @Nullable String location, ActionType type, NikoHomeControlCommunication nhcComm) {
super(id, name, type, location, nhcComm); super(id, name, type, location, nhcComm);
this.model = model; this.deviceType = deviceType;
this.technology = technology; this.deviceTechnology = deviceTechnology;
this.deviceModel = deviceModel;
} }
/** /**
@ -120,7 +122,7 @@ public class NhcAction2 extends NhcAction {
logger.debug("execute action {} of type {} for {}", command, type, id); logger.debug("execute action {} of type {} for {}", command, type, id);
String cmd; String cmd;
if ("flag".equals(model)) { if ("flag".equals(deviceModel)) {
cmd = NHCON.equals(command) ? NHCTRUE : NHCFALSE; cmd = NHCON.equals(command) ? NHCTRUE : NHCFALSE;
} else { } else {
cmd = command; cmd = command;
@ -130,16 +132,23 @@ public class NhcAction2 extends NhcAction {
} }
/** /**
* @return model as returned from Niko Home Control * @return type as returned from Niko Home Control
*/ */
public String getModel() { public String getDeviceType() {
return model; return deviceType;
} }
/** /**
* @return technology as returned from Niko Home Control * @return technology as returned from Niko Home Control
*/ */
public String getTechnology() { public String getDeviceTechnology() {
return technology; return deviceTechnology;
}
/**
* @return model as returned from Niko Home Control
*/
public String getDeviceModel() {
return deviceModel;
} }
} }

View File

@ -76,6 +76,18 @@ class NhcDevice2 {
@Nullable @Nullable
String electricalPower; String electricalPower;
@Nullable @Nullable
String electricalPowerToGrid;
@Nullable
String electricalPowerFromGrid;
@Nullable
String electricalPowerProduction;
@Nullable
String electricalPowerSelfConsumption;
@Nullable
String electricalPowerConsumption;
@Nullable
String electricalPowerProductionThresholdExceeded;
@Nullable
String reportInstantUsage; String reportInstantUsage;
// fields for access control // fields for access control
@Nullable @Nullable

View File

@ -34,14 +34,16 @@ public class NhcEnergyMeter2 extends NhcEnergyMeter {
private ScheduledExecutorService scheduler; private ScheduledExecutorService scheduler;
private volatile @Nullable ScheduledFuture<?> restartTimer; private volatile @Nullable ScheduledFuture<?> restartTimer;
private String model; private String deviceType;
private String technology; private String deviceTechnology;
private String deviceModel;
protected NhcEnergyMeter2(String id, String name, String model, String technology, protected NhcEnergyMeter2(String id, String name, String deviceType, String deviceTechnology, String deviceModel,
NikoHomeControlCommunication nhcComm, ScheduledExecutorService scheduler) { @Nullable String location, NikoHomeControlCommunication nhcComm, ScheduledExecutorService scheduler) {
super(id, name, nhcComm); super(id, name, location, nhcComm);
this.model = model; this.deviceType = deviceType;
this.technology = technology; this.deviceTechnology = deviceTechnology;
this.deviceModel = deviceModel;
this.scheduler = scheduler; this.scheduler = scheduler;
} }
@ -75,16 +77,23 @@ public class NhcEnergyMeter2 extends NhcEnergyMeter {
} }
/** /**
* @return model as returned from Niko Home Control * @return type as returned from Niko Home Control
*/ */
public String getModel() { public String getDeviceType() {
return model; return deviceType;
} }
/** /**
* @return technology as returned from Niko Home Control * @return technology as returned from Niko Home Control
*/ */
public String getTechnology() { public String getDeviceTechnology() {
return technology; return deviceTechnology;
}
/**
* @return model as returned from Niko Home Control
*/
public String getDeviceModel() {
return deviceModel;
} }
} }

View File

@ -33,14 +33,16 @@ public class NhcThermostat2 extends NhcThermostat {
private final Logger logger = LoggerFactory.getLogger(NhcThermostat2.class); private final Logger logger = LoggerFactory.getLogger(NhcThermostat2.class);
private String model; private String deviceType;
private String technology; private String deviceTechnology;
private String deviceModel;
protected NhcThermostat2(String id, String name, String model, String technology, @Nullable String location, protected NhcThermostat2(String id, String name, String deviceType, String deviceTechnology, String deviceModel,
NikoHomeControlCommunication nhcComm) { @Nullable String location, NikoHomeControlCommunication nhcComm) {
super(id, name, location, nhcComm); super(id, name, location, nhcComm);
this.model = model; this.deviceType = deviceType;
this.technology = technology; this.deviceTechnology = deviceTechnology;
this.deviceModel = deviceModel;
} }
@Override @Override
@ -59,16 +61,23 @@ public class NhcThermostat2 extends NhcThermostat {
} }
/** /**
* @return model as returned from Niko Home Control * @return type as returned from Niko Home Control
*/ */
public String getModel() { public String getDeviceType() {
return model; return deviceType;
} }
/** /**
* @return technology as returned from Niko Home Control * @return technology as returned from Niko Home Control
*/ */
public String getTechnology() { public String getDeviceTechnology() {
return technology; return deviceTechnology;
}
/**
* @return model as returned from Niko Home Control
*/
public String getDeviceModel() {
return deviceModel;
} }
} }

View File

@ -364,63 +364,77 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
} }
if ("action".equals(device.type) || "virtual".equals(device.type)) { if ("action".equals(device.type) || "virtual".equals(device.type)) {
if (!actions.containsKey(device.uuid)) { ActionType actionType;
logger.debug("adding action device {}, {}", device.uuid, device.name); switch (device.model) {
case "generic":
ActionType actionType; case "pir":
switch (device.model) { case "simulation":
case "generic": case "comfort":
case "pir": case "alarms":
case "simulation": case "alloff":
case "comfort": case "overallcomfort":
case "alarms": case "garagedoor":
case "alloff": actionType = ActionType.TRIGGER;
case "overallcomfort": break;
case "garagedoor": case "light":
actionType = ActionType.TRIGGER; case "socket":
break; case "switched-generic":
case "light": case "switched-fan":
case "socket": case "flag":
case "switched-generic": actionType = ActionType.RELAY;
case "switched-fan": break;
case "flag": case "dimmer":
actionType = ActionType.RELAY; actionType = ActionType.DIMMER;
break; break;
case "dimmer": case "rolldownshutter":
actionType = ActionType.DIMMER; case "sunblind":
break; case "venetianblind":
case "rolldownshutter": case "gate":
case "sunblind": actionType = ActionType.ROLLERSHUTTER;
case "venetianblind": break;
case "gate": default:
actionType = ActionType.ROLLERSHUTTER; actionType = ActionType.GENERIC;
break; logger.debug("device type {} and model {} not recognised for {}, {}, ignoring", device.type,
default: device.model, device.uuid, device.name);
actionType = ActionType.GENERIC; return;
logger.debug("device model {} not recognised, default to GENERIC action", device.model);
}
NhcAction2 nhcAction = new NhcAction2(device.uuid, device.name, device.model, device.technology,
actionType, location, this);
actions.put(device.uuid, nhcAction);
} }
NhcAction nhcAction = actions.get(device.uuid);
if (nhcAction != null) {
// update name and location so discovery will see updated name and location
nhcAction.setName(device.name);
nhcAction.setLocation(location);
} else {
logger.debug("adding action device {} model {}, {}", device.uuid, device.model, device.name);
nhcAction = new NhcAction2(device.uuid, device.name, device.type, device.technology, device.model,
location, actionType, this);
}
actions.put(device.uuid, nhcAction);
} else if ("thermostat".equals(device.type)) { } else if ("thermostat".equals(device.type)) {
if (!thermostats.containsKey(device.uuid)) { NhcThermostat nhcThermostat = thermostats.get(device.uuid);
logger.debug("adding thermostat device {}, {}", device.uuid, device.name); if (nhcThermostat != null) {
nhcThermostat.setName(device.name);
NhcThermostat2 nhcThermostat = new NhcThermostat2(device.uuid, device.name, device.model, nhcThermostat.setLocation(location);
device.technology, location, this); } else {
thermostats.put(device.uuid, nhcThermostat); logger.debug("adding thermostat device {} model {}, {}", device.uuid, device.model, device.name);
nhcThermostat = new NhcThermostat2(device.uuid, device.name, device.type, device.technology,
device.model, location, this);
} }
} else if ("centralmeter".equals(device.type)) { thermostats.put(device.uuid, nhcThermostat);
if (!energyMeters.containsKey(device.uuid)) { } else if ("centralmeter".equals(device.type) || "energyhome".equals(device.type)) {
logger.debug("adding centralmeter device {}, {}", device.uuid, device.name); NhcEnergyMeter nhcEnergyMeter = energyMeters.get(device.uuid);
NhcEnergyMeter2 nhcEnergyMeter = new NhcEnergyMeter2(device.uuid, device.name, device.model, if (nhcEnergyMeter != null) {
device.technology, this, scheduler); nhcEnergyMeter.setName(device.name);
energyMeters.put(device.uuid, nhcEnergyMeter); nhcEnergyMeter.setLocation(location);
} else {
logger.debug("adding energy meter device {} model {}, {}", device.uuid, device.model, device.name);
nhcEnergyMeter = new NhcEnergyMeter2(device.uuid, device.name, device.type, device.technology,
device.model, location, this, scheduler);
} }
energyMeters.put(device.uuid, nhcEnergyMeter);
} else { } else {
logger.debug("device type {} not supported for {}, {}", device.type, device.uuid, device.name); logger.debug("device type {} and model {} not supported for {}, {}", device.type, device.model, device.uuid,
device.name);
} }
} }
@ -580,18 +594,23 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
} }
private void updateEnergyMeterState(NhcEnergyMeter2 energyMeter, List<NhcProperty> deviceProperties) { private void updateEnergyMeterState(NhcEnergyMeter2 energyMeter, List<NhcProperty> deviceProperties) {
deviceProperties.stream().map(p -> p.electricalPower).filter(Objects::nonNull).findFirst() try {
.ifPresent(electricalPower -> { Optional<Integer> electricalPower = deviceProperties.stream().map(p -> p.electricalPower)
try { .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
// Sometimes API sends a fractional part, although API should only send whole units in W, .filter(Objects::nonNull).findFirst();
// therefore drop fractional part Optional<Integer> powerFromGrid = deviceProperties.stream().map(p -> p.electricalPowerFromGrid)
energyMeter.setPower((int) Double.parseDouble(electricalPower)); .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
logger.trace("setting energy meter {} power to {}", energyMeter.getId(), electricalPower); .filter(Objects::nonNull).findFirst();
} catch (NumberFormatException e) { Optional<Integer> powerToGrid = deviceProperties.stream().map(p -> p.electricalPowerToGrid)
energyMeter.setPower(null); .map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
logger.trace("received empty energy meter {} power reading", energyMeter.getId()); .filter(Objects::nonNull).findFirst();
} int power = electricalPower.orElse(powerFromGrid.orElse(0) - powerToGrid.orElse(0));
}); logger.trace("setting energy meter {} power to {}", energyMeter.getId(), electricalPower);
energyMeter.setPower(power);
} catch (NumberFormatException e) {
energyMeter.setPower(null);
logger.trace("received empty energy meter {} power reading", energyMeter.getId());
}
} }
@Override @Override
@ -876,7 +895,7 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
@Override @Override
public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) { public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) {
if (error != null) { if (error != null) {
logger.debug("Connection state: {}", state, error); logger.debug("Connection state: {}, error", state, error);
String message = error.getLocalizedMessage(); String message = error.getLocalizedMessage();
message = (message != null) ? message : "@text/offline.communication-error"; message = (message != null) ? message : "@text/offline.communication-error";
if (!MqttConnectionState.CONNECTING.equals(state)) { if (!MqttConnectionState.CONNECTING.equals(state)) {