HomeKit(HomeKit Accessory Protocol)HAP协议 Java实现

1. 此实现基于HAP-Java库实现

github地址

2. HomeKit基础知识

2.1 HomeKit配件和Apple设备的通信,主要通过两种方式:

  • IP
  • 蓝牙

2.2 HomeKit配件有两种入网形式:

  • 单个配件 Accessory
  • 桥接器 Bridge(桥接器+多个配件)

2.3 HomeKit配件的逻辑结构:

  • 桥接器 Bridge 包含若干 配件 Accessory(不适用于单个配件入网)
  • 配件包含若干服务 Service
  • 服务包含若干属性 Characteristic

3. 详解

3.1 桥接器 Bridge

  • 桥接器是一个特殊形式的配件
  • 在家庭app中添加桥接器,桥接器关联的所有配件都会加入到HomeKit里面来
  • 在家庭app中删除桥接器后,桥接器下关联的所有配件都会被删除
  • 苹果HAP限定1个桥接器的接入配件的数量上限是150,除去桥本身就是149个配件

3.2 配件 Accessory

  • 苹果HAP定义的配件:一个物理配件,通俗点就是:一个灯/窗帘/风扇/空调 等实体
  • 配件加入到家庭App需要被扫描或输入8位配对码
  • 每个配件对应一个独立的配对码
  • 每个配件是个服务器Server,包含一个或多个服务Service
  • 配件和配件之间可以直接通信,这也是苹果能把HomeKit做成去中心化的原因

3.3 服务 Service

  • 苹果HAP定义的服务:配件所包含的逻辑功能,通俗点就是:这个配件包含有某类的功能
  • 假如HomeKit里有一个带光感的灯,那么这个实体灯配件就包含 8.23 Light Bulb 灯 和 8.24 Light
  • Sensor 光照感应 的服务
  • 假如HomeKit里有一个带空气质量检测的加湿器,那么这个实体灯配件就包含 8.1.9 Humidifier
  • Dehumidifier 加湿除湿、8.20 Humidity Sensor 湿度传感器、8.3 Air Quality Sensor
    空气质量传感器 三项服务
  • 每个家庭app图标对应一个服务,有一些图标可以用户自定义
  • 有些服务是隐性服务 Hidden Service,用户在家庭app中看不到,比如 8.17 HAP Protocol
    Information HAP 信息
  • 苹果HAP限定每个配件包含服务的数量不能超过100个

苹果HAP定义的服务章节列表如下:
在这里插入图片描述

3.4 属性 Characteristic

  • 属性 Characteristic 是服务更细致的构成,详细描述服务功能
  • 比如一个灯的服务,能做到开关/调亮度/调色温/调RGB,那开关、亮度值、色温值、RGB值 都分别是这个灯的服务的属性
  • 服务可包含多项属性,至少含一项必备属性Required Characteristic,可以不包含可选属性 Optional
    Characteristic

苹果HAP定义的属性章节列表如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4. 举例详解

假如有个HomeKit配件,是一个带灯泡的电风扇,我们来看一下三种情况下的配件构成有什么区别:
在这里插入图片描述

灯光可开闭/调亮度/颜色、风扇可调速/摆动模式(上下/左右扫风)

这个配件就包含2个服务:

  • Light Bulb 灯
  • Fan 风扇

在灯的服务中,包含如下属性:

  • On 开闭
  • Brightness 亮度
  • Hue 色相
  • Saturation 饱和度

在风扇的服务中,含如下属性:

  • Active 激活
  • Rotation Speed 旋转速度
  • Swing Mode 摆动模式

5. 代码实现

5.1 项目依赖

	  <dependency>
		  <groupId>io.github.hap-java</groupId>
		  <artifactId>hap</artifactId>
		  <version>2.0.7</version>
	  </dependency>
	  <dependency>
		  <groupId>com.google.zxing</groupId>
		  <artifactId>core</artifactId>
		  <version>3.4.1</version>
	  </dependency>
  • hap库用于解析hap协议,与设备间通信
  • zxing库用于生成二维码

5.2 功能实现


