PL/Python＋Raspberry Pi＝？？？

この記事は[PostgreSQL Advent Calendar 2019](https://qiita.com/advent-calendar/2019/postgresql)の23日目の記事です。
昨日は[nuko_yokohama](https://qiita.com/nuko_yokohama)さんのQONに関する記事でした。とてもタメになる内容でしたね！本日の記事も皆さんのお役に立てるよう頑張って書きましたので、ご査収のほどよろしくお願い申し上げます。

# はじめに
Pythonできると金持ちになれると聞いて、Pythonの学習をはじめました。そう、[PL/Python](https://www.postgresql.jp/document/11/html/plpython.html)ですね！

# 学習方針
どうせお勉強するなら楽しくなくちゃね！ということで、昔買ったRaspberry Piを引っ張り出してきて、そこにPostgreSQL入れてお勉強することにしました。

## 学習環境
学習環境は以下の通りです。

| 製品/ソフトウェア | バージョン | 備考 |
|:----------------:|:-----------------:|:------------------:|
| Raspberry Pi | 1 Model B | だいぶ古い |
| Raspbian | 10(buster) | 結構新しい |
| Python | 2.7.16 | busterに入ってた |
| PostgreSQL | 11.5 | apt-getで入れた |

## 環境構築
SDカードには古いRaspbianが入ってたので、一掃してbusterを入れ直しました。ネットワークはWifiでつなげるのですが、この辺もUSBさしてチョロチョロっと設定すれば簡単につながりました。便利な世の中です。

Pythonも同梱されてるので、さくっと学習をはじめられます。

```
$ python --version
Python 2.7.16
```

続けてPostgreSQLのインストール。ソースからビルドしても良かったのですが、なんとなくapt-getでインストールしてみました。ついでに今回の主役であるPL/Pythonも。

```
# apt-get install postgresql
# apt-get install postgresql-plpython-11
```

この状態ではまだPL/Python使えないので、CREATE LANGUAGEで登録！

```sql
testdb=# CREATE LANGUAGE plpythonu;
CREATE LANGUAGE
testdb=# select * from pg_language ;
  lanname  | lanowner | lanispl | lanpltrusted | lanplcallfoid | laninline | lanvalidator | lanacl 
-----------+----------+---------+--------------+---------------+-----------+--------------+--------
 internal  |       10 | f       | f            |             0 |         0 |         2246 | 
 c         |       10 | f       | f            |             0 |         0 |         2247 | 
 sql       |       10 | f       | t            |             0 |         0 |         2248 | 
 plpgsql   |       10 | t       | t            |         13088 |     13089 |        13090 | 
 plpythonu |       10 | t       | f            |         16386 |     16387 |        16388 | 
(5 行)
```

> 余談
Rasbian は、DebianベースのOSなのでPostgreSQLパッケージの内容もちょっと違うのね。pg_ctlがなくて最初面食らったYO！
代わりにpg_ctlclusterってのが入ってた。
https://manpages.debian.org/buster/postgresql-common/pg_ctlcluster.1.en.html

## Lesson 1
やっぱり学習の最初は「Hello, World」ですね。

```sql
$ cat helloworld.sql 
CREATE OR REPLACE FUNCTION hello()
  RETURNS TEXT
AS $$
  return 'Hello, World'
$$ LANGUAGE plpythonu;

$ psql -f helloworld.sql testdb
CREATE FUNCTION

$ psql testdb -c "SELECT hello();"
    hello     
--------------
 Hello, World
(1 行)
```

動きました！
もう少し躓いたりするかな？と思ってたけど、意外とあっさり動いてしまいました。

## Lesson 2
次はせっかく Raspberry Pi 使ってるので Lチカやりましょう。Lチカ。

今回使った Raspberry Pi 1 Model B でも26pinのGPIO(General Purpose Input/Output)がついてるので、さくっと光らせてみます！

```sql
$ cat led.sql 
CREATE OR REPLACE FUNCTION led()
  RETURNS void
AS $$
  import RPi.GPIO as GPIO
  import time
  GPIO.setmode(GPIO.BCM)
  LED = 17
  WAIT_TIME = 5
  GPIO.setup(LED, GPIO.OUT)
  GPIO.output(LED, GPIO.HIGH)
  time.sleep(WAIT_TIME)
  GPIO.output(LED, GPIO.LOW)
  GPIO.cleanup()
$$ LANGUAGE plpythonu;

$ psql -f led.sql testdb
CREATE FUNCTION
```
led関数内で、

1. ピン17に電気流す(GPIO.HIGH)
2. 5秒停止
3. ピン17の電気を止める(GPIO.LOW)

するだけのシンプルなものです。
関数も登録できて「楽勝！」と思いきや、あれ？

### **「光らんばい!!」**

どうやら、postgresユーザにgpio使う権限が入ってないのが問題だった模様。
ということで、postgresくんをgpioグループ(997)に混ぜてあげて、pg_ctlclusterで再起動。
pg_ctlclusterは、引数にバージョン・クラスタを指定する必要があります。バージョン・クラスタはpg_lsclustersで確認できます。

```
# id postgres
uid=110(postgres) gid=116(postgres) groups=116(postgres),997(gpio),115(ssl-cert)

# pg_lsclusters 
Ver Cluster Port Status Owner    Data directory              Log file
11  main    5432 online postgres /var/lib/postgresql/11/main /var/log/postgresql/postgresql-11-main.log

# pg_ctlcluster 11 main restart
```

改めて、いざLチカ！

```sql
$ psql testdb -c "SELECT led()"
 led 
-----
 
(1 行)
```

![led-resize.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151699/2987220f-642e-06ff-e438-cd77ec2f3972.gif)

おー、眩しいです！

# いざ、実践！
ここまでできると廻したくなるのが人の性。
AKBまでサーボモータ買いに行ってきました！

![motor-resize.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151699/4d53841a-7f0a-a59a-4d4a-4bae90135ffe.jpeg)

トリガ関数を使って、これを回すYO！

```sql
$ cat survo_test360_trigger.sql 
CREATE TABLE IF NOT EXISTS test(i int);
CREATE OR REPLACE FUNCTION survo360_trig()
  RETURNS trigger
AS $$
  import RPi.GPIO as GPIO
  import time

  GPIO.setmode(GPIO.BCM)
  gp_out = 18
  GPIO.setup(gp_out, GPIO.OUT)

  servo = GPIO.PWM(gp_out, 50)

  servo.start(0)

  servo.ChangeDutyCycle(8.0)
  time.sleep(1.0)

  servo.stop()
  GPIO.cleanup()
$$ LANGUAGE plpythonu;
CREATE TRIGGER trig_survo360 BEFORE INSERT OR UPDATE OR DELETE ON test
    FOR EACH ROW EXECUTE FUNCTION survo360_trig();

$ psql test -f survo_test360_trigger.sql 
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
```

こちらの関数では、PWM(Pulse Width Module)というのを使って周波数密度を変えてサーボモータを制御してます。

これで無事にtestテーブルへのDML(INSERT/UPDATE/DELETE)発行でサーボモータを廻すことに成功しました！

## Zoetrope
せっかくなのでZoetrope作ってみました〜

![zoetrope-resize.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151699/3e55c6da-ea04-706b-0398-571f5a468322.jpeg)


>Zoetrope(回転のぞき絵)
回転のぞき絵（かいてんのぞきえ、英: Zoetrope、仏: Zootrope）とは、静止画を素早く入れ替えることで、あたかも動いているかのように見せる器具。
[WikiPediaより](https://ja.wikipedia.org/wiki/%E5%9B%9E%E8%BB%A2%E3%81%AE%E3%81%9E%E3%81%8D%E7%B5%B5)

slonikくんも楽しげに廻ってくれるようになりました。

![zoetrope-resize.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151699/9d874b5f-abb2-62ed-e007-9aa3f173421f.gif)

現場からは以上です。

# まとめ
今年も**毒にも薬にもならない**エントリでしたが、これからも楽しくPostgreSQLライフを送りたいと思います！
明日は[atmitani](https://qiita.com/atmitani)さんの「PostgreSQLの統計情報を可視化（環境構築編）」ということで本当に役に立ちそうで楽しみです〜。