Raspberry PiでGrove Pi+スターターキットとカメラを使う初期設定

## 概要
ハッカソンでRaspberry Piとカメラ、Grove Pi+スターターキット、kintoneを活用することになり、Raspberry Piのセットアップと初期設定について調べました。
結果、OpenCVをPython3で使う設定とGrove Pi+設定でトラブルはありましたが、Raspberry Piとカメラ、Grove Pi+スターターキット、Python library to access kintone の設定ができました。

## 使用する機器について（2020/01/03 Amazonで調査）
最新のRaspberry Pi 4はGrove Piのサポートに含まれないため、今回はRaspberry Pi 3を使用しています。
そもそもRaspberry Pi 4は高価で、今回の用途にはそこまでのスペックが必要ないでしょう。

### 今回の調査で用いた機器（一部同等品含む）
Amazonで全て購入して20,408円前後で試すことができます。

__Raspberry Pi3 Model B ボード＆ケースセット 3ple Decker対応 (Clear)-Physical Computing Lab__（6,100円）
https://www.amazon.co.jp/dp/B01CSFZ4JG/
__SanDisk microSDHC ULTRA 16GB 80MB/s SDSQUNS-016G Class10__（465円）
https://www.amazon.co.jp/dp/B074B4P7KD/
__Grove Pi+ スターターキット 初心者向け Raspberry Pi A+,B,B+&2,3適用 CE認証__（6,100円）
https://www.amazon.co.jp/dp/B07H9PFWHW/
__US電源アダプタ オン/オフスイッチ ケーブル 5V 2.5A ラズベリーパイ3に対応 軽量 携帯便利__（464円）
https://www.amazon.co.jp/dp/B07CYNGG4C/
__カメラモジュール 感光チップOV5647センサー 5M画素 Raspberry Pi 1 2 3 Model B B A+対応__（780円）
https://www.amazon.co.jp/dp/B07G572B3R/

## Raspberry Piの設定

### OSのセットアップ
以下より最新のOSをダウンロードしました。
https://www.raspberrypi.org/downloads/raspbian/

使用したOSイメージは以下です。
__Raspbian Buster with desktop__
・Version: September 2019
・Release date: 2019-09-26
・Kernel version: 4.19

OSをSDカードにセットアップする方法などは省略します。
詳しく知りたい方は以下を参照ください。

__Raspberry Pi 初期設定 Windows（ @sigma7641 さん）__
https://qiita.com/sigma7641/items/995c7bb07eab408b9d0e
__Raspberry Pi 初期設定 Mac（ @skkojiko さん）__
https://qiita.com/skkojiko/items/a7e342a8ab53b409fe6a

### コマンドラインテキストエディタについて
作業はsshで接続し、コマンドラインで行います。
コマンドラインのテキストエディタはnanoで説明しますが、使い方は以下を参照ください。

__GNU nanoを使いこなす（ @snct_hu さん）__
https://qiita.com/snct_hu/items/971d512c26dd8b3a3b3c

### 固定IPの設定
先ず最初にsshで接続しやすくするため固定IPを設定します。

```bash
$ sudo nano /etc/dhcpcd.conf
（IPアドレスは適切な内容に変更して、以下の設定を追加）
interface eth0
static ip_address=192.168.0.111/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.1 8.8.8.8
```

無線LANの設定は以下を参照ください。
__Raspberry Piの無線LAN設定をコマンドラインで行う（ @mym さん）__
https://qiita.com/mym/items/468d2cdb30d756b6df24

### OSの基本設定
OSの基本設定はraspi-configを使って行います。

```bash
$ sudo raspi-config
```

今回は、以下を設定しています。
> 1 Change User Password
> 
> 4 Localisation Options 
>  -> I1 Change Locale -> ja_JP.UTF-8 UTF-8 
>  -> I2 Change Timezone -> Asia -> Tokyo
>  -> I3 Change Keyboard Layout -> 適宜
>  -> I4 Change Wi-fi Country -> JP Japan
> 
> 5 Interfacing Options
>  -> P1 Camera -> Enable
>  -> P4 SPI -> Enable
>  -> P5 I2C -> Enable
> 
> 8 Update

設定後はOSを最新の状態にUPDATEします。

```bash
$ sudo apt-get update
$ sudo apt-get upgrade
```

### Raspberry Pi カメラ設定
Raspberry Pi カメラの設定は以下を参照ください。

__Raspberry Pi カメラで写真・ビデオを撮影する__
https://iotdiyclub.net/raspberry-pi-using-camera-1/

基本的な設定完了後、PythonのOpenCV（画像編集ライブラリィ）用の設定を行います。

