前回の投稿ではMSX0でI2C通信を試してみましたが、
添付されているサンプルコードにはインターネット上のサーバに対するHTTP通信を行うものもあります。
HTTP通信ができると、遊べることの幅がかなり広がるし通信も無線になるので柔軟性も増すように思えたので、こちらも試してみました。
MSX0側は、添付のサンプルコードを書き換えながら作りました。サンプルコード、チュートリアル大事。
構成図?概念図?はこんな感じです。
時空を超えたMSX0のちょっと面倒な問題
サンプルとしてぱっと思いついたのが、郵便番号から住所を取得するようなWeb APIへのアクセスだったのですが、ここに一つ、時空を超えてしまったMSXならではの問題が。
今どき、Web系のあらゆるサービスの文字エンコーディングは「UTF-8(Unicode)」というのを使っているわけですが、MSXのアーキテクチャが作られた当時はまだUnicodeなんて影も形もありませんでした。いやもしかすると理論的にはあったのかもしれませんが、当時の日本のパソコンで実装しているものなど皆無で、みなすべからく「シフトJISコード」で全角文字を表現していました。
そのため、どこかしらでUTF-8コードをシフトJISコードに変換してもらわねばならない、という問題があります。
また、もう一つは、MSX0単体でも解決できないわけではありませんが、WebAPIから返却されてくるレスポンスの内容は「JSON」という形式で書かれているものがほとんどで、ここから目的のデータを取り出すのが、若干面倒という問題があります。
Arduino(ESP8266)でカスタムなWebサーバを作る
ということで、上記2つを別の誰かにやらせようと思ったとき、手元にWiFi内蔵のArduino(ESP8266)があることを思い出しました。オークションでクーポン使って安く買えるので、特に何の気もなしに買っておいたものでした。
全くもって、今回のような使い方を予見していたわけではありませんw
このArduinoに
- MSX0の代理でWebAPIサーバにリクエストを出してもらう
- サーバからのレスポンス(JSON)から抜粋して応答として必要な最低限の内容に整理
- レスポンスの内容がUTF-8になっているのをシフトJISの文字列に変換してMSX0側に返却
問題の「UTF-8→シフトJISへのエンコーディング変換」ですが、こちらもすでに対応するライブラリをArduino用に作ってくださっている方がいらっしゃるので、ありがたく使わせていただきました。
https://www.mgo-tec.com/blog-entry-utf8-sjis-wroom-arduino-lib.html
さらに、JSONを取り扱うためのArduino用のライブラリもあるので、それも利用して、JSONのレスポンスから住所情報を組み合わせて、単純なテキストデータとしてMSX0側に返却します。
【M5StickC Plus/Arduino】Arduinoでjsonを扱う - ソースに絡まるエスカルゴ https://rikoubou.hatenablog.com/entry/2021/04/16/162258
これらを使って作ったカスタムWebサーバのソースがこちら。
#include <Arduino.h>
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
#include <uri/UriBraces.h>
#include "secrets.h" // add WLAN Credentials in here.
// name of the server. You reach it using http://webserver
#define HOSTNAME "webserver"
// local time zone definition (Berlin)
#define TIMEZONE "CET-1CEST,M3.5.0,M10.5.0/3"
// need a WebServer for http access on port 80.
ESP8266WebServer server(80);
ESP8266WiFiMulti WiFiMulti;
#include <UTF8toSJIS.h>
const char* UTF8SJIS_file = "/Utf8Sjis.tbl"; //SPIFFSファイルシステムで予めこのファイルをアップロードしておくこと
UTF8toSJIS u8ts;
#include <ArduinoJson.h>
// ===== Simple functions used to answer simple GET requests =====
void handlePostalProxy() {
WiFi.mode(WIFI_STA);
// wait for WiFi connection
if ((WiFiMulti.run() == WL_CONNECTED)) {
WiFiClient client;
HTTPClient http;
Serial.print("[HTTP] begin...\n");
// パスパラメータの取得
String code = server.pathArg(0);
// 送信先URL(パラメータ含む)の指定
if (http.begin(client, "http://zipcloud.ibsnet.co.jp/api/search?zipcode="+code)) {
Serial.print("[HTTP] GET...\n");
// start connection and send HTTP header
int httpCode = http.GET();
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = http.getString();
Serial.println(payload);
// WebAPIサーバから返されたレスポンスボディの内容をもとにJSONドキュメントを生成
DynamicJsonDocument doc(512);
deserializeJson(doc, payload);
String utf8resp = "address=";
utf8resp += (String)doc["results"][0]["address1"];
utf8resp += (String)doc["results"][0]["address2"];
utf8resp += (String)doc["results"][0]["address3"];
Serial.println(utf8resp);
// UTF-8の文字列(utf8resp)をSJIS(sj_txt)に変換
uint8_t sj_txt[utf8resp.length()];
uint16_t sj_length;
u8ts.UTF8_to_SJIS_str_cnv(UTF8SJIS_file, utf8resp, sj_txt, &sj_length);
// unit8_t型の配列はsendContent_P()メソッドに渡せないので、char型配列に変換
char resp[sj_length];
for(int i=0;i<sj_length;i++) {
resp[i] = sj_txt[i];
}
// MSX0(HTTPクライアント)側にレスポンスを返却
server.setContentLength(sj_length);
// レスポンスコードとMIMEタイプ/エンコーディングのみ指定
server.send(200, "text/plain;charset=Shift_JIS","");
// レスポンスボディ(SJIS文字列)を返却
server.sendContent_P(resp,sj_length);
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
server.send(200, "text/plain; charset=Shift_JIS", "ERROR!");
}
http.end();
} else {
Serial.println("[HTTP] Unable to connect");
}
}
}
void setup(void) {
delay(3000); // wait for serial monitor to start completely.
// Use Serial port for some trace information from the example
Serial.begin(9600);
Serial.printf("Starting WebServer...\n");
if (strlen(ssid) == 0) {
WiFi.begin();
} else {
WiFi.begin(ssid, passPhrase);
}
// allow to address the device by the given name e.g. http://webserver
WiFi.setHostname(HOSTNAME);
Serial.printf("Connect to WiFi...\n");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.printf(".");
}
Serial.printf("connected.\n");
// Ask for the current time using NTP request builtin into ESP firmware.
Serial.printf("Setup ntp...\n");
configTime(TIMEZONE, "pool.ntp.org");
Serial.printf("Register service handlers...\n");
// 「/zipcode/郵便番号」というURLでGETリクエストを受信したらhandlePostalProxyメソッドを実行
server.on(UriBraces("/zipcode/{}"), HTTP_GET, handlePostalProxy);
// enable CORS header in webserver results
server.enableCORS(true);
// enable ETAG header in webserver results from serveStatic handler
server.enableETag(true);
// handle cases when file is not found
server.onNotFound([]() {
// standard not found in browser.
server.send(404, "text/html", "404 Not Found");
});
server.begin();
Serial.printf("hostname=%s\n", WiFi.getHostname());
//Serial.printf("ip=%s\n",WiFi.localIP());
Serial.printf("ip addr=%s\n", WiFi.localIP().toString().c_str());
}
void loop(void) {
server.handleClient();
}
WebサーバとHTTPクライアントの両方の役割を兼任するので若干ややこしいです。
そして、結構はまってしまったのがSJIS形式に変換した後のコンテンツを返却するところです。上記にリンクしたライブラリでUTF-8文字列をSJISに変換すること自体は簡単にできたのですが、いざ、それをArduinoのWebサーバから返そうとすると、なぜか同じ文字列が2回繰り返し送信されてしまったりして、最初うまく行きませんでした。
ESP8266WebServerクラスには、レスポンスを送信するメソッドとしてsend()メソッドと、sendContent_P()メソッドというのがあるのですが、後者のメソッドですと送信するバイト数が限定できるので、こちらを使って実際のバイト数きっちりしか送り出さないようにしています。
MSX0でHTTPクライアントを作る
- [1000~2030] 初期設定等
- [3000~4000] Webサーバに接続
- [4000~5000] 検索したい郵便番号を入力
- [5000~7000] HTTPリクエストを送信
- [7000~7500] レスポンスを受信し、レスポンスを1つの文字列に連結していく
- [7500~8000] 特定のキーワード("address=")が含まれる箇所を特定しそこから後ろにある文字列だけを表示
- [8000~] 切断処理
1 'SAVE"POSTAL2.BAS"
1000 'initialize
1010 _KANJI
1020 CLEAR 800
1030 NL$=CHR$(13)+CHR$(10)
2000 'user setting
2010 SV$="192.168.1.228"
2020 PA$="msx/me/if/NET0/" 'path
2030 KY$="address="
3000 'connect
3010 _IOTPUT(PA$+ "conf/addr",SV$)
3020 _IOTPUT(PA$+ "conf/port",80)
3030 _IOTPUT(PA$+ "connect",1)
3500 'check connect status
3510 FOR I=0 TO 100:NEXT
3520 _IOTGET(PA$+ "connect",S)
3530 IF S<>1 THEN PRINT "connect fail":GOTO 8000
4000 'input postal code
4010 PRINT "POSTAL CODE=";
4020 INPUT P$
5000 'create message
5010 SM$(0)="GET /zipcode/"+P$+" HTTP/1.1"+NL$
5020 SM$(1)="Host: "+SV$+NL$
5030 SM$(2)="Content-Type: application/json"+NL$
5040 SM$(3)=""+NL$
6000 'send message
6010 PRINT NL$+"---- Send Message ----"
6020 I=0
6030 IF SM$(I)="" THEN 7000
6040 PRINT SM$(I);
6050 _IOTPUT(PA$+ "msg",SM$(I))
6060 I=I+1
6070 GOTO 6030
7000 'receive message
7010 _IOTGET(PA$+"msg",RM$):IF RM$="" THEN 7010
7020 PRINT NL$+"---- Receive Message ----"
7030 RS$="":ER=0
7040 FOR J=0 TO 15
7050 IF RM$<>"" THEN RS$=RS$+RM$:ER=0
7060 IF RM$="" THEN ER=ER+1:IF ER=2 THEN 7500
7070 _IOTGET(PA$+ "msg",RM$)
7080 FOR I=0 TO 100:NEXT I
7090 NEXT J
7500 P = INSTR(RS$,KY$)
7510 IF P<>0 THEN PRINT MID$(RS$,P+LEN(KY$))
8000 'disconnet
8010 _IOTPUT(PA$+ "connect",0)
8020 _IOTINIT()
8030 END
コメント
コメントを投稿