M5Stackでbitflyer のビットコイン価格を表示してみました。
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なので、これでも動く。
ソースコードです。
# 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();
}
}