Arduino(ESP8266)开发HomeKit入门指南

JiaoKan 发布于 2022-06-24 3 次阅读


绪论

有了iPhone却没有 HomeKit配件?年年看Apple发布会眼馋那些HomeKit功能?家中不少老旧的电气不想扔?或者......口袋里头没几个子儿?
没关系,简简单单20块之内基本可以让你家的一个电气实现智能化并接入HomeKit
或许你以为我要讲home assistant或者home bridge了,这两个我有所研究,但是我个人更喜欢今天要介绍的方法,原因大概有以下几点:

  • 不需要一个中心服务器用来装管理软件
  • 不需要花大价钱买个树莓派啥的装管理软件
  • 不需要原有配件支持米家等平台,不论多老旧的设备都可以适用
  • 设备小巧,便于安装与隐藏

硬件部分

一枚基于ESP8266的单片机

ESP8266作为一款十分畅销也十分合适的芯片,自带2.4G无线连接支持,性能也足够,官方也适配了Arduino,而且市面上各种产品令人眼花缭乱,耳熟能详的如nodeMCU、WeMos之类的就有不少
这里推荐使用ESP-01(S)搭配淘宝、拼多多上常见的小底座(有继电器、RGB灯带、DHT11等种类,基本一搜就能找到),不过有个限制,那就是设备最好要支持单线控制,否则接线会变得十分复杂,焊接工作啥的也会很繁琐,若是一定要用多根线控制,最好还是采用别的一些开发版

传感器/控制器

上面一节中提到的小底座其实已经包含了本节的一些,但是如若你想要控制一些别的东西或者获取一些别的信息,比如用红外控制空调,那么还要去采购红外模块(这里有个注意点,一定要买大功率的活着自己搭一个放大电路,否则直接接上红外灯珠的话功率会太小,只能超近距离控制)

其它

面包线、焊接设备与材料啥的都准备好
当然,你还要有一台iPhone(大于等于5s)或者iPad

代码部分

本篇采用的是MixiaoxiaoArduino-HomeKit-ESP8266仓库,安装的话可以直接在Arduino的库管理器中安装,也可以在Release里面下载,版本上略有不同,不过问题不大,本篇使用的是1.2.0版本,即库管理器中下载的版本
你要是懒得钻研例程,可以看看以下的分析,要是你愿意自己研读例程也无妨看看,以下按照我自己整理的一个开关的例程讲述

配件定义

首先,在my_accessory.c中,我们需要定义配件的各种基本信息以及控制项目或者传感器项目(统称characteristic),当你修改代码制作属于自己的配件时首先要改动的便是这里
第一部分是ACCESSORY_INFORMATION,这里面都是些类似名称、版本之类的基本信息,可以自由修改
第二部分就是配件独有的属性,若要操控别的硬件的话需要参考HAP文档,来确定具体属性及其数据类型
具体的内容请阅读以下代码段:

homekit_characteristic_t cha_on = HOMEKIT_CHARACTERISTIC_(ON, false);
homekit_characteristic_t cha_name = HOMEKIT_CHARACTERISTIC_(NAME, "JKHome Smart Socket");

homekit_accessory_t *accessories[] = {
    HOMEKIT_ACCESSORY(.id = 1, .category = homekit_accessory_category_lightbulb, .services = (homekit_service_t*[]) {
        HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics = (homekit_characteristic_t*[])
        {
            HOMEKIT_CHARACTERISTIC(NAME, "JKHome Smart Socket"),
            HOMEKIT_CHARACTERISTIC(MANUFACTURER, "JKhome"),
            HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "202111181033"),
            HOMEKIT_CHARACTERISTIC(MODEL, "FEIJI"),
            HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, "1.0"),
            HOMEKIT_CHARACTERISTIC(IDENTIFY, my_accessory_identify),
            NULL
        }),
        HOMEKIT_SERVICE(LIGHTBULB, .primary = true, .characteristics = (homekit_characteristic_t*[])
        {
            &cha_on,
            &cha_name,
            NULL
        }),
        NULL
    }),
    NULL
};

第三部分可以设置配对时的密码

homekit_server_config_t accessory_config = {
    .accessories = accessories,
    .password = "200-00-907"
};

主程序

setup部分

这一部分分为对于配件的初始化以及对于HomeKit的初始化
对于配件的初始化包括一些类似I2C连接之类的操作,和别的项目中使用的一样
对于HomeKit的初始化基本就是设置配件属性控制函数,比如给“onoff”属性指定一个函数去控制
此外,这部分中还要设置好Wi-Fi的SSID与密码
具体的内容请阅读以下代码段:

