GR-CITRUS＋WA-MIKANでHTTPS接続する方法

[RECAIUS](https://developer.recaius.io/jp/top.html)に繋げようとしたらAPIがSSLのため、GR-CITRUS＋WA-MIKANでは簡単にはSSL接続できませんでした。

なぜかというとGR-CITRUSに用意されているWiFiクラスのWiFi.httpGetとWiFi.httpPostがSSLに対応していないからです。

以下は他の方法でSSL接続を試みたときのメモです。

# (１) ATコマンドを直接たたく
WiFi.atを使って[ESP8266のATコマンド](https://espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf)を直接たたく方法です。ただ接続したいだけならこの方法で問題ありません。しかしこの方法では戻り値がコンソールに出るだけで値が保存できません。データを取得したい場合はこの方法では不十分です。

```rb
#GR-CITRUS Version 2.28

#ESP8266を一度停止させる(リセットと同じ)
pinMode(5,1)
digitalWrite(5,0)   # LOW:Disable
delay 500
digitalWrite(5,1)   # LOW:Disable
delay 500

Usb = Serial.new(0,115200)

if( System.useWiFi() == 0)then
    Usb.println "WiFi Card can't use."
   System.exit() 
end
Usb.println "WiFi Ready"

Usb.println "WiFi disconnect"
Usb.println WiFi.disconnect

Usb.println "WiFi Mode Setting"
Usb.println WiFi.setMode 1  #Station-Mode

Usb.println "WiFi connecting"
Usb.println WiFi.connect("SSID","PASSWARD")

Usb.println "WiFi ipconfig"
Usb.println WiFi.ipconfig

Usb.println "WiFi multiConnect Set"
Usb.println WiFi.multiConnect 1

Usb.println WiFi.at('AT+GMR',1)

Usb.println  WiFi.at('CIFSR',0).to_s

#####

host = "try-api.recaius.jp"
url = "/auth/v2/tokens"

Usb.println WiFi.at('AT+CIPMUX=0',1).to_s
Usb.println WiFi.at('AT+CIPSSLSIZE=4096',1).to_s
Usb.println WiFi.at('AT+CIPSTART="SSL","' + host + '",443',1).to_s

#Body部分作成
speechsynthesis_id="xxxxxx"
speechsynthesis_password="xxxxxx"
body ='{"speech_synthesis":{"service_id":"' + speechsynthesis_id + '","password":"'   + speechsynthesis_password + '"}}'

#ヘッダー部分作成
s  = "POST #{url} HTTP/1.1"
s += "\r\n"
s += "User-Agent: GR-CITRUS/2.28"
s += "\r\n"
s += "Host: #{host}"
s += "\r\n"
s += "Accept: */*"
s += "\r\n"
s += "Content-Type: application/json"
s += "\r\n"
s += "Content-Length: #{body.length.to_s}"
s += "\r\n"
s += "\r\n"
s += body
s += "\r\n"

Usb.println s

Usb.println WiFi.at('AT+CIPSEND='+s.bytesize.to_s,1).to_s
delay(100)
Usb.println WiFi.at(s,1).to_s

delay(500)

Usb.println "WiFi CLOSE"
Usb.println WiFi.at('AT+CIPCLOSE',1).to_s

```

# (２) GR-CITRUSのmrubyのソースを修正するパターン

SSLで接続し取得したデータをSDカードに保存する方法です。この方法ではGR-CITRSのファームウェアを再構築する必要があります。sWiFi.cppに以下のファンクションを追加してコンパイルしました。

（※とりあえず動くの第一優先で、動作は確認しましたがメモリあふれとか全然考えていません(^^;）
（※ファームウェアのコンパイルについては[こちら](http://qiita.com/shimo_cchi/items/645d814995c1902a2563)を参考にしてください）

```cpp:wrbb-v2lib-firm-master\firmware\wrbb_mruby\sWiFi.cpp
//**************************************************
// https POSTをSDカードに保存します: WiFi.httpsPostToSD
//  WiFi.httpsPostToSD( Filename,URL, Headers, data )
//	Filename: 保存するファイル名
//	URL: URL
//	Headers: ヘッダに追記する文字列の配列
//    data: 送信するデータ
//  戻り値は以下のとおり
//		0: 失敗
//		1: 成功
//		2: SDカードが使えない
//		... 各種エラー
//**************************************************
void at(char *atcmd)
{
	char *s = atcmd;
	int len = strlen(s);

	for(int i=0; i<254; i++){
		if( i >= len){ break; }
		WiFiData[i] = s[i];
	}
	WiFiData[len] = 0;

	RbSerial[WIFI_SERIAL]->println((const char*)WiFiData);

	//OK 0d0a か ERROR 0d0aが来るまで WiFiData[]に読むか、指定されたシリアルポートに出力します
	getData(WIFI_WAIT_MSEC);
}

mrb_value mrb_wifi_postSSLtoSD(mrb_state *mrb, mrb_value self)
{
mrb_value vFname, vURL, vHeaders, vData;
const char *tmpFilename = "wifitmp.tmp";
char	*strData, *strFname, *strURL;
char buf[256]    = {'\0'};
char sData[1024] = {'\0'};
File fp;
int len   = 0;
int sla   = 0;
int koron = 0;
int sHeader = 0;
int sBody = 0;
int cnt   = 0 ;

	//SDカードが利用可能か確かめます
	if (!sdcard_Init(mrb)){
		return mrb_fixnum_value(2);
	}

	//既にファイルがあれば消す
	if (SD.exists(tmpFilename)){
		SD.remove(tmpFilename);
	}

	int n = mrb_get_args(mrb, "SSAS", &vFname, &vURL, &vHeaders, &vData);

	strFname = RSTRING_PTR(vFname);
	strURL = RSTRING_PTR(vURL);
	strData = RSTRING_PTR(vData);
	sBody = strlen(strData);


	//1行目を生成
	{
		strcpy(sData, "POST /");

		//URLからドメインを分割する
		len = strlen(strURL);
		sla = len;
		koron = 0;
		for(int i=0; i<len; i++){
			if(strURL[i] == '/'){
				sla = i;
				break;
			}
			if(strURL[i] == ':'){
				koron = i;
			}
		}

		if(koron == 0){
			koron = sla;
		}

		cnt = 6;
		for(int i=sla + 1; i<len; i++){
			sData[cnt] = strURL[i];
			cnt++;
		}

		sData[cnt] = 0;
		strcat(sData, " HTTP/1.1\r\n");
	}

	//Hostヘッダを生成
	{
		strcat(sData, "Host: ");
	
		cnt = strlen(sData);
		for(int i=0; i<koron; i++){
			sData[cnt] = strURL[i];
			cnt++;
		}
		sData[cnt] = 0;
		strcat(sData, "\r\n");
	}

	//Content-Lengthを付けます
	{
		strcat(sData, "Content-Length: ");

		sprintf((char*)WiFiData,"%d", sBody);
		strcat(sData, (char*)WiFiData);
		strcat(sData, "\r\n");
	}

	//ヘッダ情報を付けます
	{
		int n = RARRAY_LEN( vHeaders );
		mrb_value hes;
		for (int i=0; i<n; i++) {
			hes = mrb_ary_ref(mrb, vHeaders, i);
			
			//ヘッダの追記
			strcat(sData, RSTRING_PTR(hes));
			strcat(sData, "\r\n");
		}
	}

	//改行のみの行を追加する
	strcat(sData, "\r\n");

	//送信データサイズ取得
	sHeader = strlen(sData);
	len = sHeader + sBody;

	Serial.print("Save Temp hppts Header and Body: ");
	Serial.println((const char*)WiFiData);

	//****** AT+CIPSTARTコマンド ******


	//デバック用
	buf[0] = '\0';
	strcat(buf, "AT+CIPMUX=1\r\n");
	strcat(buf, "AT+CIPSSLSIZE=4096\r\n");
	Serial.print(buf);
	at("AT+CIPMUX=1");
	at("AT+CIPSSLSIZE=4096");

	//WiFiData[]に、ドメインとポート番号を取得
	for(int i=0; i<sla; i++){
		WiFiData[i] = strURL[i];
		if(i == koron){
			WiFiData[i] = 0;
		}
	}
	WiFiData[sla] = 0;
	buf[0] = '\0';
	strcat(buf, "AT+CIPSTART=4,\"SSL\",\"");
	strcat(buf, (const char*)WiFiData);
	strcat(buf, "\",");

	if( koron < sla){
		strcat(buf, (const char*)&WiFiData[koron + 1]);
	}
	else{
		strcat(buf, "443");
	}

	Serial.println(buf);
	at(buf);

	//****** AT+CIPSEND コマンド ******

	buf[0] = '\0';
	strcat(buf, "AT+CIPSEND=4,");
	sprintf((char*)WiFiData,"%d", len);
	strcat(buf, (const char*)WiFiData);

	Serial.println(buf);
	at(buf);


	//****** 送信データ受付モードになったので、データを送信する ******
	{
		//ヘッダを送信する
		RbSerial[WIFI_SERIAL]->print((const char*)sData);
		//Serial.print(sData);

		//ボディを送信する
		RbSerial[WIFI_SERIAL]->print((const char*)strData);
		//Serial.print(strData);

		//OK 0d0a か ERROR 0d0aが来るまで WiFiData[]に読むか、指定されたシリアルポートに出力します
		getData(WIFI_WAIT_MSEC);

		if( !(WiFiData[strlen((const char*)WiFiData)-2] == 'K' || WiFiData[strlen((const char*)WiFiData)-3] == 'K')){
			return mrb_fixnum_value( 0 );
		}
		Serial.print("Send Finish: ");
		Serial.print((const char*)WiFiData);
	}
	//****** 送信終了 ******

	//****** 受信開始 ******

	if( !(fp = SD.open(tmpFilename, FILE_WRITE)) ){
		return mrb_fixnum_value( 6 );
	}

	unsigned long times;
	unsigned int wait_msec = WIFI_WAIT_MSEC;
	unsigned char recv[2];
	times = millis();

#if BOARD == BOARD_GR
	int led = digitalRead(PIN_LED0) | ( digitalRead(PIN_LED1)<<1) | (digitalRead(PIN_LED2)<<2)| (digitalRead(PIN_LED3)<<3);
#else
	int led = digitalRead(RB_LED);
#endif

	while(true){
		//wait_msec 待つ
		if(millis() - times > wait_msec){
			break;
		}

		while(len = RbSerial[WIFI_SERIAL]->available())
		{
			//LEDを点灯する
#if BOARD == BOARD_GR
			digitalWrite(PIN_LED0, HIGH);
			digitalWrite(PIN_LED1, HIGH);
			digitalWrite(PIN_LED2, HIGH);
			digitalWrite(PIN_LED3, HIGH);
#else
			digitalWrite(RB_LED, HIGH);
#endif
			for(int i=0; i<len; i++){
				recv[0] = (unsigned char)RbSerial[WIFI_SERIAL]->read();
				fp.write( (unsigned char*)recv, 1);
			}
			times = millis();
			wait_msec = 1000;	//データが届き始めたら、1sec待ちに変更する

			//LEDを消灯する
#if BOARD == BOARD_GR
			digitalWrite(PIN_LED0, LOW);
			digitalWrite(PIN_LED1, LOW);
			digitalWrite(PIN_LED2, LOW);
			digitalWrite(PIN_LED3, LOW);
#else
			digitalWrite(RB_LED, LOW);
#endif
		}
	}
	fp.flush();
	fp.close();

	//****** 受信終了 ******
	Serial.println("Recv Finish");

	//受信データに '\r\n+\r\n+IPD,4,****:'というデータがあるので削除します
	int ret = CutGarbageData("\r\n+IPD,4,", tmpFilename, strFname);
	if(ret != 1){
		return mrb_fixnum_value( 7 );
	}

	//****** AT+CIPCLOSE コマンド ******
	at("AT+CIPCLOSE=4");
	Serial.println((const char*)WiFiData);

#if BOARD == BOARD_GR
	digitalWrite(PIN_LED0, led & 1);
	digitalWrite(PIN_LED1, (led >> 1) & 1);
	digitalWrite(PIN_LED2, (led >> 2) & 1);
	digitalWrite(PIN_LED3, (led >> 3) & 1);
#else
	digitalWrite(RB_LED, led);
#endif

	return mrb_fixnum_value( 1 );
}
```

最後にhttpsPostToSDをmruby用のmofule_functionの行を追加しておく。

```cpp
	mrb_define_module_function(mrb, wifiModule, "httpPostSD", mrb_wifi_postSD, MRB_ARGS_REQ(3));
    mrb_define_module_function(mrb, wifiModule, "httpsPostToSD", mrb_wifi_postSSLtoSD, MRB_ARGS_REQ(4)); //←追加
```

使い方

```rb
#!mruby
#GR-CITRUS Version 2.28

#ESP8266を一度停止させる(リセットと同じ)
pinMode(5,1)
digitalWrite(5,0)   # LOW:Disable
delay 500
digitalWrite(5,1)   # LOW:Disable
delay 500

Usb = Serial.new(0,115200)

if( System.useWiFi() == 0)then
    Usb.println "WiFi Card can't use."
   System.exit() 
end
Usb.println "WiFi Ready"

Usb.println "WiFi Get Version"
Usb.println WiFi.version

Usb.println "WiFi disconnect"
Usb.println WiFi.disconnect

Usb.println "WiFi Mode Setting"
Usb.println WiFi.setMode 1  #Station-Mode

Usb.println "WiFi ipconfig"
Usb.println WiFi.ipconfig

Usb.println "WiFi connecting"
Usb.println WiFi.connect("SSID","PASSWARD")

Usb.println "WiFi ipconfig"
Usb.println WiFi.ipconfig

heds=["User-Agent: grcitrus","Content-Type: application/json","Accept: */*"]
data='{"speech_synthesis":{"service_id":"xxxxxx","password":"xxxxxxx"}}'
Usb.println WiFi.httpsPostToSD("recaius.htm","try-api.recaius.jp/auth/v2/tokens", heds,data).to_s

Usb.println "WiFi disconnect"
Usb.println WiFi.disconnect
```

結果

```
-------- コンソールに接続しました --------

Save Temp hppts Header and Body: 79
AT+CIPMUX=1
AT+CIPSSLSIZE=4096
AT+CIPSTART=4,"SSL","try-api.recaius.jp",443
pre httpsServer Connect: 
AT+CIPSEND=4,227
Send Finish: > 
Recv 227 bytes

SEND OK
Recv Finish
4,CLOSED

OK

1
WiFi disconnect

OK


-------- コンソールから切断されました --------
```

RECAIUS.HTMが以下で保存される。

```
HTTP/1.1 201 Created
Date: Mon, 23 Jan 2017 03:34:23 GMT
Content-Type: application/json
Content-Length: 65
Connection: keep-alive
Server: 
X-Frame-Options: DENY
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS, DELETE
Access-Control-Allow-Headers: content-type, X-TOKEN

{"expiry_sec":600,"token":"xxxxxxxxxx"}
```

# (３) Serial経由で直接ATコマンドをたたく

(1)とほとんど同じですが、これならデータも受け取れます。
ただし、レスポンスのタイミングの調整をどうしようかと悩み中

```rb
#!mruby
#GR-CITRUS Version 2.28

class MIKAN
    def initialize(params)
        @bps          = params[:bps] || 115200
        @txrxnum      = params[:txrxnum] || 3
        @esp          = Serial.new(@txrxnum.to_i,@bps.to_i)
        delay(1000)
    end
    def bps
        @bps
    end
    def txrxnum
        @txrxnum
    end    
    def cmd(command)
        resultstr=""
        @esp.println command
        delay(100)
        while(@esp.available() > 0) do
            resultstr += @esp.read()
            delay(100)
        end
        resultstr
    end
    def read
        resultstr=""
        while(@esp.available() > 0) do
            resultstr += @esp.read()
            delay(100)
        end
        resultstr
    end    
end 

#ESP8266を一度停止させる(リセットと同じ)
pinMode(5,1)
digitalWrite(5,0)   # LOW:Disable
delay 500
digitalWrite(5,1)   # LOW:Disable
delay 500

Usb = Serial.new(0,115200)
mikan = MIKAN.new(txrxnum:3,bps:115200)

Usb.println "GR-CITRUS & WA-MIKAN"

Usb.println (mikan.cmd "AT")

#Usb.println (mikan.cmd "AT+RST")
Usb.println (mikan.cmd "AT+CIFSR")
Usb.println (mikan.cmd "AT+GMR")
Usb.println (mikan.cmd "AT+CWMODE=1")
Usb.println (mikan.cmd 'AT+CWJAP="SSID","PASSWARD"')
delay(10000)

host = "try-api.recaius.jp"
url = "/auth/v2/tokens"

Usb.println (mikan.cmd "AT+CIPSSLSIZE=4096")
Usb.println (mikan.cmd 'AT+CIPSTART="SSL","' + host + '",443')

body='{"speech_synthesis":{"service_id":"xxxxx","password":"xxxxxx"}}'

#ヘッダー部分作成
s  = "POST #{url} HTTP/1.1"
s += "\r\n"
s += "User-Agent: GR-CITRUS/2.28"
s += "\r\n"
s += "Host: #{host}"
s += "\r\n"
s += "Accept: */*"
s += "\r\n"
s += "Content-Type: application/json"
s += "\r\n"
s += "Content-Length: #{body.length.to_s}"
s += "\r\n"
s += "\r\n"
s += body
s += "\r\n"

Usb.println s 

Usb.println (mikan.cmd "AT+CIPSEND=#{s.length}")
delay(500)
Usb.println (mikan.cmd s)
```