ラズパイ3で会議室の利用人数を監視する ～ラズパイセットアップ編～

# はじめに

いろいろなサイトを参考にさせていただきながらセットアップをしました。
参考にしたサイトは[末尾](#参考にさせていただいたサイト大感謝)に一覧にしました。
各種コマンド、スクリプトファイルなどは見やすさのために転記していますが、
参考にしたサイトがオリジナルであるものがほとんどです。
ありがたや...

# 3部作

本「ラズパイ3で会議室の利用人数を監視する」は3部作となっております。

* サーバー構築編
* Azureセットアップ編
* ラズパイセットアップ編 **(これです**)

# 用意するもの

以下のものを用意しました。
USBメモリーはSWAPにします。ヒートシンクは念のため。広角レンズは広い画角が欲しい場合に。

1. Raspberry Pi メインボード Bluetooth(R) Wi-Fi対応モデル Raspberry Pi 3 model B
1. カメラモジュール
1. microSDHCカード(16GB)
1. 電源アダプター (5V 2.5A)
1. USBメモリー(16GB)
1. ヒートシンク
1. セリア(100円ショップ)の広角レンズ

# セットアップ

## 基本的な環境設定

### OSイメージのダウンロード

https://www.raspberrypi.org/downloads/raspbian/
から最小構成である Raspbian Buster Lite をダウンロードします。

```
  PKG (as of 2020/01/20)                                SIZE
  ----------------------------------------------------  -------
  Raspbian Buster with desktop and recommended software 2541 MB
  Raspbian Buster with desktop                          1123 MB
  Raspbian Buster Lite                                   435 MB
  ----------------------------------------------------  -------
```

### OSイメージをmicroSDHCカードに焼く (PCを使って)

[Etcher](https://www.balena.io/etcher/)を使いました。
色々考えずにすんで便利！

### ラズパイに色々接続する

1. microSDHCカード(基板裏にスロットがあります)
2. カメラ  (フレキの向きを間違えないよう。繊細なので慎重に)
3. USBメモリ
4. USBキーボード ※セットアップ時のみ
5. HDMIケーブル + ディスプレイ ※セットアップ時のみ
6. ネットワークケーブル
7. CPUにヒートシンクを貼る
8. 電源(USB-Micro端子)

電源を接続するのは必ず最後にしましょう

### 初期パスワードでログイン

login    : pi
password : raspberry

` $ passwd `でパスワードは変更しておきましょう。

### proxy設定 (プロキシ内にいる場合)

プロキシ内にいる場合、いろいろ設定が必要なので、ここでまとめて書いておきます。
プロキシサーバが `http://proxy.xxxx:10080` であることを前提に書いています。

` $ sudo vi /etc/apt/apt.conf `

```shell
Acquire::http::proxy "http://proxy.xxxx:10080/";
Acquire::https::proxy "http://proxy.xxxx:10080/";
```
※行末のセミコロン(;)を忘れないように！

``` $ vi ~/.curlrc ```

```shell
proxy = "http://proxy.xxxx:10080"
```

` $ sudo vi /etc/wgetrc `

```shell
http_proxy=http://proxy.xxxx:10080/
https_proxy=http://proxy.xxxx:10080/
```

あとは、gitのproxyもあるのですが、gitをインストールした後に設定します。

### SSH有効化など、sudo raspi-config でやっておく事

` $ sudo raspi-config ` で設定画面を立ち上げます

#### 1. SSH有効化

5 Interfacing Options --> P2 SSH --> Would you like enable? ==> YES

#### 2. TimeZoneを東京(Tokyo)に変更

4 Localisation Options --> I2 Change Timezone --> Asia --> Tokyo

#### 3. Localeに ja_JP.EUC-JP と ja_JP.UTF-8 を追加 

4 Localisation Options --> I1 Change Locale --> ja_JP.EUC-JP EUC-JP/UTF-8にチェック --> default en_GB.UTF-8
※ロケールの生成にすこし時間がかかります

#### 4. キーボードレイアウトの変更 (JPキーボード)

4 Localisation Options --> I3 Change Keyboard Layout --> Generic 105-key PC (intl.) --> other
--> Japanese --> Japanese (OADG 109A) --> The default for... --> No Compose Key

### SSH接続テスト

` $ ifconfig -a `でIPアドレスを調べましょう。Windows/Linuxなどの別端末から

```shell
$ ssh -l pi xxx.xxx.xxx.xxx
```
で接続できればOK
Windowsであれば[Tera Term](https://ja.osdn.net/projects/ttssh2/)を使うのも便利です

### 時刻合わせ (apt-getなどが正常に動作するように)

以下のコマンドで google様から現在時刻をいただきます

```
$ sudo date -s "$(curl -s --head http://www.google.co.jp | grep ^Date | cut -b 7-)"
```

`.bashrc`にも設定してログインのたびに時刻を合わせるようにします

```
$ echo 'sudo date -s "$(curl -s --head http://www.google.co.jp | grep ^Date | cut -b 7-)"' >> .bashrc
```

### ソフトウェアアップデート

```shell
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get dist-upgrade
```
upgradeは時間がかかります

### 各種インストール

プロキシサーバが `http://proxy.xxxx:10080` であることを前提に書いています。
プロキシ環境ではない場合は、git configのproxy設定と、pipの `--proxy http://proxy.xxxx:10080` は不要です

```shell
$ sudo apt-get install git
$ sudo apt-get install samba
$ sudo apt-get install vim
$ sudo apt-get install python-dev
$ sudo apt-get install python-pip
$ sudo apt-get install libffi-dev
$ sudo apt-get install watchdog
$ sudo apt-get install imagemagick

  (以下、gitの設定)

$ git config --global http.proxy http://proxy.xxxx:10080
$ git config --global https.proxy http://proxy.xxxx:10080
$ git config --global user.email "(your mail address)"
$ git config --global user.name "(your name)"
$ git config --list

  (以下、Azureライブラリのインストール)

$ pip install --upgrade pip --proxy http://proxy.xxxx:10080
$ pip install --upgrade setuptools --proxy http://proxy.xxxx:10080

$ pip install cffi --proxy http://proxy.xxxx:10080

$ pip freeze
$ pip uninstall azure
$ pip install azure --proxy http://proxy.xxxx:10080
$ pip install azure-cosmosdb-table --proxy http://proxy.xxxx:10080
```

### samba設定

`$ sudo vim /etc/samba/smb.conf`

```shell
# pi home sharing
[pi]
path = /home/pi
read only = no
guest ok = no
force user = pi

# tmp directory sharing
[tmp]
path = /tmp
read only = yes
guest ok = no
force user = pi_guest
```

以下のコマンドで必要なユーザを作ります。
パスワードは適宜決めてください

```shell

$ sudo adduser pi_guest
$ sudo smbpasswd -a pi
$ sudo smbpasswd -a pi_guest

$ sudo systemctl enable smbd
$ sudo systemctl restart smbd
```

### watchdogを有効にする

`$ sudo vi /lib/systemd/system/watchdog.service`

```
[Install]
WantedBy=multi-user.target  <----- 追加する
```

`$ sudo update-rc.d watchdog enable`
`$ sudo modprobe bcm2835_wdt`
`$ sudo vi /etc/watchdog.conf`

```
max-load-1 = 24                 <----- コメント(行頭の#)を外す

watchdog-device = /dev/watchdog <----- コメント(行頭の#)を外す
watchdog-timeout = 10           <----- 追加する
```

### カメラを有効にする

`$ sudo raspi-config`から
5 Interfacing Options > P1 Camera > enabled? ==> YES

設定後リブートが必要です。

## darknet + YoloV2のセットアップ

### darknetのインストール

darknet本体をgithubから取得しビルドします。

```shell
$ cd
$ mkdir git

$ cd /home/pi/git
$ git clone https://github.com/pjreddie/darknet

$ cd darknet
$ make
```

重みファイルを持ってきます。

```shell
  $ wget http://pjreddie.com/media/files/yolov2.weights
```

### [テスト] 写真撮影

```shell
$ raspistill -w 600 -h 400 -o /tmp/test.jpg
```

### [テスト] 物体認識

```shell
$ cd /home/pi/git/darknet
$ ./darknet detect cfg/yolov2.cfg yolov2.weights /tmp/test.jpg -o /tmp/res
```
結果が /tmp/res.jpg に出力されます

## ラズパイのSDカードを守る(延命措置)

SDカードに繰り返し書き込みを行うと、意外に早くSDカードが死んでしまいます。
以下の4つの作業を行ってSDカードを延命します。
サーバなど常時電源ONでラズパイを運用する場合は必須だとおもいます。

(1) テンポラリ領域をtmpfs（Ramdisk上）にする
(2) ログ出力を減らす
(3) ログファイルの一次作成場所をtmpfs（Ramdisk上）にする
(4) swapを外付けUSBメモリに逃がす

### (1) テンポラリ領域をtmpfs（Ramdisk上）に設定する

`$ sudo vim /etc/fstab`で`fstab`をひらき、以下の2行を追加

```shell
tmpfs           /tmp            tmpfs   defaults,size=32m,noatime,mode=1777  0       0
tmpfs           /var/tmp        tmpfs   defaults,size=16m,noatime,mode=1777  0       0
```

ディスク上の /tmp, /var/tmp を消して再起動

```shell
$ sudo rm -rf /tmp
$ sudo rm -rf /var/tmp
$ sudo shutdown -r now
```

### (2) ログ出力を減らす

`$ sudo vim /etc/rsyslog.conf` で `rsyslog.conf` を編集して
不要そうなログを # でコメントアウトしたのち、リスタート。

```shell
$ sudo systemctl restart rsyslog
```

以下のサービスは既知の問題でエラーログを大量に出すので無効にする

```shell
$ sudo systemctl stop avahi-daemon
$ sudo systemctl disable avahi-daemon
```

### (3) ログファイルの一次作成場所をtmpfs（Ramdisk上）にする

#### 必要なスクリプト4つ

(i) セットアップ・スクリプト・ジェネレータ
(ii) セットアップ・スクリプト (1による自動生成)
(iii) バックアップ・スクリプト
(iv) init.d用スクリプト (2と3を呼び出す)

#### (i) セットアップ・スクリプト・ジェネレータ

`$ vi /home/pi/scripts/make-init-logfiles.sh`

```bash
#!/bin/sh

# 出力するスクリプトファイル
fScr="${HOME}/scripts/setup-logs.sh"

#ログファイル
stat --format='%a %U:%G %n' `find /var/log/ -maxdepth 1 -mindepth 1 -type f` | grep -v '.gz\|.log$\|1$\|.old' > ~/logFiles
cp -i ~/logFiles ~/logFiles.bak

#ログディレクトリ
stat --format='%a %U:%G %n' `find /var/log/ -maxdepth 1 -mindepth 1 -type d` > ~/logDirs
cp -i ~/logFiles ~/logDirs.bak

echo "#!/bin/sh" > $fScr
echo "\n### Make directories ###" >> $fScr

cat ~/logDirs | while read line
do
        echo "##" >> $fScr
        echo $line | awk '{print "mkdir -p " $3}' >> $fScr
        echo $line | awk '{print "chmod " $1 " " $3}' >> $fScr
        echo $line | awk '{print "chown " $2 " " $3}' >> $fScr
done
echo "\n### Make files ###" >> $fScr
cat ~/logFiles | while read line
do
        echo "##" >> $fScr
        echo $line | awk '{print "touch " $3}' >> $fScr
        echo $line | awk '{print "chmod " $1 " " $3}' >> $fScr
        echo $line | awk '{print "chown " $2 " " $3}' >> $fScr
done

# 実行権をつけておく
$ chmod +x $fScr
```

#### (ii) セットアップ・スクリプト

上記スクリプトを実行することで生成します。
後に手順を説明します。

#### (iii) バックアップ・スクリプト

`$ vi /home/pi/scripts/backup-logs.sh`

```bash
#!/bin/sh
LOG_DIR="/back/logs/"
LOG_FILE="${LOG_DIR}backup-history.txt"
sudo sh -c "date > $LOG_FILE"
sudo sh -c "rsync -av /var/log/* $LOG_DIR >> $LOG_FILE"
```

#### (iv) init.d用スクリプト

`$ vi /etc/init.d/setup-tempfs-logs`

```bash
#!/bin/sh
### BEGIN INIT INFO
# Provides:          setup-tempfs-logs
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Setup tempfs log files
### END INIT INFO
case "$1" in
  start|"")
    sh /home/pi/scripts/setup-logs.sh 2>&1 >/dev/null
    ;;
  stop)
    sh /home/pi/scripts/backup-logs.sh 2>&1 >/dev/null
    ;;
  *)
    echo "Usage: /etc/init.d/setup-tempfs-logs [start|stop]" >&2
    exit 3
    ;;
esac
```

#### 手順

Step 1) セットアップスクリプトを除く各種スクリプトを作成する
Step 2)セットアップ・スクリプト・ジェネレータの実行

```shell
$ sh /home/pi/scripts/make-init-logfiles.sh
```

Step 3) バックアップ・スクリプトを実行してバックアップ作成

