見出し画像

【GASでIoT】GASとラズパイでおこなう、お手軽データ・ロギング&フィードバック制御〔最終2/2〕~ソフトウェアのコード~

この記事のシリーズでは、Googole Apps Script(GAS)を利用して、日常生活を便利にする事をテーマにしています。

GASへのアクセスは、通常はキーボードから文字情報で行いますが、「Raspberry Pi(ラズベリーパイ)」というシングルボードコンピュータを介することで、文字以外の情報をインプットする事ができます。

今回はラズベリーパイとGASの協業により、インプットした温度センサーの情報を使って、電気鍋の制御を半導体リレーを用いて制御する事を行っています。


今回はまとめとしての最終記事で、以下のハードウェアの準備についてのまとめ記事に続く、ソフトウェアの準備の記事になります。

本記事では実装コードをご紹介していますが、様々な理由により、この説明通りにいかない場合がしばしばあります。申し訳ありませんが、プログラムについても自己責任・自己解決でお進めくださるよう、お願いいたします。 

あまり長時間の調理はノイズなどでうまく行かない事がしばしばありますので、2~3時間を目安にしてください。

ラズベリーパイでのプログラムは電源投入時に自動実行させず、プログラム画面から手動で実行させた方が無難です。


今回のテーマは、複数のアプリが介在しますので、予期しない不具合も起こりやすいと思います。運転中は装置から離れない様にしてください。


システムの概要

以下の図の様になります。

Google Apps Script(GAS) が、ラズベリーパイ、スプレッドシートにアクセスして制御します。

ラズベリーパイには、温度センサと半導体リレーがつながっています。

半導体リレーによって、電気なべを On/Off し、温度を制御します。


ハードウェアの準備については・・・


ハードウェア側の準備はこちらの記事をご覧下さい。



以下に、ソフトウェアの準備についてご説明します。

本記事では実装コードをご紹介していますが、様々な理由により、この説明通りにいかない場合がしばしばあります。申し訳ありませんが、自己責任・自己解決でお進めくださるよう、お願いいたします。 

(ご紹介の内容は、家庭内のささやかな調理に応用することを想定しており、規模の大きな調理や商用での利用は想定しておりません)

今回のテーマは、複数のアプリが介在しますので、予期しない不具合も起こりやすいと思います。ご注意ください。

現実は複雑です。センサーがお湯から露出して正しく温度が測れないなど、いろいろトラブルが予想されますので、稼働中は側を離れないでください。

ラズベリーパイ側のプログラム・コード(Python)


まず、ラズベリーパイ側で準備するプログラムです。Python で記述します。

◇Thermo.py◇(プログラム名は何でも可)


#----------ライブラリの準備----------
#---時間を利用するためのライブラリ---
import time

#---WEBにリクエストを投じるためのライブラリ
import requests
import sys

#---GPIOを利用するためのライブラリ
import RPi.GPIO as GPIO

#---1Wireを利用するためのライブラリ
import os
import glob
from time import sleep

os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')

#----------各種設定----------

#--読み出すファイルの設定
GPIO.setmode(GPIO.BCM)

#--GPIO2を出力モードに設定
GPIO.setup(26, GPIO.OUT)


#--読み出すファイルの設定

#---1WireのDS18B20温度センサの記録フォルダ
base_dir = '/sys/bus/w1/devices/'

#---28で始まるフォルダ
device_folder = glob.glob(base_dir + '28*')[0]

#---記録ファイル
device_file = device_folder + '/w1_slave'

#---温度ファイルのデータを取得する関数
def read_temp_raw():
    f = open(device_file, 'r')
    lines = f.readlines()
    f.close()
    return lines

#---温度データのみを取り出して返す関数
def read_temp():
    lines = read_temp_raw()

    #---温度データ行のみを取り出して返す関数
    #---最後の3行文字がYESでない行を取得
    while lines[0].strip()[-3:] != 'YES':
        sleep(0.2)
        lines = read_temp_raw()

    #---「t=」の文字位置を取得 
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        #---「t=」以降の文字を取得 
        temp_string = lines[1][equals_pos + 2:]
        #--- 取得した文字を1000で割ると温度となる
        temp_c = float(temp_string) / 1000.0
        return temp_c

