2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.io.homekit.internal;
15 import java.util.concurrent.ConcurrentHashMap;
16 import java.util.concurrent.ConcurrentMap;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.openhab.core.items.GenericItem;
20 import org.openhab.core.items.Item;
21 import org.openhab.core.items.StateChangeListener;
22 import org.openhab.core.types.State;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
26 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
29 * Subscribes and unsubscribes from Item changes to enable notification to HomeKit
30 * clients. Each item/key pair (key is optional) should be unique, as the underlying
31 * HomeKit library takes care of insuring only a single subscription exists for
34 * @author Andy Lintner - Initial contribution
36 public class HomekitAccessoryUpdater {
37 private final Logger logger = LoggerFactory.getLogger(HomekitAccessoryUpdater.class);
38 private final ConcurrentMap<ItemKey, Subscription> subscriptionsByName = new ConcurrentHashMap<>();
40 public void subscribe(GenericItem item, HomekitCharacteristicChangeCallback callback) {
41 subscribe(item, null, callback);
44 public void subscribe(GenericItem item, String key, HomekitCharacteristicChangeCallback callback) {
45 logger.trace("Received subscription request for {} / {}", item, key);
49 if (callback == null) {
50 logger.trace("The received subscription contains a null callback, skipping");
53 ItemKey itemKey = new ItemKey(item, key);
54 subscriptionsByName.compute(itemKey, (k, v) -> {
56 logger.debug("Received duplicate subscription for {} / {}", item, key);
57 unsubscribe(item, key);
59 logger.trace("Adding subscription for {} / {}", item, key);
60 Subscription subscription = (changedItem, oldState, newState) -> callback.changed();
61 item.addStateChangeListener(subscription);
66 public void unsubscribe(GenericItem item) {
67 unsubscribe(item, null);
70 public void unsubscribe(GenericItem item, String key) {
74 subscriptionsByName.computeIfPresent(new ItemKey(item, key), (k, v) -> {
75 logger.trace("Removing existing subscription for {} / {}", item, key);
76 item.removeStateChangeListener(v);
83 private interface Subscription extends StateChangeListener {
86 void stateChanged(Item item, State oldState, State newState);
89 default void stateUpdated(Item item, State state) {
90 // Do nothing on non-change update
94 private static class ItemKey {
95 public final GenericItem item;
96 public final String key;
98 public ItemKey(GenericItem item, String key) {
104 public int hashCode() {
105 final int prime = 31;
107 result = prime * result + ((item == null) ? 0 : item.hashCode());
108 result = prime * result + ((key == null) ? 0 : key.hashCode());
113 public boolean equals(Object obj) {
120 if (getClass() != obj.getClass()) {
123 ItemKey other = (ItemKey) obj;
125 if (other.item != null) {
128 } else if (!item.equals(other.item)) {
132 if (other.key != null) {
135 } else if (!key.equals(other.key)) {