Linux中國

Systemd 服務:響應變化

我有一個這樣的電腦棒(圖1),我把它用作通用伺服器。它很小且安靜,由於它是基於 x86 架構的,因此我為我的印表機安裝驅動沒有任何問題,而且這就是它大多數時候乾的事:與客廳的共享印表機和掃描儀通信。

一個英特爾電腦棒。歐元硬幣大小。

大多數時候它都是閑置的,尤其是當我們外出時,因此我認為用它作監視系統是個好主意。該設備沒有自帶的攝像頭,也不需要一直監視。我也不想手動啟動圖像捕獲,因為這樣就意味著在出門前必須通過 SSH 登錄,並在 shell 中編寫命令來啟動該進程。

因此,我以為應該這麼做:拿一個 USB 攝像頭,然後只需插入它即可自動啟動監視系統。如果這個電腦棒重啟後發現連接了攝像頭也啟動監視系統就更加分了。

在先前的文章中,我們看到 systemd 服務既可以手動啟動或停止,也可以在滿足某些條件時啟動或停止。這些條件不限於操作系統在啟動或關機時序中達到某種狀態,還可以在你插入新硬體或文件系統發生變化時進行。你可以通過將 Udev 規則與 systemd 服務結合起來實現。

有 Udev 支持的熱插拔

Udev 規則位於 /etc/udev/rules 目錄中,通常是由導致一個 動作 action 條件 conditions 賦值 assignments 的單行語句來描述。

有點神秘。讓我們再解釋一次:

通常,在 Udev 規則中,你會告訴 systemd 當設備連接時需要查看什麼信息。例如,你可能想檢查剛插入的設備的品牌和型號是否與你讓 Udev 等待的設備的品牌和型號相對應。這些就是前面提到的「條件」。

然後,你可能想要更改一些內容,以便以後可以方便使用該設備。例如,更改設備的讀寫許可權:如果插入 USB 印表機,你會希望用戶能夠從印表機讀取信息(用戶的列印應用程序需要知道其模型、製造商,以及是否準備好接受列印作業)並向其寫入內容,即發送要列印的內容。更改設備的讀寫許可權是通過你之前閱讀的「賦值」 之一完成的。

最後,你可能希望系統在滿足上述條件時執行某些動作,例如在插入某個外部硬碟時啟動備份程序以複製重要文件。這就是上面提到的「動作」的例子。

了解這些之後, 來看看以下幾點:

ACTION=="add", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="e207", 
SYMLINK+="mywebcam", TAG+="systemd", MODE="0666", ENV{SYSTEMD_WANTS}="webcam.service"

規則的第一部分,

ACTION=="add", SUBSYSTEM=="video4linux",  ATTRS{idVendor}=="03f0", 
ATTRS{idProduct}=="e207" [etc... ]

表明了執行你想讓系統執行的其他動作之前設備必須滿足的條件。設備必須被添加到(ACTION=="add")機器上,並且必須添加到 video4linux 子系統中。為了確保僅在插入正確的設備時才應用該規則,你必須確保 Udev 正確識別設備的製造商(ATTRS{idVendor}=="03f0")和型號(ATTRS{idProduct}=="e207")。

在本例中,我們討論的是這個設備(圖2):

這個試驗使用的是 HP 的攝像頭。

注意怎樣用 == 來表示這是一個邏輯操作。你應該像這樣閱讀上面的簡要規則:

如果添加了一個設備並且該設備由 video4linux 子系統控制,而且該設備的製造商編碼是 03f0,型號是 e207,那麼…

但是,你從哪裡獲取的這些信息?你在哪裡找到觸發事件的動作、製造商、型號等?你可要使用多個來源。你可以通過將攝像頭插入機器並運行 lsusb 來獲得 IdVendoridProduct

lsusb
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 003: ID 03f0:e207 Hewlett-Packard
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 04f2:b1bb Chicony Electronics Co., Ltd
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

我用的攝像頭是 HP 的,你在上面的列表中只能看到一個 HP 設備。ID 提供了製造商和型號,它們以冒號(:)分隔。如果你有同一製造商的多個設備,不確定哪個是哪個設備,請拔下攝像頭,再次運行 lsusb , 看看少了什麼。

或者…

拔下攝像頭,等待幾秒鐘,運行命令 udevadmin monitor --environment ,然後重新插入攝像頭。當你使用的是HP攝像頭時,你將看到:

udevadmin monitor --environment
UDEV  [35776.495221] add      /devices/pci0000:00/0000:00:1c.3/0000:04:00.0
    /usb3/3-1/3-1:1.0/input/input21/event11 (input) 
.MM_USBIFNUM=00 
ACTION=add 
BACKSPACE=guess 
DEVLINKS=/dev/input/by-path/pci-0000:04:00.0-usb-0:1:1.0-event 
     /dev/input/by-id/usb-Hewlett_Packard_HP_Webcam_HD_2300-event-if00 
