見出し画像

【GASでIoT】スプレッドシートと連携する「かんたんスマート・カレンダー」を試作する(その4)~スプレッドシートとスクリプト、およびPicoWのスケッチご紹介(続)~

WEBネイティブのアプリである、Googleスプレッドシートは、ネットに繋がる他のデバイスで容易に情報を利用することができます。

そのシンプルな応用例として、スプレッドシートに記載した、イベントが有るか/無いか程度の簡単なカレンダー情報を、LEDの点灯情報で掲示することを試作してみました。

題して「かんたんスマート・カレンダー」です。

このカレンダーのシステムは、サーバ側がGAS、クライアント側がラズベリーパイになっており、ラズベリーパイが発行デバイスを制御して稼働させています。

ラズベリーパイには、安価なPicoWを使用しています。

先回の記事では、この「かんたんスマート・カレンダー」のデータべースとなるスプレッドシートと、GASのプログラムをご紹介しました。

タイトルで提示した、クライアント側である「ラズベリーパイPico W」側の
プログラム(スケッチ)が、まだご紹介できていませんでしたので、今回の記事で取り上げます。

システムでデータベースとして使用するスプレッドシートは、試作版ですので、まともなカレンダーとしてはまだ使えません。

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

GASによるプログラムはの内容は、個人や小規模なグループ内での利用を想定しており、規模の大きなグループや商用での利用は想定しておりませんのでご注意下さい。

ラズパイPicoWのスケッチ・・・HTTPSクライアントとしてアクセスするためのコード


「かんたんスマート・カレンダー」では、クライアント側として、ラズベリーパイPico Wを使っています。これに発光デバイスであるNeoPixcelを接続して制御します。


本記事では、ラズベリーパイ(ラズパイ)Pico W の開発環境に、メジャーなMicroThonyではなく、ArduinoIDEを使用しています。

これは、筆者の環境では、なぜかMicroThonyでうまく書き込めず、たまたま成功したのがArduinoIDEであったこと、およびHTTPSクライアントとして使う為のサンプルプログラムが、この環境で見つかったためです。

ラズパイで通信するためのプログラム例の多くが、ラズベリーパイ側をサーバにして外部から「アクセスさせる」するもので、クライアント側として外部のサイトに「アクセスする」例は少ないです。更にそのサイトが「HTTPS」サイトの場合のプログラム例は殆ど見掛けません。

ArduinoIDEは、その名のとおり、本来は Arduino Uno というマイコンを前提とした環境であり、ラズベリーパイPico Wについては、ライブラリも少なく、正直不便ではあります。

しかし、とにかくにも、ラズベリーパイPico Wをクライアント側にして、HTTPSサイトにアクセスできるプログラムが現時点では限られているため、本開発環境で進めています。

ArduinoIDEではプログラムにC言語を使い、またこのプログラムのことをスケッチと称しています。

開発環境について


PicoWはマイコンであり、それ自身の中でプログラム開発できないので、PicoWの外部でプログラム開発をしたものを書き込む、という手順を踏みます。

開発環境の構築については、以下の記事をご参照ください。

詳しくは上記をお読みいただきたいのですが、環境構築は以下の3ステップでおこないます。

1.ArduinoIDEのPCへのインストール


  開発環境のインストールです


2.ボードマネージャ Raspberry Pi Pico/Rp2040 のインストール

  ArduinoUno以外のマイコンを使うためのライブラリをインストールします。この作業はArduinoIDEの「ボードマネージャ」メニューから実施できます。

3.ライブラリ:Adafruit NeoPixcel のインストール



  ラズベリーパイに接続する発光デバイスを駆動するライブラリをインストールします。この作業はArduinoIDEの「ライブラリ」メニューから実施できます。

プログラミングの前準備:SSLサイトのルート認証の取得

Googleの各サービスは、HTTPSではじまるURLでアクセスしますが、これらのサイトにアクセスするには、ルート認証という認証コードが必要です。

認証コードは、一般的にはOSやブラウザが処理していますが、ラズベリーパイPico WにはOSがブラウザがないため、プログラム中に明示的に記入しておく必要があります。

