mrubyc_arduinoでESP8266からhttps
一週間と少し前にArduinoを使ってESP8266からhttpsにアクセスできることは確認できたが、その機能をmrubyc_arduinoに組み込むのに苦労していた。 Arduinoで動いたコードをmruby/cから呼び出せるようにして、プログラムは間違っていないように見えて、httpにはアクセスできるけど、httpsだとうまく行かないという症状になり、原因がなかなか分からなかった。 プログラムをいじったりしてもほとんど症状が変らずに、諦めかけていたが、httpsの処理にはメモリを沢山使うということを思い出して、メモリの設定をいじったらうまく行くようになった。
wifiはmruby/cの標準的な機能では無いので、どのようなクラスを定義すべきかから考える必要があり、 mrubyc-esp32-wifiやmrubyc-esp32などを参考にした。 ヘッダファイルmrbc_wifi.hは、代わり映えがしないが、以下のようにした。
#ifndef _MRBC_WIFI_H
#define _MRBC_WIFI_H
#include <Arduino.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "mrubyc.h"
void mrbc_init_class_wifi(void);
#ifdef __cplusplus
#endif
#endif
メインのmrbc_wifi.cppでは基本的なmethodを定義した。
#include "mrbc_wifi.h"
#include <ESP8266WiFi.h>
extern "C" {
void c_wifi_connect(mrb_vm *vm, mrb_value *v, int argc){
if(argc>1) WiFi.begin( mrbc_string_cstr(&v[1]), mrbc_string_cstr(&v[2]) );
}
void c_wifi_disconnect(mrb_vm *vm, mrb_value *v, int argc){
WiFi.disconnect(true);
}
void c_wifi_connected(mrb_vm *vm, mrb_value *v, int argc){
SET_BOOL_RETURN( WiFi.status() == WL_CONNECTED );
}
void c_wifi_ip(mrb_vm *vm, mrb_value *v, int argc){
IPAddress ip=WiFi.localIP();
char buf[16];
sprintf(buf,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]);
SET_RETURN( mrbc_string_new_cstr(vm, buf) );
}
void c_wifi_mac(mrb_vm *vm, mrb_value *v, int argc){
SET_RETURN( mrbc_string_new_cstr(vm, WiFi.macAddress().c_str()) );
}
void c_wifi_get(mrb_vm *vm, mrb_value *v, int argc){
const char* host=mrbc_string_cstr(&v[1]);
const char* file=mrbc_string_cstr(&v[2]);
int time_out=500; //ms
int port_num=443;
MRBC_KW_ARG( timeout, port );
if( MRBC_KW_ISVALID(timeout) ) time_out = mrbc_integer(timeout);
if( MRBC_KW_ISVALID(port) ) port_num = mrbc_integer(port);
MRBC_KW_DELETE( timeout, port );
bool ssl = !(port_num%100==80);
BearSSL::WiFiClientSecure client;
WiFiClient wclient=WiFiClient(client);
if(ssl){
client.setTimeout(time_out);
client.setInsecure();
}else{
wclient.setTimeout(time_out);}
if (!( ssl ? client.connect(host,port_num) : wclient.connect(host,port_num) )) {
//if (!client.connect(host,port_num)) {
SET_RETURN( mrbc_string_new_cstr(vm, "connection failed") );
return;
}
String http_string=String(" ")+ file + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: ESP8266/1.0\r\n" +
"Connection: close\r\n";
if(argc>2){
const char* data=mrbc_string_cstr(&v[3]);
http_string = String("POST") + http_string +
"Content-Type: application/x-www-form-urlencoded;\r\n" +
"Content-Length: " + String(strlen(data)) + "\r\n\r\n" +
data;
}
else{
http_string = String("GET") + http_string + "\r\n";
}
if(ssl){ client.print(http_string); }else{ wclient.print(http_string); }
unsigned long count = micros();
while ( (ssl ? client.available() : wclient.available() ) == 0) {
if ( micros() - count > time_out*1100) {
if(ssl){client.stop();}else{wclient.stop();}
SET_RETURN( mrbc_string_new_cstr(vm, "timeout") );
return;
}
}
bool body=false;
String str="";
while( ssl ? client.available() : wclient.available() ){
String line = ssl ? client.readStringUntil('\r') : wclient.readStringUntil('\r');
if(body) str+=line+"\r";
if(line.length()<3) body=true;
if(str.length()>1000) break;
}
if(ssl){client.stop();}else{wclient.stop();}
SET_RETURN( mrbc_string_new_cstr(vm, str.c_str()) );
}
void mrbc_init_class_wifi(void){
mrb_class *wifi = mrbc_define_class(0, "WIFI", mrbc_class_object);
mrbc_define_method(0, wifi, "connect", c_wifi_connect);
mrbc_define_method(0, wifi, "disconnect", c_wifi_disconnect);
mrbc_define_method(0, wifi, "connected?", c_wifi_connected);
mrbc_define_method(0, wifi, "ip", c_wifi_ip);
mrbc_define_method(0, wifi, "mac", c_wifi_mac);
mrbc_define_method(0, wifi, "get", c_wifi_get);
mrbc_define_method(0, wifi, "post", c_wifi_get);
}
} //extern
IPを返すmethodを定義するのに少し苦労したが、それ以外の基本的なmethodは、Arduinoのコマンドを使うだけなので、すぐに書けた。 httpsにアクセスするmethodをどうしようか迷ったが、httpsをGETするgetというmethodを作ることにして、これも少し長くはなったが、Arduinoで動いたコードを流用した。 しかし、これを組み込んでmruby/cからhttpsをgetしようとしても、接続できない。 メモリの設定を「16KB+48KB IRAM and 2nd Heap (shared)」として、 mruby/c用のメモリを「#define MEMORY_SIZE (1024*10)」としたら、無事にhttpsをgetすることができた。 ただし、あまり大きなファイルは読み込めないので、1000byte以上は読み込まないようにした。 ちなみにrubyのスクリプトはこんな感じである。
loop{
WIFI.connect("SSID","password")
print "\nconnecting"
until WIFI.connected?
sleep_ms 400
print "."
end
print "connected\n"
print WIFI.ip,"\n"
print WIFI.mac,"\n"
sleep_ms 1
print WIFI.get("www.shimane-u.ac.jp","/",timeout:500),"\n"
WIFI.disconnect
print "disconnected\n"
sleep 60
}
やっと動いたので、少し機能を拡張しておいた。 getで引数が三つ以上あるときには、自動的にPOSTにするようにして、postでも同じ関数を呼び出すことにした。 timeoutの値によっては、特定のサイトへのアクセスがうまくいかなかったりするので、timeoutをキーワード引数でms単位で指定できるようにもしてみた。 httpbin.orgを使って、timeoutの指定が有効になっていることも確認した。 httpsだけでなく、httpもアクセスできるようにするために、portもキーワード引数として、100で割って余りが80のとき以外はhttpsと判断するようにした。 httpsとhttpで使うクラスが異なっており、これを一々切り替えたので、コードが汚くなってしまった。
これで、esp8266で使いたいと思ってた機能をほぼmrubyc_arduinoに組み込むことができた。 ようやくwifiを使えるという利点を生かすことができたが、メモリのオプション指定が必要だというのは盲点で、気が付くのに時間がかかってしまった。 こういうのは、結果が分かってから考えると当たり前に思えるのだが、気が付くまでは非常に苦労するものである。