import com.beowulfe.hap.sample.accessories.coldandwarmfreshair.AirConditioning;
import com.beowulfe.hap.sample.accessories.coldandwarmfreshair.Fan;
import com.beowulfe.hap.sample.accessories.doorsandwindows.ElectricSlidingDoors;
import com.beowulfe.hap.sample.accessories.light.*;
import com.beowulfe.hap.sample.accessories.sensor.AirSensor;
import com.beowulfe.hap.sample.accessories.sensor.LightSensor;
import com.beowulfe.hap.sample.accessories.sensor.MotionSensors;
import com.beowulfe.hap.sample.accessories.sunshade.HoldPosition;
import com.beowulfe.hap.sample.accessories.sunshade.HorizontalTilting;
import com.beowulfe.hap.sample.accessories.sunshade.SingleMotorCurtain;
import com.beowulfe.hap.sample.accessories.sunshade.VerticalTilting;
import com.beowulfe.hap.sample.accessories.sw.Switch;
import io.github.hapjava.server.impl.HomekitRoot;
import io.github.hapjava.server.impl.HomekitServer;
import io.github.hapjava.server.impl.crypto.HAPSetupCodeUtils;

import java.io.*;
import java.net.InetAddress;

public class Main {




    private static final int PORT =  8001
    private static final String PIN = "031-45-154" 