このコードは、PCのブラウザで、スプレッドシートなどのURLにアクセスしていただき、ブラウザの鍵マークをクリックすることで取得します。

これについても詳細は、上記の記事にありますので、ご参照ください。

スプレッドシートの公開WEBページのURL

もうひとつ、事前に取得しておくのが、前の記事で公開した、スプレッドシートの公開WEBページのURLです。メモ帳などに控えておいてください。

以上を踏まえて、ArduinoIDE上でスケッチを記述します。

ArduinoIDE上でのスケッチ

少ししたにコードが書いてありますが、各自で打ち替える部分がいくつかありますので、以下を確認しておきましょう。

1.WiFi設定の確認

利用するネットワークのSSIDPASSを、それぞれスケッチの以下の★SSID★と部分★PASS★に打ち込んでください。

#ifndef STASSID
#define STASSID "★SSID★"
#define STAPSK "★PASS★"
#endif

2.ルート認証の確認

HTTPSではじまるURLへのアクセスに必要なものです。

const char *jigsaw_cert = R"EOF(
-----BEGIN CERTIFICATE-----
★各自の取得したルート認証★
-----END CERTIFICATE-----

)EOF";
static int cnt = 0; //ループのインターバル
int MyInt = 0;

ルート認証コードで、
-----BEGIN CERTIFICATE----- 
-----END CERTIFICATE----- 
間の文字をコピーして貼り付けください、

3.公開WEBページのURLの確認

公開WEBページのURLで、~e/・・・この部分・・・/pubhtml の部分のUテキストを確認し、スケッチの下の方にある、★公開ページのURL★と★公開ページのGID★部分を各自のコードに修正ください。

//----------スプレッドシートのA1セル読み込み----------
if (https.begin("https://docs.google.com/spreadsheets/d/e/★公開ページのURL★/pubhtml?gid=★公開ページのGID★&single=true&range=A1")) {

スケッチ


以下がスケッチです。上記の打ち替えを行ったら、コンパイルしてPicoWに書き込んでみてください。(NeoPixcelの接続はまだ不要です)

/**
作:Particlemethod:2023/05/20

1.ボードマネージャ「arduino-pico(earlephilhower氏作)」を導入してください。
導入後のサンプルにある以下を参考にしています。
BasicHTTPSClient.ino
https://github.com/earlephilhower/arduino-pico/blob/master/libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino

2.ライブラリ「Adafruit NeoPixel(Adafruit社作)」を導入してください。

*/
 #include  <Arduino.h> #include  <WiFi.h> #include  <HTTPClient.h>

//----↓NeoPixelライブラリ↓---- #include  <Adafruit_NeoPixel.h> #ifdef  __AVR__
 #include  <avr/power.h> // Required for 16 MHz Adafruit Trinket #endif 

// データ線をPicoWのGPIO1とする #define  PIN        1 

// LED素子数を設定する #define  NUMPIXELS 105 

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

//ピクセルデータの更新間隔(ミリ秒) #define  DELAYVAL 200 // Time (in milliseconds) to pause between pixels
//---↑NeoPixelライブラリ↑----
 #ifndef  STASSID #define  STASSID "★SSID★" #define  STAPSK "★PASS★" #endif 

const char *ssid = STASSID;
const char *pass = STAPSK;

WiFiMulti WiFiMulti;

void setup() {

  Serial.begin(115200);
  // Serial.setDebugOutput(true);

  Serial.println();
  Serial.println();
  Serial.println();

  for (uint8_t t = 4; t > 0; t--) {
    Serial.printf("[SETUP] WAIT %d...\n", t);
    Serial.flush();
    delay(1000);
  }

  WiFi.mode(WIFI_STA);
  WiFiMulti.addAP(ssid, pass);

//----↓NeoPixelライブラリ↓---- #if  defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1); #endif 
  pixels.begin(); // INITIALIZE NeoPixel striおp object (REQUIRED)
//---↑NeoPixelライブラリ↑----


}

const char *jigsaw_cert = R"EOF(
-----BEGIN CERTIFICATE-----

★各自の取得したルート認証★

-----END CERTIFICATE-----
)EOF";

static int cnt = 0;

