[homekit] allow multiple bridge instances to break the 150 limit (#13226)
fixes #11508, #12927 Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
parent
b075d3781c
commit
e4c9a40d03
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
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;
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<>();
|
||||
|
||||
|
|
|
@ -41,6 +41,12 @@
|
|||
<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>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue