[homekit] add support for QR code based pairing (#9475)

* add support qrcode based pairing

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* add screenshots

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* fix typo

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* update config only if differnt to prevent endless update loop

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* clean up

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* add support qrcode based pairing

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* add screenshots

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* fix typo

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* update config only if differnt to prevent endless update loop

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* clean up

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* incorporate review feedback

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* fix Nullable based on feedback

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* correct the java hap version

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* Update bundles/org.openhab.io.homekit/pom.xml

* adapt groupid

Signed-off-by: Eugen Freiter <freiter@gmx.de>

* incorporate review feedback

Signed-off-by: Eugen Freiter <freiter@gmx.de>

Co-authored-by: Eugen Freiter <freiter@gmx.de>
Co-authored-by: J-N-K <J-N-K@users.noreply.github.com>
This commit is contained in:
eugen
2021-01-08 23:03:54 +01:00
committed by GitHub
parent d7d4fda2b2
commit fcb774ca35
16 changed files with 158 additions and 20 deletions

View File

@@ -44,11 +44,14 @@ public class HomekitAuthInfoImpl implements HomekitAuthInfo {
private String mac;
private BigInteger salt;
private byte[] privateKey;
private final String pin;
private String pin;
private String setupId;
public HomekitAuthInfoImpl(Storage<String> storage, String pin) throws InvalidAlgorithmParameterException {
public HomekitAuthInfoImpl(Storage<String> storage, String pin, String setupId)
throws InvalidAlgorithmParameterException {
this.storage = storage;
this.pin = pin;
this.setupId = setupId;
initializeStorage();
}
@@ -63,11 +66,28 @@ public class HomekitAuthInfoImpl implements HomekitAuthInfo {
return mac;
}
public void setMac(String mac) {
this.mac = mac;
}
@Override
public String getPin() {
return pin;
}
public void setPin(String pin) {
this.pin = pin;
}
@Override
public String getSetupId() {
return setupId;
}
public void setSetupId(String setupId) {
this.setupId = setupId;
}
@Override
public byte[] getPrivateKey() {
return privateKey;

View File

@@ -16,6 +16,8 @@ import java.io.IOException;
import java.net.InetAddress;
import java.security.InvalidAlgorithmParameterException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
@@ -33,6 +35,7 @@ import org.openhab.core.storage.StorageService;
import org.openhab.io.homekit.Homekit;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
@@ -44,45 +47,88 @@ import org.slf4j.LoggerFactory;
import io.github.hapjava.accessories.HomekitAccessory;
import io.github.hapjava.server.impl.HomekitRoot;
import io.github.hapjava.server.impl.HomekitServer;
import io.github.hapjava.server.impl.crypto.HAPSetupCodeUtils;
/**
* Provides access to openHAB items via the HomeKit API
*
* @author Andy Lintner - Initial contribution
*/
@Component(service = { Homekit.class }, configurationPid = "org.openhab.homekit", property = {
@Component(service = { Homekit.class }, configurationPid = HomekitSettings.CONFIG_PID, property = {
Constants.SERVICE_PID + "=org.openhab.homekit", "port:Integer=9123" })
@ConfigurableService(category = "io", label = "HomeKit Integration", description_uri = "io:homekit")
@NonNullByDefault
public class HomekitImpl implements Homekit {
private final Logger logger = LoggerFactory.getLogger(HomekitImpl.class);
private final NetworkAddressService networkAddressService;
private final HomekitChangeListener changeListener;
private final NetworkAddressService networkAddressService;
private final ConfigurationAdmin configAdmin;
private HomekitAuthInfoImpl authInfo;
private HomekitSettings settings;
private @Nullable InetAddress networkInterface;
private @Nullable HomekitServer homekitServer;
private @Nullable HomekitRoot bridge;
private final HomekitAuthInfoImpl authInfo;
private final HomekitChangeListener changeListener;
private final ScheduledExecutorService scheduler = ThreadPoolManager
.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON);
@Activate
public HomekitImpl(@Reference StorageService storageService, @Reference ItemRegistry itemRegistry,
@Reference NetworkAddressService networkAddressService, Map<String, Object> config,
@Reference MetadataRegistry metadataRegistry) throws IOException, InvalidAlgorithmParameterException {
@Reference NetworkAddressService networkAddressService, @Reference MetadataRegistry metadataRegistry,
@Reference ConfigurationAdmin configAdmin, Map<String, Object> properties)
throws IOException, InvalidAlgorithmParameterException {
this.networkAddressService = networkAddressService;
this.settings = processConfig(config);
this.configAdmin = configAdmin;
this.settings = processConfig(properties);
this.changeListener = new HomekitChangeListener(itemRegistry, settings, metadataRegistry, storageService);
authInfo = new HomekitAuthInfoImpl(storageService.getStorage(HomekitAuthInfoImpl.STORAGE_KEY), settings.pin);
startHomekitServer();
try {
authInfo = new HomekitAuthInfoImpl(storageService.getStorage(HomekitAuthInfoImpl.STORAGE_KEY), settings.pin,
settings.setupId);
startHomekitServer();
} catch (IOException | InvalidAlgorithmParameterException e) {
logger.warn("Cannot activate HomeKit binding. {}", e.getMessage());
throw e;
}
}
private HomekitSettings processConfig(Map<String, Object> config) {
HomekitSettings settings = (new Configuration(config)).as(HomekitSettings.class);
private HomekitSettings processConfig(Map<String, Object> properties) {
HomekitSettings settings = (new Configuration(properties)).as(HomekitSettings.class);
org.osgi.service.cm.Configuration config = null;
Dictionary<String, Object> props = null;
try {
config = configAdmin.getConfiguration(HomekitSettings.CONFIG_PID);
props = config.getProperties();
} catch (IOException e) {
logger.warn("Cannot retrieve config admin {}", e.getMessage());
}
if (props == null) { // if null, the configuration is new
props = new Hashtable<>();
}
if (settings.networkInterface == null) {
settings.networkInterface = networkAddressService.getPrimaryIpv4HostAddress();
props.put("networkInterface", settings.networkInterface);
}
if (settings.setupId == null) { // generate setupId very first time
settings.setupId = HAPSetupCodeUtils.generateSetupId();
props.put("setupId", settings.setupId);
}
// QR Code setup URI is always generated from PIN, setup ID and accessory category (1 = bridge)
String setupURI = HAPSetupCodeUtils.getSetupURI(settings.pin.replaceAll("-", ""), settings.setupId, 1);
if ((settings.qrCode == null) || (!settings.qrCode.equals(setupURI))) { // QR code was changed
settings.qrCode = setupURI;
props.put("qrCode", settings.qrCode);
}
if (config != null) {
try {
config.updateIfDifferent(props);
} catch (IOException e) {
logger.warn("Cannot update configuration {}", e.getMessage());
}
}
return settings;
}
@@ -97,10 +143,12 @@ public class HomekitImpl implements Homekit {
// the HomeKit server settings changed. we do a complete re-init
stopHomekitServer();
startHomekitServer();
} else if (!oldSettings.name.equals(settings.name) || !oldSettings.pin.equals(settings.pin)) {
// we change the root bridge only
stopBridge();
startBridge();
} else if (!oldSettings.name.equals(settings.name) || !oldSettings.pin.equals(settings.pin)
|| !oldSettings.setupId.equals(settings.setupId)) {
stopHomekitServer();
authInfo.setPin(settings.pin);
authInfo.setSetupId(settings.setupId);
startHomekitServer();
}
} catch (IOException e) {
logger.warn("Could not initialize HomeKit connector: {}", e.getMessage());

View File

@@ -18,6 +18,7 @@ package org.openhab.io.homekit.internal;
* @author Andy Lintner - Initial contribution
*/
public class HomekitSettings {
public static final String CONFIG_PID = "org.openhab.homekit";
public static final String MANUFACTURER = "openHAB Community";
public static final String SERIAL_NUMBER = "none";
public static final String MODEL = "openHAB";
@@ -26,6 +27,8 @@ public class HomekitSettings {
public String name = "openHAB";
public int port = 9123;
public String pin = "031-45-154";
public String setupId;
public String qrCode;
public int startDelay = 30;
public boolean useFahrenheitTemperature = false;
public double minimumTemperature = -100;
@@ -56,6 +59,7 @@ public class HomekitSettings {
temp = Double.doubleToLongBits(minimumTemperature);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + ((pin == null) ? 0 : pin.hashCode());
result = prime * result + ((setupId == null) ? 0 : setupId.hashCode());
result = prime * result + port;
result = prime * result + ((thermostatTargetModeAuto == null) ? 0 : thermostatTargetModeAuto.hashCode());
result = prime * result + ((thermostatTargetModeCool == null) ? 0 : thermostatTargetModeCool.hashCode());
@@ -89,6 +93,8 @@ public class HomekitSettings {
}
} else if (!pin.equals(other.pin)) {
return false;
} else if (!setupId.equals(other.setupId)) {
return false;
}
if (port != other.port) {
return false;

View File

@@ -22,7 +22,11 @@
<label>Thermostat Current Heating/Cooling Mapping</label>
<description>String values used by your thermostat to set different targetHeatingCooling modes</description>
</parameter-group>
<parameter name="qrCode" type="text" required="false" groupName="core">
<label>HomeKit QR Code</label>
<context>qrcode</context>
<description>Scan QR code with home app to add openHAB as HomeKit bridge. </description>
</parameter>
<parameter name="port" type="integer" required="true" groupName="core">
<label>Port</label>
<description>Defines the port the HomeKit integration listens on.</description>
@@ -33,6 +37,10 @@
<description>Defines the pin, used for pairing, in the form ###-##-###.</description>
<default>031-45-154</default>
</parameter>
<parameter name="setupId" type="text" pattern="[0-9A-Z]{4}" required="false" groupName="core">
<label>Setup ID</label>
<description>Setup ID used for pairing using QR Code. Alphanumeric code of length 4.</description>
</parameter>
<parameter name="networkInterface" type="text" required="false" groupName="core">
<label>Network Interface</label>
<description>Defines the IP address of the network interface to expose the HomeKit integration on.</description>