```bash
$ sudo modprobe bcm2835-v4l2
$ sudo nano /etc/modules

bcm2835-v4l2

$ sudo apt-get install libopencv-dev python-opencv
```

OpenCVの詳細については以下を参照ください。

__OpenCV__
https://opencv.org/
__Python版 OpenCVの基本__
https://cvtech.cc/py-opencv/
__画像処理入門講座 : OpenCVとPythonで始める画像処理__
https://postd.cc/image-processing-101/

Python3を使う場合は以下の設定を行います。

```bash
$ sudo apt-get install libhdf5-dev libhdf5-serial-dev libhdf5-103
$ sudo apt-get install libqtgui4 libqtwebkit4 libqt4-test python3-pyqt5
$ sudo apt-get install libatlas-base-dev
$ sudo apt-get install libjasper-dev
$ pip3 install opencv-python
```

OpenCVをPython3でそのまま利用すると import cv2 でエラーになります。
このエラーを回避するため、ロードするライブラリィを追加します。

```bash
$ nano .bashrc

export LD_PRELOAD=/usr/lib/arm-linux-gnueabihf/libatomic.so.1

$ source .bashrc
```

## Grove Pi+の設定
Grove Pi+のRaspberry Pi への取り付けは以下を参考にしてください。
__GrovePi+__
https://www.switch-science.com/catalog/2129/

Grove Pi+のソフトウェアの設定は以下を参考に行います。
__Setting Up The Software__
https://www.dexterindustries.com/GrovePi/get-started-with-the-grovepi/setting-software/

```
$ curl -kL dexterindustries.com/update_grovepi | bash
$ sudo reboot
$ cd /home/pi/Dexter
$ git clone https://github.com/DexterInd/GrovePi
$ cd /home/pi/Dexter/GrovePi/Script
$ sudo chmod +x install.sh
$ sudo ./install.sh
```

インストール後に問題なければi2cdetectコマンドで以下のようにI2Cの04ポートが確認できます。

```
$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- 04 -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
```

Grove Pi+設定後、OSやインストール済のソフト更新を行う apt-get upgrade でパッケージ依存関係に問題が発生しエラーになります。
このエラーを回避するため、以下を実行します。 

```
$ sudo apt-get --fix-broken upgrade
```

実際に動くか試験するため、以下を参考に付属のGrove LEDをGrove Pi+のD4ポートに配線します。
http://wiki.seeedstudio.com/Grove-Red_LED/#play-with-raspberry-pi-with-grovepi_plus
![sensor06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75892/dba0c98b-16e5-4c15-eae3-c0a2a5f483d6.png)

配線後に、以下のバージョン表示や、LEDの点滅を試験して問題がないか確認します。

```
$ python /home/pi/Dexter/GrovePi/Software/Python/grovepi.py
library supports this fw versions: 1.3.0

$ python /home/pi/Dexter/GrovePi/Software/Python/grove_led_blink.py
This example will blink a Grove LED connected to the GrovePi+ on the port labeled D4.
If you're having trouble seeing the LED blink, be sure to check the LED connection and the port number.
You may also try reversing the direction of the LED on the sensor.

Connect the LED to the port labele D4!
LED ON!
LED OFF!
LED ON!
LED OFF!
LED ON!
LED OFF!
```

## Grove Pi+のライブラリィ不具合

2020/02/02 時点で以下のGrove Pi+のライブラリィ 1.3.0 のファイルに不具合があり、多くのサンプルが動きません。
/home/pi/Dexter/GrovePi/Software/Python/grovepi.py
https://github.com/DexterInd/GrovePi/blob/master/Software/Python/grovepi.py


例えば grove_button.py は、0でボタンOFF、1でボタンONを表示するプログラムですが、実行すると以下の表示になります。

```bash
$ python /home/pi/Dexter/GrovePi/Software/Python/grove_button.py
255
255
255
255
255
255
```

__grovepi.pyのI2Cの読み込み部分に不具合があり、修正する必要があります。__
以下にgrovepi.pyの修正箇所を説明します。

227行目read_identified_i2c_block()while文が永久ループするため削除 

```grovepi.py
def read_identified_i2c_block(read_command_id, no_bytes):
	data = [-1]
	data = read_i2c_block(no_bytes + 1)
	return data
```

246行目analogRead()のnumber配列要素番号を修正

```grovepi.py
# Read analog value from Pin
def analogRead(pin):
	write_i2c_block(aRead_cmd + [pin, unused, unused])
	number = read_identified_i2c_block(aRead_cmd, no_bytes = 2)
	return number[1] * 256 + number[2]
```

284行目ultrasonicRead()のnumber配列要素番号を修正