```shell
$ sudo mkdir -p /back/logs
$ sh /home/pi/scripts/backup-logs.sh
$ ls /back/logs
```

step 4) init.d登録

古いコマンド (`insserv`があればこちらを利用)

```shell
$ sudo insserv -d setup-tempfs-logs
$ sudo ls -la /etc/rc*.d/*setup-tempfs-logs
```

新しいコマンド (`insserv`がなければこちらを利用)

```shell
$ sudo /lib/systemd/systemd-sysv-install enable setup-tempfs-logs
```

step 5) cron登録

`$ sudo crontab -e`

```text
0 * * * * /home/pi/scripts/backup-logs.sh
```

step 6) fstab更新

`$ sudo vim /etc/fstab`

```text
tmpfs           /var/log        tmpfs   defaults,size=32m,noatime,mode=0755  0       0
```

step 7) リブート

```shell
$ sudo rm -rf /var/log
$ sudo shutdown -r now
```

step 8) いろいろ確認

```shell
$ df /var/log
$ ls /back/logs
```

### (4) swapを外付けUSBメモリに逃がす

step 1) USBメモリをswap用にする

`-->`以降は分かりやすさのためのコメントです。(なので実際には打たない)

```shell

  $ lsblk                                   --> マウントポイントを知る ex) /dev/sda1
  $ umount /dev/sda1                        --> 念のためアンマウントする
  $ sudo mkfs.ext4 /dev/sda1                --> USBメモリをフォーマット
  $ sudo mkswap -c -v1 -L usbswap /dev/sda1 --> USBメモリをスワップに
  $ lsblk -f                                --> 確認
  $ sudo fdisk /dev/sda                     --> パーティションタイプを82に変更(注："sda1"ではなく"sda")
    p  --> パーティション一覧表示
    t  --> タイプ変更
    82 --> linux swap(82)を指定
    p  --> 表示(確認)
    w  --> 書き込み
```

