[PomeraDM100]FlashAir-Lua:GoogleDriveスクリプト

2020/03/15-1 DownloadFile関数が3KB制約に引っかかってるのを修正。
2020/03/15-2 秘匿領域からの実行に成功とPOMERA+W04(16GB)での動作実験成功（ﾂｶﾚﾀｰ

# 目的
PomeraDM100で書いた文章をPCでもスマホでもアクセスしやすくしたい(の備忘録)

POMERA DM100
 https://www.kingjim.co.jp/pomera/dm100/
FlashAir W-04
 https://jp.toshiba-memory.com/product/flashair/sduwa/index_j.htm
 ※DM100はSDHCまでしか対応してないので、64GBのSHXCを買わないこと！(1敗
あとこのプログラムは***W-04 verW4.00.03以上***でしか完全動作しないと思います(fa.requestDL制限のため

## したこと
Pomera(DM100)　⇔（SD領域）FlashAir(Wifi)　⇔　GoogleDriveAPI ⇔　PC/スマホ

以上のようになるようにFlashAirの機能を使って、GoogleDirveAPIにアップロードとダウンロードをお願いするスクリプトを書いた

####コンセプト
1.POMERA DM100は電池駆動なのでいらない電波は発信しないこと！
2.旅先でも同期したいので、SSIDを2つ切り替えられるように。
3.Pormeraでの作業は１つのをファイルに対して書いたり、消したりする。
4.GoogleDrive側も１つのファイルをバージョン管理で対応。
- 3と4の理由
    - Pomera側の時間用ボタン電池を交換し忘れそうなので、時間比較で最近更新したのもを送るなんてできそうにない
    - 書きかけを増やしていくなら、いっその事書き上げるか、諦めるかように自分をさせたい

## 課題（勉強したこと/苦労したこと）
- Lua言語の勉強
    - 主にFlashAir専用の関数の理解
    - Lua言語のルールを肌で感じる
        - ファイルの中で宣言した関数より上で使用するとエラーが出たのは他言語慣れすぎ
        - 関数に対する返り値が複数出せるのはうれしい
- Httpプロトコルの勉強
    - Url,Head,Bodyへの情報の代入方法
        - Urlは?と&で繋いでいく
        - Headerはrequest関数の用意してくれているところに、{["yoso1"] = 1,["yoso2"]=2}って入れていく
        - Bodyは、どうしてこうなるんですかね？
    - 送り方：Methodに[Get][POST]以外にも[PATCH][PUT]を知る
        - 普段非同期でDBと文字列しかやり取りしないから、、、、
- GoogleDriveAPIの勉強
    - OAuth認証の復習
        - いつやってもリダイレクトの近辺はしっくりこない
    - GoogleDrive内のデータの管理（ちょこっと理解
        - へぇ～x9
    - Client Libraryを使わずに操作
        - ファイルの更新 Update
        - ファイルのダウンロード Get
- FlashAirの使用上の注意点
    - Luaから書き出されたファイルは一度抜き差ししないと表示されない模様
    - Luaから書き出されたファイルはRTCに値が入ってないと時間が入らない模様
        - その影響でWindowsから削除が出来ない模様
    - 節電のために削除イベントが起きたらwifiが繋がるようにしたけど、大丈夫か？

##作業記録
###1.GoolgeApiのClient_ID,Client_SECRET,Refresh_tokenの取得

参考文献：
[1]Googleドライブへのアップロード
https://flashair-developers.github.io/website/docs/tutorials/lua/9.html
[2]Google APIを使用するためにGoogle OAuth認証をしようよ
https://himakan.net/websites/how_to_google_oauth

[1]があったから、こんなことをしはじめた。
Google先生も進化しているので、参考文献がの内容が違うこともあったので他にも色々みたかもしれない、、、

1-1.GoogleAPIConsoleからClient_ID,Client_SECRETの取得
参考文献通り
    - 取得物
        - Client_ID:XXXX-YYYYYY.apps.googleusercontent.com
        - Client_SECRET:_XXXXX-YYYYYYYY

1-2.自分のGoogleアカウントのGoogleDriceのアクセス権の<申請完了コード>の取得
[2]文献を参考に自分はGoogleDriveの許可がほしいので以下のように記載。
Webサービスとか使うのであれば、redirect_uriにlocalhostなんて使ってはいけないんだろうけど、今回は組み込みディバイス？だからいいのかな？

    https://accounts.google.com/o/oauth2/auth?
    response_type=code&
    client_id=XXXX-YYYYYY.apps.googleusercontent.com&
    redirect_uri=http://localhost&
    scope=https://www.googleapis.com/auth/drive&
    access_type=offline&
    approval_prompt=force

ブラウザのアドレス欄で実行すると、パスワードを入れたり、認証したりと他のサービスとの連携と同じ画面と「承認されてないアプリ」みたいなのがでて、OKする。
    - 取得物
        - 申請完了コード:4/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

1-3.PostManを使って、Refresh_tokenの取得
HTTP通信のBODYに以下の事を記載して送信（しないといけないのでPostManを使いました
送り先：https://www.googleapis.com/oauth2/v4/token
    
    code:4/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    client_id:XXXX-YYYYYY.apps.googleusercontent.com
    client_secret:_XXXXX-YYYYYYYY
    redirect_uri:http://localhost
    grant_type:authorization_code
確かaccess_type:offlineをつけるとエラーコードが返ってきた（と記憶
    - 取得物
        - Access_token:ya3-.XXXXXXXXXXXXXXYYYYYYYYY
        - Refresh_token:1//ZZZZZZZZZZZZZZZZZZZZZZZZ
 
###2.GoogleDrive上のファイルIDの取得
参考文献：
[3]GoogleDriveAPIv3:Reference:Files-list
https://developers.google.com/drive/api/v3/reference/files/list

2-1.[3]のサイトの[Try this API]を実行。自分のアカウントのGDriveの更新したいファイルの[id]を探す
FolderIDで絞込んだ記憶があったのですが、忘れてしまった、、、
    -取得物
        -GdriveFileId:1WWWWWWWWWWWWWWWWWWWWWWWWWWW

###3.FlashAirのためのプログラム
全体の参考文献:
[1]Googleドライブへのアップロード
https://flashair-developers.github.io/website/docs/tutorials/lua/9.html

開発環境の参考文献:
[4]FlashAir Developers:CONFIG
https://flashair-developers.github.io/website/docs/api/config.html
[5]FlashAir開発者向け非公式wiki
https://seesaawiki.jp/flashair-dev/
特に以下のツールには助けられました
[6]Tool:FlashTools Lua Editor (FTLE)
https://seesaawiki.jp/flashair-dev/d/Tool%3aFlashTools Lua Editor (FTLE)

Google Drive Api参考文献:
[7]GoogleDriveAPIv3:Guide:Upload files
https://developers.google.com/drive/api/v3/manage-uploads
[8]GoogleDriveAPIv3:Reference:Files-update
https://developers.google.com/drive/api/v3/reference/files/update
[9]FlashAir Developers:Lua機能:request
https://flashair-developers.github.io/website/docs/api/lua.html#request
[10]GoogleDriveAPIv3:Guide:Download files
https://developers.google.com/drive/api/v3/manage-downloads

Luaライブラリ参照:
[11]FlashAir開発者向け非公式wiki:lib:libFlashTime
https://seesaawiki.jp/flashair-dev/d/lib%3AlibFlashTime

###4成果物
GoogleDrive.lua

	-- SSID Setting
	-- コンセプトのためにSSIDをFlashairの関数で切り替える
	local SSID1 = -SSID1-
	local PW1   = -SSID1のPW-
	local SSID2 = -SSID2-
	local PW2   = -SSID2のPW-

	-- Basic account info
	-- 1で取得してきた大事な内容
	local CLIENT_ID = "XXXX-YYYYYY.apps.googleusercontent.com"
	local CLIENT_SECRET = "_XXXXX-YYYYYYYY"
	local REFRESH_TOKEN = "1//ZZZZZZZZZZZZZZZZZZZZZZZZ"

	-- Target File
	-- 2で取得したGDrive上のファイルID
	-- FlashAir上のファイルを指定
	-- バックアップはDL時のために一つ前のものを保持
	local gDriveFileId = "1WWWWWWWWWWWWWWWWWWWWWWWWWWW"
	local sDTargetFilePath = -UP/DNしたいファイル名-
	local sDBackUpFilePath = -上のバックアップ-

	-- Control File
	-- POMERA側の操作でFlashAirのプログラムを起動/変更するためのファイル指定
	local sDStartUpLoadFilePath = "UpLoad.txt"
	local sDStartDnLoadFilePath = "DnLoad.txt"
	local sDWifiFilePath = "wifi.txt"

	-- Refresh_tokenからAccess_Tokenを取得
	function GetAuth( pClient_ID, pClient_Secret, pRefresh_Token)
		local wMes = "client_id="..pClient_ID
			  .. "&client_secret="..pClient_Secret
			  .. "&refresh_token="..pRefresh_Token
			  .. "&grant_type=refresh_token"
		local wLength = string.len(wMes)

		b, c, h = fa.request{
			url = "https://www.googleapis.com/oauth2/v4/token",
			headers = {
				["Content-Type"] = "application/x-www-form-urlencoded",
				["Content-Length"] = wLength,
			},
			method = "POST",
			body=wMes
		}

		local wRes
		if c == 200 then
			local wTempTable = {}
			wTempTable = cjson.decode(b)
			wRes = wTempTable["access_token"]
		end
		return wRes
	end

	-- Gdrive上のファイルの更新
	-- MetaDataの書き込みをしてないですが、multipartで記載
	function UpDateTextFile( pAccess_Token, pSDTargetFilePath, pGDriveFileId)
		local wBoundary = "foo_bar_baz"
		local wContenttype = "multipart/related; boundary=" .. wBoundary
		local wMes = "--".. wBoundary .."\r\n"
			  .. "Content-Type: application/json; charset=UTF-8\r\n"
			  .. "\r\n"
			  .. "{}\r\n"
			  .. "\r\n"
			  .. "--".. wBoundary .. "\r\n"
			  .. "Content-Type: text/plain\r\n"
			  .. "\r\n"
			  .. "<!--WLANSDFILE-->\r\n"
			  .. "--" .. wBoundary .. "--\r\n"

		local wLength = lfs.attributes( pSDTargetFilePath,"size") + string.len(wMes) - 17

		b, c, h = fa.request{
			url = "https://www.googleapis.com/upload/drive/v3/files/" .. pGDriveFileId .. "?uploadType=multipart",
			headers = {
				["authorization"] = "Bearer " .. pAccess_Token,
				["Content-Type"] = wContenttype,
				["Content-Length"] = tostring(wLength)
			},
			method = "PATCH",
			file = pSDTargetFilePath,
			body = wMes
		}
	end

	-- Gdrive上のファイルを取得
	-- Flash Air W-04 ver.4.00.03以上でないと動きません
	-- 2020/03/15修正：ver4.00.03で追加されたファイルの直接出力に変更（これが使いたかったのに何を勘違いしたのやら
	function DownloadFile( pAccess_Token, pSDTargetFilePath, pGDriveFileId, pSDBackUpFilePath)
		--先にSD上のファイルをバックアップ
		fa.rename( pSDTargetFilePath, pSDBackUpFilePath)
		b, c, h = fa.request{
			url = "https://www.googleapis.com/drive/v3/files/" .. pGDriveFileId .. "?alt=media",
			headers = {
				["authorization"] = "Bearer " .. pAccess_Token
			},
			method = "GET",
			rcvfile = pSDTargetFilePath
		}

		if c ~= 200 then
			--失敗したらにSD上のファイルを元に戻す
			fa.rename( pSDBackUpFilePath, pSDTargetFilePath)
		end
	end

	--ここからlibFlashTime.luaより借用
	--Windowsからファイルを削除出来ないことの対策
	--Wifi環境外でもデタラメでもいいので時刻を挿入
	function GetFATtimeFromLocalTime(Year,Month,Day,Hour,min,sec)
		local Year_bit = bit32.band((Year - 1980),0x7F);
		local Month_bit = bit32.band(Month,0x0F); 
		local Day_bit = bit32.band(Day,0x3F);

		local Hour_bit = bit32.band(Hour,0x1F);
		local min_bit = bit32.band(min,0x3F);
		local sec_bit = bit32.band(sec/2,0x1F);
		local YMD_bits = bit32.bor(bit32.lshift(Year_bit,9),bit32.lshift(Month_bit,5),Day_bit); 
		local HMS_bits = bit32.bor(bit32.lshift(Hour_bit,11),bit32.lshift(min_bit,5),sec_bit);
		return YMD_bits,HMS_bits;
	end

	--FlashAir内なのか？Fatの仕様かわかってないのですが、その形式変換（だそうです、、、
	function SetTime(Year,Month,Day,Hour,min,sec)
		local YMD,HMS = GetFATtimeFromLocalTime(Year,Month,Day,Hour,min,sec);
		fa.SetCurrentTime(YMD,HMS);
	end

	--Wifi環境下にて時刻サーバーにhttpsでアクセス
	--FlashAirのRTCに時刻を追加
	function SetNICT()
		local b,s,h;
		local i;

		for i=0,10 do
			b,s,h = fa.request("https://ntp-a1.nict.go.jp/cgi-bin/time");--(libより変更点)https化
			if(s == 200)then break; end;
			sleep(1000);
		end

		if(s ~= 200)then return nil; end;
		local itr = string.gmatch (b,"%g+");
		local DOTW= itr();
		local str_moth= itr();
		local Day= itr();
		local hms= itr();
		local itr_hms = string.gmatch (hms,"[^:]+");
		local Hour = itr_hms();
		local min = itr_hms();
		local sec = itr_hms();
		local Year= itr();

		local month_table = {Jan=1,Feb=2,Mar=3,Apr=4,May=5,Jun=6,Jul=7,Aug=8,Sep=9,Oct=10,Nov=11,Dec=12};
		local Month = month_table[str_moth];

		SetTime(Year,Month,Day,Hour,min,sec);
		
		--(libより変更点)ディバック用
		return "WorkDate:" .. Year .. "/" .. Month .. "/" .. Day .. " " .. Hour .. ":" .. min .. ":" .. sec;
	end
	--ここまでlibFlashTime.luaより借用

	--ここからメイン処理
	SetTime(2000,1,1,0,0,0);			--適当な時間をセット
	local DEBUG = "0.Program-Start\n"		--ディバック用
	local NOWTIME					--ディバック用
	local mode = 0					--UP/DLの切り替え変数
	local cSSID = SSID2				--使用するWifiを設定
	local cPW = PW2					--WifiのPW
	local ufp = io.open(sDStartUpLoadFilePath,"r")	--UP用の制御ファイル管理
	local dfp = io.open(sDStartDnLoadFilePath,"r")	--DL用の制御ファイル管理
	local wln = io.open(sDWifiFilePath,"r")		--Wifi用の制御ファイル管理

	--UP/DL用の制御ファイルの両方とも削除されていた場合は、UPを優先。そしてどちらのファイルも復元
	--UP用の制御ファイルの有無調査
	if not ufp then
		DEBUG = DEBUG .. "1.Up-mode\n"
		mode = 1
		ufp = io.open(sDStartUpLoadFilePath,"w")
	end
	--DL用の制御ファイルの有無調査
	if not dfp then
		DEBUG = DEBUG .. "1.DL-mode\n"
		mode = 2
		dfp = io.open(sDStartDnLoadFilePath,"w")
	end

	--Wifi用の制御ファイルの有無調査
	--あればSSID1を使用：テザリング機能側
	if wln then
		DEBUG = DEBUG .. "2p.Wifi-SmartPhone\n"
		cSSID = SSID1
		cPW = PW1
		io.close(wln)
	else
		DEBUG = DEBUG .. "2p.Wifi-HomeWifi\n"
	end

	--終わったらさっさと閉じる！
	io.close(ufp)
	io.close(dfp)

	--CONFIGファイルの実行条件が[LUA_SD_EVENT]の場合、頻繁に実行される可能性があるので
	--制御ファイルが削除されるまでWifiは使わない
	if mode > 0 then
		DEBUG = DEBUG .. "2.Connect-Wifi\n"

		--常時接続のCONFIGファイルの場合もあるのでチェック
		if fa.WlanLink() == 0 then
			--電波状況を考えてリトライ
			for i=0,3 do
				DEBUG = DEBUG .. "Testing-" .. i .. "\n"
				--FlashAir関数でSTAモード：子機モードで親機に接続
				fa.Connect(cSSID, cPW)
				--少し待たせてから、接続確認
				sleep(1000)
				if(fa.WlanLink() == 1)then
					--接続できれば次へ
					DEBUG = DEBUG .. "  Connect-OK!\n"
					break
				else
					--接続できなければ待ってからリトライ
					sleep(9000)
				end
			end
		end

		--接続出来ていれば、本処理を実行
		if fa.WlanLink() == 1 then
			--ディバック用
			ip, mask, gw = fa.ip()
			DEBUG = "GetIP:" .. ip.. "\n" .. DEBUG .. "3.NTP-start\n"

			--RTCに現在時刻を設定
			NOWTIME = SetNICT()
			if NOWTIME then
				DEBUG = NOWTIME .. "\n" .. DEBUG .. "4.Access_Token-start\n"

				--Access_tokenを取得
				local Access_Token = GetAuth( CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN)
				if Access_Token then
					if mode == 1 then
						DEBUG = DEBUG .. "5.UP-start\n"
						--アップロード開始
						UpDateTextFile( Access_Token, sDTargetFilePath, gDriveFileId)
					elseif mode == 2 then
						DEBUG = DEBUG .. "5.Dn-start\n"
						--ダウンロード開始
						DownloadFile( Access_Token, sDTargetFilePath, gDriveFileId, sDBackUpFilePath)
					end
				end
			else
				DEBUG = DEBUG .. "E1.NTP-Error"
			end
		end
	end
	DEBUG = DEBUG .. "End.\n\n"

	print(DEBUG)

	--CONFIGファイルの実行条件が[LUA_SD_EVENT]の場合は実行がループしそうなのでデバック出力はしない
	--[[
	local f = io.open("DEBUG.txt","a+")
	f:write(DEBUG)
	io.close(f)
	]]

CONFIG:POMERA運用時

	[Vendor]

	APPMODE=2
	APPSSID=-SSID-
	APPNETWORKKEY=-PW-
	APPNAME=gDriveAir
	APPAUTOTIME=0
	CIPATH=/DCIM/100__TSB/FA000001.JPG
	CID=******************************
	DNSMODE=0
	LOCK=1
	PRODUCT=FlashAir
	REDIRECT=0
	VENDOR=TOSHIBA
	VERSION=F15DBW3BW4.00.04
	WEBDAV=0
	LUA_SD_EVENT=GoogleDrive.lua

CONFIG:FlashTools Lua Editor動作チェック時

	[Vendor]

	APPMODE=5
	APPSSID=-SSID-
	APPNETWORKKEY=-PW-
	APPNAME=gDriveAir-test
	APPAUTOTIME=0
	CIPATH=/DCIM/100__TSB/FA000001.JPG
	CID=******************************
	DNSMODE=0
	LOCK=1
	PRODUCT=FlashAir
	REDIRECT=0
	VENDOR=TOSHIBA
	VERSION=F15DBW3BW4.00.04
	WEBDAV=2
	UPLOAD=1

##今後の作業として
このままだと、FlashAirにClient_ID,Client_SECRET,Refresh_tokenが丸裸なので、W-04のver4.00.04からの秘匿領域へのスクリプトの退避を検討

参考文献：
[ex1]FlashAir Developers:Lua機能:control(“hid_set_pass”)
https://flashair-developers.github.io/website/docs/api/lua.html#controlhid_set_pass
[ex2]FlashAir Developers:Lua機能:control(“hid_store”)
https://flashair-developers.github.io/website/docs/api/lua.html#controlhid_store

以下をFlashTools Lua Editorで実行して
    
    fa.control("hid_set_pass", "12345678")
    fa.control("hid_store", "GoogleDrive.lua", "12345678")

さらに以下のCONFIG変更すればいける？

    LUA_RUN_SCRIPT=H:GoogleDrive.lua
    LUA_SD_EVNET=H:GoogleDrive.lua

######(2020/03/15追記)無事に設定できて秘匿領域からの実行に成功

##編集後記
久々に自分にとって新しい領域のプログラムをして楽しかったのと、疲れたのと、週末に何やってるんだと思ったり。
実作業としては、木曜の夜4時間、金曜の夜3時間、土曜日6時間の計13時間。
これの前に家に転がっていたPQIAirでも同じことをしようと考えたけど、https化したこの時代にはちと厳しいかったですね。（OpenSSLをArm5用にクロスコンパイルすればいいと思うのですが、ビルド経験が皆無なのでご遠慮、、、、

Qiitaにログインしてまでまとめたのは、自分の備忘録のためとGoogleDriveAPIのライブラリを使わない実例が少ないので、誰かの検索の助けになればなぁなんて、、、、

自分の投稿はこれっきりと思いますが、それでは！
(Markdown記法はやっぱり慣れない、、、、、