```grovepi.py
# Read value from Grove Ultrasonic
def ultrasonicRead(pin):
	write_i2c_block(uRead_cmd + [pin, unused, unused])
	number = read_identified_i2c_block(uRead_cmd, no_bytes = 2)
	return (number[1] * 256 + number[2])
```

320行目dht()のnumber配列要素番号、no_bytes変数値を修正

```grovepi.py
# Read and return temperature and humidity from Grove DHT Pro
def dht(pin, module_type):
	write_i2c_block(dht_temp_cmd + [pin, module_type, unused])
	number = read_identified_i2c_block(dht_temp_cmd, no_bytes = 9)
	
	if p_version==2:
		h=''
		for element in (number[1:5]):
			h+=chr(element)
		
		t_val=struct.unpack('f', h)
		t = round(t_val[0], 2)

		h = ''
		for element in (number[5:9]):
			h+=chr(element)
		
		hum_val=struct.unpack('f',h)
		hum = round(hum_val[0], 2)
	else:
		t_val=bytearray(number[1:5])
		h_val=bytearray(number[5:9])
		t=round(struct.unpack('f',t_val)[0],2)
		hum=round(struct.unpack('f',h_val)[0],2)
	if t > -100.0 and t <150.0 and hum >= 0.0 and hum<=100.0:
		return [t, hum]
	else:
		return [float('nan'),float('nan')]
```

上記の修正後に以下を実行すると、期待通りの結果を得ることができました。

```bash
$ python /home/pi/Dexter/GrovePi/Software/Python/grove_button.py
0
0
1
1
1
0
0
```

## Python library to access kintoneの設定

```bash
$ pip install pykintone
$ pip3 install pykintone
```

### node-redの設定（必要な場合のみ）
node-redの設定は以下のように行います。

```bash
$ sudo apt-get install nodered
$ npm install node-red-grovepi-nodes
$ npm install node-red-contrib-kintone
$ sudo systemctl enable nodered.service
$ sudo service nodered start
```

ブラウザで http://Raspberry PiのIPアドレス:1880/ にアクセスするとnode-redが利用できます。
![nodered.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75892/f0873480-9262-80e8-3633-bda60e9725b9.png)
詳細は以下を参照ください。
__Node-RED Raspberry Piで実行する__
https://nodered.jp/docs/getting-started/raspberrypi

### リモートディスクトップの設定（必要な場合のみ）
リモートディスクトップの設定は以下のように行います。

```bash
$ sudo apt-get install xrdp
$ cd /etc/xrdp/
$ sudo wget http://w.vmeta.jp/temp/km-0411.ini
$ sudo ln -s km-0411.ini km-e0010411.ini
$ sudo ln -s km-0411.ini km-e0200411.ini
$ sudo ln -s km-0411.ini km-e0210411.ini
$ sudo service xrdp restart
```

Raspberry PiのIPアドレスで以下のようにリモートディスクトップに接続できます。
![RaspberryPi04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75892/e0146b6f-193c-af0d-9ea9-a406be47b8ac.png)
詳細は以下を参照ください。
__Windowsパソコンからraspberrypi3にリモートデスクトップで接続する（ @t114 さん）__
https://qiita.com/t114/items/bfac508504b9a6b7570d

## 参考
ラズパイでpython3にopencvを入れたらエラーが出た【対処法】（ @XM03 さん）
https://qiita.com/XM03/items/48463fd910470b226f22

Raspberry Pi Projects for the GrovePi.
https://www.dexterindustries.com/GrovePi/projects-for-the-raspberry-pi/
https://www.dexterindustries.com/GrovePi/get-started-with-the-grovepi/setting-software/

Grove - LED
https://www.seeedstudio.com/Grove-Green-LED.html
http://wiki.seeedstudio.com/Grove-Red_LED/#play-with-raspberry-pi-with-grovepi_plus

Grove - Button（ボタン）
https://www.seeedstudio.com/Grove-Button.html
http://wiki.seeedstudio.com/Grove-Button/#play-with-raspberry-piwith-grovepi_plus

Node-RED Raspberry Piで実行する
https://nodered.jp/docs/getting-started/raspberrypi

Windowsパソコンからraspberrypi3にリモートデスクトップで接続する（ @t114 さん）
https://qiita.com/t114/items/bfac508504b9a6b7570d

GitHub icoxfog417/pykintone
https://github.com/icoxfog417/pykintone

__grovepi.py の不具合修正（227行以降）__
(Y.K Bug fixes) の記載部分に修正あり

