Twitter API から Arduino – ESP32 を使ってトレンドツイートを取得してみた

ESP32 ( ESP-WROOM-32 )

HMAC-SHA1 を mbedtls ライブラリで流用することについて

Twitter APIs を使うにあたって、HMAC-SHA1 方式の署名キー生成ライブラリが必要です。
ただ、Arduino core for ESP32 には、HMAC-SHA1 ライブラリは見当たりませんでした。

そこで、いろいろとネットで調べると、chaeplinさんが GitHub 上で、Arduino-ESP8266 で Twitter API を使うスケッチを公開されていました。

https://gist.github.com/chaeplin/32dd002ddc5fe92d026055130a519b72

見ていただけると分かるのですが、これには HMAC-SHA1関数がありますね。
そして、この関数は、Arduino core for ESP32 および ESP8266 の開発メンバーでお馴染みの Ivan Grokhotkov ( igrr )さんのコードを参考にされているとのこと。

https://github.com/igrr/axtls-8266/blob/master/crypto/hmac.c

これのライセンスは axTLS のライセンスを引き継いでいるようですので、それを参照してください。
著作権は Cameron Rich さんです。
axTLS というのは良く知らないのですが、要するに組み込みマイコン用に開発されたライブラリのようです。

残念ながら、このchaeplin さんの ESP8266 用スケッチでは、ESP32 では動作しません。

そこで、私がいろいろ試行錯誤した結果、なんと、Arduino core for ESP32 の WifiClientSecure でも使われている、mbedtls ライブラリが使えることを発見しました!!

後述するサンプルコードを参照していただければ分かるのですが、SHA1 のところを mbedtls_sha1 に変えるだけでほぼ問題無く動作しました。
これには我ながら感動してしまいました。
これで、sha1.c ファイルなどの外部ファイルを取り込まなくて済みます。

mbedtls_sha1 ライブラリ関数を使って、上記で作成したsignature base string と signing key で32桁のハッシュ値を生成します。

それを、BASE64 エンコードして 20桁の英数値に変換するのですが、Arduino core for ESP32 に base64ライブラリが入っているので、それを使えばOKです。

この署名キーが生成できさえすれば、Twitter API は自由に使えたも同然です。
スバラシイですね。

それにしても、Arduino core for ESP32 ってホントに何でもできるんですね。
Espressif Systems 開発メンバーの方々の努力もホントに頭が下がります。
こういう高度なものを私の様な素人が無料で使わせて頂けることに感謝したいですね。

Unicode ( UTF16 ) 文字列を UTF-8 文字列に変換する方法

Twitter APIs からツイートを取得する場合、JSON 形式で返って来る日本語文字列は Unicode ( UTF16 )形式です。

例えば、以下のような文字列

"ものづくりの根底"

の場合、

\u3082\u306E\u3065\u304F\u308A\u306E\u6839\u5E95

というように、’¥’またはバックスラッシュ ‘\’ の後に ’u’ が付いて、その後に16進数の2バイトコードで日本語1文字を表現している形式です。

Arduino IDE のスケッチやシリアルモニターの場合は、1.8.2 以降は UTF-8 で表示されますので、日本語文字を表示させるためには、UTF-8 に変換せねばなりません。

いろいろ調べたところ、幸いなことに、Arduino core for ESP32 の中の xtensa-esp32 の C++ ライブラリの中に、Linux の GCC ライブラリと同じものが入っているので、意外と簡単に変換できました。
変換方法は以下の記事で紹介しておりますので、参照してみてください。
後述するスケッチ例では、それを使用しています。

Unicode ( UTF16 ) 文字列を UTF-8 に変換する方法 ( Arduino – ESP32 )

Arduino – ESP32 スケッチの作成

では、上記を踏まえて、chaeplin さんと Ivan Grokhotkov さんのスケッチを大幅改良した以下のスケッチを、Arduino IDE に入力してみてください。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