#----------メイン部分----------
try:
    while True:
        #---温度
        thrm = read_temp()
        print(thrm)

        #---URLにアクセス:★GASのデプロイID★は各自で打ち替え
        print('Accesing to GAS...')
        r = requests.get("https://script.google.com/macros/s/★GASのデプロイID★/exec?thrm=" + str(thrm))

        #---レスポンスをコンソールに表示
        print(r.text)

        #---レスポンスに応じたGPIOピンのH/L
        #---rが「On」なら赤いピンをHighにする
        if(r.text == 'On'):
            GPIO.output(26, GPIO.HIGH)     #----GPIO2の出力をHigh(3.3V)にする
        else:
            GPIO.output(26, GPIO.LOW)     #----GPIO2の出力をLow(0V)にする


        #---15秒待つ
        sleep(15)

except KeyboardInterrupt:
    pass

最後の2行は強制停止(CTRL+C)させる際の内容です。

プログラム中に、★GASのデプロイID★ とあるところは、次の章のGASプログラムを実装したら、そのIDを具体的に打ってください。


なお、プログラムが稼働するためには、センサの規格である 1-wire の読み取りが、ラズベリーパイで有効になっている必要があります。ラズベリーパイ側のデスクトップから簡単な手順で設定できますので、以下の記事の内容を実施しておいてください。

このプログラムによって、ラズベリーパイはクライアント側として温度データをGASに送り、そして、リレーのON/OFFを判断するレスポンスを受け取ります。

ラズベリーパイの温度の記録とOn/Offレスポンスのプログラム・コード(GASスクリプト)


次に、GASのプログラムコードです。適当なGASプロジェクトを新規作成して記述します。

◇コード.gs◇(スクリプト名は何でも可)


//-----------------------------------------
//----ラズパイの温度データ送受信----------
//---著作:Particlemethod-2022年05月14日-----
//---無断複製・転載・配布を禁じます-------------
//-----------------------------------------
function doGet(e) {
  var thrm=e.parameter.thrm;


  if(e.parameter.thrm == null){
    return ContentService.createTextOutput('No Data');
  }else{

  //アプリケーションを取得|★スプレッドシートのID★は各自のものを記入
  var myApp = SpreadsheetApp.openById('★スプレッドシートのID★');

  //対象シートをシートの名前を指定して取得
  var mySheet = myApp.getSheetByName('温度');

  //Date型でオブジェクト生成(初期値は現在日時)
  var date = new Date();
  //現在時刻を表示
  var myDate = Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd hh:mm:ss');
  
  //最終行+1行目にデータを記録
  var NewDateRow = mySheet.getLastRow() + 1;
  mySheet.getRange(NewDateRow, 1).setValue(myDate);
  mySheet.getRange(NewDateRow , 2).setValue(thrm);
  mySheet.getRange(NewDateRow-1 , 3).copyTo(mySheet.getRange(NewDateRow , 3));


  //最終データを1行目に表示
  var LastDataTh=mySheet.getRange(NewDateRow , 2).getValue();
  var LastDataTm=mySheet.getRange(NewDateRow , 3).getValue();

  mySheet.getRange(1 , 5).setValue(LastDataTh);
  mySheet.getRange(2 , 5).setValue(LastDataTm);

  //On Off を読み込む
  var myRes=mySheet.getRange(1 , 6).getValue();

  //書き込んだ事をレスポンスとして返す
  return ContentService.createTextOutput(myRes);

  }

  
}

テンプレートはありません。新規のGASプロジェクトを作成したら、デフォルトで設定されている、「コード.gs」に上記のコードを書き込めば良いでしょう。

なお、ラズベリーパイが自由にアクセスできる様、デプロイする時は、「自分自身として」「誰でもアクセス可能」な状態を選んでください。

プログラム中に、スプレッドシートIDを記載する部分があります。