//ループのインターバル
int MyInt = 0;



void loop() {

  //======↓↓↓===NeoPixelの初期動作===↓↓↓=========
  //--------NeoPixelのクリア---------
  pixels.clear(); // Set all pixel colors to 'off'

  //----------赤を全点灯:10ポイントずつ輝度アップ----------
  for(int iL=0; iL<70; iL=iL+10) { 
    for(int i=0; i<NUMPIXELS; i++) {
      pixels.setPixelColor(i, pixels.Color(iL, 0, 0));//Increasing Brightness
    }
    pixels.show();
    delay(DELAYVAL); // Pause before next pass through loop
  }
  pixels.clear(); // Set all pixel colors to 'off'

  //----------緑を全点灯:10ポイントずつ輝度アップ----------
  for(int iL=0; iL<70; iL=iL+10) { 
    for(int i=0; i<NUMPIXELS; i++) {
      pixels.setPixelColor(i, pixels.Color(0, iL, 0));//Increasing Brightness
    }
    pixels.show();
    delay(DELAYVAL); // Pause before next pass through loop
  }
  pixels.clear(); // Set all pixel colors to 'off'

  //----------青を全点灯:10ポイントずつ輝度アップ----------
  for(int iL=0; iL<70; iL=iL+10) { 
    for(int i=0; i<NUMPIXELS; i++) {
      pixels.setPixelColor(i, pixels.Color(0, 0, iL));//Increasing Brightness
    }
    pixels.show();
    delay(DELAYVAL); // Pause before next pass through loop
  }
  pixels.clear(); // Set all pixel colors to 'off'

  //----------緑を逐次点灯----------
  for(int i=0; i<NUMPIXELS; i++) { // For each LED
    pixels.setPixelColor(i, pixels.Color(0, 30, 0));

    pixels.show();   // Send the updated pixel colors to the hardware.
    delay(DELAYVAL); // Pause before next pass through loop
    pixels.clear(); // Set all pixel colors to 'off'
  }
  pixels.clear(); // Set all pixel colors to 'off'
  pixels.show();   // Send the updated pixel colors to the hardware.

  //----------青を逐次追加点灯----------
  for(int i=0; i<NUMPIXELS; i++) { // For each pixel...
    pixels.setPixelColor(i, pixels.Color(0, 0, 30));

    pixels.show();   // Send the updated pixel colors to the hardware.
    delay(DELAYVAL); // Pause before next pass through loop
  }
  delay(DELAYVAL*2); // Pause before next pass through loop
  pixels.clear(); // Set all pixel colors to 'off'
  pixels.show();   // Send the updated pixel colors to the hardware.

  //======↑↑↑===NeoPixelの初期動作===↑↑↑=========

  // wait for WiFi connection
  if ((WiFiMulti.run() == WL_CONNECTED)) {
    HTTPClient https;
    switch (cnt) {
      case 0:
        Serial.println("[HTTPS] using insecure SSL, not validating certificate");
        https.setInsecure(); // Note this is unsafe against MITM attacks
        cnt++;
        break;
      case 1:
        Serial.println("[HTTPS] using secure SSL, validating certificate");
        https.setCACert(jigsaw_cert);
        cnt++;
        break;
      default:
        Serial.println("[HTTPS] not setting any SSL verification settings, will fail");
        cnt = 0;
    }

    Serial.print("[HTTPS] begin...\n");


    //----------スプレッドシートのA1セル読み込み----------
    if (https.begin("https://docs.google.com/spreadsheets/d/e/★公開ページのURL★/pubhtml?gid=★公開ページのGID★&single=true&range=A1")) {  // HTTPS

      Serial.print("[HTTPS] GET...\n");
      // start connection and send HTTP header
      int httpCode = https.GET();

      // httpCode will be negative on error
      if (httpCode > 0) {
        // HTTP header has been send and Server response header has been handled
        Serial.printf("[HTTPS] GET... code: %d\n", httpCode);

        // file found at server
        if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
          String payload = https.getString();

          //----------レスポンスの受け取り----------
          payload = payload.substring(payload.indexOf("▼▼▼▼"),payload.indexOf("●●●●"));
          Serial.println(payload.substring(12));
          payload =payload.substring(12);

          //----------レスポンスを受け取り損ねたら待機時間1秒、受け取ったら1時間----------
          if(payload.substring(0,1)=="D"){MyInt=60*60*1000;}else{MyInt=1000;}

          //======↓↓↓===NeoPixelの点灯===↓↓↓=========
          for(int iC=1; iC<106; iC++){

            //----------0なら消灯----------
            if (payload.substring(iC,iC+1) == "0") {
              pixels.setPixelColor(iC-1, pixels.Color(0, 0, 0));
              pixels.show();

            //----------1なら青色----------
            } else if (payload.substring(iC,iC+1) == "1") {
              pixels.setPixelColor(iC-1, pixels.Color(0, 0, 30));
              pixels.show();

            //----------2なら水色----------
            } else if (payload.substring(iC,iC+1) == "2") {
              pixels.setPixelColor(iC-1, pixels.Color(0, 15, 15));
              pixels.show();

            //----------3なら黄色----------
            } else if (payload.substring(iC,iC+1) == "3") {
              pixels.setPixelColor(iC-1, pixels.Color(15, 15, 0));
              pixels.show();

            //----------4なら赤色----------
            } else if (payload.substring(iC,iC+1) == "4") {
              pixels.setPixelColor(iC-1, pixels.Color(30, 0, 0));
              pixels.show();

            //----------その他は消灯----------
            } else {
              pixels.setPixelColor(iC-1, pixels.Color(0, 0, 0));
              pixels.show();
            }
          }
          //======↑↑↑===NeoPixelの点灯===↑↑↑=========

        }
      } else {
        Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
      }

      https.end();
    } else {
      Serial.printf("[HTTPS] Unable to connect\n");
    }
  }

  //==↓==↓==↓==↓==↓==↓My Code↓==↓==↓==↓====↓==↓==↓==↓
  if(MyInt==1000){
    //----------レスポンスを取れてない場合は次の試行に入る----------
    Serial.println("Wait 10s before next round_InPut...");
    delay(MyInt);
  }else{
    //----------レスポンスを取れていれば1時間待ってリブート----------
    Serial.println("Wait 1h before next round_InPut...");
    delay(MyInt);
    rp2040.reboot();

  }

}


