道阻且长

道阻且长

问君西游何时还,畏途巉岩不可攀。
twitter
github

基於樹莓派+紅外管的伪智能家居實現

起因#

租住的公寓提供的是美的空调,很遗憾,并不支持智能家居的接入。

隨著天氣轉熱,每天又陷入了開關空調的微妙瑣事之中。雖然大部分時間只是伸伸手,但依舊很打斷上網的衝浪體驗,加上這倒霉設備不能自動記住上次風速,所以開關後又要額外調節風速到自己喜歡的程度,對其不滿便更是與日俱增起來。

終於在上週,上了一天班回來發現出門忘記關掉,更是心疼起電費。於是看了下架子上充當旁路由吃灰的樹莓派,乾脆折騰一下。

額外材料#

  • 紅外接收管(錄製遙控器紅外信號編碼)
  • 紅外發射管(發射控制信號)
  • 杜邦線(連接作用)
  • 三極管(可選,看著說是可以增強信號,自己並沒有用到)

軟件安裝#

我的設備及系統信息如下:

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

操作完畢後,重啟樹莓派,令其生效。

硬件連接#

查詢資料可知,樹莓派的具體引腳定義如下:

image

紅外接收管連接#

image

把紅外接收管的凸起朝上,由左至右,引腳分別為數據,負極,正極。數據引腳用杜邦線連接到 GPIO18,負極接到地線,正極接到 3.3v 供電

紅外發射管連接#

image

發射管的情況如圖,長腳為正極,接入 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

數據整體分為兩段,除開引導碼和分隔碼,兩段數據的內容完全一致。每次按鍵,無論是為了調節風速還是溫度,均包含上述信息。

編碼區域關係#

注意,此處僅按照圖中規格對照,暫不討論實際情況

經由規格書可知大致存在如下關係:

image

那麼可以編寫數據 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填充到56100為特殊值,對應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,掃描後台展示的二維碼即可發現設備。

image

此時可正常獲取狀態,並且進行開關操作。

語音控制#

那麼可不可以更進一步,比如躺在床上,不想抬手的時候,語音控制就非常有用了。

很簡單,使用 快捷指令 即可完成。

當然,由於我們提供了 HTTP API,根本不需要複雜的方法,直接調用即可:

開啟空調:

image

關閉空調:

image

至此,直接對 Siri 說出你的快捷指令名稱就可以使用了

思考與總結#

本來是計劃使用 Flutter 實現一套 App+Web 的前端應用來控制的。但是突然想起來蘋果系的生態,便中途改了需求。但是也導致手機上(Android)沒辦法很方便的操作。不過總的來說搭配 HomeKit 這一套還是比較舒服的,算是有失有得。

由於時間原因,以及晚上又雙叒叕去機場蹲著的緣故,代碼方面寫的不僅粗糙,且均遵循能跑就行的原則,後續至少要進一步做出優化。

目前來看,至少一下幾個方向需要改進:

  • API 層,加入鑒權體系,配合 Cloudflare-Tunnel 穿透至外網,達成真正的遠程控制,而不是局域網內部控制。

  • 紅外編碼方面,完成更多常用組合,並且嘗試使用相關的 lib ,直接發送紅外編碼數據,而不是粗暴調用外部二進制。

  • HomeBridge 層,需要拓展更多的功能,並且搭配自動指令實現更精細化的控制,而不是現在這種當作一個燈泡去處理(笑。

  • 定時任務,在指定的時間段內自動開啟 / 關閉:樹莓派本身部署了青龍面板,簡單寫寫腳本就可以完成。

  • 外形修正:呃,工業風又不是不能用,實在沒這個天賦,就算了。

總的來說,雖然其中波折不少,最後的實現也是匆匆忙忙草草率率,但是意外地,體驗起來自身還算滿意,至少倒也不算是浪費了時間。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。