これは、クライアントであるラズベリーパイから受けたデータを記録するスプレッドシートの事です。以下のスプレッドシートをインポートした後、そのIDで打ち替えて下さい。

温度データの記録台帳(Googleスプレッドシート)

上記のGASが、取得したデータを記録する台帳として利用するスプレッドシートです。

Googleドライブ上で、スプレッドシートとしてインポートしてご利用ください。(新規のスプレッドシートを開き、ファイル⇒インポート と進むと取りこめます。)

取り込んだ後は、以下の状態となります。シート名は変えないでください。また、ご利用の際は、8行目以降をすべてデータ削除してから開始してください。


上の方に薄緑で、上限温度や上限時間を記載する部分があります。
これを元に、黄色い部分にリレーのON/OFFを表示させ、これがレスポンスといて返されます。

なお、通信が時々止まる可能性があるので、上限時間はあまり長く取らない方が良い様です。

上記のスプレッドシートのIDを、前の章のGASプログラムに反映する事を忘れないでください。

温度変化の表示プログラム(GASスクリプトとテンプレート)

最後に、スプレッドシートに記録された温度記録をリアルタイムでWEBページ上にグラフ表示させるGASのプログラムコードです。

追加でGASプロジェクトを新規作成して記述します。

◇コード.gs◇(スクリプト名は何でも可)


//---著作:particlemethod------------
//---無断複製・転載・配布を禁じます----
//★★★★doGet関数はURLから呼び出された時に実行する関数|1つだけ定義できます★★★★
function doGet(e) {

  //アプリケーションを取得|★スプレッドシートのID★は各自のものを記入
  var myApp = SpreadsheetApp.openById('★スプレッドシートのID★');


  
//対象シートを取得
  var mySheet = myApp.getSheetByName('温度');

  //データ記録範囲の行数と列数を代入
  var nRow=mySheet.getLastRow();
  var nCol=3;

  //データ記録範囲を指定して範囲を取得
  var myCells = mySheet.getRange(1,1,nRow,nCol);
  var X = mySheet.getRange(1,3,1,1);
  var Y = mySheet.getRange(1,5,1,1);

  //WEBページとして利用するテンプレートをファイル名を指定して宣言
  var myHTML = HtmlService.createTemplateFromFile('INDEX');
  
  //★★テンプレートに埋め込む変数値を指定する★★  
  myHTML.myCells = myCells.getValues();
  myHTML.X = X.getValue();
  myHTML.Y = Y.getValue();

  myHTML.myRows = nRow;
  myHTML.myCols = nCol;

  
  //HTMLファイルをホスティング|メタタグを指定してスマホ表示に対応
  return myHTML.evaluate().addMetaTag("viewport", "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.2");

}

//★★★★HTMLから呼び出されて実行する関数★★★★



//シートの最新の値を返す関数
function GetSheet(){

  //アプリケーションの取得|★スプレッドシートのID★は各自のものを記入
  var myApp = SpreadsheetApp.openById('★スプレッドシートのID★');

  //対象シートをシートの名前を指定して取得
  var mySheet = myApp.getSheetByName('温度');
  
  //最新データ記録範囲として、最終行と列を取得
  var nRow=mySheet.getLastRow();
  var nCol=2;

  //返り値の変数を定義 
  var myRes = new Array(2) ;

  myRes[0] = mySheet.getRange(nRow,nCol,1,1).getValue() ;
  myRes[1] = mySheet.getRange(nRow,nCol+1,1,1).getValue() ;



  return myTotal;
}

前の章のスプレッドシートのIDを、前の章のGASプログラムに反映する事を忘れないでください。(初期のデータと、経時変化で随時読み込む際の2カ所に記載部があります)

WEBページとして表示させる際のテンプレートは以下です。

このテンプレートには、スクリプトからデータを埋め込まれ、これが plotly-.js を利用して、WEBページ上の折れ線として表示されます。

◇INDEX.html◇(スクリプトに記載しているこの名前を使用)

<!DOCTYPE html>
<html>
<head>
  <!-- Plotly.js -->
  <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>

</head>