extern "C" homekit_server_config_t accessory_config;
extern "C" homekit_characteristic_t cha_on;
const int switchPin = 0;
const char *ssid = "ssid";
const char *password = "password";

void setOnOff(const homekit_value_t v)
{
    cha_on.value.bool_value = v.bool_value;
    digitalWrite(switchPin, cha_on.value.bool_value ? LOW : HIGH);
}

void homekitSetup()
{
    cha_on.setter = setOnOff;
    arduino_homekit_setup(&accessory_config);
}

void setup()
{
    pinMode(switchPin, OUTPUT);
    digitalWrite(switchPin, LOW);
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.persistent(false);
    WiFi.disconnect(false);
    WiFi.setAutoReconnect(true);
    WiFi.begin(ssid, password);
    WiFi.setAutoConnect(true);
    delay(1000);
    homekitSetup();
}

loop部分

这部分主要负责传感器数据回传(这里请参见下文中的空调例程,此处没有)以及获取并执行指令(实质上是每次获取一下家庭应用中的设置并运行setter)
此外还负责输出监控信息等
具体的内容请阅读以下代码段:

static long long next_heap_millis = 0;

#define LOG_D(fmt, ...)   printf_P(PSTR(fmt "\n") , ##__VA_ARGS__);

void homekitLoop()
{
    arduino_homekit_loop();//运行一遍setter以及一些别的东西
    const uint32_t t = millis();
    if (t > next_heap_millis)
    {
        next_heap_millis = t + 5 * 1000;
        LOG_D("Free heap: %d, HomeKit clients: %d", ESP.getFreeHeap(), arduino_homekit_connected_clients_count());
    }
}

void loop()
{
    homekitLoop();
    delay(10);
}

其它例程

此处给出一个控制空调的例程以供参考
my_accessory.c:

#include <homekit/homekit.h>
#include <homekit/characteristics.h>

void my_accessory_identify(homekit_value_t _value)
{
    printf("accessory identify\n");
}

homekit_characteristic_t ACName = HOMEKIT_CHARACTERISTIC_(NAME, "JKHome Air Conditioner - Kelon");
homekit_characteristic_t currentHeatingCoolingState = HOMEKIT_CHARACTERISTIC_(CURRENT_HEATING_COOLING_STATE, 0);
homekit_characteristic_t targetHeatingCoolingState = HOMEKIT_CHARACTERISTIC_(TARGET_HEATING_COOLING_STATE, 0);
homekit_characteristic_t currentTemperature = HOMEKIT_CHARACTERISTIC_(CURRENT_TEMPERATURE, 26.0);
homekit_characteristic_t targetTemperature = HOMEKIT_CHARACTERISTIC_(TARGET_TEMPERATURE, 26.0);
homekit_characteristic_t temperatureDisplayUnit = HOMEKIT_CHARACTERISTIC_(TEMPERATURE_DISPLAY_UNITS, 0);
homekit_characteristic_t currentRelativeHumidity = HOMEKIT_CHARACTERISTIC_(CURRENT_RELATIVE_HUMIDITY, 50.0);

homekit_accessory_t *accessories[] = {
    HOMEKIT_ACCESSORY(.id = 1, .category = homekit_accessory_category_air_conditioner, .services = (homekit_service_t*[]) {
        HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics = (homekit_characteristic_t*[]) {
            HOMEKIT_CHARACTERISTIC(NAME, "JKHome Air Conditioner - Kelon"),
            HOMEKIT_CHARACTERISTIC(MANUFACTURER, "JKhome"),
            HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "202108270124"),
            HOMEKIT_CHARACTERISTIC(MODEL, "Kelon"),
            HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, "1.1"),
            HOMEKIT_CHARACTERISTIC(IDENTIFY, my_accessory_identify),
            NULL
        }),//Define the accessory.
        HOMEKIT_SERVICE(THERMOSTAT, .primary = true, .characteristics = (homekit_characteristic_t*[]) {
            &ACName,
            &currentHeatingCoolingState,
            &targetHeatingCoolingState,
            &currentTemperature,
            &targetTemperature,
            &temperatureDisplayUnit,
            &currentRelativeHumidity,
            NULL
        }),//Add a thermostat to this accessory.
        NULL
    }),
    NULL
};

homekit_server_config_t accessory_config = {
    .accessories = accessories,
    .password = "200-00-907"
};

KelonAirConditioner.ino:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <arduino_homekit_server.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <ir_Kelon.h>
#include <DHT.h>

