「ラズパイ」と「スプレッドシート」で全自動水耕栽培器をつくって女の子にTwitterで実況しながら栽培してもらう:⑩各種機能編 ~Googleスプレッドシートに設定シートを作り、そこからツイートする~

女の子につぶやいてほしい

そういう願望があるので早速つぶやいてもらおう。

第十回となる今回はTwitterスプレッドシートの連携させて、スプレッドシートに記載した内容をつぶやいてもらうぞ!

 

今回つぶやいてくれる美少女

f:id:udonnamerou:20200814000701p:plain

長野県出身の川上ちりやちゃんです。かわいい。

絵の描ける友人がいたのでアイコンを書いてもらいました。よき友達をもってうれしい...。実はこの友達、僕が高校時代に作ってた動画でも絵をかいてもらったことがあるので気になる方は見てください黒歴史なのであまり見せたくはないのですが...。)

 

ツイートには何が必要?

今回はあくまで野菜の様子を美少女がつぶやくといった内容なので、ただのBotとは違います。撮影した画像を一緒に投稿したり、あたかも美少女がしゃべっている感じに仕上げたいのでリアルタイムで育てているんだなって感じを出していきたいわけです。

そういうわけで、ちりやちゃんがつぶやく内容をいくつかカテゴライズする必要があります。以下、カテゴリーのリスト

  • 日報(おはよう!などの定型文)
  • 月齢(今日はいい月だね!とかつぶやかせたい)
  • 特殊イベント(クリスマスだよ!とか特定の日付につぶやかせたい)
  • 画像付きイベント(今の様子はこんな感じだよ!ってつぶやかせたい)
  • 豆知識・雑談(ちりやちゃんに植物に関する豆知識を披露してほしい)

そして、これをシートに直すとこんな感じになる。

f:id:udonnamerou:20200814001923p:plain

これをGAS(Google Apps Script)で読み取って、つぶやかせようという感じである。
もうちょっと具体的に話すと、D列にあるcodeに記載されている内容から、条件を分岐させて、前回の記事のような感じで取得したデータをもとに、あいさつだとか雑談といった多様なツイートを実現させようという感じである。

 

例えば日報についてつぶやかせようと思ったらこうなる。

//日報の関数
function dairyReport(){
  const asa  = "asanichi";
  const hiru = "hirunichi";
  const yoru = "yorunichi"; 
  var hour = getTime();

   switch (true) {
      case hour == getSettingTime(asa):
        _greetNi(asa)
        break
      case hour == getSettingTime(hiru):
        _greetNi(hiru)
        break
      case hour == getSettingTime(yoru):
        _greetNi(yoru)
        break
  }  
}