step 2) fstabでファイルシステムに追加

`$ sudo vim /etc/fstab`

```text
LABEL=usbswap  swap  swap  default  0  0
```

step 3) リブート

```shell
$ sudo shutdown -r now
```

step 4) 既存のSWAPの解放と、USBメモリのSWAP指定

```shell
$ sudo swapoff --all
$ sudo swapon -L usbswap
$ swapon -s
```

step 5) SWAPの利用状況を確認する

`$ top`で KiB Swap: に全体の容量、使用容量、空き容量 が表示されます

```text
top - 12:11:01 up 12 min,  2 users,  load average: 1.00, 0.88, 0.52
Tasks: 134 total,   2 running, 132 sleeping,   0 stopped,   0 zombie
%Cpu(s): 25.3 us,  0.0 sy,  0.0 ni, 74.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:    880552 total,   848320 used,    32232 free,     4524 buffers
KiB Swap:  3949092 total,    17180 used,  3931912 free.    32944 cached Mem ★これ！★
```

## 監視システム起動

### 各種スクリプト

**Azure Table Storage にデータを投げるための補助スクリプト**

```python
import os
import sys
from azure.cosmosdb.table.tableservice import TableService
from azure.cosmosdb.table.models import Entity

# args[1] = pi_id
# args[2] = number (of persons)
# args[3] = time stamp (short format)
# args[4] = time stamp (long format)
# args[5] = source (last 2 octed of IP address)
# RowKey = timestamp (long format) + ':' + pi_id
args = sys.argv

# proxy
os.environ["https_proxy"] = "http://proxy.xxxx:10080"

# connect to Azure Table Service
table_service = TableService(account_name='(my storage service)', account_key='(my account key)')
table_service.create_table('(my table)')

# creat entity
task = { 'PartitionKey': 'log',
		 'RowKey': args[4] + ':' + args[1],
		 'pi_id': args[1],
		 'count': args[2],
		 'timestamp_short': args[3],
		 'timestamp_long': args[4],
		 'source': args[5] }

# insert the entity
table_service.insert_entity('(my table)', task)
```