extern "C" homekit_server_config_t accessory_config;
extern "C" homekit_characteristic_t currentHeatingCoolingState;
extern "C" homekit_characteristic_t targetHeatingCoolingState;
extern "C" homekit_characteristic_t currentTemperature;
extern "C" homekit_characteristic_t targetTemperature;
extern "C" homekit_characteristic_t temperatureDisplayUnit;
extern "C" homekit_characteristic_t currentRelativeHumidity;
static long long nextNotifyTime = 0;
const int IRPin = 3;//RX on ESP-01.
const int DHTPin = 2;//IO2 on ESP-01.
const char *ssid = "ssid";//Your WiFi SSID.
const char *password = "password";//Your WiFi Password, just ignore the encrypt method.

IRKelonAc AC(IRPin);
DHT DHTSensor(DHTPin, DHT11);//You can replace "DHT11" with "DHT21" and "DHT22", depend on your sensor.

void ACModeSetter(const homekit_value_t value)
{
    currentHeatingCoolingState.value.uint8_value = value.uint8_value;
    targetHeatingCoolingState.value.uint8_value = value.uint8_value;
    updateac();
}

void ACTemperatureSetter(const homekit_value_t value)
{
    targetTemperature.value.float_value = value.float_value;
    updateac();
}

void temperatureDisplayUnitSetter(const homekit_value_t value)
{
    temperatureDisplayUnit.value.uint8_value = value.uint8_value;
    homekitNotify();
}

void updateac()
{
    switch (targetHeatingCoolingState.value.uint8_value)
    {
        case 0:
            AC.ensurePower(false);
            break;
        case 1:
            AC.ensurePower(true);
            AC.setMode(kKelonModeHeat);
            break;
        case 2:
            AC.ensurePower(true);
            AC.setMode(kKelonModeCool);
            break;
        case 3:
            if (currentTemperature.value.float_value > targetTemperature.value.float_value + 1.0) AC.ensurePower(true), AC.setMode(kKelonModeCool);
            else if (currentTemperature.value.float_value < targetTemperature.value.float_value - 1.0) AC.ensurePower(true), AC.setMode(kKelonModeHeat);
            else AC.ensurePower(false);
            break;
    }
    AC.setTemp((int)round(targetTemperature.value.float_value));
    AC.setFan(kKelonFanMax);
    AC.send();
    homekitNotify();
}

void homekitNotify()
{
    currentTemperature.value.float_value = round(DHTSensor.readTemperature() * 10.0) / 10.0;
    currentRelativeHumidity.value.float_value = round(DHTSensor.readHumidity()) * 1.0;
    if (!(currentTemperature.value.float_value > 0 && currentTemperature.value.float_value < 100)) currentTemperature.value.float_value = 26.0;//如若不能给出正常的传感器数值,则会连接失败,下同
    if (!(currentRelativeHumidity.value.float_value > 0 || currentRelativeHumidity.value.float_value < 100)) currentRelativeHumidity.value.float_value = 50.0;
    homekit_characteristic_notify(&currentHeatingCoolingState, currentHeatingCoolingState.value);
    homekit_characteristic_notify(&targetHeatingCoolingState, targetHeatingCoolingState.value);
    homekit_characteristic_notify(&currentTemperature, currentTemperature.value);
    homekit_characteristic_notify(&targetTemperature, targetTemperature.value);
    homekit_characteristic_notify(&temperatureDisplayUnit, temperatureDisplayUnit.value);
    homekit_characteristic_notify(&currentRelativeHumidity, currentRelativeHumidity.value);
}

void homekitSetup()
{
    targetHeatingCoolingState.setter = ACModeSetter;
    targetTemperature.setter = ACTemperatureSetter;
    temperatureDisplayUnit.setter = temperatureDisplayUnitSetter;
    arduino_homekit_setup(&accessory_config);
}

void homekitLoop()
{
    arduino_homekit_loop();
    const uint32_t timer = millis();
    if (timer > nextNotifyTime)
    {
        nextNotifyTime = timer + 2 * 1000;
        homekitNotify();
    }
}

void setup()
{
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.persistent(false);
    WiFi.disconnect(false);
    WiFi.setAutoReconnect(true);
    WiFi.begin(ssid, password);
    WiFi.setAutoConnect(true);
    AC.begin();
    DHTSensor.begin();
    delay(1000);
    homekitSetup();
}

void loop()
{
    homekitLoop();
    delay(10);
}
此作者没有提供个人介绍。
最后更新于 2024-03-11