/*
 * mgo-tec modified for code created by chaeplin for ESP32
 * Reference: https://gist.github.com/chaeplin/32dd002ddc5fe92d026055130a519b72
 * 
 * ssl_hmac_sha1 function modified by mogotec for mbedtls
 * ssl_hmac_sha1 axTLS license.
 */
#include <WiFiClientSecure.h>
#include <WiFiMulti.h>
#include <WiFiUdp.h>
#include <TimeLib.h>

#include <base64.h>
#include "mbedtls/sha1.h"

#include <codecvt>
#include <string>
#include <cassert>
#include <locale>

WiFiMulti wifiMulti;

const char *ssid = "xxxxx"; //ご自分のルーターのSSIDに書き換えてください。
const char *password = "xxxxx"; //ご自分のルーターのパスワードに書き換えてください。

#define SHA1_SIZE 20

//Twitter api root CA
const char* root_ca = 
"-----BEGIN CERTIFICATE-----\n" \
"MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\n" \
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" \
"d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n" \
"ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\n" \
"MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n" \
"LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n" \
"RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n" \
"+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\n" \
"PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n" \
"xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\n" \
"Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\n" \
"hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\n" \
"EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\n" \
"MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\n" \
"FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\n" \
"nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\n" \
"eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\n" \
"hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n" \
"Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\n" \
"vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n" \
"+OkuE6N36B9K\n" \
"-----END CERTIFICATE-----\n";

const char consumer_key[]    = "xxxxxxxx";
const char consumer_secret[] = "xxxxxxxxxxxxxxxxxxxx";
const char access_token[]    = "xxxxxxxxxxxxxxxxxxxx";
const char access_secret[]   = "xxxxxxxxxxxx";

const char *base_host        = "api.twitter.com";
const char *base_URL         = "https://api.twitter.com/1.1/trends/place.json";
const char *base_URI         = "/1.1/trends/place.json";
const int httpsPort           = 443;

String woeid = "23424856"; //WOEID ( Japan )

const char *key_http_method        = "GET";
const char *key_consumer_key       = "oauth_consumer_key";
const char *key_nonce              = "oauth_nonce";
const char *key_signature_method   = "oauth_signature_method";
const char *key_timestamp          = "oauth_timestamp";
const char *key_token              = "oauth_token";
const char *key_version            = "oauth_version";
//const char *key_status             = "";
const char *key_signature          = "oauth_signature";
const char *value_signature_method = "HMAC-SHA1";
const char *value_version          = "1.0";

//-------NTPサーバー時刻取得引数初期化-----------------------------
IPAddress _NtpServerIP;
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
const int timeZone = 9;     // Tokyo
WiFiUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets
//--------------------------------------------------------

#define Display_MaxData 10
String unicode_str[Display_MaxData];
uint32_t LastTime = 0;

