All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.hapjava.server.impl.HomekitRoot Maven / Gradle / Ivy

There is a newer version: 2.0.7
Show newest version
package io.github.hapjava.server.impl;

import io.github.hapjava.accessories.Bridge;
import io.github.hapjava.accessories.HomekitAccessory;
import io.github.hapjava.server.HomekitAccessoryCategories;
import io.github.hapjava.server.HomekitAuthInfo;
import io.github.hapjava.server.HomekitWebHandler;
import io.github.hapjava.server.impl.connections.HomekitClientConnectionFactoryImpl;
import io.github.hapjava.server.impl.connections.SubscriptionManager;
import io.github.hapjava.server.impl.jmdns.JmdnsHomekitAdvertiser;
import java.io.IOException;
import java.net.InetAddress;
import javax.jmdns.JmDNS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 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, String, String)}. For
 * single accessories, this is composed by {@link HomekitStandaloneAccessoryServer}.
 *
 * @author Andy Lintner
 */
public class HomekitRoot {

  private static final Logger logger = LoggerFactory.getLogger(HomekitRoot.class);
  private static final int DEFAULT_ACCESSORY_CATEGORY = HomekitAccessoryCategories.OTHER;
  private final JmdnsHomekitAdvertiser advertiser;
  private final HomekitWebHandler webHandler;
  private final HomekitAuthInfo authInfo;
  private final String label;
  private final int category;
  private final HomekitRegistry registry;
  private final SubscriptionManager subscriptions = new SubscriptionManager();
  private boolean started = false;
  private int configurationIndex = 1;
  private int nestedBatches = 0;
  private boolean madeChanges = false;

  HomekitRoot(
      String label, HomekitWebHandler webHandler, InetAddress host, HomekitAuthInfo authInfo)
      throws IOException {
    this(label, DEFAULT_ACCESSORY_CATEGORY, webHandler, authInfo, new JmdnsHomekitAdvertiser(host));
  }

  HomekitRoot(
      String label,
      int category,
      HomekitWebHandler webHandler,
      InetAddress host,
      HomekitAuthInfo authInfo)
      throws IOException {
    this(label, category, webHandler, authInfo, new JmdnsHomekitAdvertiser(host));
  }

  HomekitRoot(
      String label,
      int category,
      HomekitWebHandler webHandler,
      HomekitAuthInfo authInfo,
      JmdnsHomekitAdvertiser advertiser)
      throws IOException {
    this.advertiser = advertiser;
    this.webHandler = webHandler;
    this.authInfo = authInfo;
    this.label = label;
    this.category = category;
    this.registry = new HomekitRegistry(label, subscriptions);
  }

  HomekitRoot(
      String label,
      int category,
      HomekitWebHandler webHandler,
      JmDNS jmdns,
      HomekitAuthInfo authInfo)
      throws IOException {
    this(label, category, webHandler, authInfo, new JmdnsHomekitAdvertiser(jmdns));
  }

  HomekitRoot(String label, HomekitWebHandler webHandler, JmDNS jmdns, HomekitAuthInfo authInfo)
      throws IOException {
    this(
        label, DEFAULT_ACCESSORY_CATEGORY, webHandler, authInfo, new JmdnsHomekitAdvertiser(jmdns));
  }

  /**
   * Begin a batch update of accessories.
   *
   * 

After calling this, you can call addAccessory() and removeAccessory() multiple times without * causing HAP-Java to re-publishing the metadata to HomeKit. You'll need to call * completeUpdateBatch in order to publish all accumulated changes. */ public synchronized void batchUpdate() { if (this.nestedBatches == 0) madeChanges = false; ++this.nestedBatches; } /** Publish accumulated accessory changes since batchUpdate() was called. */ public synchronized void completeUpdateBatch() { if (--this.nestedBatches == 0 && madeChanges) registry.reset(); } /** * Add an accessory to be handled and advertised by this root. 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); if (logger.isTraceEnabled()) { accessory.getName().thenAccept(name -> logger.trace("Added accessory {}", name)); } madeChanges = true; if (started && nestedBatches == 0) { registry.reset(); } } /** * Removes an accessory from being handled or advertised by this root. * * @param accessory accessory to cease advertising and handling */ public void removeAccessory(HomekitAccessory accessory) { if (this.registry.remove(accessory)) { if (logger.isTraceEnabled()) { accessory.getName().thenAccept(name -> logger.trace("Removed accessory {}", name)); } madeChanges = true; if (started && nestedBatches == 0) { registry.reset(); } } else { accessory.getName().thenAccept(name -> logger.warn("Could not remove accessory {}", name)); } } /** * 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; madeChanges = false; registry.reset(); webHandler .start( new HomekitClientConnectionFactoryImpl(authInfo, registry, subscriptions, advertiser)) .thenAccept( port -> { try { refreshAuthInfo(); advertiser.advertise( label, category, authInfo.getMac(), port, configurationIndex, authInfo.getSetupId()); } catch (Exception e) { throw new RuntimeException(e); } }); } /** Stops advertising and handling the HomeKit accessories. */ public void stop() { advertiser.stop(); webHandler.stop(); subscriptions.removeAll(); 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; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy