Systemd 服務:響應變化

2020-05-12 00:11:00

我有一個這樣的電腦棒(圖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

lsusbBus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching HubBus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hubBus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hubBus 003 Device 003: ID 03f0:e207 Hewlett-PackardBus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hubBus 001 Device 003: ID 04f2:b1bb Chicony Electronics Co., LtdBus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching HubBus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

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

或者…

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

udevadmin monitor --environmentUDEV  [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/video1Udevadm info starts with the device specified by the devpath and thenwalks up the chain of parent devices. It prints for every devicefound, all possible attributes in the udev rules key format.A rule to match, can be composed by the attributes of the deviceand 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=simpleExecStart=/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 scriptmplayer -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 單元處理影象將更好,更清晰和更簡單。

請期待下一篇。