走れ!ラズパイ 〜 迷走する自動車からあなたの親を救い出せ:江端さんのDIY奮闘記 介護地獄に安らぎを与える“自力救済的IT”の作り方(最終回)(4/10 ページ)
認知症患者が、自動車を運転したままさまよい続ける、という問題は社会で大きくクローズアップされています。私も、父が存命だったころ、同じような出来事で人生最大級の恐怖を味わったことがあります。そこで今回、「ラズパイ」を自動車に積み込み、迷走する自動車から運転者(認知症患者)を救い出すためのシステムを構築してみました。
「SOS通知システム」を作る
さて、では、次に、(2)の「SOS通知」システムの作り方を説明します。
このシステムは、車内の無線LANシステムを前提としていますで、ラズパイをアクセスポイント(AP)化することが必要となります。
最初に、このAP化の方法について説明します。SIMカード(ここではイプシムSIM)が準備できない場合に備えて、ポケットWi-Fiやスマホのデザリングも使えるようにしておきます。
(1)ラズパイのAP化
ここでは説明用に、以下のMACアドレスとUSB Wi-Fiを想定した環境のラズパイを想定して説明します。
(A)wlan0, wlan1を固定する
USBのWi-Fiを刺すと、wlan0, wlan1が移動してしまうので、固定します(これ、本当に困りました)。事前にifconfigでMACアドレスを取得しておいて下さい。絶対に内蔵Wi-FiとUSB Wi-Fi を混同しないように注意して下さい。
/etc/udev/rules.d/70-persistent-net.rules
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="b8:27:eb:ec:9c:0b", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="wlan*", NAME="wlan0" SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="d0:37:45:30:71:23", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="wlan*", NAME="wlan1"
(B)wlan0のIPアドレスの設定
/etc/dhcpcd.conf
interface wlan0 static ip_address=192.168.101.1/24 static routers=192.168.101.1 static domain_name_servers=192.168.101.1
この後、sudo rebootで、再起動します
(C)isc-dhcp-serverのインストールと設定
/etc/dhcp/dhcpd.confを次の内容にして保存します。
default-lease-time 600; max-lease-time 7200; ddns-update-style none; authoritative; subnet 192.168.101.0 netmask 255.255.255.0 { range 192.168.101.10 192.168.101.200; option broadcast-address 192.168.101.255; option routers 192.168.101.1; default-lease-time 600; max-lease-time 7200; option domain-name-servers 8.8.8.8, 8.8.4.4; }
/etc/default/isc-dhcp-serverを編集します。
INTERFACESv4=""の箇所をINTERFACESv4="wlan0"に変更して保存します。
sudo rebootを実行し、再び再起動します。
(D)hostapdのインストールと設定
hostapdをインストールします。
$ sudo apt install hostapd -y
/etc/hostapd/hostapd.confを作成し、次の内容を記入して保存します。
ssidとwpa_passphraseの内容は、必要に応じて変更してください。ここでは「rp1」というSSIDにします。
interface=wlan0 driver=nl80211 ssid=rp1 hw_mode=g channel=6 macaddr_acl=0 auth_algs=1 ignore_broadcast_ssid=0 wpa=2 wpa_passphrase=password wpa_key_mgmt=WPA-PSK wpa_pairwise=CCMP wpa_group_rekey=86400 ieee80211n=1 wme_enabled=1
/etc/default/hostapdを開き、# DAEMON_CONF=""の箇所をDAEMON_CONF="/etc/hostapd/hostapd.conf"に変更して保存します。
hostapdを実行できるよう、次のコマンドを実施します。
$ sudo systemctl unmask hostapd
(E)DHCPの起動タイミングの変更
ネットワークよりも先にDHCPサーバが起動する問題を回避するため、待ち時間を設定します。
/etc/init.d/isc-dhcp-serverを編集します。
log_daemon_msg “Starting $DESC” “$NAME”の次の行にsleep 10を追加して保存します。
該当箇所は、次のようになります。
log_daemon_msg “Starting $DESC” “$NAME” sleep 10
(F)インターフェースの設定
/etc/network/interfaces.d/wlan0を作成し、次の内容を記入して保存します。
auto wlan0 allow-hotplug wlan0 iface wlan0 inet static wpa-conf /etc/wpa_supplicant/wpa_supplicant_wlan0.conf
/etc/network/interfaces.d/wlan1を作成し、次の内容を記入して保存します。
auto wlan1 allow-hotplug wlan1 iface wlan1 inet dhcp wpa-conf /etc/wpa_supplicant/wpa_supplicant_wlan1.conf wlan0用のWi-Fi接続設定を/etc/wpa_supplicant/wpa_supplicant_wlan0.confに作成します。 ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 country=JP network={ ssid="ssid" psk="password" key_mgmt=WPA-PSK }
wlan1用のWi-Fi接続設定を/etc/wpa_supplicant/wpa_supplicant_wlan1.confに作成します。
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 country=JP network={ ssid="iPhone" psk="9yniyg4p4cmh1" (これ私のiPhoneのデザリングのパスワード(デタラメ)) }
sudo rebootを実行し、OSを再起動します。
(G)稼働確認
ifconfigで、以下の(ような)内容が表示されればO.K.です。
----------
wlan0: flags=4163 mtu 1500
inet 192.168.101.1 netmask 255.255.255.0 broadcast 192.168.101.255
inet6 fe80::1392:b0b4:b56a:836b prefixlen 64 scopeid 0x20
ether b8:27:eb:ec:9c:0b txqueuelen 1000 (イーサネット)
RX packets 685 bytes 61030 (59.5 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 148 bytes 23267 (22.7 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
wlan1: flags=4163 mtu 1500
inet 172.20.10.4 netmask 255.255.255.240 broadcast 172.20.10.15
inet6 fe80::e4a8:f21f:a6fa:111f prefixlen 64 scopeid 0x20
ether d0:37:45:30:71:23 txqueuelen 1000 (イーサネット)
RX packets 28 bytes 4774 (4.6 KiB)
RX errors 0 dropped 30 overruns 0 frame 0
TX packets 50 bytes 8415 (8.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
----------
表示させたいタブレットで、「rp1」というSSIDのアクセスポイントが見えて、パスワード"password"で接続できれば成功です。
(H) wlan0(無線LAN) ←→wwan0(4GPi)でインターネットに出られるようにする
念のため、最初にリブートしておきます。
$ sudo reboot
以下、手動で以下のコマンドを入力して下さい。
$ sudo iptables -xnvL $ sudo iptables -F $ sudo iptables -X $ sudo iptables -P INPUT ACCEPT $ sudo iptables -P OUTPUT ACCEPT $ sudo iptables -P FORWARD ACCEPT $ sudo iptables -t nat -A POSTROUTING -o wwan0 -j MASQUERADE
iptables が起動時に設定されるようにしておきます。
$ sudo apt-get install iptables-persistent
インストール時に現在のiptablesをipv4,ipv6ともに保存するか聞かれるので、「YES」としておいて下さい。最期に$ sudo rebootして終了です。
さて、ここで、ようやく車内LANの準備が整いました。ここから、「SOS通知」システムを作ります。まず、実施イメージをこの動画で確認して下さい。
簡単に言うと、どのクライアントからでも表示ボタンを押すと、全部のクライアントが一斉に連動するというものです。実際には、私が自宅から表示内容を変えて、自動車の窓に張りつけたタブレットの表示を変更させます。
(2)(表示画面用)ディレクトリの作成
どこにどういう名前でもいいですが、ディレクトリを作ります。(ここではwstest4としました)
cd ~; mkdir wstest4; cd wstest4
(3)(表示画面用)htmlの作成
wstest4の中に、さらに、staticというディレクトリを作って、そこに表示画面のhtmlファイルを作ります。
cd ~/wstest4; mkdir staic; cd static
blue.html
<!DOCTYPE html> <html lang="ja"> <head> <link rel="stylesheet" type="text/css" href="blue-style.css"> <meta charset="utf-8"> </head> <body> <div class="central"> <div class="subtitle"> 移動中 </div> </div> </body> </html>
上記のファイルの一部を変更して、あと2つファイルを作成して下さい。
ファイル名 | 変更点1 | 変更点2 |
---|---|---|
yellow.html | blue-style.css → yellow-style.css | 移動中→迷走中 |
red.html | blue-style.css → red-style.css | 移動中→012-345-6789 に電話下さい |
blue-style.css
body { background: blue; color: white; overflow: hidden; } .central { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; width: 100%; font-size: 220px; font-weight: bold; -webkit-animation:blink 1.5s ease-in-out infinite alternate; -moz-animation:blink 1.5s ease-in-out infinite alternate; animation:blink 1.5s ease-in-out infinite alternate; } @-webkit-keyframes blink{ 0% {opacity:0;} 100% {opacity:1;} } @-moz-keyframes blink{ 0% {opacity:0;} 100% {opacity:1;} } @keyframes blink{ 0% {opacity:0;} 100% {opacity:1; }
上記のファイルの一部を変更して、あと2つファイルを作成して下さい
ファイル名 | 変更点1 |
---|---|
yellow-style.css | blue → yellow |
red-style.css | blue → red |
(4)(表示画面用)Webサーバプログラム(Go言語)の作成
$ cd ~/wstest4
とした後、以下のWebサーバプログラムを、main.goという名前で作成して下さい。
import ( "log" "net/http" ) func main() { http.Handle("/", http.FileServer(http.Dir("/home/pi/wstest4/static"))) if err := http.ListenAndServe(":8686", nil); err != nil { log.Fatal("ListenAndServe: ", err) } }
その後、go build main.go でビルドすると、main という実行ファイルができます。
(5)(連動表示制御用)ディレクトリの作成
どこにどういう名前でもいいですが、ディレクトリを作ります(ここではwstest33としました)
cd ~; mkdir wstest33; cd wstest33
(6)(連動表示制御用)Webサーバプログラム(Go言語)の作成
$ cd ~/wstest33
とした後、以下のWebサーバプログラムを、websocket-server.goという名前で作成して下さい。
import ( "log" "net/http" "github.com/gorilla/websocket" ) // WebSocket サーバーにつなぎにいくクライアント var clients = make(map[*websocket.Conn]bool) // クライアントから受け取るメッセージを格納 var broadcast = make(chan Message) // WebSocket 更新用 var upgrader = websocket.Upgrader{} // クライアントからは JSON 形式で受け取る type Message struct { Message string `json:message` } // クライアントのハンドラ func HandleClients(w http.ResponseWriter, r *http.Request) { // ゴルーチンで起動 go broadcastMessagesToClients() // websocket の状態を更新 websocket, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Fatal("error upgrading GET request to a websocket::", err) } // websocket を閉じる defer websocket.Close() clients[websocket] = true for { var message Message // メッセージ読み込み err := websocket.ReadJSON(&message) if err != nil { log.Printf("error occurred while reading message: %v", err) delete(clients, websocket) break } // メッセージを受け取る broadcast <- message } } func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "/home/pi/wstest33/index.html") }) http.HandleFunc("/chat", HandleClients) err := http.ListenAndServe(":8080", nil) if err != nil { log.Fatal("error starting http server::", err) return } } func broadcastMessagesToClients() { for { // メッセージ受け取り message := <-broadcast // クライアントの数だけループ for client := range clients { //書き込む err := client.WriteJSON(message) if err != nil { log.Printf("error occurred while writing message to client: %v", err) client.Close() delete(clients, client) } } } }
その後、$ go build websocket-server.go でビルドすると、websocket-server という実行ファイルができます。
(7)(連動表示制御用)htmlの作成
wstest4の中にindex.htmlを作成して下さい。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>WebSocket Server</title> </head> <body> <input id="Button1" type="button" value="移動中" onclick="onButtonClic k1();" /> <input id="Button2" type="button" value="迷走中" onclick="onButtonClick2();" /> <input id="Button3" type="button" value="緊急電話" onclick="onButtonClick3();" /> <p id="input"></p> <p id="input1"></p> <p id="input2"></p> <p id="input3"></p> <pre id="output"></pre> <iframe id="image_place" src="http://192.168.101.1:8686/red.html" width="100 %" height="700"></iframe> <script> var input1 = document.getElementById('input1'); var input2 = document.getElementById('input2'); var input3 = document.getElementById('input3'); var img = document.getElementById("image_place"); var output = document.getElementById('output'); // socketの変数は、connect()の外に出しておかないとボタンに届かない var socket; function connect() { // で、ここから connect()を括る socket = new WebSocket("ws://" + window.location.host + "/chat"); socket.onopen = function() { }; socket.onmessage = function(e) { var obj = JSON.parse(e.data); // JSONオブジェクトに変換 switch(obj.Message){ case '1': img.src = "http://192.168.101.1:8686/red.html"; break; case '2': img.src = "http://192.168.101.1:8686/yellow.html"; break; case '3': img.src = "http://192.168.101.1:8686/blue.html"; break; } } socket.onclose = function(e) { console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason); setTimeout(function() { connect(); }, 1000); }; } // で、ここから connect()を閉じる connect(); // で、ここでconnect()を起動する function onButtonClick1(){ input1.value = "1"; socket.send(JSON.stringify( { message: input1.value } )); input1.value = ""; }; function onButtonClick2(){ input2.value = "2"; socket.send(JSON.stringify( { message: input2.value } )); input2.value = ""; }; function onButtonClick3(){ input3.value = "3"; socket.send(JSON.stringify( { message: input3.value } )); input3.value = ""; }; </script> </body> </html>
(8)自動起動化
上記の2つのGoで作ったサーバを自動起動するようにしておきます。
正直、2つのWebサービスを作るのは美しくない(というか醜悪)ですが、もういろいろ考えるのが面倒なので、このまま強行します。
以下を/etc/systemd/system/kanban.serviceとして、保存して下さい。
[Unit] Description = kanban daemon [Service] ExecStart = /home/pi/wstest4/main Restart = always Type = simple [Install] WantedBy = multi-user.target
$ sudo systemctl enable kanban
でエントリして、
$ sudo systemctl start kanban
で、起動確認をして下さい。
次に、以下を/etc/systemd/system/kanban-cont.serviceとして、保存して下さい。
[Unit] Description = kanban-cont daemon [Service] ExecStart = /home/pi/wstest33/websocket-server Restart = always Type = simple [Install] WantedBy = multi-user.target
$ sudo systemctl enable kanban-cont
でエントリして、
$ sudo systemctl start kanban-cont
で、起動確認をして下さい。
起動確認できていれば、sudo rebootで再起動して下さい。
車載のタブレットおよび自宅のPCのWebブラウザから"http://211.158.177.150:8080"と入力して3つのボタンを押下してください。青、黄色、赤のSOSの画面が切り替わるはずです。
Copyright © ITmedia, Inc. All Rights Reserved.