//**********セットアップ関数************************
void setup()
{
  Serial.begin(115200);
  delay(10);

  Serial.println();
  Serial.print(F("Connecting to "));
  Serial.println(ssid);

  wifiMulti.addAP(ssid, password);

  Serial.println(F("Connecting Wifi..."));
  if(wifiMulti.run() == WL_CONNECTED) {
      Serial.println("");
      Serial.println(F("WiFi connected"));
      Serial.println(F("IP address: "));
      Serial.println(WiFi.localIP());
  }
  delay(1000);

  //NTPサーバーから時刻を取得---------------------------
  const char *NtpServerName = "time.windows.com";
  WiFi.hostByName(NtpServerName, _NtpServerIP);
  Serial.print(NtpServerName);
  Serial.print(": ");
  Serial.println(_NtpServerIP);
  Udp.begin(localPort);
  setSyncProvider(getNtpTime);
  Serial.print(F("now="));
  Serial.println(now());
  delay(100);

  Tweet_Get(); //ツイート取得
  LastTime = millis();
}
//**********メインループ**********************
void loop(){
  if(millis() - LastTime > 180000){ //Tweet get every 3 minutes.
    Tweet_Get(); //ツイート取得
    LastTime = millis();
  }
}
//**********ツイート取得************************
void Tweet_Get() {
  uint32_t value_timestamp  = now();
  uint32_t value_nonce      = 1111111111 + value_timestamp;

  Serial.println(F("--------------------------"));
  String status_all = "";
  String parameter_str = make_parameter_str(status_all, value_nonce, value_timestamp);
  String sign_base_str = make_sign_base_str(parameter_str);
  String oauth_signature = make_signature(consumer_secret, access_secret, sign_base_str);
  String OAuth_header = make_OAuth_header(oauth_signature, value_nonce, value_timestamp);

  Serial.print(F("OAuth_header = "));
  Serial.println(OAuth_header);
  TwitterAPI_HTTP_Request(base_host, OAuth_header, status_all);
  
  Serial.println(F("----------GET Twitter Trends Unicode ( UTF16 )-------------"));
  for(int i=0; i<Display_MaxData; i++){
    Serial.println(unicode_str[i]);
  }

  Serial.println(F("----------GET Twitter Trends Unicode ( UTF-8 )-------------"));
  for(int i=0; i<Display_MaxData; i++){
    Serial.println( UTF16toUTF8( unicode_str[i] ) );
  }
}
//*************** HTTP GET Request***************************************
void TwitterAPI_HTTP_Request(const char* base_host, String OAuth_header, String status_all){
  WiFiClientSecure client;

  client.setCACert(root_ca);

  if (client.connect(base_host, httpsPort)) {
    Serial.print(base_host); Serial.print(F("-------------"));
    Serial.println(F("connected"));

    String str01 = String(key_http_method) + " " + String(base_URI) + "?id=" + String(woeid) + " HTTP/1.1\r\n";
    str01 += "Accept-Charset: UTF-8\r\n";
    str01 += "Accept-Language: ja,en\r\n";
    String str02 = "Authorization: " + OAuth_header + "\r\n";
    str02 += "Connection: close\r\n";
    str02 += "Content-Length: 0\r\n";
    str02 += "Content-Type: application/x-www-form-urlencoded\r\n";
    str02 += "Host: " + String(base_host) + "\r\n\r\n";

    client.print( str01 );
    client.print( str02 );

    Serial.println(F("-------------------- HTTP GET Request Send"));
    Serial.print( str01 );
    Serial.print( str02 );

    String res_str = "";
    String name_str = "";

    uint16_t from, to;
    uint8_t n_cnt = 0;
    String name_begin_str = "\"name\":\"";
    int16_t name_begin_flag = 0;
    Serial.println(F("--------------------HTTP Response"));

    while(client.connected()){
      while (client.available()) {
        res_str = client.readStringUntil('\n');
        Serial.println(res_str);
        if(res_str.indexOf("\r") <= 2){
          Serial.println(F("-------------JSON GET ALL------------"));
          while(client.connected()){
            while(client.available()){
              res_str = client.readStringUntil(',');
              name_begin_flag = res_str.indexOf(name_begin_str);
              
              if( name_begin_flag >= 0){
                from = name_begin_flag + name_begin_str.length();
                to = res_str.length() - 1;
                name_str = res_str.substring(from,to) + '\0';
                Serial.println(name_str);
                name_str.replace("#", ""); //ハッシュタグ消去

                if(n_cnt < Display_MaxData){
                  unicode_str[n_cnt] = name_str;
                }
                name_str = "";
                n_cnt++;
                res_str = "";
              }
            }
          }
        }
      }
    }
    client.flush();
    delay(10);
    client.stop();
    delay(10);
    Serial.println(F("--------------------Client Stop"));
  }else {
    // if you didn't get a connection to the server2:
    Serial.println(F("connection failed"));
  }
}
//*************************************************
String make_parameter_str(String status_all, uint32_t value_nonce, uint32_t value_timestamp) {
  String parameter_str = "id=" + woeid;
  parameter_str += "&";
  parameter_str += key_consumer_key;
  parameter_str += "=" ;
  parameter_str += consumer_key;
  parameter_str += "&";
  parameter_str += key_nonce;
  parameter_str += "=";
  parameter_str += value_nonce;
  parameter_str += "&";
  parameter_str += key_signature_method;
  parameter_str += "=";
  parameter_str += value_signature_method;
  parameter_str += "&";
  parameter_str += key_timestamp;
  parameter_str += "=";
  parameter_str += value_timestamp;
  parameter_str += "&";
  parameter_str += key_token;
  parameter_str += "=";
  parameter_str += access_token;
  parameter_str += "&";
  parameter_str += key_version;
  parameter_str += "=";
  parameter_str += value_version;
  Serial.print(F("parameter_str = "));
  Serial.println(parameter_str);
  return parameter_str;
}
//*************************************************
String make_sign_base_str(String parameter_str) {
  String sign_base_str = key_http_method;
  sign_base_str += "&";
  sign_base_str += URLEncode(base_URL);
  sign_base_str += "&";
  sign_base_str += URLEncode(parameter_str.c_str());
  Serial.print(F("sign_base_str = "));
  Serial.println(sign_base_str);
  return sign_base_str;
}
//*************************************************
String make_signature(const char* secret_one, const char* secret_two, String sign_base_str) {
  String signing_key = URLEncode(secret_one);
  signing_key += "&";
  signing_key += URLEncode(secret_two);
  Serial.print(F("signing_key = "));
  Serial.println(signing_key);

  unsigned char digestkey[32];
  mbedtls_sha1_context context;

  mbedtls_sha1_starts(&context);
  mbedtls_sha1_update(&context, (uint8_t*) signing_key.c_str(), (int)signing_key.length());
  mbedtls_sha1_finish(&context, digestkey);

  uint8_t digest[32];
  ssl_hmac_sha1((uint8_t*) sign_base_str.c_str(), (int)sign_base_str.length(), digestkey, SHA1_SIZE, digest);

  String oauth_signature = URLEncode(base64::encode(digest, SHA1_SIZE).c_str());
  Serial.print(F("oauth_signature = "));
  Serial.println(oauth_signature);
  return oauth_signature;
}
//*************************************************
String make_OAuth_header(String oauth_signature, uint32_t value_nonce, uint32_t value_timestamp) {
  String OAuth_header = "OAuth ";
  OAuth_header += "id=\"";
  OAuth_header += woeid;
  OAuth_header += "\", ";
  OAuth_header += key_consumer_key;
  OAuth_header += "=\"";
  OAuth_header += consumer_key;
  OAuth_header += "\",";
  OAuth_header += key_nonce;
  OAuth_header += "=\"";
  OAuth_header += value_nonce;
  OAuth_header += "\",";
  OAuth_header += key_signature;
  OAuth_header += "=\"";
  OAuth_header += oauth_signature;
  OAuth_header += "\",";
  OAuth_header += key_signature_method;
  OAuth_header += "=\"";
  OAuth_header += value_signature_method;
  OAuth_header += "\",";
  OAuth_header += key_timestamp;
  OAuth_header += "=\"";
  OAuth_header += value_timestamp;
  OAuth_header += "\",";
  OAuth_header += key_token;
  OAuth_header += "=\"";
  OAuth_header += access_token;
  OAuth_header += "\",";
  OAuth_header += key_version;
  OAuth_header += "=\"";
  OAuth_header += value_version;
  OAuth_header += "\"";
  return OAuth_header;
}
//*************************************************
// Reference: http://hardwarefun.com/tutorials/url-encoding-in-arduino
// modified by chaeplin
String URLEncode(const char* msg) {
  const char *hex = "0123456789ABCDEF";
  String encodedMsg = "";

  while (*msg != '\0') {
    if ( ('a' <= *msg && *msg <= 'z')
         || ('A' <= *msg && *msg <= 'Z')
         || ('0' <= *msg && *msg <= '9')
         || *msg  == '-' || *msg == '_' || *msg == '.' || *msg == '~' ) {
      encodedMsg += *msg;
    } else {
      encodedMsg += '%';
      encodedMsg += hex[*msg >> 4];
      encodedMsg += hex[*msg & 0xf];
    }
    msg++;
  }
  return encodedMsg;
}
//*************************************************
//Reference: https://github.com/igrr/axtls-8266/blob/master/crypto/hmac.c
//License axTLS 1.4.9 Copyright (c) 2007-2016, Cameron Rich
void ssl_hmac_sha1(uint8_t *msg, int length, const uint8_t *key, int key_len, unsigned char *digest) {
  mbedtls_sha1_context context;
  uint8_t k_ipad[64];
  uint8_t k_opad[64];
  int i;

  memset(k_ipad, 0, sizeof k_ipad);
  memset(k_opad, 0, sizeof k_opad);
  memcpy(k_ipad, key, key_len);
  memcpy(k_opad, key, key_len);

  for (i = 0; i < 64; i++)
  {
    k_ipad[i] ^= 0x36;
    k_opad[i] ^= 0x5c;
  }

  mbedtls_sha1_starts(&context);
  mbedtls_sha1_update(&context, k_ipad, 64);
  mbedtls_sha1_update(&context, msg, length);
  mbedtls_sha1_finish(&context, digest);
  mbedtls_sha1_starts(&context);
  mbedtls_sha1_update(&context, k_opad, 64);
  mbedtls_sha1_update(&context, digest, SHA1_SIZE);
  mbedtls_sha1_finish(&context, digest);
}
//********** Unicode ( UTF16 ) to UTF-8 convert ********************************
String UTF16toUTF8(String str){
  str.replace("\\u","\\");
  str += '\0';
  uint16_t len = str.length();
  char16_t utf16code[len];

  int i=0;
  String str4 = "";
  for(int j=0; j<len; j++){
    if(str[j] == 0x5C){ //'\'を消去
      j++;
      for(int k=0; k<4; k++){
        str4 += str[j+k];
      }
      utf16code[i] = strtol(str4.c_str(), NULL, 16); //16進文字列を16進数値に変換
      str4 = "";
      j = j+3;
      i++;
    }else if(str[j] == 0x23){ //'#'を消去
      utf16code[i] = 0xFF03; //全角#に変換
      i++;
    }else{
      utf16code[i] = (char16_t)str[j];
      i++;
    }
  }

  std::u16string u16str(utf16code);
  std::string u8str = utf16_to_utf8(u16str);
  String ret_str = String(u8str.c_str());
  //URLに影響のある特殊文字を全角に変換
  ret_str.replace("+", "+");
  ret_str.replace("&", "&");
  ret_str.replace("\\", "¥");

  return ret_str;
}
//***********************************************************************
std::string utf16_to_utf8(std::u16string const& src){
  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
  return converter.to_bytes(src);
}
//*********************** NTP Time **************************************
time_t getNtpTime(){
  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  sendNTPpacket(_NtpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}
//*********************** NTP Time ************************************
void sendNTPpacket(IPAddress &address){
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;         
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

【解説】

●8-10行:
Arduino core for ESP32 標準のライブラリです。
今回は WiFiMulti ライブラリを使います。
これは、複数のアクセスポイントを登録できるので便利です。

●11行目:
Arduino 標準の Time ライブラリです。
先に述べたように、予めインストールしておいてください。

●13行目:
Arduino core for ESP32 にある base64ライブラリです。

●14行目:
これが、Authorization で必要になる mbedtls ライブラリです。

●16-19行目:
さき程紹介した、Unicode ( UTF16 )文字列を UTF-8 へ変換するための xtensa-esp32 ライブラリのインクルードです。

●23-24行:
ご自分のルーターの SSID と パスワードに書き換えてください。

●29-52行:
これは、Twitter APIs のルートCA証明書です。
これは、154行でセットしていますが、無くても一応通信できます。
このルートCA証明書の取得方法は以下の記事を参照してください。

Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法

●54-57行:
先に述べた方法で取得した、ご自分のキーに書き換えてください。
これは他人に見られないように十分注意してください

●79-84行:
NTP サーバーから時刻を取得する引数初期化です。
これは Arduino IDE サンプルスケッチを流用しています。

●87行;
ここで、シリアルモニターに表示するツイート結果を最大10としています。

●101-109行:
wifiMultiライブラリを使って、ルーターに WiFi接続します。

●113-121行:
ここで、NTPサーバーから現在時刻を取得しています。

●124行:
ここで、Twitter API からデータを取得しています。

●129-132行:
メインループ内で、3分毎にツイートを取得しています。

●135-159行:
Twitter API からツイートを取得する関数です。

●161-234行:
ここで、Twitter API サーバーに HTTP GET リクエストを送信して、返信されてきた JSON 文字列から、ツイートデータを抽出しています。
“name” というキーワードを起点として抽出しています。

●236-265行:
Twitter API のパラメーター文字列を作成している関数です。

●267-276行:
sigunature base 文字列を作成している関数です。

●278-299行:
oauth_signature の値を生成しています。
ここでは、SHA1ライブラリを私が mbedtls ライブラリ用に改変しました。

●301-335行:
ここで、HTTP リクエストのOAuth ヘッダー文字列を作成しています。

●339-357行:
ここでは、文字列を URL エンコード ( %エンコード ) しています。
これは、chaeplin さんが改変したコードをそのまま流用しています。

●361-386行:
これが今回のメインともいえる関数で、HMAC-SHA1 関数です。
私がIvan Grokhotkov さんのスケッチを mbedtls_sha1 ライブラリ用に改変しました。

●388-429行:
Unicode ( UTF16 )文字列を UTF-8 文字列へ変換する関数です。
これについては、以下の記事を参照してください。
Unicode ( UTF16 ) 文字列を UTF-8 に変換する方法 ( Arduino – ESP32 )

●431-467行:
Arduino IDE の NTPサーバー時刻取得のサンプルスケッチを流用しています。

コンパイル書き込み実行

では、インターネットに接続されている、Wi-Fiルーターを起動しておき、Arduino IDE でスケッチをコンパイル書き込み実行してみてください。

間髪入れずに、シリアルモニターを 115200bps で起動し、無事に Twitter API に接続できて、認証されると、下図の様に表示されます。

このように、Twitter からは、日本語文字列はUnicode ( UTF16 )で返ってきます。
アルファベットはそのまま送信されてきます。
これを先ほど述べた UTF16→UTF8変換して日本語文字列をシリアルモニターに表記しています。

ご自分のツイッターページを開いて、下図の様におすすめトレンド欄のデータと比べてみてください。


見ると分かると思いますが、結果が少し違っていますね。

私も良く分からないのですが、Twitter API からの結果が最新なのか、ツイッターページのおすすめトレンド欄が最新なのかは分かりません。
恐らく、私の予想では、Twitter API からのデータが最新だと思われます。

まとめ

どうでしょうか?
うまく動作しましたか?

今回は、とても難解な OAuth 認証と、Unicode ( UTF16 ) の UTF-8変換が肝ですね。
これさえできれば、Twitter API からのデータ取得は自在です。

ネットの情報を見る限り、Arduino – ESP32 で Twitter API を使っている情報は見かけませんので、我ながらよくやったなぁと思っています。

これができれば、Web上で Twitter API を使う応用もできますし、Google Home にツイートを喋らせることもできます。
というか、現にやっています。

後日、その方法は紹介したいと思います。

ということで、今回もこの記事を書くのに疲れ果てました。
もう、こんな割に合わない作業やめようかなぁ・・・。

ではまた・・・。

コメント

  1. けんにぃ より:

    雑誌掲載おめでとうございます☆
    すごいですね!
    これからもご活躍楽しみにしています。

    • mgo-tec mgo-tec より:

      けんにぃさん

      ご無沙汰しております。
      うれしいコメントありがとうございます。
      m(_ _)m
      記事を寄稿したわけではなく、転載ですが、それでも雑誌になるというのは嬉しいものがありますね。

      ところで、今、ESP32 の SSID セレクターライブラリを作成中です。
      近日中に公開予定です。
      これには、パスワード送信が絡んでいるため、SSL通信が必須です。
      そのため、ESP8266ではメモリが少ない為、動作しない可能性があり、ESP8266用の公開は未定です。
      とりあえず、お知らせでした・・・。

  2. けんにぃ より:

    お久しぶりです☆
    転載されるというのは、魅力的な記事だからだと思います☆
    ESP32のライブラリーは興味津々です♪楽しみにしています。まだESP32買ったままで使えてないですが、何か始めたいと思っていますので。
    ESP8266でなんとか粘ってましたが、策尽き果てました(笑)
    希望としてはESP32で大きめドットのディスプレイをSSIDやpasswordをスマホから取得して駆動(一度取得したら繋がらなくならない限りROMから読み出し)し、外部からのコメントも表示(これは元気8266で外部サーバーに書き込みしたファイルの読み出しで出来ています)をしたいと思っています。(ヤフーのニュースは最悪見れなくてもOK)
    元々ヤフーのメッセージを切替していた感じで、ガスの炎監視やコンセントの電流センサーで消し忘れ監視をディスプレイで表示(これも8266ではある程度可能)を考えてます。
    なので、記事を参考にさせてもらいながら色々やってみたいと思いますので楽しみにしています♪

    • mgo-tec mgo-tec より:

      なるほど。
      けんにぃさんのやろうとしていることは、これからの高齢化社会に必須のアイテムになりそうですね。
      スバラシイです。
      そうなると、益々セキュリティの高い通信が重要になってきますね。

      ESP-WROOM-02(ESP8266) は何故かフラッシュが2MB版が販売されていて、凝ったものを作ろうとすると明らかにメモリが足りないですね。
      外付けSRAM や SDカードを追加すればある程度のことはできますが、それならばESP-WROOM-32を使った方が圧倒的に安上がりで、幅広いプログラミングができますね。
      これからは ESP32 を中心に工作していった方が良いと思います。

      ということで、今年度末で多忙ですので、SSIDセレクターはもうちょいお待ちください。

    • mgo-tec mgo-tec より:

      ということで、Arduino – ESP32 で SSLサーバーを構築して、スマホブラウザでセキュアな Wi-Fi アクセスポイントセレクターライブラリ作ってみました。
      以下の記事をアップしましたのでご覧ください。
      Arduino – ESP32 で SSLサーバーを構築し、セキュアな Wi-Fi アクセスポイントセレクターを作ってみました

  3. けんにぃ より:

    ありがとうございます☆
    ついに完成したのですね♪
    是非とも参考にさせてもらいESP32使ってみたいと思います☆
    自分は、ESP8266でスマートコンフィグではとりあえず使えるようになりましたので、これを32でやっていけるように頑張ります♪

    • mgo-tec mgo-tec より:

      OpenSSL で証明書発行が最初の壁ですが、1回作ることが出来れば、あとは簡単です。
      素人コードなので無駄が多いと思いますが、機会があったら使ってみてくださいませ。
      m(_ _)m

タイトルとURLをコピーしました