【GASでIoT】GASと「Pico W」を使ったギミックのケーシングを考える~(6)ラズベリーパイ Pico Wにプログラムを書き込む~
Goole Apps Script(GAS)を使って、ネット通信可能なマイコンと連携して、「IoT」をするのが本記事シリーズです。
今回取り上げるIoTのギミックは、スプレッドシートに表記された天気予報の情報を、マイコンで読み取って、予報内容をLEDの点灯色で表示する「お天気インジゲータ」です。
そして特に本記事シリーズでは、低コストで見栄え良くするために「ラズベリーパイPico W」を使い、これをペーパークラフトの箱を使ってケーシングすることを試み、表示デバイスである「NeoPixcel」をレンズカバー付きでケース材におさめ、自作の配線ツールで「ラズベリーパイPico W」、「バッテリー」と「NeoPixcel」を配線してみました。
また、情報源となるスプレッドシートのデータは、こちらの記事で作成しています。
上記の表を、以下の単純なテキストに置き換えて、これを外部から読み取れる様にシートをWEBとして公開して使います。
今回の記事は、用意したギミックで、スプレッドシートの公開データ利用するため、ラズベリーパイPico Wにプログラムを書き込みます。
一連の記事シリーズの最終記事なります。
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を控えた上で、プログラムを記述します。
なお、実機を伴うトライでは、さまざまな事が原因で、うまく動かないことが起きがちです。
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();
}
}
解説は次の記事で行います。