momoのアイコン画像
momo 2022年02月19日作成 (2022年02月24日更新) © MIT
製作品 製作品 閲覧数 1780
momo 2022年02月19日作成 (2022年02月24日更新) © MIT 製作品 製作品 閲覧数 1780

ESP8266で為替レート表示 / Exchange rate display

ESP8266で為替レート表示 / Exchange rate display

はじめに

最近ちょこっとFXをやっていまして、為替レートが常に気になる今日この頃です。
しょっちゅうスマホでレートチェックしていましたが、それも面倒になり
常時、為替レートを表示してくれるモノがあればいいなぁと思い↓こんなのを作ってみました。

キャプションを入力できます

システム概要

仕組みとしては、為替情報を提供しているサイトからWebAPIを使ってJSONデータを取得します。
そして、その情報を見やすい形に加工してI2C経由でディスプレイにその情報を表示します。

ディスプレイはグラフも表示したかったので128x64ドットの有機LED(OLED) SSD1306を使いました。
Arduino環境だと表示ライブラリもそろっているし、他の方もよく使っているみたいなのでハマった時に
いろいろな情報も得やすいと思います。

インターネット接続する為にはWiFi接続ができるESP-01を使用しました。
ESP-01はESP8266が搭載されて超小型なマイコンボードです。
機能的には他のESP8266搭載ボート同じですが、端子が少なかったりFLASHのサイズが小さいという制限はあります。
WIFI接続するシステムをコンパクトに作るにはよいモジュールだと思います。それに安いし。

為替レートの情報は外為オンラインのサイトで公開されていたので利用させてもらいました。
登録やログイン等も不要でデータが取得できるのでありがたいです。

ハードウェア

モジュールを使い部品点数を少なくし、できるだけ簡素でコンパクトに作りました。

バッテリー駆動も考えましたが単4電池だと3,4本くらいは必要そうで
電池BOXも含めるとかさばりそうなのでやめました。
なので、電源はUSBから供給するようにしDC-DCコンバータで3.3Vに降圧して使っています。

ESP-01のプログラム書き込みの為にはGPIO0をLowにしてリセット解除する必要があります。
その為、通常はRESETとGPIO0端子にタクトSWなどをつけるのですがこれらのスイッチ類も省略しました。
ESP-01はソケットで接続するようにし、プログラムの書き換え時はESP-01を抜いて別基板で書き込むようにしました。

主要部品

# 品名 備考
1 ESP-01ボード 1個400円くらい。
2 SSD1306 LCDディスプレイ 1個800円くらい。AlliExpressだと200円くらいで買えました(1ヵ月くらいかかるけど)。
3 AMS1117-3.3 DC-DC ステップダウン パワーモジュール 10個で600円くらい。
4 マイクロUSB → DIP 5ピン(2.54mm) 変換ボード 10個で500円くらい。
5 抵抗10kΩ x2個 プルアップ用なので無ければ他の抵抗値でもよいかも。
6 SSD1306専用 スタンド AlliExpressで良さげなSSD1306専用の透明なスタンドを見つけたので、そこに回路基板も一緒に詰め込んでみました。ちなみに1個46円でした。
7 ヘッダピンソケット(4x2列) ESP-01接続用。
- USBケーブル、ジャンパ線、ユニバーサル基板、他

配線図

配線
SCL/SDAのラインにもプルアップ抵抗を付けた方がよいかもしれません。
とりあえずこれで動いたので今回はこれで良しとします。
もしかしたらESP-01の端子かOLEDの基盤側でプルアップされているのかな?

ソフトウェア

Arduino IDE 1.8.19 で開発しました。

使用したライブラリは、LCD表示にSSD1306、WiFi接続にWiFiClientSecure
JSONの読み込みにArduinoJsonを使いました。

最近のサイトは、ほとんどHttpsなのでアクセスには認証が必要です。
今回利用したサイトもHttpsだったので為替データを取得するのにちょっと手こずりました…。
認証用のFingerprintを送信する方法もありますが、それだと有効期限が切れると接続できなくなります。
そこで、初期化時にsetInsecure() を実行して証明書による認証を行わないようにしています。

ソースコード