```grovepi.py
（前略）

# Read I2C block from the GrovePi
def read_i2c_block(no_bytes = max_recv_size):
	data = data_not_available_cmd
	counter = 0
	while data[0] in [data_not_available_cmd[0], 255] and counter < 3:
		try:
			data = i2c.read_list(reg = None, len = no_bytes)
			time.sleep(0.002 + additional_waiting)
			if counter > 0:
				counter = 0
		except:
			counter += 1
			time.sleep(0.003)
			
	return data

# (Y.K Bug fixes)
def read_identified_i2c_block(read_command_id, no_bytes):
	data = [-1]
	data = read_i2c_block(no_bytes + 1)
	return data

# Arduino Digital Read
def digitalRead(pin):
	write_i2c_block(dRead_cmd + [pin, unused, unused])
	data = read_identified_i2c_block( dRead_cmd, no_bytes = 1)[0]
	return data

# Arduino Digital Write
def digitalWrite(pin, value):
	write_i2c_block(dWrite_cmd + [pin, value, unused])
	read_i2c_block(no_bytes = 1)
	return 1

# Read analog value from Pin (Y.K Bug fixes)
def analogRead(pin):
	write_i2c_block(aRead_cmd + [pin, unused, unused])
	number = read_identified_i2c_block(aRead_cmd, no_bytes = 2)
	return number[1] * 256 + number[2]


# Write PWM
def analogWrite(pin, value):
	write_i2c_block(aWrite_cmd + [pin, value, unused])
	read_i2c_block(no_bytes = 1)
	return 1

# Setting Up Pin mode on Arduino
def pinMode(pin, mode):
	if mode == "OUTPUT":
		write_i2c_block(pMode_cmd + [pin, 1, unused])
	elif mode == "INPUT":
		write_i2c_block(pMode_cmd + [pin, 0, unused])
	read_i2c_block(no_bytes = 1)
	return 1


# Read temp in Celsius from Grove Temperature Sensor
def temp(pin, model = '1.0'):
	# each of the sensor revisions use different thermistors, each with their own B value constant
	if model == '1.2':
		bValue = 4250  # sensor v1.2 uses thermistor ??? (assuming NCP18WF104F03RC until SeeedStudio clarifies)
	elif model == '1.1':
		bValue = 4250  # sensor v1.1 uses thermistor NCP18WF104F03RC
	else:
		bValue = 3975  # sensor v1.0 uses thermistor TTC3A103*39H
	a = analogRead(pin)
	resistance = (float)(1023 - a) * 10000 / a
	t = (float)(1 / (math.log(resistance / 10000) / bValue + 1 / 298.15) - 273.15)
	return t


# Read value from Grove Ultrasonic (Y.K Bug fixes)
def ultrasonicRead(pin):
	write_i2c_block(uRead_cmd + [pin, unused, unused])
	number = read_identified_i2c_block(uRead_cmd, no_bytes = 2)
	return (number[1] * 256 + number[2])


# Read the firmware version
def version():
	write_i2c_block(version_cmd + [unused, unused, unused])
	number = read_identified_i2c_block(version_cmd, no_bytes = 3)
	return "%s.%s.%s" % (number[0], number[1], number[2])


# Read Grove Accelerometer (+/- 1.5g) XYZ value
# Need to investigate why this reports what was read with the previous command
# Doesn't look to be implemented on the GrovePi
def acc_xyz():
	write_i2c_block(acc_xyz_cmd + [unused, unused, unused])
	number = read_identified_i2c_block(acc_xyz_cmd, no_bytes = 3)
	if number[1] > 32:
		number[1] = - (number[1] - 224)
	if number[2] > 32:
		number[2] = - (number[2] - 224)
	if number[3] > 32:
		number[3] = - (number[3] - 224)
	return (number[0], number[1], number[2])


# Read from Grove RTC
# Doesn't look to be implemented on the GrovePi
def rtc_getTime():
	write_i2c_block(rtc_getTime_cmd + [unused, unused, unused])
	number = read_i2c_block()
	return number

# Read and return temperature and humidity from Grove DHT Pro (Y.K Bug fixes)
def dht(pin, module_type):
	write_i2c_block(dht_temp_cmd + [pin, module_type, unused])
	number = read_identified_i2c_block(dht_temp_cmd, no_bytes = 9)
	
	if p_version==2:
		h=''
		for element in (number[1:5]):
			h+=chr(element)
		
		t_val=struct.unpack('f', h)
		t = round(t_val[0], 2)

		h = ''
		for element in (number[5:9]):
			h+=chr(element)
		
		hum_val=struct.unpack('f',h)
		hum = round(hum_val[0], 2)
	else:
		t_val=bytearray(number[1:5])
		h_val=bytearray(number[5:9])
		t=round(struct.unpack('f',t_val)[0],2)
		hum=round(struct.unpack('f',h_val)[0],2)
	if t > -100.0 and t <150.0 and hum >= 0.0 and hum<=100.0:
		return [t, hum]
	else:
		return [float('nan'),float('nan')]

（後略）
```