**監視システム本体**

```bash
#!/bin/sh

# unique id for each Raspberry PI
pi_id=3

# swap
sudo swapoff --all
sudo swapon -L usbswap

# initialize loop flag
echo "1" > /home/pi/scripts/flag.txt
flag=`cat /home/pi/scripts/flag.txt`

# loop
while [ $flag -eq "1" ]
do
  # capture and person detection
  raspistill -w 608 -h 456 -o /tmp/cam.jpg
  cd /home/pi/git/darknet
  ./darknet detect cfg/yolov2.cfg yolov2.weights /tmp/cam.jpg -out /tmp/res > /tmp/result0.txt
  cat /tmp/result0.txt | grep person > /tmp/result1.txt

  res=`cat /tmp/result1.txt | wc -l`

  # timestamp
  long_ts=`stat /tmp/cam.jpg | grep Modify | sed -r "s/^Modify:\s*([0-9 :\-]+).*$/\1/"`
  short_ts=`stat /tmp/cam.jpg | grep Modify | sed -r "s/^Modify:\s*[0-9]{4}\-([0-9]{2})\-([0-9]{2})\s([0-9]{2}):([0-9]{2}).*$/\1\/\2 \3:\4/"`

  # source
  src=`ip a | grep "xx.xx" | sed -r "s/^\s*inet\s*[0-9]+\.[0-9]+\.([0-9]+\.[0-9]+).*$/\1/"`

  # call WebAPI
  curl -F "pi_id=${pi_id}" -F "number=$res" -F "short_timestamp=$short_ts" -F "long_timestamp=$long_ts" -F "source=$src" https://xxxx.herokuapp.com/faces/create

  # call Azure
  python /home/pi/scripts/azure_test.py ${pi_id} $res "$short_ts" "$long_ts" "$src"

  # jpg rotation (for debug)
  for cnt in 9 8 7 6 5 4 3 2 1
  do
    src=`expr ${cnt} - 1`
    mv -f /tmp/cam${src}.jpg /tmp/cam${cnt}.jpg
    mv -f /tmp/res${src}.jpg /tmp/res${cnt}.jpg
  done
  cp /tmp/cam.jpg /tmp/cam0.jpg
  cp /tmp/res.jpg /tmp/res0.jpg

  # check flag
  flag=`cat /home/pi/scripts/flag.txt`
done
```
 
