メディア
連載
» 2020年03月30日 10時30分 公開

江端さんのDIY奮闘記 介護地獄に安らぎを与える“自力救済的IT”の作り方(最終回):走れ!ラズパイ 〜 迷走する自動車からあなたの親を救い出せ (4/10)

[江端智一,EE Times Japan]

「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.

RSSフィード

公式SNS

All material on this site Copyright © ITmedia, Inc. All Rights Reserved.
This site contains articles under license from AspenCore LLC.