注目キーワード

【Raspberry Pi 】時々、連絡が途絶える貴方へ(Wi-Fiの通信監視と自動復旧!)

すけろく
うっ。
拙者ももはや、これまでか。。。
げんろく
どうした!?
気をしっかり持つのじゃ! 何があった?
すけろく
わしの相棒(ラズパイ)からの連絡がここしばらくない。。
嫌われたかのう(涙)。。。
げんろく
なんだ、そんなことか。
心配して損したのう。Rassbery Pi OS側のネットワークが
無効になったりすることもあるぞ。。
すけろく
・・・なに!
嫌われたのではないのだな!
げんろく
ネットワーク環境の監視ツールを手直ししたから、
これを定期実行させるといいぞ!
Raspberry Piを使っていると、たまに「ネットワークが無効になってる!?」ということありますよね~。
私ははずれを引いたのか、以前購入したRassbery Pi 3 Model B+でよく出ていました。
Raspberry Piは、ディスプレイやキーボードが無いような利用環境を想定することが多いので、勝手にネットワーク断されると困るんですよね~。
センサーなんかのデータをクラウドにアップロードする時なんか、「えっ、とれてない。。。」という悲惨な結末になります。
やはりIoTということを考えると、ネットワークの通信状態を定期的に確認し、ルータにつながらない場合に、怪しい箇所をチェックして再起動するようなツールって必須だと考えています。
(以前は、シェルで作って、cronで定期実行をしていました。)
しかし、この記事のカテゴリは、「開発環境(IDE):Python」です。
そうだ!
Pythonで作ってみよう!
”subprocess”というPythonからシェルコマンドを実行できるライブラリを使って、「ちょちょい、ちょいと。はい出来上がり!(笑)」という記事にしようと思ったんですが・・・
はまりました。
ええ、はまりました。
でもはまった結果、Raspberry Pi OSの仕組みをより深く知ることができたので良かったと思います。
ということで、今回は、「とりあえず定期的に動かして、Wi-Fiで通信断が起きていたら怪しい箇所を再起動(ブロック解除)する」ものができたのでご紹介したいと思います。
githubに公開していますので、良ければ使ってやってください。。。
GitHub

Contribute to karakuri-musha/iot_comm_mon development by cre…

【合わせて読みたい】、Raspberry Piをご利用の方向けの人気記事も、ぜひご覧ください!
関連記事

すけろく Raspberry PiやJetsonのシングルボードコンピュータを使っていると 必要になるmicroSDカード。 何を買えばよいのやら。。。 げんろく microSDカードには意外といろ[…]

関連記事

こんな悩みありませんか? 「Raspberry Pi はじめたいけど、何があれば遊べるの? いくらかかるの?」 そんなご質問に答えるべく、購入品リストをまとめてみました。 本記事は以下の記事「Raspberry Piのはじ[…]

 

通信断を検知しよう

Wi-Fiでクラウドサービスや他のパソコンの共有ドライブへの書き込み時に、「通信相手につながらない」場合、皆さんはどうしていますか?

そんなとき、インフラエンジニアをしていると頭の中では次のような図が出てきます。

どこが悪いのか、特定するために確認する順番としては、責任範囲を考慮して「自責」→「他責」の順番がよいです。

まず、「自分に悪いところはないかを確認しましょう」ということです。

自分の責任範囲外のものが通信断していた場合、修復することは不可能に近いです。その場合は、復旧することを願って待つか、問い合わせるしかないです。

通信断の判断基準とは?

自分から、相手へ通信ができるかを確認するコマンドが「ping」です。Windowsにもあります。

まずは、Raspberry Piからみて最初に通信する相手への通信を確認します。先ほどの図だと「Wi-Fiルータ」になります。

Wi-Fiルータは、Raspberry Piから見ると「デフォルトルート」の役割を持ちます。これは、「通信したい相手(クラウドサービスやパソコンなど)に連絡を取る場合、初めにどこに連絡すればよいか」の「どこ」になります。
(設定は手動でも行えます。)

