とある実験のーと

趣味ブログ

Arduino MKR WiFi 1010でGoogleサーバ(GAS)対してSSL通信するのにつまずいたのでメモ

Arduino MKR WiFi 1010はWiFi経由でArduino cloudに簡単に繋がるが、Arduiono cloudは課金しないとデータが1日で消えてしまう。

バックアップとしてデータをGoogle spread sheetにもポストしておきたいと思い、Google Apps Script(GAS)経由でスプレッドシートに入れる方法を採用した。

Google spread sheet側の設定は他の方々の設定どおりなのでそちらを参照していただきたい。

ここに初めてたどり着く人は居ないと思うが、念の為簡単に仕組みを説明すると、Google spread sheetに紐付いたGASのURLにデータを付加してGetすることでスプレッドシートに自動的に書き込まれるというものだ。このURLをArduinoで呼ばないと(Getしないと)いけない。

Googleスプレッドシートをウェブアプリ化してシートにネット経由でデータを書き込めるようにしたので、MQTTでパブリッシュしているデータをシートに蓄積するためのデータ転送プログラムも作った。その1 - 知的好奇心 for IoT

【arduino_IDE】googlesheetに書き込んでみた | ラズベリーパン

ESP32からGoogleスプレッドシートにデータを送信する! | DIY × 農業日記

ESP32のライブラリがMKRシリーズでは利用できない

皆が主に報告しているのはESP32を搭載したボードで、そこではあまり苦労なくURLをGet出来ているようだった。

一方で今回用いるMKR WiFi1010は載っているWiFiモジュール等が異なるためESP32で使われてるライブラリ(WiFiClientSecure)が使えないらしい・・・。

最初、ArduinoHttpCliant.h ライブラリを用いてhttp.get (URL)を行おうと思ったがサーバに接続される気配がなかった。

よくよく調べてみると、GoogleへのアクセスはSSL接続が必須であり、ArduinoからURLにアクセスしてもhttpsにリダイレクト等はされないとのことだった(どこか海外サイトに書いてた)。ArduinoHttpCliantはMKRシリーズに最適化されているらしいがSSL接続はできないのか・・・?私の確認不足かもしれない。

ライブラリを探す放浪の旅、そして結局WiFiNINA.hのサンプルコードで出来た・・・

次に試したのは ArduinoBearSSL.h 。BearSSLをArduinoに最適化したものでArduino MKR ZEROでSSL通信に使えるとの報告があり。ただ、これまた私の技量不足がたたって、サンプルコードでwww.google.comに接続出来ず・・・。

iw-circuitdesign.net

このボードが近年出たものだからか、海外でもあまり報告が見当たらず(出来ないという報告はある・・・)、とりあえず周辺のライブラリのサンプルコードを眺めてた。

WiFiNiNA.hのサンプルコード群に"WiFiSSLClient"の記載があることが分かったので、こちらを適応してみたところSSL通信でGoogleと通信出来たことを確認!

WiFiNiNA.hはMKR WiFi 1010でWiFiを利用するために読み込む最も基本的なライブラリ。

ポイントとしては

  • cliantとして "WiFiCliant"ではなく、"WiFiSSLClient client"を定義する。

  • ポートとして "80"ではなく"443"を指定。

*GET等の書式が他のライブラリと異なる。

以下にサンプルコードを記載する。ベースはサンプルコードのままだが、日本語でコメントしている部分は私のメモ。 通信が上手くいかないと、"disconnectiong from server" を吐く。

/*
  This example creates a client object that connects and transfers
  data using always SSL.
  It is compatible with the methods normally related to plain
  connections, like client.connect(host, port).
  Written by Arturo Guadalupi
  last revision November 2015
*/

#include <SPI.h>
#include <WiFiNINA.h>
#include <NTPClient.h>

char ssid[] = "******"; //  your network SSID (name)自分のWiFi環境
char pass[] = "******";    // your network password (use for WPA, or use as key for WEP)自分のWiFi環境
int keyIndex = 0;            // your network key Index number (needed only for WEP)

int status = WL_IDLE_STATUS;
// if you don't want to use DNS (and reduce your sketch size)
// use the numeric IP instead of the name for the server:
//IPAddress server(74,125,232,128);  // numeric IP for Google (no DNS)
const char* server = "www.google.com";    // name address for Google (using DNS)

// Initialize the Ethernet client library
// with the IP address and port of the server
// that you want to connect to (port 80 is default for HTTP):
WiFiSSLClient client;  //ここがポイント?

//**ここはwifi経由で標準時間を取得した名残、結局今回は特に時刻会わせ関係無くSSL通信が出来たので不要****
  WiFiUDP udp;
  NTPClient ntp(udp, "ntp.nict.jp", 0, 1000);  // udp, ServerName, timeOffset, updateInterval
  unsigned long getCurrentTime() {
  WiFiUDP udp;
  NTPClient ntpClient(udp);
  // Set the NTP server and adjust for your timezone offset if needed
  ntpClient.begin();
  ntpClient.setTimeOffset(0); // Example: Adjust time offset for GMT+0
  // Fetch the current time from the NTP server
  ntpClient.update();
  // Get the current UNIX timestamp
  unsigned long currentTime = ntpClient.getEpochTime();
  // Cleanup
  ntpClient.end();
  udp.stop();
  return currentTime;
  }
//************************ここまで******************************

void setup() {
  //Initialize serial and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue:
    while (true);
  }

  // attempt to connect to WiFi network:
  while (status != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);

    // wait 10 seconds for connection:
    delay(10000);
  }
  Serial.println("Connected to wifi");
  printWiFiStatus();
  
//***標準時を所得(今回は結果的に不要)*********
  unsigned long time = getCurrentTime();
    Serial.println(time);
//**********************************************
 

  // if you get a connection, report back via serial:
  //google.comに接続し、検索をポストする。シリアルモニタにhtmlの羅列が出てきたら通信成功。
  if (client.connect(server, 443)) {         //SSL通信の為443を指定
    Serial.println("connected to server");
    // Make a HTTP request:
    client.println("GET  /search?q=arduino HTTP/1.1"); 
    client.println("Host: www.google.com");
    client.println("Connection: close");
    client.println();
  }
}


void loop() {
  // if there are incoming bytes available
  // from the server, read them and print them:
  while (client.available()) {
    char c = client.read();
    Serial.write(c);
  }

  // if the server's disconnected, stop the client:
  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting from server.");
    client.stop();

    // do nothing forevermore:
    while (true);
  }
}

void printWiFiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

GAS用に改変例

以下のスクリプトで、

client.println("GET  /search?q=arduino HTTP/1.1"); 

の部分を、

client.println("GET https://script.google.com/macros/s/AKfycbzRneHkovV9auBKvOjXAeKGjQ*******************/exec?***=***&***=***  HTTP/1.0") ; 
//****は各々

となるように改変することで、GASを動かすURLをポストできた。ここらへんはESP32と同じなので詳しくは書かない。

上手くいったらSerial Monitorにこんな感じに出力される