M5Stackでbitflyerのビットコイン価格を表示

M5Stackでbitflyer のビットコイン価格を表示してみました。


![m5bf.jpg](https://qiita-image-store.s3.amazonaws.com/0/42120/b28515ce-d21e-cb41-9c8e-c59d0c96675d.jpeg)

M5Stackは液晶が付いていてWi-Fi接続できるプログラム可能なデバイスです。 aliexpressで売っています。技適のシールも付いています。

前回の記事との違いは、HTTPSを使っている点です。　

# HTTPSのルート証明書の準備

* bitflyerのAPIはHTTPS経由でアクセスします。 
* HTTPSでアクセスするためにはルート証明書の設定が必要です。
* PCのブラウザでbitflyerにアクセスして、ルート証明書の情報を調べます。
* bitflyer は DigiCertHighAssuranceEVRootCA を使うのでブラウザからエクスポートするか、DigiCertからダウンロードします。
* ブラウザからエクスポートした方が簡単な気がします。
* DigiCertからダウンロードした場合はOpenSSLのコマンドでx509なテキスト形式に変換します。
* ルート証明書はソースコードに埋め込みます。

# メモ
* esp_log_level_set("*", ESP_LOG_VERBOSE) でesp-idfのログを有効化できる
* HTTPSの接続エラーは、最近追加されたlastError()メソッドでエラーの内容を確認できます。
* CONNECTION CLOSE を指定しても、接続が切断されないので JSONの終わりの"}"を検出して結果の終わりを検出している。 今回はネストしていないjsonなので、これでも動く。


ソースコードです。

```cpp

#include <M5Stack.h>
#include <WiFiClientSecure.h>

WiFiMulti WiFiMulti;

int status = WL_IDLE_STATUS;
int lastPrice = 0;
int currentPrice;
int minPrice = 9999999; 
int maxPrice = 0;
const char *servername = "api.bitflyer.jp";
String answer;
WiFiClientSecure client;

//echo | openssl s_client -showcerts -servername api.bitflyer.jp -connect api.bitflyer.jp:443 2>/dev/null | openssl x509 
//echo | openssl s_client -showcerts -servername api.bitflyer.jp -connect api.bitflyer.jp:443 2>/dev/null | openssl x509 -inform pem -noout -text
//https://www.digicert.com/digicert-root-certificates.htm
//https://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt
//openssl x509 -inform DER -in DigiCertHighAssuranceEVRootCA.crt -text
//openssl x509 -inform DER -in DigiCertHighAssuranceEVRootCA.crt -out outcert.pem -text

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-----";

// the setup routine runs once when M5Stack starts up
void setup(){

  esp_log_level_set("*", ESP_LOG_VERBOSE);
  
  WiFiMulti.addAP("SSID", "PASSWORD");
  Serial.begin(115200);

  // Initialize the M5Stack object
  M5.begin();

  // LCD display
  M5.Lcd.println("bitcoin");

  while (WiFiMulti.run() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.printf(".");
  }
  M5.Lcd.println("wifi connect ok");

  ConnectToClient();
}

void ConnectToClient(){

    client.setCACert(root_ca);

  if (client.connect(servername, 443)) {
    // Make a HTTPS request:
    client.print(String("GET https://api.bitflyer.jp/v1/getticker?product_code=FX_BTC_JPY HTTP/1.1\r\n")  +
                 "Host: api.bitflyer.jp\r\n" +
                 "User-Agent: esp-idf/1.0 esp32\r\n" +
                 "Connection: close\r\n\r\n");
  }
  else {
    char buf[256]; 
    client.lastError(buf, 256);
    Serial.println("Connection failed!");
    Serial.printf("reason %s\n", buf);
  }
}

// the loop routine runs over and over again forever
void loop() {

  if (client.available()) {
    char c = client.read();
    answer += c;
  }

  // if detect of JSON, stop the client:
  if (answer.charAt(answer.length() - 1) == '}' ) {
    m5.update();
    client.stop();

    String jsonAnswer;
    int jsonIndex;

    for (int i = 0; i < answer.length(); i++) {
      if (answer[i] == '{') {
        jsonIndex = i;
        break;
      }
    }

    jsonAnswer = answer.substring(jsonIndex);
    jsonAnswer.trim();

    int rateIndex = jsonAnswer.indexOf("ltp");
    String priceString = jsonAnswer.substring(rateIndex + 5, rateIndex + 12);
    Serial.println(jsonAnswer);
    Serial.println("");
    Serial.println(priceString);
    priceString.trim();
    String Amount =  priceString ;
    currentPrice = (priceString).toInt();

    minPrice = min(minPrice,currentPrice);
    maxPrice = max(maxPrice,currentPrice);
    
    m5.Lcd.fillScreen(0x0000);
    m5.Lcd.setFont(&FreeSans9pt7b);
    
    m5.Lcd.setTextColor(RED);
    m5.Lcd.setCursor(20, 20);
    m5.Lcd.printf(("Min: " + String(minPrice).substring(0,priceString.length()) ).c_str());
    
    m5.Lcd.setTextColor(GREEN);
    m5.Lcd.setCursor(205, 20);
    m5.Lcd.printf(("Max: " + String(maxPrice).substring(0,priceString.length()) ).c_str());

    m5.Lcd.setTextColor(WHITE);
    m5.Lcd.setCursor(5, 80);
    m5.Lcd.setFont(&FreeMonoBold24pt7b);
    m5.Lcd.printf("BitFlyer FX");
    m5.Lcd.printf("\r\n");
    
    m5.Lcd.setCursor(50, 140);
    m5.Lcd.printf(Amount.c_str());

    if (currentPrice >= lastPrice) //UP
    {
      m5.Lcd.fillTriangle(140, 205, 180, 205, 160, 180, GREEN);
    }
    else if (currentPrice < lastPrice) //Down
    {
      m5.Lcd.fillTriangle(140, 205, 180, 205, 160, 230, RED);
    }
    
    lastPrice = currentPrice;
    
    // wait 30 seconds and key check
    for (int i = 0; i < 30; i++){
      if(M5.BtnA.wasPressed()) {
        m5.lcd.setBrightness(0);
      }
      if(M5.BtnB.wasPressed()) {
        m5.lcd.setBrightness(25);
      }
      if(M5.BtnC.wasPressed()) {
        m5.lcd.setBrightness(150);
      }
      m5.update();
      delay(1000);
    }
    answer = "";
    Amount = "";
    currentPrice = 0;
    ConnectToClient();
  }
}
```