Wi-Fiルータ(デフォルトルート)にも、当然IPアドレスはあるので、そのアドレスがRaspberry Pi上の「default route」に保存されています。次の図のイメージです。

Wi-Fiルータ(デフォルトルート)は、Raspberry Piにとって必ず通信しなければならない相手ですので、Raspberry PiからWi-Fiルータへの通信が正常に行えているかを確認することで、「自責範囲」の通信断が確認できます。

IPアドレスを確認する

IPアドレスは、通信を行う際に使用する機器の住所(アドレス)になります。これがないと通信ができません。最近はWi-Fiルータに接続した際に自動で割り振られることが多いので、意識することはないかもしれませんが、場合によっては、手動で割りつけてアドレスを固定することが必要になる場合があります。

IPアドレスには主にIPv4とIPv6の2つの方式がありますが、ここではIPv4を使った環境(一般的)を前提に記載します。


1
コマンドプロンプトを起動して、Raspberry Pi にSSHでログインします。

$ ssh "ユーザ名"@"ホスト名またはIPアドレス".local -p ”接続ポート番号"
入力例:
$ ssh pythonpi@raspi01.local -p 54321

※「ユーザ名」は前回設定した「pi」ではない管理者ユーザを指定します。
※「接続ポート番号」は前回設定したポート番号を指定します。


2
デフォルトルートのIPアドレスを確認します。

$ ip route | grep default | awk '{print$3}'
192.168.1.1  ←これがデフォルトルートのIPアドレス(※例示です。ご自身の環境によりアドレスは違います。)

3
念のためRaspberry Pi自身のIPアドレスも以下のコマンドで確認します。

$ ip -f inet -o addr show wlan0 | awk '{print$4}' | cut -d '/' -f 1
192.168.1.50 ←これがRaspberry PiのIPアドレス(※例示です。ご自身の環境によりアドレスは違います。)

4
「ping」を使ってルータと通信できるかを確認します。

$ ping -4 -c 1 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=4.34 ms
--- 192.168.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 4.342/4.342/4.342/0.000 ms

ちなみに「ping」に与えているオプションと値は以下の意味を持ちます。
-4 :IPv4を使ってください
-c 1 :pingを実行する回数は1回です。
192.168.1.1:通信先(この場合はDefault route)

これまで記載したのは成功の場合で、失敗の場合は以下のような出力例になります。

connect: Network is unreachable

ここで説明したコマンドを使えば、Raspberry PiとWi-Fiルータとの通信断を判断できますね。

次からは、通信断した場合にどうするか。対処方法になります。

 

「dhcpcd」と「rfkill」を確認する

Raspberry Piのネットワークが無効になっている場合に、よく確認するコマンドがあります。

「dhcpcd」と「rfkill」です。それぞれ説明していきます。

「dhcpcd」とは?

dhcpcd – DHCP クライアントデーモンと呼ばれるものです。インターネットの説明によると次のように説明されます。

DHCP(Dynamic Host Configuration Protocol)は、TCP/IPネットワークにおいてホストに設定情報を伝達するための仕組みです。 DHCPはクライアント・サーバ方式で、DHCPサーバがネットワークアドレスを動的に割り当て、設定情報とともにホストに送信します。

今回の環境でいうと、DHCPサーバは「Wi-Fiルータ」で、DHCPクライアントは「Raspberry Pi」になります。Wi-Fiに接続した時点で、Wi-FiルータからRaspberry Piへ自動的にIPアドレスが割り当てられる仕組みです。

多くの場合、この仕組みを使うことが多いのではないでしょうか。

つまりこの「dhcpcd 」が正常に動作していない場合、通信ができなくなっている可能性があるのです。

では、「dhcpcd」の状態を確認してみましょう。


1
コマンドプロンプトを起動して、Raspberry Pi にSSHでログインします。