### 定時起動、定時終了するように設定する

Step 1) serviceスクリプトを書く

`$vi /etc/systemd/system/person_detection.service`

```shell

[Unit]
#just what it does
Description= person detection service

[Service]
#not run by root, but by me
User=pi
#we assume the full service as active one the script was started
Type=simple
#where to find the executable
ExecStart=/home/pi/scripts/person_detection.sh
#what you want: make sure it always is running
Restart=always

[Install]
#which service wants this to run - default.target is just it is loaded by default
WantedBy=default.target
```

念のためモード変更

```shell
$ sudo chmod 777 person_detection.service
```

Step 2) serviceに登録し起動する

```shell
$ sudo systemctl enable person_detection.service
$ sudo systemctl start person_detection.service
```

Step 3) 確認

```shell
$ sudo systemctl status person_detection.service
```

```text
    ● person_detection.service - person detection service
       Loaded: loaded (/etc/systemd/system/person_detection.service; enabled)
       Active: active (running) since Thu 2020-01-16 18:54:48 JST; 6min ago
     Main PID: 17989 (person_detectio)
       CGroup: /system.slice/person_detection.service
               tq17989 /bin/sh /home/pi/scripts/person_detection.sh
               tq18380 /bin/sh /home/pi/scripts/person_detection.sh
               tq18381 ./darknet detect cfg/yolov2.cfg yolov2.weights /tmp/cam.jpg -out /tmp/res
               tq18382 grep person
               mq18383 wc -l
    (ログは略)
```