    public static void main(String[] args) {
         try {
            File authFile = new File("auth-state.bin");
            MockAuthInfo mockAuth;
            if (authFile.exists()) {
                FileInputStream fileInputStream = new FileInputStream(authFile);
                ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
                try {
                    System.out.println("Using persisted auth");
                    AuthState authState = (AuthState) objectInputStream.readObject();
                    mockAuth = new MockAuthInfo(authState);
                } finally {
                    objectInputStream.close();
                }
            } else {
                mockAuth = new MockAuthInfo("111-11-111");
            }


            HomekitServer homekit = new HomekitServer(PORT);
            HomekitRoot bridge = homekit.createBridge(mockAuth, PIN , 2, "Bridge, Inc.", "M1",PIN , "1.0", "1.0");

            String setupURI = HAPSetupCodeUtils.getSetupURI(mockAuth.getPin().replace("-", ""), mockAuth.getSetupId(), 2);
            QRtoConsole.printQR(setupURI);


            mockAuth.onChange(state -> {
                try {
                    System.out.println("State has changed! Writing");
                    FileOutputStream fileOutputStream = new FileOutputStream(authFile);
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
                    objectOutputStream.writeObject(state);
                    objectOutputStream.flush();
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            bridge.batchUpdate(); 
            MockLightAndFan mockLightAndFan = new MockLightAndFan(3, "light and fan", "LF", "LF", "withlight", "firmware", "hardware");
			bridge.addAccessory(mockLightAndFan);
            bridge.completeUpdateBatch();
            bridge.start();


            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                System.out.println("Stopping homekit server.");
                homekit.stop();
            }));

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


}



import java.io.Serializable;
import java.math.BigInteger;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

class AuthState implements Serializable {
    private static final long serialVersionUID = 1L;
    String PIN;
    final String mac;
    final BigInteger salt;
    final byte[] privateKey;
    final String setupId;
    final ConcurrentMap<String, byte[]> userKeyMap = new ConcurrentHashMap<>();

    public AuthState(String _PIN, String _mac, BigInteger _salt, byte[] _privateKey, String _setupId) {
        PIN = _PIN;
        salt = _salt;
        privateKey = _privateKey;
        mac = _mac;
        setupId = _setupId;
    }
}

import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.util.function.Consumer;

import io.github.hapjava.server.HomekitAuthInfo;
import io.github.hapjava.server.impl.HomekitServer;
import io.github.hapjava.server.impl.crypto.HAPSetupCodeUtils;

/**
 * This is a simple implementation that should never be used in actual production. The mac, salt, and privateKey
 * are being regenerated every time the application is started. The user store is also not persisted. This means pairing
 * needs to be re-done every time the app restarts.
 *
 * @author Andy Lintner
 */
public class MockAuthInfo implements HomekitAuthInfo {

    private final AuthState authState;

    Consumer<AuthState> callback;

    public MockAuthInfo(String pin) throws InvalidAlgorithmParameterException {
        this(new AuthState(pin, HomekitServer.generateMac(), HomekitServer.generateSalt(),
                           HomekitServer.generateKey(), HAPSetupCodeUtils.generateSetupId()));
    }

    public MockAuthInfo(AuthState _authState) {
        authState = _authState;
        System.out.println("The PIN for pairing is " + authState.PIN);
    }

    @Override
    public String getPin() {
        return authState.PIN;
    }

    @Override
    public String getMac() {
        return authState.mac;
    }

    @Override
    public BigInteger getSalt() {
        return authState.salt;
    }

    @Override
    public byte[] getPrivateKey() {
        return authState.privateKey;
    }

    @Override
    public String getSetupId() {
        return authState.setupId;
    }

    @Override
    public void createUser(String username, byte[] publicKey) {
        if (!authState.userKeyMap.containsKey(username)) {
            authState.userKeyMap.putIfAbsent(username, publicKey);
            System.out.println("Added pairing for " + username);
            notifyChange();
        } else {
            System.out.println("Already have a user for " + username);
        }
    }

    @Override
    public void removeUser(String username) {
        authState.userKeyMap.remove(username);
        System.out.println("Removed pairing for " + username);
        notifyChange();
    }

    @Override
    public byte[] getUserPublicKey(String username) {
        return authState.userKeyMap.get(username);
    }

    public void onChange(Consumer<AuthState> _callback) {
        callback = _callback;
        notifyChange();
    }

    private void notifyChange() {
        if (callback != null) {
            callback.accept(authState);
        }
    }
    @Override
    public boolean hasUser() {
        return !authState.userKeyMap.isEmpty();
    }


}

import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ported code from https://github.com/gtanner/qrcode-terminal
 */
public class QRtoConsole {
    private static final Logger logger = LoggerFactory.getLogger(QRtoConsole.class);

    private static final char WHITE_ALL = '\u2588';
    private static final char WHITE_BLACK = '\u2580';
    private static final char BLACK_WHITE = '\u2584';
    private static final char BLACK_ALL = ' ';


    public static void printQR(String setupURI) {
        try {
            BitMatrix matrix = new MultiFormatWriter().encode(
                    setupURI, BarcodeFormat.QR_CODE, 10,
                    30);
            for(int y = 0; y < matrix.getHeight();y+=2) {
                for(int x = 0; x < matrix.getWidth(); x++) {
                    boolean firstRow = matrix.get(x,y);
                    boolean secondRow = matrix.get(x,y+1);
                    if(firstRow && secondRow) {
                        System.out.print(BLACK_ALL);
                    } else if(firstRow) {
                        System.out.print(BLACK_WHITE);
                    } else if(secondRow) {
                        System.out.print(WHITE_BLACK);
                    } else {
                        System.out.print(WHITE_ALL);
                    }
                }
                System.out.println();
            }
        } catch(WriterException e) {
            logger.error("error creating qr code", e);
        }
    }
}


import io.github.hapjava.accessories.FanAccessory;
import io.github.hapjava.accessories.LightbulbAccessory;
import io.github.hapjava.accessories.optionalcharacteristic.*;
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
import io.github.hapjava.characteristics.impl.fan.CurrentFanStateEnum;
import io.github.hapjava.characteristics.impl.fan.RotationDirectionEnum;
import io.github.hapjava.characteristics.impl.fan.SwingModeEnum;
import io.github.hapjava.characteristics.impl.fan.TargetFanStateEnum;
import io.github.hapjava.services.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.concurrent.CompletableFuture;

 
public class MockLightAndFan implements
        FanAccessory,
        LightbulbAccessory,
        AccessoryWithFanState,
        AccessoryWithHardwareRevision,
        AccessoryWithBrightness,
        AccessoryWithColor,
        AccessoryWithRotationDirection,
        AccessoryWithRotationSpeed,
        AccessoryWithSwingMode {


    private static final Logger logger = LoggerFactory.getLogger(MockLightAndFan.class);




    /**
     * 配件唯一id
     */
    private int id;
    /**
     * 配件名称
     */
    private String name;
    /**
     * 配件序列号
     */
    private String serialNumber;
    /**
     * 配件型号
     */
    private String model;
    /**
     * 配件制造商
     */
    private String manufacturer;
    /**
     * 配件固件版本号
     */
    private String firmwareRevision;
    /**
     * 配件硬件版本号
     */
    private String hardwareRevision;
    /**
     * 初始灯的状态
     */
    private boolean powerState = false;
    private boolean active = false;
    /**
     * 当前风扇状态
     */
    private CurrentFanStateEnum currentFanStateEnum = CurrentFanStateEnum.IDLE;
    /**
     * 风扇目标状态、/手动/自动
     */
    private TargetFanStateEnum targetFanStateEnum = TargetFanStateEnum.MANUAL;
    /**
     * 亮度
     */
    private int brightness = 0;
    /**
     * 风扇旋转方向
     */
    private RotationDirectionEnum rotationDirectionEnum = RotationDirectionEnum.CLOCKWISE;
    /**
     * 旋转速度
     */
    private double rotationSpeed = 1.0d;
    /**
     * 摆动模式
     */
    private SwingModeEnum swingModeEnum = SwingModeEnum.SWING_DISABLED;
    /**
     * 色相
     */
    private double hue = 1d;
    /**
     * 饱和度
     */
    private double saturation = 1d;
    /**
     * 灯回调
     */
    private HomekitCharacteristicChangeCallback lightSubscribeCallback = null;
    /**
     * 风扇状态回调
     */
    private HomekitCharacteristicChangeCallback fanSubscribeCallback = null;
    /**
     * 风扇目标状态回调
     */
    private HomekitCharacteristicChangeCallback fanTargetSubscribeCallback = null;
    /**
     * 灯光亮度回调
     */
    private HomekitCharacteristicChangeCallback brightnessSubscribeCallback = null;
    /**
     * 旋转方向回调
     */
    private HomekitCharacteristicChangeCallback rotationSubscribeCallback = null;
    /**
     * 旋转速度回调
     */
    private HomekitCharacteristicChangeCallback rotationSpeedSubscribeCallback = null;
    /**
     * 摆动模式回调
     */
    private HomekitCharacteristicChangeCallback swingModeSubscribeCallback = null;
    /**
     * 色相回调
     */
    private HomekitCharacteristicChangeCallback hueSubscribeCallback = null;
    /**
     * 饱和度回调
     */
    private HomekitCharacteristicChangeCallback saturationSubscribeCallback = null;

    public MockLightAndFan(int id, String name, String serialNumber, String model, String manufacturer, String firmwareRevision, String hardwareRevision) {
        this.id = id;
        this.name = name;
        this.serialNumber = serialNumber;
        this.model = model;
        this.manufacturer = manufacturer;
        this.firmwareRevision = firmwareRevision;
        this.hardwareRevision = hardwareRevision;

    }

    @Override
    public int getId() {
        return this.id;
    }

    @Override
    public CompletableFuture<String> getName() {
        return CompletableFuture.completedFuture(this.name);
    }

    /**
     * 触发
     */
    @Override
    public void identify() {
        logger.info("家庭APP 点击 识别按钮后触发:Light Fan " + this.name);
    }

    /**
     * 序列号
     */
    @Override
    public CompletableFuture<String> getSerialNumber() {
        return CompletableFuture.completedFuture(this.serialNumber);
    }

    /**
     * 型号
     */
    @Override
    public CompletableFuture<String> getModel() {
        return CompletableFuture.completedFuture(this.model);
    }

    /**
     * 生产厂商
     */
    @Override
    public CompletableFuture<String> getManufacturer() {
        return CompletableFuture.completedFuture(this.manufacturer);
    }

    /**
     * 固件版本号
     */
    @Override
    public CompletableFuture<String> getFirmwareRevision() {
        return CompletableFuture.completedFuture(this.firmwareRevision);
    }

    /**
     * 硬件版本号
     */
    @Override
    public CompletableFuture<String> getHardwareRevision() {
        return CompletableFuture.completedFuture(this.hardwareRevision);
    }

    /**
     * 获取灯的开关状态
     */
    @Override
    public CompletableFuture<Boolean> getLightbulbPowerState() {
        return CompletableFuture.completedFuture(this.powerState);
    }

    /**
     * 设置灯的开关装填
     */
    @Override
    public CompletableFuture<Void> setLightbulbPowerState(boolean powerState) throws Exception {
        this.powerState = powerState;
        if (lightSubscribeCallback != null) {
            lightSubscribeCallback.changed();
        }
        logger.info(this.name + " The lightbulb is now " + (powerState ? "on" : "off"));
        return CompletableFuture.completedFuture(null);
    }

    /**
     * 订阅等的开关状态
     */
    @Override
    public void subscribeLightbulbPowerState(HomekitCharacteristicChangeCallback callback) {
        this.lightSubscribeCallback = callback;
    }

    /**
     * 取消订阅灯的开关状态
     */
    @Override
    public void unsubscribeLightbulbPowerState() {
        this.lightSubscribeCallback = null;
    }

    @Override
    public CompletableFuture<Boolean> isActive() {
        return CompletableFuture.completedFuture(this.active);
    }

    @Override
    public CompletableFuture<Void> setActive(boolean state) throws Exception {
        this.active = state;
        if (this.fanSubscribeCallback != null) {
            fanSubscribeCallback.changed();
        }
        logger.info(this.name + " The fun is now " + (powerState ? "on" : "off"));
        return CompletableFuture.completedFuture(null);
    }

    @Override
    public void subscribeActive(HomekitCharacteristicChangeCallback callback) {
        this.fanSubscribeCallback = callback;
    }

    @Override
    public void unsubscribeActive() {
        this.fanSubscribeCallback = null;
    }

    /**
     * 获取服务
     */
    @Override
    public Collection<Service> getServices() {
        Collection<Service> services = new LinkedHashSet<>(FanAccessory.super.getServices());
        services.addAll(LightbulbAccessory.super.getServices());
        return services;
    }

    /**
     * 获取灯的主要服务
     */
    @Override
    public Service getPrimaryService() {
        return null;
    }


    /**
     * 获取亮度
     */
    @Override
    public CompletableFuture<Integer> getBrightness() {
        return CompletableFuture.completedFuture(this.brightness);
    }

    /**
     * 设置亮度
     */
    @Override
    public CompletableFuture<Void> setBrightness(Integer value) throws Exception {
        this.brightness = value;
        if (brightnessSubscribeCallback != null) {
            brightnessSubscribeCallback.changed();
        }
        logger.info(this.name + " The light  brightness is:" + value);
        return CompletableFuture.completedFuture(null);
    }


    /**
     * 订阅亮度
     */
    @Override
    public void subscribeBrightness(HomekitCharacteristicChangeCallback callback) {
        this.brightnessSubscribeCallback = callback;
    }

    /**
     * 取消订阅亮度
     */
    @Override
    public void unsubscribeBrightness() {
        this.brightnessSubscribeCallback = null;
    }

    /**
     * 获取旋转方向
     */
    @Override
    public CompletableFuture<RotationDirectionEnum> getRotationDirection() {
        return CompletableFuture.completedFuture(this.rotationDirectionEnum);
    }

    /**
     * 设置旋转方向
     */
    @Override
    public CompletableFuture<Void> setRotationDirection(RotationDirectionEnum direction) throws Exception {
        this.rotationDirectionEnum = direction;
        if (rotationSubscribeCallback != null) {
            rotationSubscribeCallback.changed();
        }
        logger.info(this.name + " The fan  rotation direction is:" + direction.getCode());
        return CompletableFuture.completedFuture(null);
    }

    /**
     * 订阅旋转方向
     */
    @Override
    public void subscribeRotationDirection(HomekitCharacteristicChangeCallback callback) {
        this.rotationSubscribeCallback = callback;
    }

    /**
     * 取消订阅旋转方向
     */
    @Override
    public void unsubscribeRotationDirection() {
        this.rotationSubscribeCallback = null;
    }

    /**
     * 获取旋转速度
     */
    @Override
    public CompletableFuture<Double> getRotationSpeed() {
        return CompletableFuture.completedFuture(this.rotationSpeed);
    }

    /**
     * 设置旋转速度
     */
    @Override
    public CompletableFuture<Void> setRotationSpeed(Double speed) throws Exception {
        this.rotationSpeed = speed;
        if (rotationSpeedSubscribeCallback != null) {
            rotationSpeedSubscribeCallback.changed();
        }
        logger.info(this.name + " The fan  rotation speed is:" + speed);
        return CompletableFuture.completedFuture(null);
    }

    /**
     * 订阅旋转速度
     */
    @Override
    public void subscribeRotationSpeed(HomekitCharacteristicChangeCallback callback) {
        this.rotationSpeedSubscribeCallback = callback;
    }

    /**
     * 取消订阅旋转速度
     */
    @Override
    public void unsubscribeRotationSpeed() {
        this.rotationSpeedSubscribeCallback = null;
    }

    /**
     * 获取摆动模式
     */
    @Override
    public CompletableFuture<SwingModeEnum> getSwingMode() {
        return CompletableFuture.completedFuture(this.swingModeEnum);
    }

    /**
     * 设置摆动模式
     */
    @Override
    public CompletableFuture<Void> setSwingMode(SwingModeEnum swingMode) {
        this.swingModeEnum = swingMode;
        if (swingModeSubscribeCallback != null) {
            swingModeSubscribeCallback.changed();
        }
        logger.info(this.name + " The fan  swing mode   is:" + swingMode.getCode());
        return CompletableFuture.completedFuture(null);
    }

    /**
     * 订阅摆动模式
     */
    @Override
    public void subscribeSwingMode(HomekitCharacteristicChangeCallback callback) {
        this.swingModeSubscribeCallback = callback;
    }

    /**
     * 取消摆动模式
     */
    @Override
    public void unsubscribeSwingMode() {
        this.swingModeSubscribeCallback = null;
    }

    @Override
    public CompletableFuture<Double> getHue() {
        return CompletableFuture.completedFuture(this.hue);
    }

    @Override
    public CompletableFuture<Void> setHue(Double value) throws Exception {
        this.hue = value;
        if (hueSubscribeCallback != null) {
            hueSubscribeCallback.changed();
        }
        logger.info(this.name + " The light  hue  is:" + value);
        return CompletableFuture.completedFuture(null);
    }

    @Override
    public void subscribeHue(HomekitCharacteristicChangeCallback callback) {
        this.hueSubscribeCallback = callback;
    }

    @Override
    public void unsubscribeHue() {
        this.hueSubscribeCallback = null;
    }

    @Override
    public CompletableFuture<Double> getSaturation() {
        return CompletableFuture.completedFuture(this.saturation);
    }

    @Override
    public CompletableFuture<Void> setSaturation(Double value) throws Exception {
        this.saturation = value;
        if (saturationSubscribeCallback != null) {
            saturationSubscribeCallback.changed();
        }
        logger.info(this.name + " The light  saturation  is:" + value);
        return CompletableFuture.completedFuture(null);
    }

    @Override
    public void subscribeSaturation(HomekitCharacteristicChangeCallback callback) {
        this.saturationSubscribeCallback = callback;
    }

    @Override
    public void unsubscribeSaturation() {
        this.saturationSubscribeCallback = null;
    }

    @Override
    public CompletableFuture<CurrentFanStateEnum> getCurrentFanState() {
        return CompletableFuture.completedFuture(this.currentFanStateEnum);
    }

    @Override
    public CompletableFuture<TargetFanStateEnum> getTargetFanState() {
        return CompletableFuture.completedFuture(this.targetFanStateEnum);
    }

    @Override
    public CompletableFuture<Void> setTargetFanState(TargetFanStateEnum targetState) {
        this.targetFanStateEnum = targetState;
        if (fanTargetSubscribeCallback != null) {
            fanTargetSubscribeCallback.changed();
        }
        logger.info(this.name + " The fan  target  is:" + targetState.getCode());
        return CompletableFuture.completedFuture(null);
    }

    @Override
    public void subscribeCurrentFanState(HomekitCharacteristicChangeCallback callback) {
        this.fanSubscribeCallback = callback;
    }

    @Override
    public void unsubscribeCurrentFanState() {
        this.fanSubscribeCallback = null;
    }


    @Override
    public void subscribeTargetFanState(HomekitCharacteristicChangeCallback callback) {
        this.fanTargetSubscribeCallback = callback;
    }

    @Override
    public void unsubscribeTargetFanState() {
        this.fanTargetSubscribeCallback = null;
    }
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值