$ ssh "ユーザ名"@"ホスト名またはIPアドレス".local -p ”接続ポート番号"
入力例:
$ ssh pythonpi@raspi01.local -p 54321

※「ユーザ名」は前回設定した「pi」ではない管理者ユーザを指定します。
※「接続ポート番号」は前回設定したポート番号を指定します。


2
以下のコマンドを入力し、デーモンの状況を出力します。

$ systemctl status dhcpcd
dhcpcd.service - dhcpcd on all interfaces
Loaded: loaded (/lib/systemd/system/dhcpcd.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2021-07-08 11:21:12 JST; 2min 21s ago
Process: 2484 ExecStart=/usr/lib/dhcpcd5/dhcpcd -q -b (code=exited, status=0/SUCCESS)
Main PID: 2486 (dhcpcd)
Tasks: 2 (limit: 4915)
CGroup: /system.slice/dhcpcd.service
├─2486 /sbin/dhcpcd -q -b
└─2513 wpa_supplicant -B -c/etc/wpa_supplicant/wpa_supplicant.conf -iwlan0 -xxxxxxx,wext
Jul 08 11:21:18 ’hostname’ dhcpcd[2486]: wlan0: connected to Access Point `Wi-FiのSSIDが表示されます。'
Jul 08 11:21:18 ’hostname’ dhcpcd[2486]: DUID xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
Jul 08 11:21:18 ’hostname’ dhcpcd[2486]: wlan0: IAID xx:xx:xx:xx
Jul 08 11:21:18 ’hostname’ dhcpcd[2486]: wlan0: adding address xxxx::xxxx::xxxx::xxxx::xxxx ←IPv6のアドレス
Jul 08 11:21:18 ’hostname’ dhcpcd[2486]: wlan0: soliciting an IPv6 router
Jul 08 11:21:18 ’hostname’ dhcpcd[2486]: wlan0: rebinding lease of 192.168.1.50 ←IPv4のアドレス
Jul 08 11:21:18 ’hostname’ dhcpcd[2486]: wlan0: leased 192.168.1.50 for 86400 seconds
Jul 08 11:21:18 ’hostname’ dhcpcd[2486]: wlan0: adding route to 192.168.1.0/24
Jul 08 11:21:18 ’hostname’ dhcpcd[2486]: wlan0: adding default route via 192.168.1.1 ←default routeのIPアドレス
Jul 08 11:21:31 ’hostname’ dhcpcd[2486]: wlan0: no IPv6 Routers available

これまで説明したIPアドレスなどがいろいろと出てきます。

ちなみにこのコマンドの出力内容にはセキュリティ的に注意しなければならない情報も含まれていますので、ご自身の情報を扱う場合は注意してください。

重要なのは、3行目の「Active: active (running) since Thu 2021-07-08 11:21:12 JST; 2min 21s ago」の部分でいつ頃からこのサービスがActiveになっているのかを示しています。
この状態が「Active」になっていればOKということです。

「dhcpcd」が停止している場合には、以下のような出力結果が出ます。「Active: inactive (dead)」になっています。

dhcpcd.service - dhcpcd on all interfaces
Loaded: loaded (/lib/systemd/system/dhcpcd.service; enabled; vendor preset: enabled)
Active: inactive (dead) since Thu 2021-07-08 11:37:08 JST; 3s ago
~以下省略~

 

「rfkill」とは?

ネットワーク関係のコマンドにもう一つ、rfkillというものがあります。インターネットの説明によると次のように説明されます。

RFKill は、コンピュータシステムの無線送信器が、クエリ、アクティベート、非アクティブ化されるインターフェースを提供する Linux カーネルのサブシステムです。無線送信器が非アクティブ化されると、それらはソフトウェアが再アクティベートできる状態 (ソフトブロック) に置かれるか、またはソフトウェアが再アクティベートできない状態 (ハードブロック) に置かれます。

補足するとRaspberry Piに実装されているWi-FiとBluetoothの無線送信機に対して、アクティブと非アクティブの2つの状態に変更することができるコマンドになります。

アクティブ状態 :無線送信機がブロック解除されている状態
非アクティブ状態:無線送信機がブロックされている状態

というものになります。アクティブ状態の場合のみWi-FiやBluetoothが使用できます。

それでは、「rfkill」の状態を確認してみましょう。


1
コマンドプロンプトを起動して、Raspberry Pi にSSHでログインします。

$ ssh "ユーザ名"@"ホスト名またはIPアドレス".local -p ”接続ポート番号"
入力例:
$ ssh pythonpi@raspi01.local -p 54321

※「ユーザ名」は前回設定した「pi」ではない管理者ユーザを指定します。
※「接続ポート番号」は前回設定したポート番号を指定します。


2
以下のコマンドを入力し、状態を出力します。

$ rfkill list
0: phy0: Wireless LAN
 Soft blocked: no
 Hard blocked: no
1: hci0: Bluetooth
 Soft blocked: no
 Hard blocked: no

Raspberry Piで実行すると「0: phy0: Wireless LAN」と「1: hci0: Bluetooth」という2つのデバイスとして認識されていることがわかります。それぞれ頭についている数字「0 or 1」で各デバイスを指定できます。

また、それぞれのデバイスに対して以下の2つの状態が出力されています。

Soft blocked: no  :再アクティベーションできるブロック状態(例はnoなのでブロック解除状態)
Hard blocked: no :再アクティベーションできないブロック状態(例はnoなのでブロック解除状態)

Raspberry PiのWi-FiやBluetoothが無効化されている場合、「Soft blocked: yes」となっていることがあります。

0: phy0: Wireless LAN
 Soft blocked: yes
 Hard blocked: no
1: hci0: Bluetooth
 Soft blocked: yes
 Hard blocked: no

「rfkill」コマンドを使うことで状態を変更し、無効化状態を有効(ブロック解除)化できます。

以上で、Raspberry Piのネットワーク周りでよく設定を確認するものについて確認できました。

 

Pythonでシェルコマンドを実行できる「subprocess」

これまで、通信状況の確認と、主なネットワーク状態確認コマンドを見てきました。

本章では、確認した各コマンドをPythonから実行できる「subprocess」についてご紹介します。

「subprocess」 モジュールは、新しいプロセスの開始、入力/出力/エラーパイプの接続、リターンコードの取得を可能になっています。このモジュールは「os.system」や「os.spawn」などの古いモジュールや関数を置き換えることを目的としています。

主に使用する関数を次にまとめました。

関数 特徴 注意点
subprocess.run() subprocessのすべての用法を扱うことができる関数。
実行すると内部でPopenを使用しているとのこと。
Python3.5以降で追加
subprocess.Popen() run関数の内部処理に使われている下層の関数。
より柔軟な使い方ができる開発者向きのもの

使用例は次のとおり。

# Python 
import subprocess
cmd = 'ls -l'
res = subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
for line in res.stdout.splitlines(): print(line)
【出力】
total 3780
drwxr-xr-x 2 root root 4096 Jun 17 07:34 Bookshelf
drwxr-xr-x 2 admin admin 4096 Jun 29 16:50 Desktop
drwxr-xr-x 3 admin admin 4096 Jul 5 17:08 Documents
drwxr-xr-x 2 admin admin 4096 Jun 29 16:50 Downloads
~ 以下省略 ~

 

「import subprocess」でモジュールを読み込みます。
「cmd」という変数にシェルコマンド「ls -l」を格納し、「subprocess.run(cmd, **)」で実行しています。
実行結果は「res」という変数に格納され、次の「for」文で一行毎に出力「print」しています。「subprocess.run」のパラメータは非常に多いですが、例では次のものを指定しています。

パラメータ 説明
arg cmd(実行するコマンド内容)を与えます。
shell = True コマンドシェルのような記述方法(例えば「ls -l 」のような書き方)を許可します。
check= True 0以外のリターンコードの場合に例外 subprocess.CallProcessError がスローされます。エラー時の強制終了などに利用できます。
stdout=subprocess.PIPE 実行したサブ(子)プロセスの出力を親プロセスに渡す設定
stderr=subprocess.PIP 実行したサブ(子)プロセスのエラー出力を親プロセスに渡す設定
universal_newlines=True Python3系の場合はbytesとして結果が返ってきますが、このオプションをつけるとStr型で渡されます。

その他にも以下のようなパラメータがあります。

パラメータ 説明
stdin=password + ‘\n’ サブプロセスに渡すインプット情報です。sudo入りのコマンドを実行する場合は左のように管理者のパスワードと改行コードを指定します。
timeout=2 タイムアウトまでの時間を指定します。タイムアウトした場合はsubprocess.TimeoutExpiredがスローされます。

これで確認したネットワークのコマンドをPythonから実行できるようになりましたね。
最後に今回作ったツールの紹介と、使用方法を記載します。

 

ツールのご紹介と使い方

ネットワーク通信状況の確認と、エラーになった時の「dhcpcd」と「rmkill」での状態確認、そしてPythonでのこれらのコマンドの実行方法をご紹介しました。

その内容を使って以下の仕組みを持つツールを作成しました。

GitHubにて公開していますので、こちらもご覧ください。

GitHub

Contribute to karakuri-musha/iot_comm_mon development by cre…

 

開発環境

ツールの作成環境は次の通りです。

  • kernelバージョン:5.10.17-v7l+(Raspberry Pi Imager 1.6.2 のRaspberry Pi OS Desktopを使用)
  • Pythonバージョン:3.7.3

 

ツール提供方法

ツールは、GitHubにて公開していますので、そちらからどうぞ。
ライセンスはMITライセンスにしています。使用は自己責任でお願いしますね!

ファイルは次の3つあります。

ファイル名 形式 説明
IoT_comm_monitor.py .py ツール本体です。1つのファイルに詰め込んでいます。
IoTCNTconfig.json .json ツールの動作設定格納用のファイルです。json形式で編集してください。
IoT_pass_crypt.py .py ツールで使用する管理者パスワード暗号化ツールです。
「IoTCNTconfig.json」の「user_passphrase」に記載されたパスワードを暗号化し「user_passphrase_enc」というエントリとして保存します。

 

使用方法

GitHubのReadMeにも記載しています!合わせてご確認ください。

1
コマンドプロンプトを起動して、Raspberry Pi にSSHでログインします。

$ ssh "ユーザ名"@"ホスト名またはIPアドレス".local -p ”接続ポート番号"
入力例:
$ ssh pythonpi@raspi01.local -p 54321

※「ユーザ名」は前回設定した「pi」ではない管理者ユーザを指定します。
※「接続ポート番号」は前回設定したポート番号を指定します。


2
GitHubからプログラムをダウンロードしてください。
ダウンロード先の例:/opt/IoTCMT ユーザのホームでもよいかと思います。

$ sudo git clone https://github.com/karakuri-musha/iot_comm_mon.git

3
ダウンロード先にある環境設定ファイル「IoTCNTconfig.json」を編集します。
user_passphrase」に、sudoコマンドで使用している管理者パスワードを入力します。

$ sudo vi IoTCNTconfig.json
{
  "default_route_addr": "192.168.1.1",    ← default routeのIPアドレスを記載します
  "ping_retry_save_cnt": "8",         ← 通信断と判断するpingリトライ時のNG回数
  "ping_retry_max_cnt": "10",        ← pingのリトライ回数
  "auto_recovery": "True",           ← dhcpcd,rfkill設定の自動復旧の有効化
  "comm_retry_intarval": "30",         ← 設定変更のリトライ間隔(秒)
  "comm_retry_cnt": "2",           ← 設定変更のリトライ回数
  "comm_recovery_err_cmd": "",        ← 自動復旧ができない場合に実行するコマンド 
  "user_passphrase": "管理者パスワード"    ← ツール実行時の管理者アカウントのパスワード
}

4
「IoT_pass_crypt.py」を実行して、「IoTCNTconfig.json」に記載したパスワードを暗号化します。

$ sudo python3 IoT_pass_crypt.py IoTCNTconfig.json

5
「IoTCNTconfig.json」を確認し、パスワードが暗号化されていることを確認します。
ファイルは保存せずに終了します。

$ vi IoTCNTconfig.json
{
  "default_route_addr": "192.168.1.1",    
  "ping_retry_save_cnt": "8",        
  "ping_retry_max_cnt": "10",        
  "auto_recovery": "True",           
  "comm_retry_intarval": "30",         
  "comm_retry_cnt": "2",          
  "comm_recovery_err_cmd": "",        
  "user_passphrase_enc": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"    ← 暗号化された内容が入っています。
}

6
「IoT_comm_monitor.py」を実行するシェルを記載します。
$ vi /usr/local/bin/comm_mon.sh
#!/bin/sh
sudo python3 /opt/IoTCMT/iot_comm_mon/IoT_comm_monitor.py /opt/IoTCMT/iot_comm_mon/IoTCNTconfig.json

7
シェルに実行権限を付与します。
$ sudo chmod +x /usr/local/bin/comm_mon.sh

8
「crontab」を編集して先ほどのシェルを実行するスケジュールを追加します。
$ sudo crontab -e

ファイルの最後に以下のスケジュールを記述し、保存します。
必要に応じて実行時の環境変数PATHや言語環境LANGの設定をしてください。

*/5 * * * * sh /usr/local/bin/comm_mon.sh
※上記のスケジュールは5分間隔で実行する場合です。ご自身の環境に合わせて設定ください。


「cron」を再起動します。

$ sudo /etc/init.d/cron restart

10
スケジュールで起動された後、ツールのディレクトリに「Log」ディレクトリが作成されます。
フォルダ内に実行時のログが保存されることを確認してください。

【ログ例】
2021-07-16 11:55:03,311@ __main__ [INFO] <module>: Input file is [/opt/IoTCMT/iot_comm_mon/IoTCNTconfig.json] I checked the configuration file. The process will start.
2021-07-16 11:55:03,345@ __main__ [INFO] <module>: The operating system is [Linux]
2021-07-16 11:55:03,378@ __main__ [INFO] <module>: The model name is [Raspberry Pi Zero W Rev 1.1]
2021-07-16 11:55:03,379@ __main__ [INFO] <module>: The automatic recovery function is [ enabled ].
2021-07-16 11:55:03,381@ __main__ [INFO] <module>: The network monitoring tool is running.
2021-07-16 11:55:03,560@ __main__ [INFO] <module>: Default route ping result [ 192.168.1.1: OK ]
2021-07-16 11:55:03,562@ __main__ [INFO] <module>: Exit the network monitoring tool.
※実行環境によっては、サービスがうまく再起動できないなど発生する可能性がありますので、ご自身の環境に合わせてプログラムや、実行設定、権限構成などを変えて使ってみてくださいね!
使い方については以上です。

編集後記

いかがだったでしょうか。
そういや昔作ったな。
ということを思い出して自分用に作ったプログラムです。うまく動かない場合は、ちょこっと直してくださいね。
少しは皆さんのお役に立てれば幸いです。
【合わせて読みたい】、Raspberry Piをご利用の方向けの人気記事も、ぜひご覧ください!
関連記事

すけろく Raspberry PiやJetsonのシングルボードコンピュータを使っていると 必要になるmicroSDカード。 何を買えばよいのやら。。。 げんろく microSDカードには意外といろ[…]

関連記事

こんな悩みありませんか? 「Raspberry Pi はじめたいけど、何があれば遊べるの? いくらかかるの?」 そんなご質問に答えるべく、購入品リストをまとめてみました。 本記事は以下の記事「Raspberry Piのはじ[…]