見出し画像

【GASでIoT】GASと「Pico W」を使ったギミックのケーシングを考える~(6)ラズベリーパイ Pico Wにプログラムを書き込む~

Goole Apps Script(GAS)を使って、ネット通信可能なマイコンと連携して、「IoT」をするのが本記事シリーズです。

今回取り上げるIoTのギミックは、スプレッドシートに表記された天気予報の情報を、マイコンで読み取って、予報内容をLEDの点灯色で表示する「お天気インジゲータ」です。

そして特に本記事シリーズでは、低コストで見栄え良くするために「ラズベリーパイPico W」を使い、これをペーパークラフトの箱を使ってケーシングすることを試み、表示デバイスである「NeoPixcel」をレンズカバー付きでケース材におさめ、自作の配線ツールで「ラズベリーパイPico W」、「バッテリー」と「NeoPixcel」を配線してみました。

また、情報源となるスプレッドシートのデータは、こちらの記事で作成しています。


記事では、インポート用のワークシートを用意しています。地域名などを修正してお使い頂けます。

記事では、このシートで必要な、ユーザ関数ImportJSON()と、データ転記のためのスクリプトも掲載しています。再現される場合は、この対応も忘れずお願いします。

上記の表を、以下の単純なテキストに置き換えて、これを外部から読み取れる様にシートをWEBとして公開して使います。

セキュリティ面などから、ブックは公開せず、対象シートだけを公開する様にします。


今回の記事は、用意したギミックで、スプレッドシートの公開データ利用するため、ラズベリーパイPico Wにプログラムを書き込みます。

一連の記事シリーズの最終記事なります。

この記事は、一介のアマチュアが、断片的な手がかりを寄せ集め、試行錯誤しならがら行った記録です。

ラズベリーパイPico Wのプログラム書き込みには、「Arduino IDE」を使っていますが、失念による「手順の記載もれ」、たまたま上手くできた条件を見逃している「前提条件の未記載」、誤認や理解の浅さによる「誤り」を含んでいる可能性が大いにあります。

また、配線をはじめとしたハードウェアの準備では、様々の理由により、意図した様に作動しない場合がありえます。

こうした背景から、本記事の内容がうまく再現できない場合があることをご了承頂き、自己責任、自己解決を前提にお読み頂くことをお願いします。


HTTPSにクライアント側でアクセスするための、ラズベリーパイPico Wのプログラム~引用するテストプログラムを振り返る~


今回ご紹介するプログラムは、以下の記事でご紹介した、ラズベリーパイPico WとGASを連携させるテストプログラムを、ほぼそのまま引用し、受け取ったデータを元にNeoPixcelを点灯させる部分を加えてたものです。

他の環境でうまく開発できなかった事もあり、開発環境は「Arduino IDE」で、使用言語は「C言語」を使っています。

そのプログラムをご紹介する前に、少し上のテストプログラムの振り返りを記載します。

ベースとなるテストプログラムの振り返り①~どうして「Arduino IDE」というマイナー環境を使っているのか~

ネットに公開されたGASにアクセスするには、HTTPSではじまるURLに、クライアント側としてアクセスする必要があります。

ところが、ラズベリーパイPico Wを使ってHTTPSへアクセスするプログラム例がなかなか見つかりません。

ほとんどは、ラズベリーパイPico Wをサーバ側として使うものであり、残った事例も、HTTP(Sがない)ではじまるサイトへのアクセス事例でした。

HTTPSへアクセスするには、SSL認証の処理を行う必要がありますが、どうプログラムしてよいか判らず途方に暮れました。

そんな時見つけた希少なHTTPSへのアクセスするためのプログラムが、「Arduino IDE」で、使用言語は「C言語」を使ったものだったのです。


ベースとなるテストプログラムの振り返り②~どうして引用データを記したスプレッドシートをWEB公開するのか~

GASを、ラズベリーパイPico Wで連携させるには、GASの利用URLへアクセスした上で、そのレスポンスを受け取る必要があります。

