ラズパイ + golang + gobotで作る家庭菜園見守りカメラ

<img src="https://qiita-image-store.s3.amazonaws.com/0/6158/1c5f71ab-ec49-caaa-1c5a-654dfbfd023c.jpeg" width=60%>

最近、ベランダにプランターを設置して野菜を育てています。
日々成長する野菜を見るのはとても楽しいです。できればいつでもどこでも、見守っていたい。

そこで、家庭菜園見守りカメラを作ることにしました。

ちなみに写真はホウレンソウです☺

# 構成
- ハードウェア
  - Raspberry Pi Zero
  - Raspberry Pi用 カメラモジュール
  - [SG90サーボ用 2軸カメラマウント](https://www.amazon.co.jp/gp/product/B01E2SLESW/ref=oh_aui_detailpage_o03_s01?ie=UTF8&psc=1)
- ソフトウェア
  - golang
  - gobot

golangで電子工作はできるのか？と思い調べてみたところ、 `gobot` という素晴らしいフレームワークがあることが分かったので、これを使うことにしました。

# 製作物（途中）
<img src="https://qiita-image-store.s3.amazonaws.com/0/6158/c9e1a7a6-ab35-b121-ac88-da30bd3119ab.jpeg" width=50%>

↑のような網にラズパイとカメラを設置し、作物を見守れるようにする予定です。
実際に運用を始める際には雨対策が必要ですね。

ちなみに、ベランダでどうやって電源をとるか？という問題はこれで解決しました。

<img src="https://qiita-image-store.s3.amazonaws.com/0/6158/8b864ee1-e5dc-f3ac-e86e-921cdfd6f020.jpeg" width=40%>


うすーいケーブルを窓に挟み込んでサッシを超えてベランダまで電源を持ち込むという、力技のアイデア商品です。
残念なことに現在は製造していないようで、中古のものをAmazonで購入しました。

# Gobot
本当は家庭菜園の様子をネット配信して、リアルタイムで見てもらえるようにしたかったのですが、現在も誠意製作中で完成品をお披露目することはできません…
そこで、今回は製作に用いているgobotというフレームワークについて解説をします。

この記事を書いている時点でのgobotの最新バージョンは`1.7.1`です。
古い記事のサンプルコードは現在のバージョンと互換性がない場合が多いのでご注意ください。

## Gobotとは？
名前のとおり、Go言語で開発可能な電子工作やIoTの開発に用いるフレームワークです。
https://gobot.io/

gobotの素晴らしい点のひとつは、多様なプラットフォームに対応している点です。
ラズパイ、Arduinoはもちろん、腕時計のPebbleやドローンなど、32のプラットフォームに対応しています。

また、LEDやサーボなど多様なパーツを操作するためのドライバが用意されており、APIを通して簡単に操作できます。

以下に簡単なサンプルコードをご紹介します。

### LED点滅のサンプルコード
まずはHello WorldとしてLED点滅させてみます。

```go
package main

import (
  "time"

  "gobot.io/x/gobot"
  "gobot.io/x/gobot/drivers/gpio"
  "gobot.io/x/gobot/platforms/raspi"
)

func main() {
  raspiAdaptor := raspi.NewAdaptor()
  led := gpio.NewLedDriver(raspiAdaptor, "12")

  work := func() {
    gobot.Every(1*time.Second, func() {
      led.Toggle()
    })
  }

  robot := gobot.NewRobot("bot",
    []gobot.Connection{raspiAdaptor},
    []gobot.Device{led},
    work,
  )

  robot.Start()
}
```
かなりシンプルに書けるのが分かってもらえると思います。
今回はラズパイで動かしていますが、↓の部分を別のAdaptorに換えるだけで別のプラットフォームに対応可能です。

```go
raspiAdaptor := raspi.NewAdaptor()
led := gpio.NewLedDriver(raspiAdaptor, "12")
```
めっちゃ便利ですよね！

なお、 `12` はピン番号の指定です。適宜、使用するピン番号に書き換える必要があります。
GPIOの番号ではないことにご注意ください。

LEDに対する命令を記述している部分は `work` 関数です。
サンプルでは、1秒ごとにLEDの点灯/消灯を切り替える実装です。

### サーボモータを動かすサンプルコード
つづいてサーボモーターを動かしてみます。
使っているサーボモータはSG90です。

```go
package main

import (
  "fmt"
  "time"

  "gobot.io/x/gobot"
  "gobot.io/x/gobot/drivers/gpio"
  "gobot.io/x/gobot/platforms/raspi"
)

func main() {
  adaptor := raspi.NewAdaptor()
  servo := gpio.NewServoDriver(adaptor, "16")
  
  offset := 13

  work := func() {
    servo.Move(uint8(27))

    gobot.Every(1*time.Second, func() {
      i := gobot.Rand(28)
      fmt.Println("Turning", i + offset)
      servo.Move(uint8(i + offset))
    })
  }

  robot := gobot.NewRobot("servoBot",
    []gobot.Connection{adaptor},
    []gobot.Device{servo},
    work,
  )

  robot.Start()
}
```
1秒ごとにランダムのステップで動かしています。
このようにサーボを動かすのも簡単なコードで実現できます。

当初、間違っていたのですが `servo.Move` には角度を指定するのではなさそうです。
SG90の場合、`13〜41`の間のステップで動作し、`13`が0度の位置、`41`が180度の位置となっていました。

他のサーボではまた異なったステップ範囲かもしれません。
この辺りはドキュメントで仕様を確認できなかったため手探り状態で自信がありません…すみません。

### 【注意】ラズパイでサーボを動かす場合
サーボに限らずPWMをラズパイで使う場合は、gobot以外にインストールの必要なソフトウェアがあります。

pi-blasterという、ラズパイでPWMをサポートするためのソフトウェアです。
https://github.com/sarfata/pi-blaster

これを`How to install`にある手順でインストール後、`sudo ./pi-blaster`で起動しておきます。
その上で、サンプルプログラムを実行しないと動作しませんので、ご注意ください。

また、サンプルプログラムを停止後、再度実行する際にはpi-blasterも一度killして起動し直す必要がありました。
これは何らかの方法で回避できそうな気もするのですが、まだ解決できていません。

### Web APIのサンプルコード
最後にWeb APIを試してみます。
なんと、gobotはwebサーバの機能も兼ね備えており、簡単にWeb API化することができます。
ですから、ブラウザなどからサーボを動かす、などの命令を簡単に行うことができるわけです。

```go
package main

import (
  "fmt"

  "gobot.io/x/gobot"
  "gobot.io/x/gobot/api"
  "gobot.io/x/gobot/drivers/gpio"
  "gobot.io/x/gobot/platforms/raspi"
)

func main() {
  gbot := gobot.NewMaster()

  adaptor := raspi.NewAdaptor()
  x_servo := gpio.NewServoDriver(adaptor, "16")
  y_servo := gpio.NewServoDriver(adaptor, "18")

  offset := 13
  x_i := 14 // center
  y_i := 14 // center

  work := func() {
    fmt.Println("Turning x", x_i)
    fmt.Println("Turning y", y_i)
    x_servo.Move(uint8(x_i + offset))
    y_servo.Move(uint8(y_i + offset))
  }

  robot := gobot.NewRobot("servoBot",
    []gobot.Connection{adaptor},
    []gobot.Device{x_servo, y_servo},
    work,
  )

  // Starts the API server on default port 3000
  api.NewAPI(gbot).Start()

  servo_endpoint := gbot.AddRobot(robot)

  // Accessible via http://localhost:3000/robots/servoBot/commands/move_servo
  servo_endpoint.AddCommand("move_servo", func(params map[string]interface{}) interface{} {
    req_param := fmt.Sprintf("Params: %+v\n", params)

    x_i = int(params["x_num"].(float64))
    y_i = int(params["y_num"].(float64))

    robot.Start()
    return req_param
  })

  gbot.Start()
}
```
これは、2つのサーボモータを指定した値のステップで動かすプログラムです。

たとえば、curlで下のようにリクエストします。
Content-Typeは`application/json`である必要があります。jsonでないとparams引数に値が入ってこないのでご注意ください。

```bash
curl -v -H 'Content-Type:application/json' 'http://<ラスパイのIP>:3000/api/robots/servoBot/commands/move_servo' --data '{"x_num":0,"y_num":5}'
```

### 【注意】gobotのAPIクライアントを使用する場合
gobotには下のようなブラウザから使えるAPIクライアントも付属しており、`http://<ラスパイのIP>:3000/` にブラウザからアクセスして使用できます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/6158/58033879-4cf3-1041-de73-b857829cf0a5.png" width=50%>

このAPIクライアントに関して注意点があります。
入力フォームの`key`と`value`に指定した値は`Content-Type: application/x-www-form-urlencoded`として扱われ、AddCommandで指定した関数のparams引数に値が入りません。

ですから、何か値を渡して動作させるエンドポイントの場合は、curl等でContent-Typeを指定してリクエストする必要があります。

## おわりに
少ないサンプルしか提供できず申し訳ないですが、シンプルでありながら高機能なフレームワークであることが伝わると嬉しいです。
いろんなプラットフォームに対応しているので、golangひとつで色々遊べるのが魅力ですね。

自分はとりあえず、家庭菜園見守りカメラを完成させようと思います！