momo が 2022年02月24日16時50分10秒 に編集
コメント無し
本文の変更
## はじめに 最近ちょこっとFXをやっていまして、為替レートが常に気になる今日この頃です。 しょっちゅうスマホでレートチェックしていましたが、それも面倒になり 常時、為替レートを表示してくれるモノがあればいいなぁと思い↓こんなのを作ってみました。 ![キャプションを入力できます](https://camo.elchika.com/9df8dfa7975b0fe3176ed06aa7817c771fc4f8d0/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65343964323538632d663631612d346363642d393865302d3439323165303732366539302f62343532396466642d663531362d343335322d383663382d386131306530316532333764/) ## システム概要 仕組みとしては、為替情報を提供しているサイトからWebAPIを使ってJSONデータを取得します。 そして、その情報を見やすい形に加工してI2C経由でディスプレイにその情報を表示します。 :::plantuml @startuml cloud web [ FXのWEBサイト ] node rooter [ WiFiルーター ] rectangle "Exchange rate display" { node cpu [ マイコン\nESP-01 ] node oled [ ディスプレイ\n(SSD1306) ] } web .. rooter:https rooter .. cpu: WiFi/https cpu =r= oled: I2C @enduml ::: ディスプレイはグラフも表示したかったので128x64ドットの有機LED(OLED) SSD1306を使いました。 Arduino環境だと表示ライブラリもそろっているし、他の方もよく使っているみたいなのでハマった時に いろいろな情報も得やすいと思います。 インターネット接続する為にはWiFi接続ができるESP-01を使用しました。
ESP-01はESP8266が搭載されて超小型なマイコンボードです。機能的には他のESP8266搭載ボート同じですが、 端子が少なかったりFLASHのサイズが小さいという制限はあります。
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ボード|3個で400円くらい。|
|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ケーブル、ジャンパ線、ユニバーサル基板、他| ### 配線図 ![配線](https://camo.elchika.com/751008eb22e8a80e3573238936b0d9509681fd2e/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65343964323538632d663631612d346363642d393865302d3439323165303732366539302f35643130306234302d663965332d343332372d393162662d616164656339626265396235/) ++SCL/SDAのラインにもプルアップ抵抗を付けた方がよいかもしれません。 とりあえずこれで動いたので今回はこれで良しとします。 もしかしたらESP-01の端子かOLEDの基盤側でプルアップされているのかな?++ ## ソフトウェア Arduino IDE 1.8.19 で開発しました。 使用したライブラリは、LCD表示に`SSD1306`、WiFi接続に`WiFiClientSecure`、 JSONの読み込みに`ArduinoJson`を使いました。 最近のサイトは、ほとんどHttpsなのでアクセスには認証が必要です。 今回利用したサイトもHttpsだったので為替データを取得するのにちょっと手こずりました…。 認証用のFingerprintを送信する方法もありますが、それだと有効期限が切れると接続できなくなります。 そこで、初期化時に`setInsecure()` を実行して証明書による認証を行わないようにしています。 ```cpp:ソースコード #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がパイロットランプみたいになっていい感じになりました。 下記は起動した直後なので、まだグラフは表示されません。 ![キャプションを入力できます](https://camo.elchika.com/dd67138cd2cc75af249bbf277c8082256974e2e7/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65343964323538632d663631612d346363642d393865302d3439323165303732366539302f31663834356166632d626333662d343265362d386333662d343761653036656136353738/) グラフは過去128サンプル分をプロットしています。 一応、最高値~最低値を考慮してグラフが収まるように簡易的にスケーリングして表示しています。 ![キャプションを入力できます](https://camo.elchika.com/4c22cee405130f98f7e854e5c1e1fa4c90d642d7/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65343964323538632d663631612d346363642d393865302d3439323165303732366539302f35343738373062362d346166352d343762392d623934382d333034623264653638633664/) 後ろ側。 ![キャプションを入力できます](https://camo.elchika.com/8cc6168d72464ba7099d7f25bce988e42b350e53/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65343964323538632d663631612d346363642d393865302d3439323165303732366539302f32653235616638392d636633622d346130342d383561622d393433323537393438333236/) こんな感じでESP-01はソケットで接続するようになっていて、プログラムを書き込む為に外れるようになっています。 ![キャプションを入力できます](https://camo.elchika.com/b38d273be9e4c47020fdb57fa6bb1c55087e4b8b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65343964323538632d663631612d346363642d393865302d3439323165303732366539302f34373035383064312d373539352d343963302d396433392d636361333735646133656261/) PCモニタ(23inc)の上に置くとこんな感じ。 軽すぎて、固いUSBケーブルだと引きずられて傾いてしまうので要注意。 ![キャプションを入力できます](https://camo.elchika.com/23c459c062cdc79f242ddf1af79704e4f6312101/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65343964323538632d663631612d346363642d393865302d3439323165303732366539302f39303462353133622d343739362d343939372d613564362d613063386464643063393731/) あー!暴落~! ![キャプションを入力できます](https://camo.elchika.com/3187b1749ef7caffc88cf9501b89d54d7c04a465/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65343964323538632d663631612d346363642d393865302d3439323165303732366539302f39326537386539632d366161352d343530352d383135362d656439343033373137356462/) # まとめ 為替情報をディスプレイ表示するシステムを最小構成で作ってみました。 インターネットから目的の情報をJSON形式で取得してディスプレイに表示するという この仕組みは他にもいろいろ応用できそうです。 - 株価情報の表示 - 天気予報の表示 - ニュースヘッドラインの表示 - バス・電車の到着予定時間の表示 - HPの更新通知 - Mail/Lineなどの着信通知 …など ++あと、今回はリセットスイッチすら省きましたが、1個くらいスイッチをつけておけばよかったと後悔…。 スイッチで通貨ペアの切り替えや、表示モード切替して他の情報も表示できたのに。 また時間があったら拡張しよう。++