//dairyReport
function _greetNi(code){
  var currentrow = returnRow(code)
  var message = csheet.getRange(currentrow, C_SEF).getValue()

 //作成したメッセージをツイートしてくれる自作関数sendMessageToTw sendMessageToTw(message)

今回、GASを使ったスクリプトは大量にあるので、それをすべて紹介することは書く側も読む側も負担になると考える。したがって、のちにgithubにあげておくことにする。

 

また、このようにして作成した文章はTwitterAPIを使うことで、つぶやくことができる。こちらの記事を参照されたい。

さて、次回はいままでの機能をcronにして、ひとまずプロダクトが完成させるぞ!それではごきげんよう

前回:⑨各種機能編 ~取得情報をスプレッドシートにまとめる~

次回:⑪各種機能編 ~定期実行編~

 

 

 

「ラズパイ」と「スプレッドシート」で全自動水耕栽培器をつくって女の子にTwitterで実況しながら栽培してもらう:⑨各種機能編 ~取得した情報をスプレッドシートにまとめる~

Get Data Into Sheet!

今回はラズパイで取得したデータをスプレッドシートに書き込んでいく。これができれば晴れてIoTと語れるようになるので頑張っていこう。

 

スプレッドシートに情報を取得するってどんな感じなの?

こんな感じです。緯度経度・日の出・日の入り・画像パスなどの情報はセキュリティ的に問題があるので伏せておきます。

f:id:udonnamerou:20200814010725p:plain



どうやってデータをスプレッドシートに送るのか?

「HAGUKUMU」ではスプレッドシートに情報を送る方法として以下の3つがある。それぞれ解説していきたい。

  1. Pydriveを使ってGoogleDrive経由でスプレッドシートに情報を送る
  2. ラズパイからURLを送り、シート側がdoGetで受け取ることで情報を取得する
  3. APIを使ってスプレッドシート側から取得する

 

1.Pydriveを使ってGoogleDrive経由でスプレッドシートに情報を送る

1.については以前の記事でやったことで、Pydriveを経由してGoogleドライブに画像を送信する方法である。なお、Googleドライブにある画像をスプレッドシートで取得する方法についてはまだ述べていないので、ここでさらっとソースコードを張り付けておく。

 

  1. GASを使ってGoogleDriveに上がっている画像IDを取得するプログラム
//GAS
function
getImageFromDrive() { var imageid = "your_folderID"; var gif_imageid = "your_folderID"; var array = {}; ipath = _returnImagePath(imageid); gpath = _returnImagePath(gif_imageid); array["ipath"] = ipath; array["gpath"] = gpath; return array; } function _returnImagePath(id){ list = []; folder = DriveApp.getFolderById(id); files = folder.getFiles(); while(files.hasNext()) { var buff = files.next(); list.push([buff.getId()]); }; list = list.reverse(); imagepath = list.pop();
//imagepathを返す return imagepath; }

やっていることはこの記事に近い。

 

2.ラズパイからURLを送り、シート側がdoGetで受け取ることで情報を取得する

 

1.取得した高さデータをラズパイ側でhttpでポストする

//f_countは高さデータ 0.05.0などのフロート型が入る
def witeToGs(f_count):  
    value = str(f_count)
    if (f_count == 0):
  f_countが0の場合計測できていないので、エラーと判定する
        value = "err"
        
    url = "https://script.google.com/macros/s/your_spreadID/exec?data1=" + value
    requests.get(url)

2.スプレッドシート側のdoGetメソッドで高さを取得

function doGet(e) {
  var lastRow = dsheet.getLastRow();
  var spreadsheetId = "your_spreadsheetID";
  var sheetName = "your_sheet";
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var sheet = spreadsheet.getSheetByName(sheetName);
  
  var value = e.parameter.data1;
  sheet.getRange(lastRow,D_HEI,1,1).setValue(value);
}

やっていることはこの記事に近い。

 

3.APIを使ってスプレッドシート側から取得する

「HAGUKUMU」はSORACOMのサマーインターンシップ応募作品として作成しているため、SORACOMさんからGPSマルチユニットの供与を受けている。GPSマルチユニットは気温や湿度、位置情報などのデータを取得してくれるすごいやつで、それらのデータはSORACOMのエンドポイントを設定してあげることでAPIで取得できる。

SORACOMのAPIについては公式の利用ガイドを参照してほしいのだが、せっかくなので、取得に必要なコードをここに記載しておく。

function getSoracomInfo() {

  //APIを叩く  
  rawdata      = _requestJson();
  //JSONをパースする
  soracomInfos = _jsonParse(rawdata)
    
  var Array = {};
  Array["lat"]  = soracomInfos["lat"];
  Array["lon"]  = soracomInfos["lon"];
  Array["bat"]  = soracomInfos["bat"];
  Array["temp"] = soracomInfos["temp"];
  Array["humi"] = soracomInfos["humi"];
  
  return Array
  }

function _requestJson(){
  const IMSI        = "your_IMSI"
  const URL         = "https://api.soracom.io/v1/subscribers/your_IMSI/data"
  
 //アクセストークンの取得
  var array = _returnAuth ()

  var apikey = array["apiKey"]
  var token = array["token"]
  
  var options = {
     "headers" : {
       "X-Soracom-API-Key" : apikey,
       "X-Soracom-Token": token
     }
  } 
  
  var rawdata = UrlFetchApp.fetch(URL, options).getContentText();
  return rawdata
}

function _jsonParse(r){
  var jdata = JSON.parse(r)
  var latestjdata = jdata[0]
  
  //文字型で認識されているようなので、もういちどJSONへパース
  var content  = JSON.parse(latestjdata["content"]);
  var payload  = content["payload"];
  var decoded  = Utilities.base64Decode(payload);
  var data     = Utilities.newBlob(decoded).getDataAsString();
  
  //もういちどJSONへパース
  var soracomInfos = JSON.parse(data);

  return soracomInfos
}
//soracomのトークンは定期的に途切れる仕様になっているので、実行するたびにトークンを取得しに行く関数
function _returnAuth (){
  var url = 'https://api.soracom.io/v1/auth';
  var headers = {
             "Accept": "application/json",
  };

  var payload = {
              "email":"your_email",
              "password":"your_pass"
  };

   var options =
   {
     "contentType": "application/json",
     "headers":headers,
     "method": "post",
     "payload" : JSON.stringify(payload)
   };

  var response = UrlFetchApp.fetch(url,options);
  var data = JSON.parse(response.getContentText());
  
  var array = {};
  array = {"apiKey":data["apiKey"],"token":data["token"]}
  return array;

 

長くなってしまったので、今回はこれくらいにしておこう

次回はツイッターで美少女がツイートできるように設定していくぞ!

前回:⑧各種機能編 ~写真をGoogleドライブにアップロードする~

次回:⑩各種機能編 ~Googleスプレッドシートとツイート~

 

「ラズパイ」と「スプレッドシート」で全自動水耕栽培器をつくって女の子にTwitterで実況しながら栽培してもらう:⑧各種機能編 ~写真をGoogleドライブにアップロードする~

きれいな体、残しておきたいですよね?

というわけで第八回目は植物の写真を撮ってGoogleDriveにアップロードする作業を行う。

 

画像をラズパイに保存する

この辺のことはわざわざ書かなくてもほかの記事でたくさん書かれているので割愛。

カメラモジュールは1700円くらいのラズパイ対応のやつを買えばいいと思うよ。

 

ラズパイからGoogleDriveに画像をアップする

この辺も記事はあるので、検索しながらやりかたを探してほしい。自分が参考にしたのはこの記事。ただし、cronを使って画像アップロード機能を実行する場合、Googleの認証に使う「credentials.json」が見つからないので(cronを実行する場合は絶対パスで指定しないと読み込んでくれないため)、少々設定をいじってやる必要がある。日本語での記事がなく解決に四苦八苦したのだが、なんとか対応できたのでQiitaに記事としてアップしておくことにする。

 

GIF画像も作成する。

野菜の成長過程をタイムラプスにしたいという思いからgif画像も作成してアップロードすることにする。gif作成には「Pillow」というライブラリを使って実装する。(参考にしたサイト

 

画像アップロードテスト

うまくいったみたいですな。

 

以下、ソースコード

main.py

import create_gif as cg
import take_picture as tp
import image_uploader as iu

take_pict   = "jpeg"
take_pictid = "フォルダーID"
take_pictdir = "指定のディレクトリ/save_image"

create_gif   = "gif"
create_gifid   = "フォルダーID"
create_gifdir = "指定のディレクトリ/save_gif"

tp.take_picture()
iu.image_uploader(take_pict,take_pictid,take_pictdir)
cg.create_gif()
iu.image_uploader(create_gif,create_gifid,create_gifdir)
print("allok")

take_picture.py

import time
import datetime
import picamera

def take_picture():
    with picamera.PiCamera() as camera:
     camera.resolution = (640, 480)
     camera.start_preview()
     dt_now  = datetime.datetime.now()
     saveimage =  '指定のディレクトリ/save_image/%s.jpg' % (dt_now)
     camera.capture(saveimage)

create_gif.py

from PIL import Image, ImageDraw
import os
import glob
import datetime
import shutil

def create_gif():
    runnum = 10

 #画像を保存しているディレクトリを指定
    flists = os.listdir('指定のディレクトリ/save_image')
    count = len(flists)  

 #画像が一定数以上ならgifを作成する
    if count >= runnum:
        dt_now  = datetime.datetime.now()
        files = sorted(glob.glob('指定のディレクトリ/save_image/*.jpg'))
        images = list(map(lambda file: Image.open(file), files))
        #gif画像を作成し、保存
        images[0].save('指定のディレクトリ' + str(dt_now) + '.gif', save_all=True, append_images=images[1:], duration=40, loop=0)
       #ディレクトリを削除、再び作成
        shutil.rmtree('指定のディレクトリ/save_image/')
        os.mkdir('指定のディレクトリ/save_image/')
        shutil.rmtree('指定のディレクトリ/save_gif/')
        os.mkdir('指定のディレクトリ/save_gif/')

image_uploader.py

from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
import os
SETTINGS = "指定のディレクトリ/settings.yaml"

def image_uploader(imagetype,folder_id,direct):
 #cronで実行する場合はsettings.yamlの引数に入れる
    gauth = GoogleAuth(SETTINGS)
    gauth.CommandLineAuth()
    drive = GoogleDrive(gauth)

    lists = os.listdir(direct)

    if len(lists) > 0:
        sortedlists = sorted(lists, reverse=True)
        
        image_name = sortedlists[0]
        image_path = direct + '/' + sortedlists[0]
        print(sortedlists[0])
        
        f = drive.CreateFile({
            'title': image_name,
            'mimeType': 'image/' + imagetype,
            "parents": [{"kind": "drive#fileLink","id": folder_id}]})

        f.SetContentFile(image_path)
        f.Upload()
    else:
        print("No images")

 

次回はツイッターで美少女がツイートできるように設定していくぞ!

前回:⑦各種機能編 ~植物の高さを計測する~ 

次回:⑨各種機能編 ~取得した情報をスプレッドシートにまとめる~

「ラズパイ」と「スプレッドシート」で全自動水耕栽培器をつくって女の子にTwitterで実況しながら栽培してもらう:⑦各種機能編 ~植物の高さを計測する~

ギアとモーターは男のロマン

第七回目ともなると冒頭に書くことが思い浮かばなくなってくる。「HAGUKUMU」の設計解説ブログはもうちょっと続くことになるだろうが。いきなり本題に入った場合は何も思い浮かばなかったんだろうなと考えてほしい。

さて、今回はギアとモーターとセンサーを駆使しながら物体の高さを図る機能を実装していくよ。

 

どうやって高さを図るの?

お答えします。

f:id:udonnamerou:20200813210913j:plain

仕組みは上図のとおりである。ギアとモーターによって上下運動することでセンサーを動かす。光センサーによって、センサーは物体が存在するかどうかを識別することができるので、物体が識別されている状態が続く間はモーターが上に移動し、最終的に物体が識別されなくなるまで稼働させる。モーターが動いた時間を距離に換算することで高さが図れるといった次第である。

 

材料を用意しよう

高さを図るために必要な部材は以下のとおりである。

 

作る前に注意しておくこと

サーボモーターとギヤを取り付けることは口径が合わない関係ため、取り付けることはできない。しかし、ハンドドリルのような工具を使ってあげれば穴のサイズを合わせることができるので取り付けが可能になる。

 

1枚目:ドリルで穴のサイズを調節してあげれば...。

f:id:udonnamerou:20200813214241j:plain

 

2枚目:サイズの合わない者同士を合わせることができる。

f:id:udonnamerou:20200813214237j:plain

作ってみよう

サーボモーターとギヤとベルトの取り付け

 とりあえず無理やり、板にサーボモーターとギヤ、ベルトを取り付け、稼働テストをしてみる。

精度は悪そうだが、その辺はギアとか調節すればなんとかなりそうだ。

 

改良版のテスト

調整の結果精度が上がったぞい。

光センサーのテスト

こんな感じの配線をする。この辺の記事を読んでいけば実装できると思う。

ちなみに黒いジャンパワイヤがGNDです。

f:id:udonnamerou:20200813214852j:plain


んでもって、テストをしてみる。

どうやらうまくいったようなので、次はギヤとモーターとセンサーを組み合わせて高さを計測してみる。

 

高さを計測する

以下が実装に際して使用したソースコードである。

言い訳するわけではないが、時間がなかったのでリファクタリングなどは一切行っていないのでお見苦しいコードである。許して。

 

main.py

import servo    as SERV
import distance as DIST
import RPi.GPIO as GPIO
import time

import math
digit = 4
count = 0.0

while (DIST.distance() == True):
    count = SERV.servoup(count)
    time.sleep(0.5)

SERV.servoreverse(count)
f_count = math.floor(count * digit)/digit
print("this is " + str(f_count) + "cm.")
GPIO.cleanup()

distance.py

import RPi.GPIO as GPIO
import time

GPIO_Nbr = 24

GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_Nbr, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

def distance():
    if GPIO.input(GPIO_Nbr) == GPIO.LOW:
        print("no")
        return 0
    if GPIO.input(GPIO_Nbr) == GPIO.HIGH:
        print("touch")
        return 1
    time.sleep(0.3)

servo.py

def servoup(count):
    GPIO.setmode(GPIO.BCM)
    gp_out = 4
    GPIO.setup(gp_out, GPIO.OUT)
    servo = GPIO.PWM(gp_out, 50)
    servo.start(0)
    servo.ChangeDutyCycle(7.5)
    time.sleep(0.5)
    servo.stop()

    return count + 1

def servoreverse(recount):
    GPIO.setmode(GPIO.BCM)
    gp_out = 4
    GPIO.setup(gp_out, GPIO.OUT)
    servo = GPIO.PWM(gp_out, 50)
    
    while (recount > 0) :
        servo.start(0)        
        servo.ChangeDutyCycle(6.9)
        time.sleep(0.5)
        recount -= 0.8

    servo.stop()

 稼働テスト

動いたのでよかった('ω')

 

次回はラズパイから画像をアップロードする方法について話していく。

それではごきげんよう

 

前回:⑥各種機能編 ~ポンプを制御する~ 

次回:⑧各種機能編 ~写真をGoogleドライブにアップロードする~

「ラズパイ」と「スプレッドシート」で全自動水耕栽培器をつくって女の子にTwitterで実況しながら栽培してもらう:⑥各種機能編 ~ポンプを制御する~

自動水耕栽培器のコア、ポンプを制御しよう

第六回となる今回はポンプを制御して、自動水やり機能を作ってしまおう。どうでもいいが、照明を切り替えてから作業効率が上がった気がする。LEDばんざーい。

 

pythonでコードを書く

とはいっても今回はコードを書いて、指定のGPIOピンに配線するだけなのでこれでおわり。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import time

COUNT = 3
PIN = 11

GPIO.setmode(GPIO.BOARD)
GPIO.setup(PIN,GPIO.OUT)
count = 0

while True:
  GPIO.output(PIN, True)
  count += 1
  print(count)
  if (count > 1000000):
      break
  
GPIO.output(PIN,False)
GPIO.cleanup()
print("stop")

 

稼働テストの様子。あとはこれを定期実行できればよさそうだ。

定期実行については次回以降、cronについての項でやることにしよう。

 

次回は植物の高さを計測していきたい。

 

前回:⑤各種機能編 ~ラズパイセットアップと自作コンセント~

次回:⑦各種機能編 ~植物の高さを計測する~

「ラズパイ」と「スプレッドシート」で全自動水耕栽培器をつくって女の子にTwitterで実況しながら栽培してもらう:⑤各種機能編 ~ラズパイセットアップと自作コンセント~

ラズパイと自作コンセントでIoT家電の第一歩へ

これ書いているときに家の明かりがつかなくなり、しぶしぶ3000円もするLED照明を買いに行くことになった。ラーメン一杯分を800円とすると3.75ラーメン使ってしまったことになる。残念...。大阪にはおいしいラーメン屋がたくさんあるのでこの記事を見ている方がいたらぜひ足を運んでほしい。おすすめは「人類みな麺類」というお店である。

 

さて、第五回の今日はラズパイのセットアップと自作コンセントの作成とする。

 

ラズパイセットアップ

セットアップについてはウェブにたくさんあるので割愛する。筆者は以下の記事を参考にセットアップを行った。

自作コンセントの作成

自作コンセントについてはこちらを参考に作成した。使う材料もこの記事通りに作る。

自作コンセントの概観

1枚目:作った当初。シンプルでいいね。

f:id:udonnamerou:20200813204045j:plain

 

2枚目:「HAGUKUMU」完成後の写真。

配線がごちゃごちゃしているがこれでも頑張った。配線のコツは、ジャンパピンの色を使いわけ、どれがどれなのかわかりやすくすること、GNDは複数ある場合まとめてしまうことの2つである。今回でよく学んだ...。f:id:udonnamerou:20200813204051j:plain

自作コンセントのテスト

どうやらうまく動いたようなので、ラズパイと繋げてPCから起動できるようにしてみる。

 

ラズパイと繋げてプログラムから制御する

適当にGPIOピンを設定してあげて(今回は21番)、Lチカと同じ要領で制御するとこんな感じに一般の電化製品なども制御できるようになる。

 

#!/usr/bin/python
# coding: utf-8
 
# モジュールをインポートする
import RPi.GPIO as GPIO
import time
 
# GPIO指定をGPIO番号で行う
GPIO.setmode(GPIO.BCM)
 
# GPIO21ピンを出力モードに設定
GPIO.setup(21, GPIO.OUT)
 
# GPIO21番ピンを3.3Vに設定
GPIO.output(21, 1)
 
# 1秒待つ
time.sleep(1)
 
# GPIO21番ピンを0Vに設定
GPIO.output(21, 0)
 
# GPIO設定をリセット
GPIO.cleanup()

https://tool-lab.com/raspberrypi-startup-26/

動かしたかっただけなのでソースコードはお借りしました。

次回はポンプをラズパイで制御して、最低限水耕栽培ができるようにするよ。

前回:④各種機能編 ~水耕栽培土台~

次回:⑥各種機能編 ~ポンプで制御する~

「ラズパイ」と「スプレッドシート」で全自動水耕栽培器をつくって女の子にTwitterで実況しながら栽培してもらう:④各種機能編 ~水耕栽培土台~

水耕栽培の土台をつくる

好きなポケモンはドダイドスです。

というわけで水耕栽培作成第五回は水耕栽培の基礎となる土台を作っていきます。

 

はじめに

ラズパイを使った自動水耕栽培器についてのサイトは山ほどあるが、今回はこのサイトを参考にしながら進めていく。サイトの作りが体系的で、説明もわかりやすいので水耕栽培を始めてみたいという方は必読。以降、水耕栽培の基礎的な部分はこの記事を引用しつつ進めていく。

jitaku-yasai.com

 

土台とは?

土台とは、実装に当たって便宜上読んでいるものであり、水耕栽培の形式のことを指す。水耕栽培の代表的な形式として2つあり、それぞれ「DFT方式」「NFT方式」と呼ぶ。簡単にいうと...。

  • DFT方式:根を溶液に浸す方式
  • NFT方式:浅く培養液を流し続ける方式

だそうで、今回は壊れにくそうなDFT方式を採用していくことにする。

「DFT方式」「NFT方式」の詳しい説明はこちら

 

DFTの土台を実装する(材料編)

f:id:udonnamerou:20200813175925j:plain

まずはこんな感じの材料を用意する

  • 木製置台
  • 岩崎工業AGキーパー(透明の箱)
  • 塩ビパイプ
  • TS VS13(塩ビパイプのソケット)
  • ユニオンパッキン(TS VS13に合うようなやつ)
DFTの土台を実装する(組み立て編)

木製置台を加工

木製置台の真ん中の板をくり抜き...

f:id:udonnamerou:20200813180600j:plain

 

AGキーパーの加工

AGキーパーの真ん中に穴をあけ...

f:id:udonnamerou:20200813180553j:plain

 

取付

AGキーパーにソケット・塩ビパイプ・パッキンを取付、グルーガンで固めたら完成

f:id:udonnamerou:20200813180556j:plain

 

DFT方式で動いているところ

試しにポンプを付けて動かしてみました。ちゃんとできてるっぽいね!

 

植物栽培用のポット穴をつける

もちろんこれだけでは植物を育てるスペースがないので、植物を育てるためのポットを取り付ける。

 

鉢とスポンジセットを購入

スポンジと鉢を購入して...

f:id:udonnamerou:20200813205424j:plain

 

蓋を加工する

AGキーパーの蓋を鉢がはいるように加工していく

f:id:udonnamerou:20200813205408j:plain

f:id:udonnamerou:20200813205413j:plain

 

完成

適当に買ってきた豆苗とカイワレを植えてみる。よさそう。

f:id:udonnamerou:20200813205418j:plain

 

次回はラズパイのセットアップと自作コンセントの作成について書いていくことにする。こうご期待。

前回:③実装概要編

次回:⑤各種機能編 ~ラズパイセットアップと自作コンセント~