DEVNAME=/dev/input/event11 
DEVPATH=/devices/pci0000:00/0000:00:1c.3/0000:04:00.0/
     usb3/3-1/3-1:1.0/input/input21/event11 
ID_BUS=usb 
ID_INPUT=1 
ID_INPUT_KEY=1 
ID_MODEL=HP_Webcam_HD_2300 
ID_MODEL_ENC=HPx20Webcamx20HDx202300 
ID_MODEL_ID=e207 
ID_PATH=pci-0000:04:00.0-usb-0:1:1.0 
ID_PATH_TAG=pci-0000_04_00_0-usb-0_1_1_0 
ID_REVISION=1020 
ID_SERIAL=Hewlett_Packard_HP_Webcam_HD_2300 
ID_TYPE=video 
ID_USB_DRIVER=uvcvideo 
ID_USB_INTERFACES=:0e0100:0e0200:010100:010200:030000: 
ID_USB_INTERFACE_NUM=00 
ID_VENDOR=Hewlett_Packard 
ID_VENDOR_ENC=Hewlettx20Packard 
ID_VENDOR_ID=03f0 
LIBINPUT_DEVICE_GROUP=3/3f0/e207:usb-0000:04:00.0-1/button 
MAJOR=13 
MINOR=75 
SEQNUM=3162 
SUBSYSTEM=input 
USEC_INITIALIZED=35776495065 
XKBLAYOUT=es 
XKBMODEL=pc105 
XKBOPTIONS= 
XKBVARIANT=

可能看起來有很多信息要處理,但是,看一下這個:列表前面的 ACTION 欄位, 它告訴你剛剛發生了什麼事件,即一個設備被添加到系統中。你還可以在其中幾行中看到設備名稱的拼寫,因此可以非常確定它就是你要找的設備。輸出里還顯示了製造商的ID(ID_VENDOR_ID = 03f0)和型號(ID_VENDOR_ID = 03f0)。

這為你提供了規則條件部分需要的四個值中的三個。你可能也會想到它還給了你第四個,因為還有一行這樣寫道:

SUBSYSTEM=input

小心!儘管 USB 攝像頭確實是提供輸入的設備(鍵盤和滑鼠也是),但它也屬於 usb 子系統和其他幾個子系統。這意味著你的攝像頭被添加到了多個子系統,並且看起來像多個設備。如果你選擇了錯誤的子系統,那麼你的規則可能無法按你期望的那樣工作,或者根本無法工作。

因此,第三件事就是檢查網路攝像頭被添加到的所有子系統,並選擇正確的那個。為此,請再次拔下攝像頭,然後運行:

ls /dev/video*

這將向你顯示連接到本機的所有視頻設備。如果你使用的是筆記本,大多數筆記本都帶有內置攝像頭,它可能會顯示為 /dev/video0 。重新插入攝像頭,然後再次運行 ls /dev/video*

現在,你應該看到多一個視頻設備(可能是/dev/video1)。

現在,你可以通過運行 udevadm info -a /dev/video1 找出它所屬的所有子系統:

udevadm info -a /dev/video1

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

 looking at device '/devices/pci0000:00/0000:00:1c.3/0000:04:00.0
  /usb3/3-1/3-1:1.0/video4linux/video1':
 KERNEL=="video1"
 SUBSYSTEM=="video4linux"
 DRIVER==""
 ATTR{dev_debug}=="0"
 ATTR{index}=="0"
 ATTR{name}=="HP Webcam HD 2300: HP Webcam HD"

[etc...]

輸出持續了相當長的時間,但是你感興趣的只是開頭的部分:SUBSYSTEM =="video4linux"。你可以將這行文本直接複製粘貼到你的規則中。輸出的其餘部分(為簡潔未顯示)為你提供了更多的信息,例如製造商和型號 ID,同樣是以你可以複製粘貼到你的規則中的格式。

現在,你有了識別設備的方式嗎,並明確了什麼事件應該觸發該動作,該對設備進行修改了。