#include <ESP8266WiFi.h> #include <WiFiClientSecure.h> #include <SSD1306.h> #include <ArduinoJson.h> static const char* ssid = "?????????"; static const char* password = "????????"; // FX exchange pair static const char *pair = "USDJPY"; class Rbf { private: static const int buf_size = 128; float buf[buf_size]; int idx, len; public: Rbf() { idx = len = 0; } ~Rbf() {;} void push(float val) { buf[idx++] = val; idx = (buf_size > idx) ? idx : 0; len = (buf_size > len) ? len + 1 : buf_size; } float pop(void) { return -1; // Not supported. }; float ref(int ofs) { return buf[(buf_size + idx - ofs - 1) % buf_size]; } void get_min_max(float *mx, float *mn) { *mx = *mn = ref(0); for (int i=1; i<len; i++) { float r = ref(i); *mn = (r < *mn) ? r : *mn; *mx = (r > *mx) ? r : *mx; } } }; SSD1306 display(0x3c, 0, 2, GEOMETRY_128_64); // LCD setting (slaveadr, sda, scl, oled_type) WiFiClientSecure WiFiClient; Rbf rate; void setup() { // Initialize LCD display.init(); display.flipScreenVertically(); display.setBrightness(128); // 0:low - 254:high display.setFont(ArialMT_Plain_10); // Initialize Wifi display.drawString(0, 10, "Connecting Wifi..."); display.display(); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); WiFiClient.setInsecure(); while (WiFi.status() != WL_CONNECTED) { delay(1000); } char str_ip[16]; IPAddress ip = WiFi.localIP(); sprintf(str_ip, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); display.drawString(0, 20, "Connection successful!"); display.drawString(0, 35, str_ip); display.drawString(0, 45, WiFi.macAddress()); display.display(); delay(3000); } void loop() { TaskGetRate(); TaskDisplay(); delay(1000 * 60 * 10); // 10分間隔で更新 ※サーバーに負荷がかかる気がするのであまり頻繁にアクセスしないように } void TaskDisplay(void) { display.clear(); display.setFont(ArialMT_Plain_10); display.drawString(5, 6, pair); char str_val[8]; sprintf(str_val, "%3.2f", rate.ref(0)); display.setFont(ArialMT_Plain_24); display.drawString(50, 0, str_val); grph_plot(); display.drawRect(0, 25, 128, 39); display.display(); } void TaskGetRate(void) { static const char* host = "www.gaitameonline.com"; static const String url = "/rateaj/getrate"; static const String header = String("GET https://") + host + url + " HTTP/1.1\r\n" + "Host: " + String( host ) + "\r\n" + "User-Agent: ESP8266\r\n" + "Connection: close\r\n\r\n" + "\0"; if (!WiFiClient.connect(host, 443)) { // HTTPS Port:443 disp_error("Https connect error."); return; } WiFiClient.print(header); WiFiClient.flush(); if(!WiFiClient){ disp_error("Send header error."); return; } String res = WiFiClient.readString(); int pos = res.indexOf("\r\n\r\n") + 4; WiFiClient.stop(); DynamicJsonDocument doc(4096); DeserializationError err = deserializeJson(doc, res.substring(pos)); if (err) { disp_error("Json deserialize failed."); return; } for (JsonObject quote : doc["quotes"].as<JsonArray>()) { const char* quote_currencyPairCode = quote["currencyPairCode"]; if (0 == strncmp(quote_currencyPairCode, pair, sizeof(pair))) { // jonson format: {"high":"114.24","open":"114.24","bid":"114.24","currencyPairCode":"USDJPY","ask":"114.50","low":"114.24"}, float value = ((String)(quote["ask"])).toFloat(); rate.push(value); return; } } disp_error("Json parse error."); } int grph_cnv_y(float r_val, float r_max, float r_min) { const int h = 40; // グラフの高さ const float r_lim = 0.4; // レートの最大~最小幅がこの値以下なら拡大しない float d = r_max - r_min; if (d < r_lim) { r_min = (r_max + r_min - r_lim) / 2; d = r_lim; } return 64 - (int)(h * (r_val - r_min) / d); } void grph_plot(void) { int y0, y1; float r_max, r_min; rate.get_min_max(&r_max, &r_min); y0 = grph_cnv_y(rate.ref(127), r_max, r_min); for (int x=1; x<128; x++) { y1 = grph_cnv_y(rate.ref(127 - x), r_max, r_min); display.drawLine(x - 1, y0, x, y1); y0 = y1; } } void disp_error(const char *msg) { display.clear(); display.setFont(ArialMT_Plain_10); display.drawString(0, 10, "Error!!!"); display.drawStringMaxWidth(0, 25, 128, msg); display.display(); delay(1000 * 30); }

