]> git.basschouten.com Git - openhab-addons.git/commitdiff
[homekit] allow multiple bridge instances to break the 150 limit (#13226)
authorCody Cutrer <cody@cutrer.us>
Fri, 12 Aug 2022 07:55:39 +0000 (01:55 -0600)
committerGitHub <noreply@github.com>
Fri, 12 Aug 2022 07:55:39 +0000 (09:55 +0200)
fixes #11508, #12927

Signed-off-by: Cody Cutrer <cody@cutrer.us>
bundles/org.openhab.io.homekit/README.md
bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitChangeListener.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/java/org/openhab/io/homekit/internal/HomekitTaggedItem.java
bundles/org.openhab.io.homekit/src/main/resources/OH-INF/config/config.xml
bundles/org.openhab.io.homekit/src/main/resources/OH-INF/i18n/homekit.properties

index d71332ef15ff9ec71c23c125f2ae481273aa7d76..8ef2b857812dbba1915afce08302f2a104430b91 100644 (file)
@@ -97,6 +97,7 @@ org.openhab.homekit:networkInterface=192.168.0.6
 org.openhab.homekit:useOHmDNS=false
 org.openhab.homekit:blockUserDeletion=false
 org.openhab.homekit:name=openHAB
+org.openhab.homekit:instances=1
 ```
 
 ### Overview of all settings
@@ -110,11 +111,12 @@ org.openhab.homekit:name=openHAB
 | pin                      | Pin code used for pairing with iOS devices. Apparently, pin codes are provided by Apple and represent specific device types, so they cannot be chosen freely. The pin code 031-45-154 is used in sample applications and known to work. | 031-45-154    |
 | startDelay               | HomeKit start delay in seconds in case the number of accessories is lower than last time. This helps to avoid resetting home app in case not all items have been initialised properly before HomeKit integration start.                 | 30            |
 | useFahrenheitTemperature | Set to true to use Fahrenheit degrees, or false to use Celsius degrees. Note if an item has a QuantityType as its state, this configuration is ignored and it's always converted properly.                                              | false         |
-| thermostatTargetModeCool | Word used for activating the cooling mode of the device (if applicable). It can be overwritten at item level.                                                                                                                                                               | CoolOn        |
-| thermostatTargetModeHeat | Word used for activating the heating mode of the device (if applicable). It can be overwritten at item level.                                                                                                                                                                | HeatOn        |
-| thermostatTargetModeAuto | Word used for activating the automatic mode of the device (if applicable). It can be overwritten at item level.                                                                                                                                                               | Auto          |
-| thermostatTargetModeOff  | Word used to set the thermostat mode of the device to off (if applicable).  It can be overwritten at item level.                                                                                                                                                             | Off           |
+| thermostatTargetModeCool | Word used for activating the cooling mode of the device (if applicable). It can be overwritten at item level.                                                                                                                           | CoolOn        |
+| thermostatTargetModeHeat | Word used for activating the heating mode of the device (if applicable). It can be overwritten at item level.                                                                                                                           | HeatOn        |
+| thermostatTargetModeAuto | Word used for activating the automatic mode of the device (if applicable). It can be overwritten at item level.                                                                                                                         | Auto          |
+| thermostatTargetModeOff  | Word used to set the thermostat mode of the device to off (if applicable).  It can be overwritten at item level.                                                                                                                        | Off           |
 | name                     | Name under which this HomeKit bridge is announced on the network. This is also the name displayed on the iOS device when searching for available bridges.                                                                               | openHAB       |
+| instances                | Defines how many bridges to expose. Necessary if you have more than 149 accessories. Accessories must be assigned to additional instances via metadata. Additional bridges will use incrementing port numbers.                          | 1             |
 
 ## Item Configuration
 
@@ -836,6 +838,29 @@ Number          cooler_cool_thrs           "Cooler Cool Threshold Temp [%.1f °C
 Number          cooler_heat_thrs           "Cooler Heat Threshold Temp [%.1f °C]" (gCooler)            {homekit="HeatingThresholdTemperature" [minValue=0.5, maxValue=20]}
 ```
 
+## Multiple Instances
+
+Homekit has a limitation of 150 accessories per bridge.
+The bridge itself counts as an accessory, so in practice it's 149.
+In order to overcome this limitation, you can instruct OpenHAB to expose multiple bridges to Homekit, and then manually assign specific accessories to different instances.
+You will need to manually add each additional bridge in the Home app, since the QR Code in settings will only be for the primary bridge; however the same PIN is still used.
+In order to assign a particular accessory to a different bridge, set the `instance` metadata parameter:
+
+```
+Switch kitchen_light {homekit="Lighting" [instance=2]}
+```
+
+Note that instances are numbered starting at 1.
+If you reference an instance that doesn't exist, then that accessory won't be exposed on _any_ bridge.
+
+For complex items, only the root group needs to be tagged for the specific instance:
+
+```
+Group           gSecuritySystem            "Security System Group"                                     {homekit="SecuritySystem" [instance=2]}
+String          security_current_state     "Security Current State"               (gSecuritySystem)    {homekit="SecuritySystem.CurrentSecuritySystemState"}
+String          security_target_state      "Security Target State"                (gSecuritySystem)    {homekit="SecuritySystem.TargetSecuritySystemState"}
+```
+
 ## Additional Notes
 
 HomeKit allows only a single pairing to be established with the bridge.
index daf3a5c77db4a6cd8190c25a000ac84bbbf7838a..18d3ea6ce1b1c85a6bc6dc0fecc7717747dd5f44 100644 (file)
@@ -62,6 +62,7 @@ public class HomekitChangeListener implements ItemRegistryChangeListener {
     private HomekitAccessoryUpdater updater = new HomekitAccessoryUpdater();
     private HomekitSettings settings;
     private int lastAccessoryCount;
+    private int instance;
 
     private final Set<String> pendingUpdates = new HashSet<>();
 
@@ -79,11 +80,12 @@ public class HomekitChangeListener implements ItemRegistryChangeListener {
     private final Debouncer applyUpdatesDebouncer;
 
     HomekitChangeListener(ItemRegistry itemRegistry, HomekitSettings settings, MetadataRegistry metadataRegistry,
-            Storage<String> storage) {
+            Storage<String> storage, int instance) {
         this.itemRegistry = itemRegistry;
         this.settings = settings;
         this.metadataRegistry = metadataRegistry;
         this.storage = storage;
+        this.instance = instance;
         this.applyUpdatesDebouncer = new Debouncer("update-homekit-devices", scheduler, Duration.ofMillis(1000),
                 Clock.systemUTC(), this::applyUpdates);
         metadataChangeListener = new RegistryChangeListener<Metadata>() {
@@ -131,7 +133,7 @@ public class HomekitChangeListener implements ItemRegistryChangeListener {
         itemRegistry.getItems().forEach(this::createRootAccessories);
         initialiseRevision();
         makeNewConfigurationRevision();
-        logger.info("Created {} HomeKit items.", accessoryRegistry.getAllAccessories().size());
+        logger.info("Created {} HomeKit items in instance {}.", accessoryRegistry.getAllAccessories().size(), instance);
     }
 
     private void initialiseRevision() {
@@ -297,17 +299,14 @@ public class HomekitChangeListener implements ItemRegistryChangeListener {
      * @return primary accessory type
      */
     private HomekitAccessoryType getPrimaryAccessoryType(Item item,
-            List<Entry<HomekitAccessoryType, HomekitCharacteristicType>> accessoryTypes) {
-        if (accessoryTypes.size() > 1) {
-            final @Nullable Map<String, Object> configuration = HomekitAccessoryFactory.getItemConfiguration(item,
-                    metadataRegistry);
-            if (configuration != null) {
-                final @Nullable Object value = configuration.get(HomekitTaggedItem.PRIMARY_SERVICE);
-                if (value instanceof String) {
-                    return accessoryTypes.stream()
-                            .filter(aType -> ((String) value).equalsIgnoreCase(aType.getKey().getTag())).findAny()
-                            .orElse(accessoryTypes.get(0)).getKey();
-                }
+            List<Entry<HomekitAccessoryType, HomekitCharacteristicType>> accessoryTypes,
+            @Nullable Map<String, Object> configuration) {
+        if (accessoryTypes.size() > 1 && configuration != null) {
+            final @Nullable Object value = configuration.get(HomekitTaggedItem.PRIMARY_SERVICE);
+            if (value instanceof String) {
+                return accessoryTypes.stream()
+                        .filter(aType -> ((String) value).equalsIgnoreCase(aType.getKey().getTag())).findAny()
+                        .orElse(accessoryTypes.get(0)).getKey();
             }
         }
         // no primary accessory found or there is only one type, so return the first type from the list
@@ -358,35 +357,57 @@ public class HomekitChangeListener implements ItemRegistryChangeListener {
         final List<Entry<HomekitAccessoryType, HomekitCharacteristicType>> accessoryTypes = HomekitAccessoryFactory
                 .getAccessoryTypes(item, metadataRegistry);
         final List<GroupItem> groups = HomekitAccessoryFactory.getAccessoryGroups(item, itemRegistry, metadataRegistry);
-        if (!accessoryTypes.isEmpty()
-                && (groups.isEmpty() || groups.stream().noneMatch(g -> g.getBaseItem() == null))) {
-            final HomekitAccessoryType primaryAccessoryType = getPrimaryAccessoryType(item, accessoryTypes);
-            logger.trace("Item {} is a HomeKit accessory of types {}. Primary type is {}", item.getName(),
-                    accessoryTypes, primaryAccessoryType);
-            final HomekitOHItemProxy itemProxy = new HomekitOHItemProxy(item);
-            final HomekitTaggedItem taggedItem = new HomekitTaggedItem(new HomekitOHItemProxy(item),
-                    primaryAccessoryType, HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry));
-            try {
-                final HomekitAccessory accessory = HomekitAccessoryFactory.create(taggedItem, metadataRegistry, updater,
-                        settings);
-
-                accessoryTypes.stream().filter(aType -> !primaryAccessoryType.equals(aType.getKey()))
-                        .forEach(additionalAccessoryType -> {
-                            final HomekitTaggedItem additionalTaggedItem = new HomekitTaggedItem(itemProxy,
-                                    additionalAccessoryType.getKey(),
-                                    HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry));
-                            try {
-                                final HomekitAccessory additionalAccessory = HomekitAccessoryFactory
-                                        .create(additionalTaggedItem, metadataRegistry, updater, settings);
-                                accessory.getServices().add(additionalAccessory.getPrimaryService());
-                            } catch (HomekitException e) {
-                                logger.warn("Cannot create additional accessory {}", additionalTaggedItem);
-                            }
-                        });
-                accessoryRegistry.addRootAccessory(taggedItem.getName(), accessory);
-            } catch (HomekitException e) {
-                logger.warn("Cannot create accessory {}", taggedItem);
-            }
+        final @Nullable Map<String, Object> itemConfiguration = HomekitAccessoryFactory.getItemConfiguration(item,
+                metadataRegistry);
+        if (accessoryTypes.isEmpty() || !(groups.isEmpty() || groups.stream().noneMatch(g -> g.getBaseItem() == null))
+                || !itemIsForThisBridge(item, itemConfiguration)) {
+            return;
+        }
+
+        final HomekitAccessoryType primaryAccessoryType = getPrimaryAccessoryType(item, accessoryTypes,
+                itemConfiguration);
+        logger.trace("Item {} is a HomeKit accessory of types {}. Primary type is {}", item.getName(), accessoryTypes,
+                primaryAccessoryType);
+        final HomekitOHItemProxy itemProxy = new HomekitOHItemProxy(item);
+        final HomekitTaggedItem taggedItem = new HomekitTaggedItem(new HomekitOHItemProxy(item), primaryAccessoryType,
+                itemConfiguration);
+        try {
+            final HomekitAccessory accessory = HomekitAccessoryFactory.create(taggedItem, metadataRegistry, updater,
+                    settings);
+
+            accessoryTypes.stream().filter(aType -> !primaryAccessoryType.equals(aType.getKey()))
+                    .forEach(additionalAccessoryType -> {
+                        final HomekitTaggedItem additionalTaggedItem = new HomekitTaggedItem(itemProxy,
+                                additionalAccessoryType.getKey(), itemConfiguration);
+                        try {
+                            final HomekitAccessory additionalAccessory = HomekitAccessoryFactory
+                                    .create(additionalTaggedItem, metadataRegistry, updater, settings);
+                            accessory.getServices().add(additionalAccessory.getPrimaryService());
+                        } catch (HomekitException e) {
+                            logger.warn("Cannot create additional accessory {}", additionalTaggedItem);
+                        }
+                    });
+            accessoryRegistry.addRootAccessory(taggedItem.getName(), accessory);
+        } catch (HomekitException e) {
+            logger.warn("Cannot create accessory {}", taggedItem);
+        }
+    }
+
+    private boolean itemIsForThisBridge(Item item, @Nullable Map<String, Object> configuration) {
+        // non-tagged accessories belong to the first instance
+        if (configuration == null) {
+            return (instance == 1);
+        }
+
+        final @Nullable Object value = configuration.get(HomekitTaggedItem.INSTANCE);
+        if (value == null) {
+            return (instance == 1);
+        }
+        if (value instanceof Number) {
+            return (instance == ((Number) value).intValue());
         }
+        logger.warn("Unrecognized instance tag {} ({}) for item {}; assigning to default instance.", value,
+                value.getClass(), item.getName());
+        return (instance == 1);
     }
 }
index 563c6a3f486a703fef341efea62b9e4aa9c5a66e..f982701aa05b5ed4dd8a484cb0d7651d489b278d 100644 (file)
@@ -69,18 +69,20 @@ import io.github.hapjava.server.impl.crypto.HAPSetupCodeUtils;
 public class HomekitImpl implements Homekit, NetworkAddressChangeListener {
     private final Logger logger = LoggerFactory.getLogger(HomekitImpl.class);
 
+    private final StorageService storageService;
     private final NetworkAddressService networkAddressService;
     private final ConfigurationAdmin configAdmin;
-    private final Storage<String> storage;
+    private final ItemRegistry itemRegistry;
+    private final MetadataRegistry metadataRegistry;
 
-    private HomekitAuthInfoImpl authInfo;
+    private final List<HomekitAuthInfoImpl> authInfos = new ArrayList<>();
     private HomekitSettings settings;
     private @Nullable InetAddress networkInterface;
-    private @Nullable HomekitServer homekitServer;
-    private @Nullable HomekitRoot bridge;
+    private final List<HomekitServer> homekitServers = new ArrayList<>();
+    private final List<HomekitRoot> bridges = new ArrayList<>();
     private MDNSClient mdnsClient;
 
-    private final HomekitChangeListener changeListener;
+    private final List<HomekitChangeListener> changeListeners = new ArrayList<>();
 
     private final ScheduledExecutorService scheduler = ThreadPoolManager
             .getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON);
@@ -90,15 +92,15 @@ public class HomekitImpl implements Homekit, NetworkAddressChangeListener {
             @Reference NetworkAddressService networkAddressService, @Reference MetadataRegistry metadataRegistry,
             @Reference ConfigurationAdmin configAdmin, @Reference MDNSClient mdnsClient, Map<String, Object> properties)
             throws IOException, InvalidAlgorithmParameterException {
+        this.storageService = storageService;
         this.networkAddressService = networkAddressService;
         this.configAdmin = configAdmin;
         this.settings = processConfig(properties);
         this.mdnsClient = mdnsClient;
-        this.storage = storageService.getStorage(HomekitAuthInfoImpl.STORAGE_KEY);
+        this.itemRegistry = itemRegistry;
+        this.metadataRegistry = metadataRegistry;
         networkAddressService.addNetworkAddressChangeListener(this);
-        this.changeListener = new HomekitChangeListener(itemRegistry, settings, metadataRegistry, storage);
         try {
-            authInfo = new HomekitAuthInfoImpl(storage, settings.pin, settings.setupId, settings.blockUserDeletion);
             startHomekitServer();
         } catch (IOException | InvalidAlgorithmParameterException e) {
             logger.warn("cannot activate HomeKit binding. {}", e.getMessage());
@@ -150,81 +152,87 @@ public class HomekitImpl implements Homekit, NetworkAddressChangeListener {
             settings = processConfig(config);
             if ((oldSettings == null) || (settings == null))
                 return;
-            changeListener.updateSettings(settings);
-            if (!oldSettings.networkInterface.equals(settings.networkInterface) || oldSettings.port != settings.port
-                    || oldSettings.useOHmDNS != settings.useOHmDNS) {
+            if (!oldSettings.name.equals(settings.name) || !oldSettings.pin.equals(settings.pin)
+                    || !oldSettings.setupId.equals(settings.setupId)
+                    || !oldSettings.networkInterface.equals(settings.networkInterface)
+                    || oldSettings.port != settings.port || oldSettings.useOHmDNS != settings.useOHmDNS
+                    || oldSettings.instances != settings.instances) {
                 // 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)
-                    || !oldSettings.setupId.equals(settings.setupId)) {
-                stopHomekitServer();
-                authInfo.setPin(settings.pin);
-                authInfo.setSetupId(settings.setupId);
-                startHomekitServer();
+            } else {
+                for (HomekitChangeListener changeListener : changeListeners) {
+                    changeListener.updateSettings(settings);
+                }
             }
-        } catch (IOException e) {
+        } catch (IOException | InvalidAlgorithmParameterException e) {
             logger.warn("could not initialize HomeKit bridge: {}", e.getMessage());
         }
     }
 
-    private void stopBridge() {
-        final @Nullable HomekitRoot bridge = this.bridge;
-        if (bridge != null) {
-            changeListener.unsetBridge();
-            bridge.stop();
-            this.bridge = null;
+    private HomekitRoot startBridge(HomekitServer homekitServer, HomekitAuthInfoImpl authInfo,
+            HomekitChangeListener changeListener, int instance) throws IOException {
+        String name = settings.name;
+        if (instance != 1) {
+            name += " (" + instance + ")";
         }
-    }
-
-    private void startBridge() throws IOException {
-        final @Nullable HomekitServer homekitServer = this.homekitServer;
-        if (homekitServer != null && bridge == null) {
-            final HomekitRoot bridge = homekitServer.createBridge(authInfo, settings.name,
-                    HomekitAccessoryCategories.BRIDGES, HomekitSettings.MANUFACTURER, HomekitSettings.MODEL,
-                    HomekitSettings.SERIAL_NUMBER, FrameworkUtil.getBundle(getClass()).getVersion().toString(),
-                    HomekitSettings.HARDWARE_REVISION);
-            changeListener.setBridge(bridge);
-            this.bridge = bridge;
-            bridge.setConfigurationIndex(changeListener.getConfigurationRevision());
-            bridge.refreshAuthInfo();
-            final int lastAccessoryCount = changeListener.getLastAccessoryCount();
-            int currentAccessoryCount = changeListener.getAccessories().size();
-            if (currentAccessoryCount < lastAccessoryCount) {
-                logger.debug(
-                        "it looks like not all items were initialized yet. Old configuration had {} accessories, the current one has only {} accessories. Delay HomeKit bridge start for {} seconds.",
-                        lastAccessoryCount, currentAccessoryCount, settings.startDelay);
-                scheduler.schedule(() -> {
-                    if (currentAccessoryCount < lastAccessoryCount) {
-                        // the number of items is still different, maybe it is desired.
-                        // make new configuration revision.
-                        changeListener.makeNewConfigurationRevision();
-                    }
-                    bridge.start();
-                }, settings.startDelay, TimeUnit.SECONDS);
-            } else { // start bridge immediately.
+        final HomekitRoot bridge = homekitServer.createBridge(authInfo, name, HomekitAccessoryCategories.BRIDGES,
+                HomekitSettings.MANUFACTURER, HomekitSettings.MODEL, HomekitSettings.SERIAL_NUMBER,
+                FrameworkUtil.getBundle(getClass()).getVersion().toString(), HomekitSettings.HARDWARE_REVISION);
+        changeListener.setBridge(bridge);
+        bridges.add(bridge);
+        bridge.setConfigurationIndex(changeListener.getConfigurationRevision());
+        bridge.refreshAuthInfo();
+        final int lastAccessoryCount = changeListener.getLastAccessoryCount();
+        int currentAccessoryCount = changeListener.getAccessories().size();
+        if (currentAccessoryCount < lastAccessoryCount) {
+            logger.debug(
+                    "it looks like not all items were initialized yet. Old configuration had {} accessories, the current one has only {} accessories. Delay HomeKit bridge start for {} seconds.",
+                    lastAccessoryCount, currentAccessoryCount, settings.startDelay);
+            scheduler.schedule(() -> {
+                if (currentAccessoryCount < lastAccessoryCount) {
+                    // the number of items is still different, maybe it is desired.
+                    // make new configuration revision.
+                    changeListener.makeNewConfigurationRevision();
+                }
                 bridge.start();
-            }
-        } else {
-            logger.warn(
-                    "trying to start bridge but HomeKit server is not initialized or bridge is already initialized");
+            }, settings.startDelay, TimeUnit.SECONDS);
+        } else { // start bridge immediately.
+            bridge.start();
         }
+        return bridge;
     }
 
-    private void startHomekitServer() throws IOException {
+    private void startHomekitServer() throws IOException, InvalidAlgorithmParameterException {
         logger.trace("start HomeKit bridge");
-        if (homekitServer == null) {
+        if (homekitServers.isEmpty()) {
             try {
                 networkInterface = InetAddress
                         .getByName(((settings.networkInterface != null) && (!settings.networkInterface.isEmpty()))
                                 ? settings.networkInterface
                                 : networkAddressService.getPrimaryIpv4HostAddress());
+            } catch (UnknownHostException e) {
+                logger.warn("cannot resolve the Pv4 address / hostname {}.",
+                        networkAddressService.getPrimaryIpv4HostAddress());
+            }
+
+            for (int i = 0; i < settings.instances; ++i) {
+                String storage_key = HomekitAuthInfoImpl.STORAGE_KEY;
+                if (i != 0) {
+                    storage_key += i;
+                }
+                Storage<String> storage = storageService.getStorage(storage_key);
+                HomekitAuthInfoImpl authInfo = new HomekitAuthInfoImpl(storage, settings.pin, settings.setupId,
+                        settings.blockUserDeletion);
+
+                @Nullable
+                HomekitServer homekitServer = null;
                 if (settings.useOHmDNS) {
                     for (JmDNS mdns : mdnsClient.getClientInstances()) {
                         if (mdns.getInetAddress().equals(networkInterface)) {
                             logger.trace("suitable mDNS client for IP {} found and will be used for HomeKit",
                                     networkInterface);
-                            homekitServer = new HomekitServer(mdns, settings.port);
+                            homekitServer = new HomekitServer(mdns, settings.port + i);
                         }
                     }
                 }
@@ -233,12 +241,14 @@ public class HomekitImpl implements Homekit, NetworkAddressChangeListener {
                         logger.trace("no suitable mDNS server for IP {} found", networkInterface);
                     }
                     logger.trace("create HomeKit server with dedicated mDNS server");
-                    homekitServer = new HomekitServer(networkInterface, settings.port);
+                    homekitServer = new HomekitServer(networkInterface, settings.port + i);
                 }
-                startBridge();
-            } catch (UnknownHostException e) {
-                logger.warn("cannot resolve the Pv4 address / hostname {}.",
-                        networkAddressService.getPrimaryIpv4HostAddress());
+                homekitServers.add(homekitServer);
+                HomekitChangeListener changeListener = new HomekitChangeListener(itemRegistry, settings,
+                        metadataRegistry, storage, i + 1);
+                changeListeners.add(changeListener);
+                bridges.add(startBridge(homekitServer, authInfo, changeListener, i + 1));
+                authInfos.add(authInfo);
             }
         } else {
             logger.warn("trying to start HomeKit server but it is already initialized");
@@ -247,49 +257,56 @@ public class HomekitImpl implements Homekit, NetworkAddressChangeListener {
 
     private void stopHomekitServer() {
         logger.trace("stop HomeKit bridge");
-        final @Nullable HomekitServer homekit = this.homekitServer;
-        if (homekit != null) {
-            if (bridge != null) {
-                stopBridge();
-            }
-            homekit.stop();
-            this.homekitServer = null;
+        for (int i = 0; i < homekitServers.size(); ++i) {
+            changeListeners.get(i).unsetBridge();
+            bridges.get(i).stop();
+            homekitServers.get(i).stop();
+            changeListeners.get(i).stop();
         }
+        homekitServers.clear();
+        bridges.clear();
+        changeListeners.clear();
+        authInfos.clear();
     }
 
     @Deactivate
     protected void deactivate() {
         networkAddressService.removeNetworkAddressChangeListener(this);
-        changeListener.clearAccessories();
+        for (HomekitChangeListener changeListener : changeListeners) {
+            changeListener.clearAccessories();
+        }
         stopHomekitServer();
-        changeListener.stop();
     }
 
     @Override
     public void refreshAuthInfo() throws IOException {
-        final @Nullable HomekitRoot bridge = this.bridge;
-        if (bridge != null) {
+        for (HomekitRoot bridge : bridges) {
             bridge.refreshAuthInfo();
         }
     }
 
     @Override
     public void allowUnauthenticatedRequests(boolean allow) {
-        final @Nullable HomekitRoot bridge = this.bridge;
-        if (bridge != null) {
+        for (HomekitRoot bridge : bridges) {
             bridge.allowUnauthenticatedRequests(allow);
         }
     }
 
     @Override
     public List<HomekitAccessory> getAccessories() {
-        return new ArrayList<>(this.changeListener.getAccessories().values());
+        List<HomekitAccessory> accessories = new ArrayList<>();
+        for (HomekitChangeListener changeListener : changeListeners) {
+            accessories.addAll(changeListener.getAccessories().values());
+        }
+        return accessories;
     }
 
     @Override
     public void clearHomekitPairings() {
         try {
-            authInfo.clear();
+            for (HomekitAuthInfoImpl authInfo : authInfos) {
+                authInfo.clear();
+            }
             refreshAuthInfo();
         } catch (Exception e) {
             logger.warn("could not clear HomeKit pairings", e);
@@ -302,23 +319,13 @@ public class HomekitImpl implements Homekit, NetworkAddressChangeListener {
         removed.forEach(i -> {
             logger.trace("removed interface {}", i.getAddress().toString());
             if (i.getAddress().equals(networkInterface)) {
-                final @Nullable HomekitRoot bridge = this.bridge;
-                if (bridge != null) {
-                    bridge.stop();
-                    this.bridge = null;
-                }
-                final @Nullable HomekitServer homekitServer = this.homekitServer;
-                if (homekitServer != null) {
-                    homekitServer.stop();
-                    this.homekitServer = null;
-                }
-                logger.trace("bridge stopped");
+                stopHomekitServer();
             }
         });
-        if ((this.bridge == null) && (!added.isEmpty())) {
+        if (bridges.isEmpty() && !added.isEmpty()) {
             try {
                 startHomekitServer();
-            } catch (IOException e) {
+            } catch (IOException | InvalidAlgorithmParameterException e) {
                 logger.warn("could not initialize HomeKit bridge: {}", e.getMessage());
             }
         }
index c30f2240f7e33446e016c606f67a3746bf68e486..9529187563a0dcdd94b05e8681524d4aacac8f1b 100644 (file)
@@ -26,6 +26,7 @@ public class HomekitSettings {
 
     public String name = "openHAB";
     public int port = 9123;
+    public int instances = 1;
     public String pin = "031-45-154";
     public String setupId;
     public String qrCode;
@@ -92,6 +93,9 @@ public class HomekitSettings {
         if (port != other.port) {
             return false;
         }
+        if (instances != other.instances) {
+            return false;
+        }
         if (thermostatTargetModeAuto == null) {
             if (other.thermostatTargetModeAuto != null) {
                 return false;
index 056f760d479ca9d12ba220453821443cd7438866..905bce725ea5f72f81b608824251f9288ad1cc3f 100644 (file)
@@ -43,6 +43,7 @@ public class HomekitTaggedItem {
     public final static String DELAY = "commandDelay";
     public final static String INVERTED = "inverted";
     public final static String PRIMARY_SERVICE = "primary";
+    public final static String INSTANCE = "instance";
 
     private static final Map<Integer, String> CREATED_ACCESSORY_IDS = new ConcurrentHashMap<>();
 
index 16d0a6905e64ce25a63d3384a7c1edc1cab237eb..cd5ab8e252742e2effdff1c7d0c6ad5e11956057 100644 (file)
                        <description>Defines the port the HomeKit integration listens on.</description>
                        <default>9123</default>
                </parameter>
+               <parameter name="instances" type="integer" required="true" groupName="core">
+                       <label>Instances</label>
+                       <description>Defines how many bridges to expose. Necessary if you have more than 149 accessories. Accessories must be
+                               assigned to additional instances via metadata. Additional bridges will use incrementing port numbers.</description>
+                       <default>1</default>
+               </parameter>
                <parameter name="pin" type="text" pattern="\d{3}-\d{2}-\d{3}" required="true" groupName="core">
                        <label>Pin</label>
                        <description>Defines the pin, used for pairing, in the form ###-##-###.</description>
index 918f9ed5b68eb4171508faeabd464cfe6380a930..5cc5bc2ea5f6c80d2d66d56d35ac7acc00d7e3b7 100644 (file)
@@ -9,6 +9,8 @@ io.config.homekit.group.thermostatCurrentHeatingCooling.label = Thermostat Curre
 io.config.homekit.group.thermostatCurrentHeatingCooling.description = String values used by your thermostat to set different targetHeatingCooling modes
 io.config.homekit.group.thermostatTargetHeatingCooling.label = Thermostat Target Heating/Cooling Mapping
 io.config.homekit.group.thermostatTargetHeatingCooling.description = String values used by your thermostat to set different targetHeatingCooling modes
+io.config.homekit.instances.label = Instances
+io.config.homekit.instances.description = Defines how many bridges to expose. Necessary if you have more than 149 accessories. Accessories must be assigned to additional instances via metadata. Additional bridges will use incrementing port numbers.
 io.config.homekit.name.label = Bridge name
 io.config.homekit.name.description = Name of the HomeKit bridge
 io.config.homekit.networkInterface.label = Network Interface