起因#
租住的公寓提供的是美的空调,很遗憾,并不支持智能家居的接入。
隨著天氣轉熱,每天又陷入了開關空調的微妙瑣事之中。雖然大部分時間只是伸伸手,但依舊很打斷上網的衝浪體驗,加上這倒霉設備不能自動記住上次風速,所以開關後又要額外調節風速到自己喜歡的程度,對其不滿便更是與日俱增起來。
終於在上週,上了一天班回來發現出門忘記關掉,更是心疼起電費。於是看了下架子上充當旁路由吃灰的樹莓派,乾脆折騰一下。
額外材料#
- 紅外接收管(錄製遙控器紅外信號編碼)
- 紅外發射管(發射控制信號)
- 杜邦線(連接作用)
- 三極管(可選,看著說是可以增強信號,自己並沒有用到)
軟件安裝#
我的設備及系統信息如下:
ubuntu@ubuntu:~$ uname -a
Linux ubuntu 5.15.0-1027-raspi #29-Ubuntu SMP PREEMPT Mon Apr 3 10:12:21 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux
先安裝 lirc
相關的包
sudo apt update
sudo apt install lirc
安裝完畢後配置 config
,這一步教程大多給的是 /boot/config.txt
位置,但是自己這邊並非如此,注意甄別吧
sudo vi /boot/firmware/config.txt
# 追加如下內容
dtoverlay=gpio-ir,gpio_pin=18
dtoverlay=gpio-ir-tx,gpio_pin=17
修改 lirc 配置
sudo vi /etc/lirc/lirc_options.conf
# 修改之前
driver = devinput
device = auto
# 修改之後
driver = default
device = /dev/lirc0
操作完畢後,重啟樹莓派,令其生效。
硬件連接#
查詢資料可知,樹莓派的具體引腳定義如下:
紅外接收管連接#
把紅外接收管的凸起朝上,由左至右,引腳分別為數據,負極,正極。數據引腳用杜邦線連接到 GPIO18,負極接到地線,正極接到 3.3v 供電
紅外發射管連接#
發射管的情況如圖,長腳為正極,接入 GPIO17,短腳接入地線即可。
紅外信號相關#
信號記錄#
要控制空調,最初始的思路就是用紅外管模擬遙控器按鍵信號,然後編碼控制。
要模擬信號,我們需要先記錄之。在終端執行 mode2 -m -d /dev/lirc1
,就可以開始記錄。這時用遙控器按下一個按鍵,可以看到屏幕上類似下面的輸出:
ubuntu@ubuntu:/boot/firmware$ mode2 -m -d /dev/lirc1
Using driver default on device /dev/lirc1
Trying device: /dev/lirc1
Using device: /dev/lirc1
16777215
4475 4380 576 1580 574 497
579 1580 582 1576 571 500
584 498 576 1578 570 497
585 491 579 1575 580 498
580 496 580 1575 580 1585
570 496 581 1582 571 1575
583 493 586 1582 566 1582
573 1576 579 1575 579 1575
579 1581 573 497 579 1577
578 496 580 498 579 497
582 503 574 497 575 498
579 499 579 1575 578 498
580 498 584 491 580 497
578 498 578 507 579 1586
567 504 573 1581 588 1561
578 1575 583 1571 580 1576
579 1576 573 5184 4422 4409
573 1581 549 524 580 1577
576 1579 577 497 577 502
556 1600 553 568 505 543
558 1586 570 497 575 502
558 1597 550 1606 552 521
555 1629 549 1600 529 542
532 1605 552 1600 555 1624
530 1602 555 1625 550 1578
553 542 530 1627 554 520
530 545 533 545 528 546
533 542 549 529 531 547
533 1623 528 547 528 547
531 545 532 545 556 521
532 542 531 1631 528 545
529 1623 531 1624 532 1623
531 1625 555 1600 528 1626
532 5224 4401 4434 525 1627
529 1624 527 550 529 1626
522 545 531 1624 531 545
532 1624 530 545 532 1624
531 1624 531 545 532 545
532 1624 530 1630 525 545
531 545 531 548 529 545
531 546 531 547 532 552
535 538 531 548 530 544
532 545 529 548 530 543
532 547 532 549 529 547
531 538 531 553 523 546
531 546 530 546 534 543
530 546 532 553 528 543
534 543 531 546 530 1626
526 1624 530 1628 526 547
530 1625 536 1623 524 17394-pulse
捨棄掉第一行的 16777215
與最後面的 17394-pulse
,剩余的數據便是我們要記錄的當前按鍵代表的信息(比如我這裡是 制冷,24 度,風速 1%)。
配置編寫#
以此,我們便可以編寫自己的配置:
sudo vi sudo vi /etc/lirc/lircd.conf.d/aircon.lircd.conf
# 內容如下:
begin remote
name aircon // 設備名稱,可自行修改
flags RAW_CODES
eps 30
aeps 100
gap 19991
begin raw_codes
name c_25_1
4469 4386 578 1574 581 496 580 1577 577 1574 580 497 584 494 578 1574 580 497 580 497 580 1575 580 496 580 498 578 1575 580 1574 580 496 580 1575 579 1574 580 499 579 1575 579 1575 579 1575 580 1575 581 1573 580 1574 579 498 580 1577 577 498 579 497 582 508 570 493 580 497 581 496 579 1575 579 1575 580 498 579 497 580 496 556 521 579 498 581 496 580 497 579 497 580 1575 579 1577 578 1577 577 1575 579 1576 580 1576 580 5174 4449 4383 581 1574 555 520 579 1576 579 1577 577 501 577 496 579 1576 555 521 579 498 556 1598 580 499 578 497 556 1598 556 1599 555 521 556 1601 554 1599 579 501 554 1597 555 1599 579 1576 555 1599 578 1576 579 1575 556 521 579 1577 577 499 577 522 556 497 555 544 532 545 532 521 582 1575 554 1599 556 545 531 522 578 497 578 499 556 521 556 521 578 498 555 522 554 1600 578 1578 578 1576 554 1599 555 1623 531 1623 532 5201 4423 4408 555 1600 555 1623 531 545 531 1623 532 547 531 1622 555 521 532 1623 533 544 532 1623 532 1623 532 545 531 545 556 1599 531 1623 531 545 532 545 557 520 532 545 531 545 532 545 556 520 532 546 531 545 531 545 531 545 532 545 531 545 531 546 531 546 531 545 532 545 532 546 531 546 556 521 531 549 529 546 529 545 533 544 531 545 532 545 532 544 531 1623 532 1623 531 1629 526 546 531 1624 531 1623 531
name off
4465 4388 578 1574 580 496 583 1572 580 1577 580 496 578 497 580 1575 580 496 581 496 580 1574 581 496 581 496 581 1577 578 1574 581 502 574 1575 579 496 584 1571 581 1574 581 1574 581 1575 580 496 580 1575 580 1574 580 1575 583 494 582 495 580 497 580 498 578 1574 580 497 580 497 580 1575 580 1576 579 1574 580 498 582 495 580 497 581 497 580 499 578 497 581 497 578 497 582 1572 580 1576 581 1574 579 1576 579 1575 580 5179 4450 4382 579 1575 579 500 577 1575 580 1576 578 497 556 521 580 1577 580 496 580 497 579 1579 576 497 579 506 574 1576 552 1599 581 496 579 1576 579 498 581 1575 578 1579 576 1576 579 1577 578 498 579 1575 580 1575 579 1577 555 521 556 521 579 498 556 524 578 1576 577 498 579 498 556 1599 556 1599 579 1576 578 498 579 499 555 522 555 521 556 521 556 522 578 497 556 522 558 1597 579 1575 558 1597 555 1602 576 1576 579
end raw_codes
end remote
此處定義了兩條指令:c_25_1 代表制冷,25 度,1% 風速;off 代表關閉空調。
保存文件,繼續執行 sudo systemctl restart lircd
重啟服務,讓配置生效。
這時,就可以在終端中發送指令:irsend send_once aircon c_25_1
來完成空調的控制。
編碼理解#
無腦的拷貝編碼雖然方便,但是總有種霧里看花的不爽快感覺。那么嘗試著分析一下具體含義。
注意,下面內容均基於《美的 R05D/BG 型號遙控器電控功能規格書》分析而來,感興趣者請自行閱讀原文
具體結構#
由搜集的資料可知,在只涉及 溫度
,模式
,風力強弱(非具體風速)
的情況下,編碼的結構大致為
LAA'BB'CC'SLAA'BB'CC'Z
其中,我們可以設定:
L 為引導碼,對應信號 [4500, 4500]
S 為分隔碼,對應信號 [540, 5200]
Z 為結束標識,對應信號 550
A 為固定的識別碼,對應二進制序列 10110010
B 和風速有關,C 和溫度以及模式有關
並且,二進制序列和信號編碼存在互換關係:1 代表高電平,對應信號 [540, 1600]
,0 代表低電平,對應信號:[550, 550]
那麼經過換算,上面的 A(識別碼)便對應如此數據:540 1600 550 550 540 1600 540 1600 550 550 550 550 540 1600 550 550
。
數據整體分為兩段,除開引導碼和分隔碼,兩段數據的內容完全一致。每次按鍵,無論是為了調節風速還是溫度,均包含上述信息。
編碼區域關係#
注意,此處僅按照圖中規格對照,暫不討論實際情況
經由規格書可知大致存在如下關係:
那麼可以編寫數據 ABC
,內容如下:
10110010 10011111 01000000
可以對照翻譯為
固定識別碼 低風 制冷 24度
由於數據結構為 AA'BB'CC'
,其中 X' 代表 X 的反碼,那麼可擴充為:
10110010 01001101 10011111 01100000 01000000 10111111
按照上面的高低電平規則轉換,可以得到 LAA'BB'CC'
的規則如下:
4500 4500 540 1600 550 550 540 1600 540 1600 550 550 550 550 540 1600 550 550 550 550 550 550 540 1600 550 550 550 550 540 1600 540 1600 550 550 540 1600 550 550 540 1600 550 550 550 550 540 1600 540 1600 540 1600 540 1600 540 1600 550 550 550 550 540 1600 540 1600 550 550 550 550 550 550 550 550 550 550 550 550 550 550 540 1600 550 550 550 550 550 550 550 550 550 550 550 550 550 550 540 1600 550 550 540 1600 540 1600 540 1600 540 1600 540 1600 540 1600
按照結構繼續擴充,得到 LAA'BB'CC'SLAA'BB'CC'Z
的規則:
4500 4500 540 1600 550 550 540 1600 540 1600 550 550 550 550 540 1600 550 550 550 550 550 550 540 1600 550 550 550 550 540 1600 540 1600 550 550 540 1600 550 550 540 1600 550 550 550 550 540 1600 540 1600 540 1600 540 1600 540 1600 550 550 550 550 540 1600 540 1600 550 550 550 550 550 550 550 550 550 550 550 550 550 550 540 1600 550 550 550 550 550 550 550 550 550 550 550 550 550 550 540 1600 550 550 540 1600 540 5200 4500 4500 540 1600 540 1600 540 1600 540 1600 540 1600 550 550 540 1600 550 550 540 1600 540 1600 550 550 550 550 540 1600 550 550 550 550 550 550 540 1600 550 550 550 550 540 1600 540 1600 550 550 540 1600 550 550 540 1600 550 550 550 550 540 1600 540 1600 540 1600 540 1600 540 1600 550 550 550 550 540 1600 540 1600 550 550 550 550 550 550 550 550 550 550 550 550 550 550 540 1600 550 550 550 550 550 550 550 550 540 5200 4500 4500 550 550 550 550 550 550 540 1600 550 550 540 1600 540 1600 540 1600 540 1600 540 1600 540 1600 550
理解進階#
至此,理解了大致規則後,覺得可以編寫腳本自動生成各種組合了?
然而並不能。
查閱的資料發佈時間是 2008 年,距今已經 15 年了。雖然部分規則可以對照得上,但是很多細節已經有了區別。
其中最顯著的差異:我的空調是可以具體調節風速級別的,從 1% 到 100%。而在抓取了這部分的紅外編碼後,發現其格式如下:
LAA'BB'CC'SLAA'BB'CC'SLDEFNNGZ
可知,其數據長度從兩端擴展成三段,並且第三段不再為 AA'BB'CC'
這種互為反碼的結構。
於是,我使用 mode2 -m -d /dev/lirc1 | tee -a output.conf
命令,把同樣模式和溫度下,風速從 1 到 100 的編碼全部記錄,並用腳本轉換為二進制序列,尋找其中規律
可以直接說結論,對於額外的數據來說,大致有如下規律:
額外塊的第一行固定 11010101
第二行風速的百分比二進制(左側填充0
)
第三行固定填充 0
第四行只有在100
風速的時候為 00000010
,其餘時候填充 0
第五行固定填充 0
第六行自10
進制的214
開始填充,到255
變為從1
開始,到99
填充到56
,100
為特殊值,對應59
(實際均轉換為二進制表達)
由於我個人只需要
溫度
,模式
,風速
三個參數,加上資料的缺失,並未繼續深入研究。
至此,可以勉強編寫腳本來生成 1~100
風速,17~30
溫度,冷/熱
模式 的不同組合命令。
自動化探究#
雖然已經研究了很多,但是對實際需求的推進並不理想。畢竟,我需要的是足夠方便地控制空調,而不是研究如何山寨一個空調遙控器。
那麼繼續擴展思路:
API 調用#
要實現一定程度上的智能 / 自動化,最簡單,也最容易實現的方案就是暴露一個 API 接口,供給第三方服務調用之。
出於時間因素,這裡使用 golang 做一個盡可能最小化的實現:
// main.go 文件
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
var isON = false
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/aircon/:mode/:temp/:flow", func(c *gin.Context) {
mode := c.Params.ByName("mode")
temp := c.Params.ByName("temp")
flow := c.Params.ByName("flow")
cmd := mode + "_" + temp + "_" + flow
isON = true
IrsenAircon(cmd)
c.JSON(http.StatusOK, gin.H{"cmd": cmd})
})
r.GET("/aircon/off", func(c *gin.Context) {
IrsenAircon("off")
isON = false
c.JSON(http.StatusOK, gin.H{"cmd": "off"})
})
r.GET("/aircon/status", func(c *gin.Context) {
var result string
if isON {
result = "1"
} else {
result = "0"
}
c.String(http.StatusOK, result)
})
return r
}
func main() {
IrsenAircon("off")
r := setupRouter()
r.Run(":9997")
}
// aircon.go 文件
package main
import (
"fmt"
"log"
"os/exec"
)
func IrsenAircon(cmd string) {
cmdOutput, err := exec.Command("irsend", "send_once", "aircon", cmd).Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", cmdOutput)
}
將上述代碼編譯為樹莓派 4B 可運行的二進制:
export GOARCH=arm64
export GOROOT_BOOTSTRAP=/usr/local/go
export GOOS=linux
go build
將編譯好的二進制文件上傳到樹莓派即可。
這兩個文件實現的內容很簡單:在 9997 端口上暴露接口,通過 GET 請求拼接出不同的命令,傳遞給系統內的 irsend
二進制文件,發送對應的紅外信號。
須知,當發送了含有當前場景信息的信號時,空調會直接被開啟。所以並不存在單獨的開啟
命令,但是會存在固定的關閉
命令。
我的樹莓派的內網 IP 為 192.168.2.224
。那麼根據接口,有三組命令:
# 空調設置為 25度,制冷,風速1%
➜ Desktop curl http://192.168.2.224:9997/aircon/c/25/1
{"cmd":"c_25_1"}⏎
# 關閉空調
➜ Desktop curl http://192.168.2.224:9997/aircon/off
{"cmd":"off"}⏎
# 查詢狀態(0代表關閉,1代表開啟)
➜ Desktop curl http://192.168.2.224:9997/aircon/status
0⏎
空調無法向樹莓派回調自身的當前狀態,我們也無法主動探測其當前的運行信息,所以一旦發射信號的操作被執行,我們就認為是成功,並且更新內部維護的開關狀態。而為了近可能保持狀態的準確性,在啟動程序的時候先執行一次關閉空調的操作作為校準。
最後,使用 pm2 守護對應進程:
pm2 --name lirc-web start /mnt/sda/Downloads/lirc-web
# 按提示操作,設置自啟動
pm2 startup
sudo env PATH=$PATH:/home/ubuntu/.nvm/versions/node/v18.13.0/bin /home/ubuntu/.nvm/versions/node/v18.13.0/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu
「智能家居」#
雖然實現了編程接口,但是並不代表需求被全部解決。如果控制空調靠 在瀏覽器訪問 url
或者 命令行中發起 curl
,那還不如使用遙控器。
由於 macOS 和 iPadOS 上均自帶 家庭
App,可以進行智能家居的聯動控制,那麼只要將上面簡陋的接口接入 HomeKit
,就可以進一步提升便捷程度。
HomeBridge#
我們使用 HomeBridge
應用來完成這一步驟。其官網的描述如下:
Homebridge 允許您與不支持 HomeKit 的智能家居設備集成。有超過 2,000 個 Homebridge 插件,支持數千種不同的智能配件。
不廢話,直接 Docker-Compose
一把梭:
cd ~/docker/homebridge/
vi docker-compose.yml
# 編寫如下內容:
version: '3'
services:
homebridge:
image: oznu/homebridge:ubuntu
container_name: homebridge
restart: always
network_mode: host
environment:
- HOMEBRIDGE_CONFIG_UI_PORT=10000
volumes:
- homebridge:/homebridge
volumes:
homebridge:
保存後執行 docker-compose up -d
等待鏡像拉取完畢後,會自動開始運行。
此應用安裝和運行時需要訪問 NPM 倉庫,如果你的網絡存在問題,訪問失敗,會導致無法啟動。請自行配置代理或者嘗試換源等操作。不過即使服務啟動失敗,UI 的訪問是不受影響的,所以仍可以自行在瀏覽器內查詢日誌
稍等一會兒,訪問 http://<server ip>:10000
即可進入後台界面。
配置配件#
此處依舊按照最小化的需求場景去實現,即,將空調的複雜操作簡化至兩個 —— 開啟 & 關閉。其中開啟對應
25 度,制冷,風速 1%
,關閉為關閉空調。更複雜的實現請自行探索
要調用 HTTP 接口,我們需要安裝插件:homebridge-http
。
安裝完畢後在後台做如下配置:
{
"bridge": {
"name": "Homebridge",
"username": "1D:42:45:4B:E4:A4",
"port": 51177,
"pin": "XXX-XX-XXX",
"advertiser": "bonjour-hap"
},
"accessories": [
{
"accessory": "Http",
"name": "Media Aircon",
"switchHandling": "realtime",
"on_url": "http://192.168.2.224:9997/aircon/c/25/1",
"off_url": "http://192.168.2.224:9997/aircon/off",
"status_url": "http://192.168.2.224:9997/aircon/status"
}
],
"platforms": [
{
"name": "Config",
"port": 10000,
"platform": "config"
}
]
}
其中主要是 accessories
字段,新增對象。其中 on_url
off_url
status_url
分別代表開啟,關閉,查詢狀態所使用的接口。當然,插件支持更多參數,具體的可查詢其 GitHub 倉庫說明。
配置完畢後,重啟 HomeBridge 服務,打開家庭 App,掃描後台展示的二維碼即可發現設備。
此時可正常獲取狀態,並且進行開關操作。
語音控制#
那麼可不可以更進一步,比如躺在床上,不想抬手的時候,語音控制就非常有用了。
很簡單,使用 快捷指令
即可完成。
當然,由於我們提供了 HTTP API,根本不需要複雜的方法,直接調用即可:
開啟空調:
關閉空調:
至此,直接對 Siri 說出你的快捷指令名稱就可以使用了
思考與總結#
本來是計劃使用 Flutter 實現一套 App+Web 的前端應用來控制的。但是突然想起來蘋果系的生態,便中途改了需求。但是也導致手機上(Android)沒辦法很方便的操作。不過總的來說搭配 HomeKit 這一套還是比較舒服的,算是有失有得。
由於時間原因,以及晚上又雙叒叕去機場蹲著的緣故,代碼方面寫的不僅粗糙,且均遵循能跑就行的原則,後續至少要進一步做出優化。
目前來看,至少一下幾個方向需要改進:
-
API 層,加入鑒權體系,配合
Cloudflare-Tunnel
穿透至外網,達成真正的遠程控制,而不是局域網內部控制。 -
紅外編碼方面,完成更多常用組合,並且嘗試使用相關的 lib ,直接發送紅外編碼數據,而不是粗暴調用外部二進制。
-
HomeBridge 層,需要拓展更多的功能,並且搭配自動指令實現更精細化的控制,而不是現在這種當作一個燈泡去處理(笑。
-
定時任務,在指定的時間段內自動開啟 / 關閉:樹莓派本身部署了
青龍面板
,簡單寫寫腳本就可以完成。 -
外形修正:呃,工業風又不是不能用,實在沒這個天賦,就算了。
總的來說,雖然其中波折不少,最後的實現也是匆匆忙忙草草率率,但是意外地,體驗起來自身還算滿意,至少倒也不算是浪費了時間。