うまく作動すれば、ArduinoIDEのシリアルモニタに、得られた文字が表示されるはずです。(以下の例は通信テスト時のものです)

シリアルモニタの例


スケッチのポイント

このスケッチは、ラズベリーパイPico Wのボードマネージャを導入した際に添付されていた、HTTPSクライアント用のサンプルスケッチを少しばかり修正したものです。

長いコードですが、実際にプログラムした部分はわずかです。

NeoPixcel制御のためのコード

ライブラリを宣言しています。

//----↓NeoPixelライブラリ↓----
#include <Adafruit_NeoPixel.h>

ライブラリが提供する、setPixelColor、show という関数で簡単に制御できます。

pixels.setPixelColor(iC-1, pixels.Color(0, 0, 0));
pixels.show();

データの再取得時のコード

ネットワークに接続してから、スプレッドシートの公開ページのレスポンスを得るまでの間に何らかの問題があって、データを取り損なうケースはしばしばあります。

もともとのスケッチには、その様な場合にエラーをトラップして再トライするコードになっています。

しかし、一定時間後に再度データを取得する際は、こうしたエラートラップをかいくぐってしまうことがしばしばあり、悩んだあげく、インターバルを置いてデータを再度取得する場合は、ラズベリーパイPico Wを再起動させることにしました。

スケッチの最後にある以下の短いコードがそれです。

rp2040.reboot();

マイコンは、デバイスが安価なかわりに、ユーザ側ですべき作業が多く、準備はそれなりに手間がかかります。

OSのあるラズベリーパイシリーズを使えば、デバイスは高額ですが、このあたりがずっと楽になります。プログラムの開発もデバイス自身の中で行えます。

OSのあるラズベリーパイとしてZero Wを 使用した例は以下に記事にしておりますのでご興味の方は覗いてみてください。

次回は、カレンダー本体である発光デバイスについてご説明します。

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