背景介紹
hi 大家好,我是三合,作為一個非著名懶人,每天上完班回到家,癱在沙發上一動都不想動,去開個燈我都嫌累,此時,智慧家居拯救了我,只需要在手機點點點,開關燈,空調,窗簾就都搞定了,一開始我用的是開源的home assistan,俗稱HA,搭配上hass-xiaomi-miot以及hap-python這幾個外掛,就可以將米家的智慧家居裝置接入蘋果的homekit生態,整體而言在蘋果手機上使用體驗非常好,但是有一個致命的問題,每隔一段時間,米家的裝置在homekit上就會失效,需要我重置,為此沒少被老婆埋怨這智慧家居怎麼這麼難用,然後ha又是python寫的,說句實話,我不太喜歡這種動態語言,各種奇怪用法,看得我雲裡霧裡,更談不上除錯了,此時我就在想,如果我用c#寫homeKit和米家的sdk,自己搞一個智慧家居,豈不美哉,畢竟有了原生api,那就有了無限可能。有了這個奇怪的想法後,我首先上github上搜尋了c#的智慧家居sdk,發現c#在智慧家居這個領域,幾乎一片空白,更談不上原生的sdk了,基本都是python的專案,即使有一些c#的,也需要搭配ha或者HAP-NodeJS使用, 沒辦法,我只能參考hass-xiaomi-miot這個專案移植了米家的sdk並命名為【MiHome.Net】,以及參考hap-python這個專案移植了蘋果homekit的sdk並命名為【Homekit.Net】,MiHome.Net還在為開源前作最後的程式碼修改,今天釋出的,正是蘋果homekit的sdk【Homekit.Net】,接下來,我將介紹他的用法。
HomeKit中的一些基本概念
HomeKit中每一個智慧家居稱為一個配件(Accessory),每個配件擁有多個服務(Services),每個服務又有多個特徵(characteristics),所有配件都有配件資訊服務,這個服務裡包含了2個特性,1名字,2韌體版本號,並且配件根據功能還有另外一些獨有的服務,我們以一個開關為例,開關本身就是一個配件,配件種類是switch,他就擁有一個服務叫Switch,這個服務下面,有一個特性叫on,也就是開關,我們給這個特性賦值true,就代表開,賦值false,就代表關。
Homekit.Net存在的意義
透過本依賴包,使用者可以透過程式碼模擬出各種各樣的智慧家居裝置,並新增到蘋果手機的家庭app中,這樣我們就能在手機上控制這些模擬的智慧家居裝置執行一些我們在程式碼裡配置好的操作,比如我們可以透過程式碼控制電腦開啟或者關閉某個應用,然後利用本庫封裝為一個開關,那麼我們就可以用家庭app中的這個模擬開關來控制應用了。有了原生api,大家就可以盡情的發揮想象力去搞事情了,比如DIY一個自動餵魚機?
Getting Started
Nuget
接下來我將演示如何使用【Homekit.Net】,你可以執行以下命令在你的專案中安裝 Homekit.Net 。
PM> Install-Package Homekit.Net
支援框架
net 6,net 8
示例
透過繼承類Accessory,我們就可以自定義一個自己的配件,在下面的示例中,我們定義一個開關,在建構函式中,我們載入一個名為Switch的服務,並且定義配件型別為開關,從switch服務中獲取on這個特性,透過操作on這個特性,我們就可以透過程式碼模擬開關狀態變化了,並且可以在蘋果手機的家庭app上看到開關狀態的變化。
public class Switch : Accessory
{
private bool IsOn;
private Timer timer;
public Characteristics CurrentOnCharacteristics { get; set; }
public event Action<object> OnChange;
public Switch(AccessoryDriver accessoryDriver, string name, int? aid = null) : base(accessoryDriver, name, aid)
{
//載入switch開關服務
var service = AddPreloadService("Switch");
//定義配件種類為開關
Category = Category.CATEGORY_SWITCH;
//從switch服務中獲取on這個特性
CurrentOnCharacteristics = service.GetCharacteristics("On");
//新增開關狀態被家庭app改變後的回撥函式
CurrentOnCharacteristics.SetValueCallback = (o =>
{
OnChange(o);
this.IsOn = (bool)o ;
});
//定義一個定時器,定時改變開關狀態,用來模擬開關狀態變化
//timer = new Timer(Test, default, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
}
public void Test(object? state)
{
var random = new Random();
var number = random.Next(1, 50);
var isOn = number % 2 == 0;
CurrentOnCharacteristics.SetValue(isOn);
timer.Change(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
}
}
接下來,我們再來一個示例,定義一個溫度感測器,在建構函式中,我們載入一個名為TemperatureSensor的服務,並且定義配件型別為感測器,從TemperatureSensor服務中獲取CurrentTemperature(當前溫度)這個特性,透過程式碼操作CurrentTemperature這個特性,我們就可以模擬溫度變化,並且在蘋果手機的家庭app上看到溫度變化了。
public class TemperatureSensor : Accessory
{
public Characteristics CurrentTemperatureCharacteristics { get; set; }
private Timer timer;
public TemperatureSensor(AccessoryDriver accessoryDriver, string name, CancellationToken token = default) : base(accessoryDriver, name)
{
//載入TemperatureSensor溫度服務
var service = AddPreloadService("TemperatureSensor");
//定義配件種類為感測器
Category = Category.CATEGORY_SENSOR;
//從TemperatureSensor服務中獲取CurrentTemperature(當前溫度)這個特性
CurrentTemperatureCharacteristics = service.GetCharacteristics("CurrentTemperature");
//設定溫度為1
CurrentTemperatureCharacteristics.SetValue(1);
//定義一個定時器,定時改變溫度,用來模擬溫度變化
timer = new Timer(Test, token, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
}
public void Test(object? state)
{
if (state is CancellationToken token && token.IsCancellationRequested)
{
return;
}
// Console.WriteLine(DateTime.Now+"觸發了定時任務");
var random = new Random();
var wd = random.Next(1, 50);
// Console.WriteLine($"設定溫度為{wd}度");
CurrentTemperatureCharacteristics.SetValue(wd);
timer.Change(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
}
}
更多的配件型別,歡迎大家自行解鎖,配件的所有服務和特徵彙總,可以在程式執行起來後,檢視Resources資料夾下的json檔案
配件定義結束以後,我們就要讓這個配件跑起來了,新建一個控制檯程式,程式碼如下:
internal class Program
{
private async static Task SingleAccessory()
{
var cts = new CancellationTokenSource();
//先定義驅動
var driver = new AccessoryDriver(port: 6555);
//定義配件
var switchAccessory1 = new Switch(driver, "switch開關");
//新增開關狀態被蘋果手機的家庭app改變後的回撥
switchAccessory1.OnChange += async (o) =>
{
Console.WriteLine("The switch state has changed.開關狀態變化了");
};
driver.AddAccessory(switchAccessory1);
await driver.StartAsync(cts.Token);
}
private async static Task MultipleAccessories()
{
var cts = new CancellationTokenSource();
//先定義驅動
var driver = new AccessoryDriver(port: 6554);
//定義閘道器
var bridge = new Bridge(driver, "閘道器");
//定義配件1開關
var switchAccessory1 = new Switch(driver, "開關switch");
bridge.AddAccessory(switchAccessory1);
//新增開關狀態被蘋果手機的家庭app改變後的回撥
switchAccessory1.OnChange += async (o) =>
{
Console.WriteLine("The switch state has changed.開關狀態變化了");
};
//定義配件2感測器
var temperatureSensor= new TemperatureSensor(driver, "感測器TemperatureSensor");
bridge.AddAccessory(temperatureSensor);
driver.AddAccessory(bridge);
await driver.StartAsync(cts.Token);
}
async static Task Main(string[] args)
{
//Test Multiple Accessories 測試單配件
await SingleAccessory();
//Test Multiple Accessories 測試多配件
//await MultipleAccessories();
}
}
以上這段程式碼分為2個部分,SingleAccessory單配件示例,和MultipleAccessories多配件示例,大體流程就是首先定義一個驅動,接著例項化之前定義的配件,並且把配件加入到驅動中,最後啟動驅動即可。啟動後效果如下圖,他會在控制檯上列印出一個二維碼,
接著我們使用蘋果手機的家庭app掃描這個二維碼(新增配件需要在同一個區域網中),即可新增我們程式碼中自定義的配件。
如果我們想在程式中定義多個配件,那麼參考MultipleAccessories方法,首先得定義一個閘道器,接著把我們定義的多個配件新增到閘道器裡,最後再啟動驅動。
開源地址,歡迎star
本專案基於MIT協議開源,地址為
https://github.com/TripleView/HomeKit.Net
同時感謝以下專案
-
HAP-Python
-
ZeroConfig
寫在最後
如果各位靚仔覺得這個專案不錯,歡迎一鍵三連(推薦,star,關注)