規則的下一部分,SYMLINK+="mywebcam", TAG+="systemd", MODE="0666" 告訴 Udev 做三件事:首先,你要創建設備的符號鏈接(例如 /dev/video1/dev/mywebcam。這是因為你無法預測系統默認情況下會把那個設備叫什麼。當你擁有內置攝像頭並熱插拔一個新的時,內置攝像頭通常為 /dev/video0,而外部攝像頭通常為 /dev/video1。但是,如果你在插入外部 USB 攝像頭的情況下重啟計算機,則可能會相反,內部攝像頭可能會變成 /dev/video1 ,而外部攝像頭會變成 /dev/video0。這想告訴你的是,儘管你的圖像捕獲腳本(稍後將看到)總是需要指向外部攝像頭設備,但是你不能依賴它是 /dev/video0/dev/video1。為了解決這個問題,你告訴 Udev 創建一個符號鏈接,該鏈接在設備被添加到 video4linux 子系統的那一刻起就不會再變,你將使你的腳本指向該鏈接。

第二件事就是將 systemd 添加到與此規則關聯的 Udev 標記列表中。這告訴 Udev,該規則觸發的動作將由 systemd 管理,即它將是某種 systemd 服務。

注意在這個兩種情況下是如何使用 += 運算符的。這會將值添加到列表中,這意味著你可以向 SYMLINKTAG 添加多個值。

另一方面,MODE 值只能包含一個值(因此,你可以使用簡單的 = 賦值運算符)。MODE 的作用是告訴 Udev 誰可以讀或寫該設備。如果你熟悉 chmod(你讀到此文, 應該會熟悉),你就也會熟悉如何用數字表示許可權。這就是它的含義:0666 的含義是 「向所有人授予對設備的讀寫許可權」。

最後, ENV{SYSTEMD_WANTS}="webcam.service" 告訴 Udev 要運行什麼 systemd 服務。

將此規則保存到 /etc/udev/rules.d 目錄名為 90-webcam.rules(或類似的名稱)的文件中,你可以通過重啟機器或運行以下命令來載入它:

sudo udevadm control --reload-rules && udevadm trigger

最後的服務

Udev 規則觸發的服務非常簡單:

# webcam.service

[Service]
Type=simple
ExecStart=/home/[user name]/bin/checkimage.sh

基本上,它只是運行存儲在你個人 bin/ 中的 checkimage.sh 腳本並將其放到後台。這是你在先前的文章中看過的內容。它看起來似乎很小,但那只是因為它是被 Udev 規則調用的,你剛剛創建了一種特殊的 systemd 單元,稱為 device 單元。 恭喜。

至於 webcam.service 調用的 checkimage.sh 腳本,有幾種方法從攝像頭抓取圖像並將其與前一個圖像進行比較以檢查變化(這是 checkimage.sh 所做的事),但這是我的方法:

#!/bin/bash 
# This is the checkimage.sh script

mplayer -vo png -frames 1 tv:// -tv driver=v4l2:width=640:height=480:device=
    /dev/mywebcam &>/dev/null 
mv 00000001.png /home/[user name]/monitor/monitor.png 

while true 
do 
   mplayer -vo png -frames 1 tv:// -tv driver=v4l2:width=640:height=480:device=/dev/mywebcam &>/dev/null 
   mv 00000001.png /home/[user name]/monitor/temp.png 

   imagediff=`compare -metric mae /home/[user name]/monitor/monitor.png /home/[user name]
       /monitor/temp.png /home/[user name]/monitor/diff.png 2>&1 > /dev/null | cut -f 1 -d " "` 
   if [ `echo "$imagediff > 700.0" | bc` -eq 1 ] 
       then 
           mv /home/[user name]/monitor/temp.png /home/[user name]/monitor/monitor.png 
       fi 

   sleep 0.5 
done

首先使用MPlayer從攝像頭抓取一幀(00000001.png)。注意,我們怎樣將 mplayer 指向 Udev 規則中創建的 mywebcam 符號鏈接,而不是指向 video0video1。然後,將圖像傳輸到主目錄中的 monitor/ 目錄。然後執行一個無限循環,一次又一次地執行相同的操作,但還使用了Image Magick 的 compare 工具來查看最後捕獲的圖像與 monitor/ 目錄中已有的圖像之間是否存在差異。

如果圖像不同,則表示攝像頭的鏡框里某些東西動了。該腳本將新圖像覆蓋原始圖像,並繼續比較以等待更多變動。

插線

所有東西準備好後,當你插入攝像頭後,你的 Udev 規則將被觸發並啟動 webcam.servicewebcam.service 將在後台執行 checkimage.sh ,而 checkimage.sh 將開始每半秒拍一次照。你會感覺到,因為攝像頭的 LED 在每次拍照時將開始閃。

與往常一樣,如果出現問題,請運行:

systemctl status webcam.service

檢查你的服務和腳本正在做什麼。

接下來

你可能想知道:為什麼要覆蓋原始圖像?當然,系統檢測到任何動靜,你都想知道發生了什麼,對嗎?你是對的,但是如你在下一部分中將看到的那樣,將它們保持原樣,並使用另一種類型的 systemd 單元處理圖像將更好,更清晰和更簡單。

請期待下一篇。

via: https://www.linux.com/blog/intro-to-linux/2018/6/systemd-services-reacting-change

作者:Paul Brown 選題:lujun9972 譯者:messon007 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國