]> git.basschouten.com Git - openhab-addons.git/commitdiff
[homekit] add support for QR code based pairing (#9475)
authoreugen <freiter@gmail.com>
Fri, 8 Jan 2021 22:03:54 +0000 (23:03 +0100)
committerGitHub <noreply@github.com>
Fri, 8 Jan 2021 22:03:54 +0000 (23:03 +0100)
* 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>
16 files changed:
bundles/org.openhab.io.homekit/README.md
bundles/org.openhab.io.homekit/doc/add_homekit_tag.png [new file with mode: 0644]
bundles/org.openhab.io.homekit/doc/ios_add_accessory.png [new file with mode: 0644]
bundles/org.openhab.io.homekit/doc/ios_add_accessory_wizard.png [new file with mode: 0644]
bundles/org.openhab.io.homekit/doc/ios_add_anyway.png [new file with mode: 0644]
bundles/org.openhab.io.homekit/doc/ios_add_new_home.png [new file with mode: 0644]
bundles/org.openhab.io.homekit/doc/ios_scan_qrcode.png [new file with mode: 0644]
bundles/org.openhab.io.homekit/doc/item_add_metadata_button.png [new file with mode: 0644]
bundles/org.openhab.io.homekit/doc/select_homekit_accessory_type.png [new file with mode: 0644]
bundles/org.openhab.io.homekit/doc/select_homekit_namespace.png [new file with mode: 0644]
bundles/org.openhab.io.homekit/doc/settings_qrcode.png [new file with mode: 0644]
bundles/org.openhab.io.homekit/pom.xml
bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitAuthInfoImpl.java
bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java
bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitSettings.java
bundles/org.openhab.io.homekit/src/main/resources/OH-INF/config/config.xml

index 6567024c2034e4dbb9314b870c6d5274f62ab23f..6cc248fe47ffeb90153acdaa4a0051de7a55ffee 100644 (file)
@@ -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 (file)
index 0000000..01e632c
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 (file)
index 0000000..3d157d3
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 (file)
index 0000000..7aa391c
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 (file)
index 0000000..5c3f9d4
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 (file)
index 0000000..4a25a05
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 (file)
index 0000000..8ee9913
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 (file)
index 0000000..d99ceb6
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 (file)
index 0000000..0ecf80f
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 (file)
index 0000000..dceba17
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 (file)
index 0000000..4aecccf
Binary files /dev/null and b/bundles/org.openhab.io.homekit/doc/settings_qrcode.png differ
index a93b9890ce2645cc9abc8ebf68fa054ddf3b5402..f13eb8b741f855964559682d636abfd0da020ef8 100644 (file)
@@ -23,7 +23,7 @@
     <dependency>
       <groupId>com.github.j-n-k</groupId>
       <artifactId>hap-java</artifactId>
-      <version>2.0.0.OH2</version>
+      <version>2.1.0.OH</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
index 54f94e5a756e7ea4e497a231e58fba51b86a984b..234a25c086d088cb066262bd950b15e85e789ba5 100644 (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;
index cf2f2add3153654b047e0e06b5a0b55b935a00a5..e4dc5c0de79d9ac742fa54c58e153140d62ed374 100644 (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 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());
index 26af87843c0652c542d579d1ad4c357fb11a8c9c..978be779361cb85cc8d7c2b8cb22fac2142d0b5a 100644 (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;
index 08837e2bd4cab2a1716482418650fe44206a558e..836d07aa8e213cbaefc5c23dccf4e5748c865ae0 100644 (file)
                        <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>
                        <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>