<body>
 <!---- コメント|変数値はdoGET関数からの渡し値---->
 <p id="msg">データを描画中・・・</p>
 

<p>原本のデータは<a href= "https://docs.google.com/★スプレッドシートのID★/edit?usp=sharing" >こちら</a></p>



<!---- テーブルタグとID名称宣言---style="display:none-->
<table id="TableBody" style="display:none">

<!---- JavaScriptのインライン記述-【↓ここから】---->

<? 

// GsValuesの各行 iRow に対して実行。myCellsはGASから渡される。
for(var iRow in myCells) {

   // 各行を Row に代入
   var Row = myCells[iRow]; 

   // 行頭タグ 「tr」|タグは ?>・・・<?で挟む
   ?><tr><?

   // カレント行の 各列 iCol に対して実行
   for(var iCol in Row) {

      // 該当行の 各列 iCol をCellに代入
      var Cell = Row[iCol];

               ?><td><?=Cell ?></td><?
    } 
   ?></tr><?
}
?>
<!---- JavaScriptのインライン記述-【↑ここまで】---->
</table>

 <div id="myPlot"><!-- Plotly chart will be drawn inside this DIV --></div>


<!---- テーブルの値の抽出--【↓ここから】---->
<script>
   //テーブルの取得
   var myTable = document.getElementById('TableBody');
   
   //テーブルの行数、列数の宣言
   var nRow = myTable.rows.length;
  var nCol = myTable.rows[0].cells.length;
  
   //テーブルの行数だけ要素のある配列を宣言
   var myData = new Array(nRow-1);

   //テーブルの列数だけ子要素を宣言
   for (var iRow = 0; iRow < nRow; iRow++){
    myData[iRow] = new Array(nCol-1);
  }
   

   //テーブルのrowsコレクションで行数を取得| 各行でループ
  for (var iRow = 1 ; iRow < myTable.rows.length; iRow++) {
  
    //iRow行のcellsコレクションで列数を取得| 各列でループ
  for (var iCol=1 ; iCol < myTable.rows[iRow].cells.length; iCol++) {
      
	 	//iRow行目のiCol列目のセルのテキスト値を取得
		  myData[iRow-1][iCol-1] =Number(myTable.rows[iRow].cells[iCol].textContent);
   }
  }

//<!---- テーブルの値の出力--【↑ここまで】---->


myY=new Array(nRow-1);// グローバル変数
myX=new Array(nRow-1);// グローバル変数

for(var iD=0;iD<nRow-1;iD++){
 myY[iD]=myData[iD][0];
 myX[iD]=myData[iD][1];
}


    trace1 = {
      x: myX,
      y: myY,

      type: 'scatter'
    };

    data = [trace1];

     layout = {
      xaxis: {range: [0, 200], title: "経過時間(分)"},
      yaxis: {range: [-10, 110], title: "温度(℃)"},
      title: "温度変化 "
    };

    Plotly.newPlot('myPlot', data, layout);
  


    window.setInterval(function(){
      google.script.run.withSuccessHandler(onSuccess2).GetSheet();

       function onSuccess2(res){

        //コメントの取得 
        var msg = document.getElementById("msg");

        //コメントの更新 
        msg.textContent ="最新データ 経過時間"+res[1]+"分 温度"+res[0]+"";

        // data[1] = {...} の場合、凡例で非表示にしても1秒後に表示されしまうので注意
            myY.push(Number(res[0]));
            myX.push(Number(res[1]));

            Plotly.update('myPlot', data, layout);
     }

   }, 3000);      
 

</script>
<!---- JavaScriptの記述-【↑ここまで】---->

</body>
</html>

スプレッドシートを除くためのリンクを表示させるため、★スプレッドシートのID★となっている部分が1カ所あります。ここは各自のものを記入してください。

これをデプロイすると、温度変化がグラフとしてリアルタイムでWEB上から見られます。

以上、結構複雑な仕組みになりました。
うまく動くことを!

それから、100V電源を使いますので、くれぐれもご安全に!


この記事が気に入ったらサポートをしてみませんか?