ところが、ラズベリーパイPico Wのでは、レスポンスの取得ができませんでした。OSを有するタイプのラズベリーパイ(ラズベリーパイ Zero W など)では容易に取得できたのですが・・・。

GASは、レスポンスを返す際には、アクセスしたのとは異なるリダイレクト先のURLから返しますが、Pico Wでは、このリダイレクト先を追いかけられない様です。

仕方なく、HTTPSアクセスはデータの引き渡し用途に限定し、データの受け取りは、レスポンス経由ではなく、異なる方法で行う方法にしました。

使うデータはシンプルなものですので、最も簡便な方法として、スプレッドシートをWEB公開して、公開URLからデータを得ることにしました。(このURLも、HTTPSではじまるものです)

ただし受け取るデータには、スプレッドシートの書式設定など余計なテキストが多数含まれるため、必要なデータは特定の文字列で挟んだ文字列としておくことで、利用したいテキスト部分のみを抽出して使う事にしました。


開発環境「Arduino IDE」でのライブラリ類の準備

ラズベリーパイPico Wの書き込みプログラムを書き込む前に・・・

以下に、ラズベリーパイPico Wの書き込みプログラムをご紹介します。ArduinoIDE上で、事前に、以下の必要なライブラリ類を導入しておいてください。

ボードマネージャ:Raspberry Pi Pico/Rp2040 by Earlephilhower



ライブラリ:Adafruit NeoPixcel by Adafruit


ブラウザからの、ルート認証の取得


準備はまだあります。

また、HTTPSへアクセスするために、ご自身のCのブラウザから、WEBページとして公開したスプレッドシートにアクセスし、SSL認証を通過するための「ルート認証」を取得しておいてください。

ルート認証は、長いテキストコードですが、、取得方法は以下の記事を参考にしてください。

ブラウザにもよりますが、鍵マークをクリックしてメニューを開いて取得します。



それでは、RaspberryPicoWのプログラム


読み取り用のWEBページのWEB公開ができたら、そのURLを控えた上で、プログラムを記述します。


このプログラムでは、テストプログラムの内、GASへのアクセス部分は不要なため削除し、WEBページとして公開したシートへのアクセス部分のみ利用しています。

手元環境で動作することは試していますが、Raspberry Pico Wの様な、OSという仲立ち役の無いデバイスではプログラムはとてもシビアで、特にネット通信はちょっとした事で、プロセスが止まりがちです。

申し訳ございませんが、問題については自己解決を前提にお試しください。


なお、実機を伴うトライでは、さまざまな事が原因で、うまく動かないことが起きがちです。

RaspberryPicoWの初学者の方は、本記事のプログラムを試す前に、一度以下の記事にトライ頂くことをお勧めします。

それでは、プログラムです。Arduino IDE で記述します。


なお、以下の4ヵ所は各自で書き換えてください。
・接続先のWiFiルータのSSID
・接続先のWiFiルータのパスワード
・引用するWEB公開したスプレッドシートのURL
・ルート認証(テキストデータ)
(コード中で★マークを付けています★)

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

1.ボードマネージャ「arduino-pico(Erlephilhower氏作)」を導入してください。
導入後のサンプルにある以下を参考にしています。
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 20 

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

//ピクセルデータの更新間隔(ミリ秒)

#define DELAYVAL 200 // Time (in milliseconds) to pause between pixels

//---↑NeoPixelライブラリ↑----

#ifndef STASSID

//★各自のSSID★を書き換え

#define STASSID "★各自のSSID★"

//★各自のPassWord★を書き換え

#define STAPSK "★各自のPassWord★"

#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 strip 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セル読み込み-★各自の公開シートID★は書き換え---------
    if (https.begin("https://docs.google.com/spreadsheets/d/e/★各自の公開シートID★/pubhtml?gid=296573493&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<21; 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();

  }

}

解説は次の記事で行います。



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