日記です。
見たところESP32からTwitterに低めのやる気で投稿する際に、AWS LambdaやCloudFunctionを噛ませている例がなかったので、書いておきます。
はじめに
この度、初めてESP32関連のデバイスに触れました。やりたかったこととしては、物理ボタンを押すとツイートを投稿することです。
先例を探していくと以下の手法が見つかりました
- 外部の中継サービスにHTTPでツイート文を送信し、中継サービスがツイートする
- IFTTTにHTTPSで送信し、IFTTTがツイートする
- ESP32からツイートする。ライブラリあんまりないのでゴリゴリ実装する。
まず、HTTPは個人的には使いたくありません。IFTTTなどのサービスも費用がかかるので避けています。
ESP32から直接ツイートを投稿するのは、これができるのがベストなのですが、既存のライブラリもろくになさそうです。Twitter API から Arduino – ESP32 を使ってトレンドツイートを取得してみたという先行事例はあるのですが、ここまでの気合は今日はありません。
Espruino on ESP32 で Twitter(その3) - いっぺーちゃんの いろいろやってみよ~はJavaScriptでツイート投稿をしています。終わってから見つけましたが、これでもよかったな
というわけで、次のようなフローで組みました。M5StickからCloudFunctionにはお気楽HTTPSリクエストを投げ、CloudFunctionからTwitterに投稿してもらいます。先の例でいう中継サービスを自分で作る感じですね。
なんとなく多数派っぽいArduinoので書いていましたが、JavaScriptやPythonでも書けるらしいですね。
ボタンを押すとCloudFunctionを発火させる
HTTPSを叩く方法です。先例がいろいろあります。
- Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法
- WiFiClientSecure - Arduinoで遊ぶページ
画面の表示のリファレンスです。
ボタンの押し方です。
いい感じに足し合わせるとこんな感じになります
スケッチ
#include <M5StickCPlus.h>
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
// Googleのルート証明書 Googleでない場合は異なる
const char * rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n"\
"MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw\n"\
"CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\n"\
"MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\n"\
"MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\n"\
"Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA\n"\
"A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo\n"\
"27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w\n"\
"Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw\n"\
"TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl\n"\
"qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH\n"\
"szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8\n"\
"Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk\n"\
"MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92\n"\
"wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p\n"\
"aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN\n"\
"VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID\n"\
"AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\n"\
"FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb\n"\
"C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe\n"\
"QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy\n"\
"h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4\n"\
"7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J\n"\
"ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef\n"\
"MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/\n"\
"Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT\n"\
"6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ\n"\
"0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm\n"\
"2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb\n"\
"bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c\n"\
"-----END CERTIFICATE-----\n";
WiFiMulti WiFiMulti;
void setup() {
Serial.begin(115200);
Serial.println();
// WiFiの初期設定・接続
WiFi.mode(WIFI_STA);
WiFiMulti.addAP("{WiFiのSSID}", "{WiFiのパスワード}");
M5.begin();
Serial.print("Waiting for WiFi to connect...");
while ((WiFiMulti.run() != WL_CONNECTED)) {
Serial.print(".");
delay(250);
}
// 画面とシリアルにIPアドレスを出しておく
Serial.println(WiFi.localIP());
M5.Lcd.setCursor(0, 0);
M5.Lcd.println(WiFi.localIP());
}
void tweet(int button) {
WiFiClientSecure client;
client.setCACert(rootCACertificate);
// 接続先
const char * server = "asia-northeast1-your-project-name.cloudfunctions.net";
if (!client.connect(server, 443))
Serial.println("Connection failed");
else {
Serial.println("Connected to server");
client.println(String("GET https://") + String(server) + String("/your-function-name?auth=12345&button=") + String(button == 0 ? "A" : "B") + String(" HTTP/1.0\r\n"));
client.println(String("Host: ") + String(server));
client.println("User-Agent: ESP32");
client.println("Connection: close");
client.println();
}
int status_code = 0;
while (client.connected()) {
String line = client.readStringUntil('\n');
Serial.println(line);
if (status_code == 0) {
Serial.println(line.substring(9, 12));
status_code = line.substring(9, 12).toInt();
}
if (line == "\r") {
Serial.println("headers received");
break;
}
}
while (client.available()) {
char c = client.read();
Serial.write(c);
}
client.stop();
M5.Lcd.setCursor(0, 0, 4);
M5.Lcd.setTextSize(1);
if (status_code == 200) {
// ツイートをして色が変わると楽しい
M5.Lcd.fillScreen(NAVY);
M5.Lcd.setTextColor(WHITE, NAVY);
M5.Lcd.println(String("Tweeted!\n") + String(button == 0 ? "A" : "B"));
} else {
M5.Lcd.fillScreen(PURPLE);
M5.Lcd.setTextColor(WHITE, PURPLE);
M5.Lcd.println("Tweet failed.");
}
delay(3000);
M5.Lcd.fillScreen(BLACK);
}
void loop() {
M5.update();
M5.Lcd.setCursor(0, 12, 4);
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.println("ready");
if (M5.BtnA.wasPressed()) {
Serial.println("BtnA.wasPressed() == TRUE");
M5.Lcd.println("Sending...");
tweet(0);
} else if (M5.BtnB.wasPressed()) {
Serial.println("BtnB.wasPressed() == TRUE");
M5.Lcd.println("Sending...");
tweet(1);
}
delay(200);
}
CloudFunctionからツイートを送る
TwitterのAPIキーやアクセストークンなどを取得する必要があります。色々なサイトで紹介されています。Read/Write権限を貰うところまで忘れずにやりましょう。
ライブラリや実装はなんでもいいんですがnode-twitter-api-v2を使いました。CloudFunctionは次の設定にしました。
title | 設定 |
---|---|
環境 | 第1世代 |
トリガー | HTTP, 未認証の呼び出しを許可, HTTPSが必須 |
メモリ | 128MB |
タイムアウト | 20秒 |
最小インスタンス数 | 0 |
最大インスタンス数 | 3 |
CloudFunctionsのコード
{
"name": "sample-http",
"version": "0.0.1",
"dependencies": {
"twitter-api-v2": "^1.12.0"
}
}
/**
* Responds to any HTTP request.
*
* @param {!express:Request} req HTTP request context.
* @param {!express:Response} res HTTP response context.
*/
const TwitterApiV2 = require('twitter-api-v2');
exports.helloWorld = async (req, res) => {
if (req.query.auth === undefined || req.query.auth !== "12345qwerty") {
res.status(403).send("")
return
}
const appKey = "12345"
const appSecret = "67890"
const accessToken = "233456-abcde"
const accessSecret = "abcdefg"
const twitterClient = new TwitterApiV2.TwitterApi({
appKey,
appSecret,
accessToken,
accessSecret
});
const rwClient = twitterClient.readWrite;
let tweetText = ""
const jstNow = new Date((new Date()).getTime() + 9 * 3600 * 1000).toISOString().split('.')[0]
const textList = [
"にゃーん", "わんわん", "がおー"
]
tweetText += textList[~~(Math.random() * textList.length)]
tweetText += ` ${jstNow}`
const {
data: createdTweet
} = await rwClient.v2.tweet(tweetText);
console.log('Tweet', createdTweet.id, ':', createdTweet.text);
let message = req.query.message || req.body.message || 'Hello World!';
res.status(200).send(message);
};
おわりに
お手軽に直接ツイートしたいのですが、M5Stick自体も初心者だったので、HTTPSを送信するところだけ頑張りました。面倒くさそうなところはCloudFunctionsに丸投げしています。AWS Lambdaとかでもできると思います。