1111 字
6 分钟
让小米设备支持支持苹果homekit

起因#

我现在主要用的设备是苹果的,用了一些智能家居设备,都使用苹果的home控制。现在我用的小米直流变频风扇1X,只能在米家上使用。像使用siri能控制但不好用,home也不能用。

实现思路#

通过万能的搜索,可以知道有homeassistant、homebridge这两个开源智能家居系统。去年我尝试使用homeassistant,看它的文档没搞明白我需要怎么做才能添加自定义组件。当然,我只是想用苹果home控制小米的设备,不想搞这么冗余的项目。 so,我就自己搞一个吧。 要实现苹果home控制小米的设备,需要搞定两部分

  1. 模拟出一个苹果的homekit设备
  2. 通过code可以控制小米的设备。

实现功能#

控制小米设备#

小米的智能家居设备,有一套miio的控制协议。通过这个规范,就可以方便的与设备通信。在github上我找到了现成的开源库python-miio,并支持1X风扇。

通过python-miio控制小米设备,需要有小米设备的ip、token。ip直接从路由器获得,获取token,我是用的charles抓了米家的包。

看python-miio项目的说明,直接实例一个风扇的对象,就可以通过api控制风扇。

小米1X风扇,对应的model类型是dmaker.fan.p5,我看源码上,Fan实例时有model参数,我就直接设置为了dmaker.fan.p5,以为它是自动根据这个类型使用FanP5的实例。结果一直提示user ack timeout,于是我就换了各种方法,cli命令。怎么都不行。最后是在cli命令上,看到有FanP5的类型,试了下,ok。才发现是我用错了。

展示下控制小米风扇的代码

from miio.fan import FanP5, OperationMode, MoveDirection


def test():
    fan = FanP5(ip="192.168.1.1", token="bdae31092379167c30228549fd53137d")
    print(fan.info())
    print(fan.status())
    fan.on()
    # 设置模式  自然风OperationMode.Nature  直吹风OperationMode.Normal
    print(fan.set_mode(OperationMode.Nature))

    # 设置风速 0-100
    print(fan.set_speed(50))
    # 设置摆头角度[30, 60, 90, 120, 140]
    print(fan.set_angle(60))
    # 设置打开摇头
    print(fan.set_oscillate(False))
    # 设置指示灯
    print(fan.set_led(False))
    # 设置提示音
    print(fan.set_buzzer(False))
    # 设置童锁
    print(fan.set_child_lock(False))
    # 定时关闭  分钟
    # print(fan.delay_off(99))
    # 向左、向右移动  -5、+5
    print(fan.set_rotate(MoveDirection.Left))
    print(fan.status())
test()

模拟苹果homekit设备#

同样也是在github找实现了HomeKit Accessory Protocol (HAP)的开源项目,我选用了HAP-python.

苹果的homekit预定义了各种设备,设备称作service,设备的功能称作characteristics。 设备信息、具体的功能,可以进HAP-python的项目资源查看。 我用的设备定义是Fanv2,有开关、调速、扇页旋转方向、摇头、童锁。 小米风扇1X的功能比苹果定义的风扇多,由于苹果的限制,我只能让苹果home控制风扇的主要功能。

使用HAP-python,需要继承Accessory,然后为各个功能设置使用的方法就可以。

class MiFan(Accessory):
    """代理小米风扇,实现homekit风扇设备"""

    category = CATEGORY_FAN

    def __init__(self, param, *args, **kwargs):
        super().__init__(*args, **kwargs)
        #初始化小米设备
        self.fan = FanP5(
            ip=param.host,
            token=param.token
        )

        mi_fan = self.add_preload_service('Fanv2',
                                          chars=['Name', 'Active', 'RotationSpeed', 'SwingMode', 'LockPhysicalControls',
                                                 'RotationDirection'])
        info = self.fan.info()
        logger.info(info)
        self.set_info_service(
            manufacturer='Xiaomi',
            model=info.model,
            firmware_revision=info.firmware_version,
            serial_number='1'
        )

        mi_fan.configure_char('Name', value='Mi Fan')
        # 设备开关
        mi_fan.configure_char('Active', setter_callback=self.set_power, getter_callback=self.get_power)
        # 风扇速度
        mi_fan.configure_char('RotationSpeed', setter_callback=self.set_speed, getter_callback=self.get_speed)
        # homekit定义是旋转方向,这个配置成直吹风\自然风
        mi_fan.configure_char('RotationDirection', setter_callback=self.set_mode, getter_callback=self.get_mode)
        # 是否摇头
        mi_fan.configure_char('SwingMode', setter_callback=self.set_oscillate, getter_callback=self.get_oscillate)
        # 童锁
        mi_fan.configure_char('LockPhysicalControls', setter_callback=self.set_child_lock,
                              getter_callback=self.get_child_lock)

    def set_mode(self, value):
        if value == 1:
            self.fan.set_mode(OperationMode.Nature)
        else:
            self.fan.set_mode(OperationMode.Normal)

    def get_mode(self):
        return int(self.fan.status().mode == OperationMode.Nature)

    def set_speed(self, value):
        self.fan.set_speed(value)

    def get_speed(self):
        return self.fan.status().speed

    def set_oscillate(self, value):
        self.fan.set_oscillate(bool(value))

    def get_oscillate(self):
        return int(self.fan.status().oscillate)

    def get_power(self):
        return int(self.fan.status().is_on)

    def set_power(self, value: int):
        if value == 1:
            self.fan.on()
        else:
            self.fan.off()

    def set_child_lock(self, value):
        self.fan.set_child_lock(bool(value))

    def get_child_lock(self):
        return int(self.fan.status().child_lock)

搞定实现后,加上启动类可以用了

def main(args):
    import logging
    import signal
    from pyhap.accessory_driver import AccessoryDriver

    logging.basicConfig(level=logging.INFO)

    driver = AccessoryDriver(port=51826)
    accessory = MiFan(args, driver, 'Fanv2')
    driver.add_accessory(accessory=accessory)

    signal.signal(signal.SIGTERM, driver.signal_handler)
    driver.start()

运行程序,会在终端上打印出homekit的二维码和数字码,用手机添加就ok。 同时会生成一个accessory.state文件。当你再次启动程序,会使用这个文件信息直接启动设备,不会在提示添加到home。需要重新添加设备时,删除accessory.state

效果#

主控制界面,可以开关风扇、调节速度。扇叶旋转方向被我设置成了小米风扇的直吹风、自然风模式。 -w288

摇头和童锁功能是正常的。 -w288

结尾#

我用python-miioHAP-python两个库搞定了苹果home控制小米的风扇,主要功能都可以用。 用到的代码,我放到了github上https://github.com/jianyun8023/homekit-diy

让小米设备支持支持苹果homekit
https://www.jianyun.run/posts/xiaomifan-support-homekit/
作者
唐长老日志
发布于
2020-06-06
许可协议
CC BY-NC-SA 4.0