ssid とpassword の"?????????"は適宜変更してください。
pair = "USDJPY" には表示したい通貨ペアを大文字で指定してください。
下記のペアが選択可能です。
GBPNZD,CADJPY,GBPAUD,AUDJPY,AUDNZD,EURCAD,EURUSD,NZDJPY,USDCAD,EURGBP,GBPUSD,ZARJPY,EURCHF,CHFJPY,AUDUSD,USDCHF,EURJPY,GBPCHF,EURNZD,NZDUSD,USDJPY,EURAUD,AUDCHF,GBPJPY

外観

たまたまですが、DC-DCモジュールの赤LEDがパイロットランプみたいになっていい感じになりました。
下記は起動した直後なので、まだグラフは表示されません。
キャプションを入力できます

グラフは過去128サンプル分をプロットしています。
一応、最高値~最低値を考慮してグラフが収まるように簡易的にスケーリングして表示しています。
キャプションを入力できます

後ろ側。
キャプションを入力できます

こんな感じでESP-01はソケットで接続するようになっていて、プログラムを書き込む為に外れるようになっています。
キャプションを入力できます

PCモニタ(23inc)の上に置くとこんな感じ。
軽すぎて、固いUSBケーブルだと引きずられて傾いてしまうので要注意。
キャプションを入力できます

あー!暴落~!
キャプションを入力できます

まとめ

為替情報をディスプレイ表示するシステムを最小構成で作ってみました。
インターネットから目的の情報をJSON形式で取得してディスプレイに表示するという
この仕組みは他にもいろいろ応用できそうです。

  • 株価情報の表示
  • 天気予報の表示
  • ニュースヘッドラインの表示
  • バス・電車の到着予定時間の表示
  • HPの更新通知
  • Mail/Lineなどの着信通知
    …など

あと、今回はリセットスイッチすら省きましたが、1個くらいスイッチをつけておけばよかったと後悔…。
スイッチで通貨ペアの切り替えや、表示モード切替して他の情報も表示できたのに。
また時間があったら拡張しよう。

4
momoのアイコン画像
組み込みSWエンジニア
  • momo さんが 2022/02/19 に 編集 をしました。 (メッセージ: 初版)
  • momo さんが 2022/02/24 に 編集 をしました。
  • Closed
    lshnccpkesのアイコン画像 lshnccpkes 2022/08/22

    We are a white label IT Company, 19 years in business, based in India and I have some projects that we want Freelancer or Agency to work on them. Is this something you might be interested in discussing further? Here is our program in more detail: Food Delivery App Development

    1 件の返信が折りたたまれています
  • Opening
    Cookieのアイコン画像 Cookie 2023/09/01

    Esp8266開発ボードに配線4本でGBPJPYのASK表示を簡単に作ることができました。
    しかし、できたらBIDを表示したいので、0.03円少なく表示するにはどうすれば良いのでしょうか?
    おそらく88行目のstr_val辺りをどうにかするとできそうに思うのですが...

    momoのアイコン画像 momo 2023/09/01

    126行目付近の "ask""bid" に変更してみてください。
    ここで、jonson形式のデータから値を抽出しています。

    126: float value = ((String)(quote["ask"])).toFloat();

    やはり、これだけ円安になったら売ですか。今日の雇用統計も下だったし。
    それにしても、この記事を書いた時から$は30円も円安になったな…。

    Cookieのアイコン画像 Cookie 2023/09/03

    ありがとうございます。
    こんなにお返事早いなんて嬉しいです。
    今日早速試してみます。

    ちなみに、半分サイズの0.91インチ128×32のSSD1306OLEDにグラフとBIDの表示が横並びに出来たらいいなと思っていますが、大きく変更しなきゃいけませんかね?
    何かを変えたらできそうな気がしますが、どう変えたらいいのか素人なので...

    momoのアイコン画像 momo 2023/09/09

    128x64のOLED専用で作っているので変更はちょっと面倒かもしれません。
    一応、下記にヒントを書いておきますのでを参考にしてみてください。

    • LCDサイズ変更。GEOMETRY_128_64 → GEOMETRY_128_32 @L:43
    • display.drawStringで表示している文字の位置を修正。
    • display.setFontで指定している文字サイズを小さくした方がよいかも。
    • グラフの高さを小さくする。const int h = 40; @L:135
    • グラフの幅を小さくする。grph_plot()でx=1~128場で描画しているので小さくする。
      const int h = 40; // グラフの高さ @L:146
    • グラフデータのバッファサイズも小さくする。
      class Rbf の static const int buf_size = 128; @L:146
    • etc…
    3 件の返信が折りたたまれています
ログインしてコメントを投稿する