Step 4) crontab で定時起動、定時終了

`$ sudo crontabl -e`

```text
0 8 * * 1-5 systemctl start person_detection.service
0 21 * * 1-5 systemctl stop person_detection.service
```

以上で完了です
お疲れ様でした！！

# 参考にさせていただいたサイト(大感謝)

【構築編】RaspberryPi + 物体検出 で室内の人数を slack へ可視化
https://qiita.com/ryhoh/items/a9191d65214f42fb94d4

RaspberryPiにtiny-Yolov2を実装してリアルタイム画像検出を行ってみた
https://qiita.com/daiarg/items/2ef986732632af5a5133

Raspberry PiでDarknetを使う
https://qiita.com/Brutus/items/b898a4b0e28303f3edfa

Raspberry Pi 3を買ってみた、必要な物やOSのインストールなど
https://jyn.jp/raspberrypi3-getting-started/

Raspberry PiのSDカードが壊れた！寿命を延ばす方法 5+1選!【運用編を追加】
https://iot-plus.net/make/raspi/extend-sdcard-lifetime-5plus1/

【STEP-27】swap領域を別デバイスに移動してSSD最適化
https://www.fabshop.jp/%E3%80%90step-27%E3%80%91swap%E9%A0%98%E5%9F%9F%E3%82%92%E5%88%A5%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%81%AB%E7%A7%BB%E5%8B%95%E3%81%97%E3%81%A6ssd%E6%9C%80%E9%81%A9%E5%8C%96/

avahi-daemon constantly reporting "Invalid response packet from host"
https://bugs.launchpad.net/ubuntu/+source/avahi/+bug/1342400

永久ループのあるcronコマンドを実行するにはどうすればよいですか？
https://tutorialmore.com/questions-58529.htm

crontabの書き方
https://www.server-memo.net/tips/crontab.html

ラズパイにpipをインストールする
http://poohkids.com/raspberrypi/raspi_pipinstall/

ゼロからはじめるAzure 第2回 ストレージの種類と概要
https://news.mynavi.jp/article/zeroazure-2/

テーブルストレージを用いたWebアプリケーション
https://news.mynavi.jp/article/zeroazure-3/

テーブルストレージによる更新、削除、複雑な検索処理
https://news.mynavi.jp/article/zeroazure-4/

【Microsoft公式】
Python を使用して Azure Table Storage と Azure Cosmos DB Table API を使用する
https://docs.microsoft.com/ja-jp/azure/cosmos-db/table-storage-how-to-use-python

[GitHub] Azure/azure-cosmos-table-python
https://github.com/Azure/azure-cosmos-table-python/tree/master/azure-cosmosdb-table

YOLOを使って認識した結果のバウンディングボックスを構成する座標値とそれに対応したクラス名を取得したいです
https://teratail.com/questions/106933

ルーターもアップグレード完了
https://seasky.blue/weblog/index.php?e=2268

ubuntu18.04 - Ubuntu 18にinsservがありません
https://tutorialmore.com/questions-293762.htm

mkswap - システム管理コマンドの説明 - Linux コマンド集 一覧表
https://kazmax.zpp.jp/cmd/m/mkswap.8.html

Darknetのサンプルのソースを覗いてみた。(I looked into the sample source of Darknet.)
https://iwaki2009.blogspot.com/2017/06/darkneti-looked-into-sample-source-of.html

curlコマンドから HTTP POST する方法
https://qiita.com/letsspeak/items/8c7266742371699ab45e

ラズパイ2/3のスペック差
https://ja.wikipedia.org/wiki/Raspberry_Pi

Raspberry Pi 3B+のウオッチドッグタイマー（Watch Dog Timer:WDT）を有効化して自動再起動する方法
https://atooshi-note.com/raspberry-pi-watchdog/

[EOF]