diff --git a/bundles/org.openhab.io.homekit/README.md b/bundles/org.openhab.io.homekit/README.md index 6567024c203..6cc248fe47f 100644 --- a/bundles/org.openhab.io.homekit/README.md +++ b/bundles/org.openhab.io.homekit/README.md @@ -34,6 +34,40 @@ HomeKit integration supports following accessory types: - Carbon Dioxide Sensor - Carbon Monoxide Sensor +## Quick start + +- install homekit binding via UI + +- add metadata to an existing item (see [UI based configuration](#UI-based-Configuration)) + +- go to scan QR code from UI->Setting-HomeKit Integration + + ![settings_qrcode.png](doc/settings_qrcode.png) + +- open home app on your iPhone or iPad +- create new home + + ![ios_add_new_home.png](doc/ios_add_new_home.png) + +- add accessory + + ![ios_add_accessory.png](doc/ios_add_accessory.png) + +- scan QR code from UI->Setting-HomeKit Integration + + ![ios_scan_qrcode.png](doc/ios_scan_qrcode.png) + +- click "Add Anyway" + + ![ios_add_anyway.png](doc/ios_add_anyway.png) + +- follow the instruction of the home app wizard + + ![ios_add_accessory_wizard.png](doc/ios_add_accessory_wizard.png) + +Add metadata to more item or fine-tune your configuration using further settings + + ## Global Configuration Your first step will be to create the `homekit.cfg` in your `$OPENHAB_CONF/services` folder. @@ -93,8 +127,30 @@ Complex accessories require a tag on a Group Item indicating the accessory type, A HomeKit accessory has mandatory and optional characteristics (listed below in the table). The mapping between openHAB items and HomeKit accessory and characteristics is done by means of [metadata](https://www.openhab.org/docs/concepts/items.html#item-metadata) -e.g. +### UI based Configuration +In order to add metadata to an item: +- select desired item in mainUI +- click on "Add Metadata" + + ![item_add_metadata_button.png](doc/item_add_metadata_button.png) + +- select "Apple HomeKit" namespace + + ![select_homekit_namespace.png](doc/select_homekit_namespace.png) + +- click on "HomeKit Accessory/Characteristic" + + ![add_homekit_tag.png](doc/add_homekit_tag.png) + +- select required HomeKit accessory type or characteristic + + ![select_homekit_accessory_type.png](doc/select_homekit_accessory_type.png) + +- click on "Save" + + +### Textual configuration ```xtend Switch leaksensor_metadata "Leak Sensor" {homekit="LeakSensor"} ``` diff --git a/bundles/org.openhab.io.homekit/doc/add_homekit_tag.png b/bundles/org.openhab.io.homekit/doc/add_homekit_tag.png new file mode 100644 index 00000000000..01e632c195d Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/add_homekit_tag.png differ diff --git a/bundles/org.openhab.io.homekit/doc/ios_add_accessory.png b/bundles/org.openhab.io.homekit/doc/ios_add_accessory.png new file mode 100644 index 00000000000..3d157d3fd72 Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/ios_add_accessory.png differ diff --git a/bundles/org.openhab.io.homekit/doc/ios_add_accessory_wizard.png b/bundles/org.openhab.io.homekit/doc/ios_add_accessory_wizard.png new file mode 100644 index 00000000000..7aa391c5090 Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/ios_add_accessory_wizard.png differ diff --git a/bundles/org.openhab.io.homekit/doc/ios_add_anyway.png b/bundles/org.openhab.io.homekit/doc/ios_add_anyway.png new file mode 100644 index 00000000000..5c3f9d4749f Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/ios_add_anyway.png differ diff --git a/bundles/org.openhab.io.homekit/doc/ios_add_new_home.png b/bundles/org.openhab.io.homekit/doc/ios_add_new_home.png new file mode 100644 index 00000000000..4a25a05ae01 Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/ios_add_new_home.png differ diff --git a/bundles/org.openhab.io.homekit/doc/ios_scan_qrcode.png b/bundles/org.openhab.io.homekit/doc/ios_scan_qrcode.png new file mode 100644 index 00000000000..8ee9913a850 Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/ios_scan_qrcode.png differ diff --git a/bundles/org.openhab.io.homekit/doc/item_add_metadata_button.png b/bundles/org.openhab.io.homekit/doc/item_add_metadata_button.png new file mode 100644 index 00000000000..d99ceb65915 Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/item_add_metadata_button.png differ diff --git a/bundles/org.openhab.io.homekit/doc/select_homekit_accessory_type.png b/bundles/org.openhab.io.homekit/doc/select_homekit_accessory_type.png new file mode 100644 index 00000000000..0ecf80f339b Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/select_homekit_accessory_type.png differ diff --git a/bundles/org.openhab.io.homekit/doc/select_homekit_namespace.png b/bundles/org.openhab.io.homekit/doc/select_homekit_namespace.png new file mode 100644 index 00000000000..dceba171fb4 Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/select_homekit_namespace.png differ diff --git a/bundles/org.openhab.io.homekit/doc/settings_qrcode.png b/bundles/org.openhab.io.homekit/doc/settings_qrcode.png new file mode 100644 index 00000000000..4aecccfa324 Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/settings_qrcode.png differ diff --git a/bundles/org.openhab.io.homekit/pom.xml b/bundles/org.openhab.io.homekit/pom.xml index a93b9890ce2..f13eb8b741f 100644 --- a/bundles/org.openhab.io.homekit/pom.xml +++ b/bundles/org.openhab.io.homekit/pom.xml @@ -23,7 +23,7 @@ com.github.j-n-k hap-java - 2.0.0.OH2 + 2.1.0.OH compile diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitAuthInfoImpl.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitAuthInfoImpl.java index 54f94e5a756..234a25c086d 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitAuthInfoImpl.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitAuthInfoImpl.java @@ -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 storage, String pin) throws InvalidAlgorithmParameterException { + public HomekitAuthInfoImpl(Storage 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; diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java index cf2f2add315..e4dc5c0de79 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java @@ -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 config, - @Reference MetadataRegistry metadataRegistry) throws IOException, InvalidAlgorithmParameterException { + @Reference NetworkAddressService networkAddressService, @Reference MetadataRegistry metadataRegistry, + @Reference ConfigurationAdmin configAdmin, Map 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 config) { - HomekitSettings settings = (new Configuration(config)).as(HomekitSettings.class); + private HomekitSettings processConfig(Map properties) { + HomekitSettings settings = (new Configuration(properties)).as(HomekitSettings.class); + org.osgi.service.cm.Configuration config = null; + Dictionary 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()); diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitSettings.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitSettings.java index 26af87843c0..978be779361 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitSettings.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitSettings.java @@ -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; diff --git a/bundles/org.openhab.io.homekit/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.io.homekit/src/main/resources/OH-INF/config/config.xml index 08837e2bd4c..836d07aa8e2 100644 --- a/bundles/org.openhab.io.homekit/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.io.homekit/src/main/resources/OH-INF/config/config.xml @@ -22,7 +22,11 @@ String values used by your thermostat to set different targetHeatingCooling modes - + + + qrcode + Scan QR code with home app to add openHAB as HomeKit bridge. + Defines the port the HomeKit integration listens on. @@ -33,6 +37,10 @@ Defines the pin, used for pairing, in the form ###-##-###. 031-45-154 + + + Setup ID used for pairing using QR Code. Alphanumeric code of length 4. + Defines the IP address of the network interface to expose the HomeKit integration on.