package com.beowulfe.hap;
import com.beowulfe.hap.impl.HomekitRegistry;
import com.beowulfe.hap.impl.HomekitWebHandler;
import com.beowulfe.hap.impl.accessories.Bridge;
import com.beowulfe.hap.impl.connections.HomekitClientConnectionFactoryImpl;
import com.beowulfe.hap.impl.connections.SubscriptionManager;
import com.beowulfe.hap.impl.jmdns.JmdnsHomekitAdvertiser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetAddress;
/**
* Provides advertising and handling for Homekit accessories. This class handles the advertising of Homekit accessories and
* contains one or more accessories. When implementing a bridge accessory, you will interact with this class directly. Instantiate
* it via {@link HomekitServer#createBridge(HomekitAuthInfo, String, String, String, String)}. For single accessories, this is composed
* by {@link HomekitStandaloneAccessoryServer}.
*
* @author Andy Lintner
*/
public class HomekitRoot {
private final static Logger logger = LoggerFactory.getLogger(HomekitRoot.class);
private final JmdnsHomekitAdvertiser advertiser;
private final HomekitWebHandler webHandler;
private final HomekitAuthInfo authInfo;
private final String label;
private final HomekitRegistry registry;
private final SubscriptionManager subscriptions = new SubscriptionManager();
private boolean started = false;
private int configurationIndex = 1;
HomekitRoot(String label, HomekitWebHandler webHandler, InetAddress localhost,
HomekitAuthInfo authInfo) throws IOException {
this(label, webHandler, authInfo, new JmdnsHomekitAdvertiser(localhost));
}
HomekitRoot(String label, HomekitWebHandler webHandler, HomekitAuthInfo authInfo,
JmdnsHomekitAdvertiser advertiser) throws IOException {
this.advertiser = advertiser;
this.webHandler = webHandler;
this.authInfo = authInfo;
this.label = label;
this.registry = new HomekitRegistry(label);
}
/**
* Add an accessory to be handled and advertised by this root. Any existing Homekit connections will be terminated to allow
* the clients to reconnect and see the updated accessory list. When using this for a bridge, the ID of the accessory must be
* greater than 1, as that ID is reserved for the Bridge itself.
*
* @param accessory to advertise and handle.
*/
public void addAccessory(HomekitAccessory accessory) {
if (accessory.getId() <= 1 && !(accessory instanceof Bridge)) {
throw new IndexOutOfBoundsException("The ID of an accessory used in a bridge must be greater than 1");
}
addAccessorySkipRangeCheck(accessory);
}
/**
* Skips the range check. Used by {@link HomekitStandaloneAccessoryServer} as well as {@link #addAccessory(HomekitAccessory)};
*
* @param accessory to advertise and handle.
*/
void addAccessorySkipRangeCheck(HomekitAccessory accessory) {
this.registry.add(accessory);
logger.info("Added accessory " + accessory.getLabel());
if (started) {
registry.reset();
webHandler.resetConnections();
}
}
/**
* Removes an accessory from being handled or advertised by this root. Any existing Homekit connections will be terminated to allow
* the clients to reconnect and see the updated accessory list.
*
* @param accessory accessory to cease advertising and handling
*/
public void removeAccessory(HomekitAccessory accessory) {
this.registry.remove(accessory);
logger.info("Removed accessory " + accessory.getLabel());
if (started) {
registry.reset();
webHandler.resetConnections();
}
}
/**
* Starts advertising and handling the previously added Homekit accessories. You should try to call this after you have used the
* {@link #addAccessory(HomekitAccessory)} method to add all the initial accessories you plan on advertising, as any later additions
* will cause the Homekit clients to reconnect.
*/
public void start() {
started = true;
registry.reset();
webHandler.start(new HomekitClientConnectionFactoryImpl(authInfo,
registry, subscriptions, advertiser)).thenAccept(port -> {
try {
refreshAuthInfo();
advertiser.advertise(label, authInfo.getMac(), port, configurationIndex);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
/**
* Stops advertising and handling the Homekit accessories.
*/
public void stop() {
advertiser.stop();
webHandler.stop();
started = false;
}
/**
* Refreshes auth info after it has been changed outside this library
*
* @throws IOException if there is an error in the underlying protocol, such as a TCP error
*/
public void refreshAuthInfo() throws IOException {
advertiser.setDiscoverable(!authInfo.hasUser());
}
/**
* By default, most homekit requests require that the client be paired. Allowing unauthenticated requests
* can be useful for debugging, but should not be used in production.
*
* @param allow whether to allow unauthenticated requests
*/
public void allowUnauthenticatedRequests(boolean allow) {
registry.setAllowUnauthenticatedRequests(allow);
}
/**
* By default, the bridge advertises itself at revision 1. If you make changes to the accessories you're
* including in the bridge after your first call to {@link start()}, you should increment this number. The
* behavior of the client if the configuration index were to decrement is undefined, so this implementation will
* not manage the configuration index by automatically incrementing - preserving this state across invocations should
* be handled externally.
*
* @param revision an integer, greater than or equal to one, indicating the revision of the accessory information
* @throws IOException if there is an error in the underlying protocol, such as a TCP error
*/
public void setConfigurationIndex(int revision) throws IOException {
if (revision < 1) {
throw new IllegalArgumentException("revision must be greater than or equal to 1");
}
this.configurationIndex = revision;
if (this.started) {
advertiser.setConfigurationIndex(revision);
}
}
HomekitRegistry getRegistry() {
return registry;
}
}