Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 60 additions & 40 deletions bundles/org.openhab.binding.nikohomecontrol/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,29 @@ public class NikoHomeControlBindingConstants {
public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "thermostat");
public static final ThingTypeUID THING_TYPE_ENERGYMETER_LIVE = new ThingTypeUID(BINDING_ID, "energyMeterLive");
public static final ThingTypeUID THING_TYPE_ENERGYMETER = new ThingTypeUID(BINDING_ID, "energyMeter");
public static final ThingTypeUID THING_TYPE_ENERGYMETER_HOME = new ThingTypeUID(BINDING_ID, "energyMeterHome");
public static final ThingTypeUID THING_TYPE_GASMETER = new ThingTypeUID(BINDING_ID, "gasMeter");
public static final ThingTypeUID THING_TYPE_WATERMETER = new ThingTypeUID(BINDING_ID, "waterMeter");
public static final ThingTypeUID THING_TYPE_ACCESS = new ThingTypeUID(BINDING_ID, "access");
public static final ThingTypeUID THING_TYPE_ACCESS_RINGANDCOMEIN = new ThingTypeUID(BINDING_ID,
"accessRingAndComeIn");
public static final ThingTypeUID THING_TYPE_ALARM = new ThingTypeUID(BINDING_ID, "alarm");
public static final ThingTypeUID THING_TYPE_CAR_CHARGER = new ThingTypeUID(BINDING_ID, "carCharger");

// thing type sets
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Set.of(BRIDGEI_THING_TYPE, BRIDGEII_THING_TYPE);
public static final Set<ThingTypeUID> ACTION_THING_TYPES_UIDS = Set.of(THING_TYPE_PUSHBUTTON,
THING_TYPE_ON_OFF_LIGHT, THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_BLIND);
public static final Set<ThingTypeUID> THERMOSTAT_THING_TYPES_UIDS = Set.of(THING_TYPE_THERMOSTAT);
public static final Set<ThingTypeUID> METER_THING_TYPES_UIDS = Set.of(THING_TYPE_ENERGYMETER_LIVE,
THING_TYPE_ENERGYMETER, THING_TYPE_GASMETER, THING_TYPE_WATERMETER);
THING_TYPE_ENERGYMETER, THING_TYPE_ENERGYMETER_HOME, THING_TYPE_GASMETER, THING_TYPE_WATERMETER);
public static final Set<ThingTypeUID> ACCESS_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCESS,
THING_TYPE_ACCESS_RINGANDCOMEIN);
public static final Set<ThingTypeUID> ALARM_THING_TYPES_UIDS = Set.of(THING_TYPE_ALARM);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.of(BRIDGE_THING_TYPES_UIDS.stream(), ACTION_THING_TYPES_UIDS.stream(),
THERMOSTAT_THING_TYPES_UIDS.stream(), METER_THING_TYPES_UIDS.stream(),
ACCESS_THING_TYPES_UIDS.stream(), ALARM_THING_TYPES_UIDS.stream())
public static final Set<ThingTypeUID> CAR_CHARGER_THING_TYPES_UIDS = Set.of(THING_TYPE_CAR_CHARGER);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream.of(BRIDGE_THING_TYPES_UIDS.stream(),
ACTION_THING_TYPES_UIDS.stream(), THERMOSTAT_THING_TYPES_UIDS.stream(), METER_THING_TYPES_UIDS.stream(),
ACCESS_THING_TYPES_UIDS.stream(), ALARM_THING_TYPES_UIDS.stream(), CAR_CHARGER_THING_TYPES_UIDS.stream())
.flatMap(i -> i).collect(Collectors.toSet());

// List of all Channel ids
Expand All @@ -85,15 +87,22 @@ public class NikoHomeControlBindingConstants {
public static final String CHANNEL_HEATING_DEMAND = "heatingdemand";

public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_POWER_FROM_GRID = "powerfromgrid";
public static final String CHANNEL_POWER_TO_GRID = "powertogrid";
public static final String CHANNEL_PEAK_POWER_FROM_GRID = "peakpowerfromgrid";
public static final String CHANNEL_ENERGY = "energy";
public static final String CHANNEL_ENERGY_FROM_GRID = "energyfromgrid";
public static final String CHANNEL_ENERGY_TO_GRID = "energytogrid";
public static final String CHANNEL_ENERGY_SELF_CONSUMPTION = "energyselfconsumption";
public static final String CHANNEL_GAS = "gas";
public static final String CHANNEL_WATER = "water";
public static final String CHANNEL_ENERGY_DAY = "energyday";
public static final String CHANNEL_GAS_DAY = "gasday";
public static final String CHANNEL_WATER_DAY = "waterday";
public static final String CHANNEL_ENERGY_LAST = "energylast";
public static final String CHANNEL_GAS_LAST = "gaslast";
public static final String CHANNEL_WATER_LAST = "waterlast";
public static final String CHANNEL_ENERGY_FROM_GRID_DAY = "energyfromgridday";
public static final String CHANNEL_ENERGY_TO_GRID_DAY = "energytogridday";
public static final String CHANNEL_ENERGY_SELF_CONSUMPTION_DAY = "energyselfconsumptionday";
public static final String CHANNEL_MEASUREMENT_TIME = "measurementtime";

public static final String CHANNEL_BELL_BUTTON = "bellbutton";
public static final String CHANNEL_RING_AND_COME_IN = "ringandcomein";
Expand All @@ -103,6 +112,17 @@ public class NikoHomeControlBindingConstants {
public static final String CHANNEL_ARMED = "armed";
public static final String CHANNEL_STATE = "state";

public static final String CHANNEL_STATUS = "status";
public static final String CHANNEL_CHARGING_STATUS = "chargingStatus";
public static final String CHANNEL_EV_STATUS = "evStatus";
public static final String CHANNEL_COUPLING_STATUS = "couplingStatus";
public static final String CHANNEL_CHARGING_MODE = "chargingMode";
public static final String CHANNEL_TARGET_DISTANCE = "targetDistance";
public static final String CHANNEL_TARGET_TIME = "targetTime";
public static final String CHANNEL_BOOST = "boost";
public static final String CHANNEL_REACHABLE_DISTANCE = "reachableDistance";
public static final String CHANNEL_NEXT_CHARGING_TIME = "nextChargingTime";

public static final String CHANNEL_ALARM = "alarm";
public static final String CHANNEL_NOTICE = "notice";

Expand All @@ -124,11 +144,14 @@ public class NikoHomeControlBindingConstants {

public static final String METER_ID = "meterId";
public static final String CONFIG_METER_REFRESH = "refresh";
public static final String CONFIG_METER_START_DATE = "startDate";

public static final String CONFIG_ACCESS_ID = "accessId";

public static final String CONFIG_ALARM_ID = "alarmId";

public static final String CONFIG_CAR_CHARGER_ID = "carChargerId";

// Thing properties
public static final String PROPERTY_DEVICE_TYPE = "deviceType";
public static final String PROPERTY_DEVICE_TECHNOLOGY = "deviceTechnology";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlAlarmHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler1;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler2;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlCarChargerHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlMeterHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlThermostatHandler;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
Expand All @@ -40,6 +42,7 @@
* handlers.
*
* @author Mark Herwege - Initial Contribution
* @author Mark Herwege - Add car chargers
*/

@NonNullByDefault
Expand All @@ -48,12 +51,14 @@ public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory {

private final NetworkAddressService networkAddressService;
private final TimeZoneProvider timeZoneProvider;
private final HttpClientFactory httpClientFactory;

@Activate
public NikoHomeControlHandlerFactory(final @Reference NetworkAddressService networkAddressService,
final @Reference TimeZoneProvider timeZoneProvider) {
final @Reference TimeZoneProvider timeZoneProvider, final @Reference HttpClientFactory httpClientFactory) {
this.networkAddressService = networkAddressService;
this.timeZoneProvider = timeZoneProvider;
this.httpClientFactory = httpClientFactory;
}

@Override
Expand All @@ -65,7 +70,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
protected @Nullable ThingHandler createHandler(Thing thing) {
if (BRIDGE_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
if (BRIDGEII_THING_TYPE.equals(thing.getThingTypeUID())) {
return new NikoHomeControlBridgeHandler2((Bridge) thing, networkAddressService, timeZoneProvider);
return new NikoHomeControlBridgeHandler2((Bridge) thing, networkAddressService, timeZoneProvider,
httpClientFactory);
} else {
return new NikoHomeControlBridgeHandler1((Bridge) thing, networkAddressService, timeZoneProvider);
}
Expand All @@ -79,6 +85,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return new NikoHomeControlAccessHandler(thing);
} else if (ALARM_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
return new NikoHomeControlAlarmHandler(thing);
} else if (CAR_CHARGER_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
return new NikoHomeControlCarChargerHandler(thing);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright (c) 2010-2025 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.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
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 javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Utility to provide a shared SSLContext and Jetty SslContextFactory
* that trusts the built-in Niko Home Control controller certificates.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public final class SslContextProvider {

private static final Logger LOGGER = LoggerFactory.getLogger(SslContextProvider.class);

private @Nullable static SSLContext sslContext;
private @Nullable static TrustManager @Nullable [] trustManagers = null;

private SslContextProvider() {
}

public static synchronized SSLContext getSSLContext() throws CertificateException {
SSLContext context = sslContext;
if (context == null) {
context = buildSSLContext();
}
sslContext = context;
return context;
}

public static synchronized TrustManager[] getTrustManagers() throws CertificateException {
TrustManager[] managers = trustManagers;
if (managers == null) {
managers = importCertificates();
}
trustManagers = managers;
return managers;
}

public static synchronized SslContextFactory.Client getSslContextFactory() throws CertificateException {
SslContextFactory.Client factory = new SslContextFactory.Client();
factory.setSslContext(getSSLContext());
factory.setEndpointIdentificationAlgorithm(null); // disable hostname verification (allows IP to be used)
return factory;
}

private static SSLContext buildSSLContext() throws CertificateException {
SSLContext context;
try {
context = SSLContext.getInstance("TLS");
context.init(null, getTrustManagers(), null);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
LOGGER.debug("error with SSL context creation: {}", e.getMessage());
throw new CertificateException("SSL context creation exception", e);
}

LOGGER.debug("Initialized SSLContext with embedded Niko Home Control certificates");
return context;
}

private static TrustManager[] importCertificates() 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.debug("error with SSL context creation: {} ", e.getMessage());
throw new CertificateException("SSL context creation exception", e);
} finally {
ResourceBundle.clearCache();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAccess;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAlarm;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcCarCharger;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
Expand All @@ -41,6 +42,7 @@
* {@link NikoHomeControlDiscoveryService} is used to return Niko Home Control Actions as things to the framework.
*
* @author Mark Herwege - Initial Contribution
* @author Mark Herwege - Add car chargers
*/
@Component(scope = ServiceScope.PROTOTYPE, service = NikoHomeControlDiscoveryService.class)
@NonNullByDefault
Expand Down Expand Up @@ -85,6 +87,7 @@ public void discoverDevices() {
discoverMeterDevices(thingHandler, nhcComm);
discoverAccessDevices(thingHandler, nhcComm);
discoverAlarmDevices(thingHandler, nhcComm);
discoverCarChargerDevices(thingHandler, nhcComm);
}

private void discoverActionDevices(NikoHomeControlBridgeHandler bridgeHandler,
Expand Down Expand Up @@ -143,6 +146,10 @@ private void discoverMeterDevices(NikoHomeControlBridgeHandler bridgeHandler,
addDevice(new ThingUID(THING_TYPE_ENERGYMETER_LIVE, bridgeHandler.getThing().getUID(), deviceId),
METER_ID, deviceId, thingName, thingLocation);
break;
case ENERGY_HOME:
addDevice(new ThingUID(THING_TYPE_ENERGYMETER_HOME, bridgeHandler.getThing().getUID(), deviceId),
METER_ID, deviceId, thingName, thingLocation);
break;
case ENERGY:
addDevice(new ThingUID(THING_TYPE_ENERGYMETER, bridgeHandler.getThing().getUID(), deviceId),
METER_ID, deviceId, thingName, thingLocation);
Expand Down Expand Up @@ -198,6 +205,18 @@ private void discoverAlarmDevices(NikoHomeControlBridgeHandler bridgeHandler,
});
}

private void discoverCarChargerDevices(NikoHomeControlBridgeHandler bridgeHandler,
NikoHomeControlCommunication nhcComm) {
Map<String, NhcCarCharger> carChargerDevices = nhcComm.getCarChargerDevices();

carChargerDevices.forEach((deviceId, nhcCarCharger) -> {
String thingName = nhcCarCharger.getName();
String thingLocation = nhcCarCharger.getLocation();
addDevice(new ThingUID(THING_TYPE_CAR_CHARGER, bridgeHandler.getThing().getUID(), deviceId),
CONFIG_CAR_CHARGER_ID, deviceId, thingName, thingLocation);
});
}

private void addDevice(ThingUID uid, String deviceIdKey, String deviceId, String thingName,
@Nullable String thingLocation) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ public NikoHomeControlBaseHandler(Thing thing) {

@Override
public void dispose() {
Future<?> commStartThread = this.commStartThread;
if (commStartThread != null) {
commStartThread.cancel(true);
commStartThread = null;
this.commStartThread = null;
}
super.dispose();
}
Expand Down Expand Up @@ -141,6 +142,7 @@ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
if (ThingStatus.ONLINE.equals(bridgeStatus)) {
if (!initialized) {
Future<?> commStartThread = this.commStartThread;
if (commStartThread != null) {
commStartThread.cancel(true);
}
Expand Down
Loading