SyntaxHighlighter

---SyntaxHighlighter ウィジェット---

2015年12月27日日曜日

FlashAirでLuaスクリプトを組む時のコツ

※本情報は、参考のために残しており、古い情報を含みます。
 現在主に更新しているのはwikiですので、最新の情報はwikiを参照してください。

---
FlashAirでLuaスクリプトを組む時のコツ

※FlashAir本体をアップデートすること!
 W3.00.01にしないと、さまざまなバグに悩まされます。

・メモリは少ない。
 スクリプト含めて16KBくらいと思っておくこと。
 なので、配列確保できるのは大抵10KBくらい。
 大きなデータを処理するのなら、マイコンのようにちまちま小分けで処理しないといけない。

・メモリのお掃除は頻繁に。
 Luaは本来メモリが潤沢にある環境で使うものだと思うのだが、
 そうでないので仕方がない。
 メモリ不足になると、メモリ確保に時間を食うので、処理速度が落ちる。
 また、メモリ不足のメッセージが出力に混ざりこむ。
 これを避けるには、collectgarbage()をプログラムの随所に入れておくこと。
 そうすると速度が落ちない。
 ただ、collectgarbage()自体が時間を食うので、頻繁に呼びすぎてもいけない。

・ライブラリは小さく。
 ライブラリのコードサイズがおもいっきりメモリを食い潰す。
 ライブラリ読み込み時にcollectgarbage()する必要が出ることもある。

・コメント少なく。
 コメントもコードの一部。コメントが大きすぎるとそれだけでメモリエラーになる。
 リリース時にはコメントを全削除するなり、別ファイルにするなりする方がいい。

・変数名・関数名は短く。別名を使うのが良い。
 変数名も多分食ってる。関数名も。
 なので、組み込み関数には別名を割り振って使うと多分軽くなる。
 ただ、これは自分がそう思っているだけで、実際には違うかもしれない。

・argの制限
 arg[0]で自身のパスが分かる。
 arg[1]で引数がわかる。
 引数はrun.lua?Helloのように与えることができる。
 file=A&name=Bのように与えても、分解されることはなくそのままでてくる。
 引数がなければnil。
 URLエンコードしたものは解かれる。バイナリを渡すとバグる。
 URLは(たしか全長含めて)128文字しか受け付けない。
 超えると414が帰る(3.00.01)。古いバージョン(3.00.00)だとバグる。
 末端にはLFがくっついているので注意。

・mathが無いので
 数学関数mathがない。ので、浮動小数点を切り捨てることすら出来ない。
 ビット演算は適当に丸めやがるので、切り捨てたい時には使えない。
 解決方法: block = tonumber(string.format ("%d",num));
 アホみたいだが、こういうやり方になる。

・RTCあるって知ってた?
 地味にあるんです。


・ファイルの書き込みという悪魔の所業
 ファイルの書き込みは色々と問題があるので、正直やりたいものではない。
 ホスト機器に差し込まれているときは尚更だ。
 外部からuploadやPUTをした時は、FATにロックが掛かることを忘れるな。
 Luaスクリプトから書き換えるときは、FATにロックがかからないことを忘れるな。
 FATにロックを掛けたければ
 fa.request("http://127.0.0.1/upload.cgi?WRITEPROTECT=ON")
 逆に、ファイルシステム破壊を恐れず解除したければ(再マウントすれば問題ない)
 fa.request("http://127.0.0.1/upload.cgi?WRITEPROTECT=OFF")


・共有メモリというスクリプト間共通変数
 一旦終了したスクリプトから、次に起動するスクリプトに情報を渡すには
 ファイルに書き込む方法を思いつくだろうが、それは上記の理由でおすすめしない。
 W3.00.01から、Luaで共有メモリにアクセスできるようになったので、
 こちらを使うことをおすすめする。512バイトまで記録できる。
 ブラウザからアクセスすることもできる。


・fa.requestの罠
 まず、3KBまでしか受信できない。
 タイムアウトがない。
 エラーが起きると最悪応答がなくなる。
 ポート番号も指定できない。
 ただ、TLS(https)はできる。
 せめてタイムアウト指定とか、なければタイマー割り込みとかほしい...


・隠し関数は積極的に
 ファイルの削除とか、ファイル名変更とか、ping飛ばしたりとか、できるんですよ実は。
 将来的に削除される可能性もありますが。 

・処理速度は20kHz?
 fa.pioで色々測ってみた限り、1処理に500usくらい食ってる感がある。
 また、処理中にWi-Fi関係とかの割り込みが掛かっている感がある。
 

・Wi-Fi起動電流に注意
 かなり食います。電流測定用にテスターかませるだけでダメになることも。
 起動時に200mA、定常120mA程度食うようです。
 Wi-fiをオフにすると数mA~数十mAで収まります。
 参考。

・pioとホスト
 fa.pioはホストとの通信を妨害します。
 ホストが初期化する前にpioを呼ぼうものなら、信号が衝突しますし、
 初期化後に拒否られた状態でpio呼んでも不安定になるようです。

 対策:スクリプトの冒頭に下のような行を入れる。
 sleep(15000);
 if(fa.pio(0,0) == 0)then return; end;

・結果を手早く見れるのはLEDと液晶
 ブラウザからLuaスクリプトを起動すると、CGIなので
 スクリプトが終了するまで結果が出てきません。
 なので、途中状態を知りたいのなら、LEDなり液晶なりを繋ぐか、
 fa.requestを受け取るサーバーでも用意するか、な必要があります。


・デバッグ面倒くさいならFTLEがおすすめ(ステマ)
 ブラウザからLuaスクリプトの書き換え・デバッグができるエディターあります。
 デバッグが楽になること間違いなし。


・IOポートの動作確認から始めるといいよ(ステマ)
 IOポートでなにかするのなら、まずはIOポートの出力が対象に伝わっているか
 からチェックするのがいいと思います。便利なツールが有ります。

・ライブラリを使いましょう
 悩む時間のほうがもったいないので。
 Airioを使うならこれ。
 AirioRPをつかうならこれ
 秋月FlashAir DIP IOボードを使うならこれ。

・ブリッジモードオススメ
 APモードでデバッグというのはやりづらいです。(ネットから切断されるので)
 なので、ネットが使えるSTAモードやブリッジモードを使うことをおすすめします。

 STAモードだとIPアドレス変わるからデバッグしにくいじゃないか、という方は
 こちらのIPアドレス固定方法を御覧ください。
 APモードやブリッジモードのFlashAir側のIPアドレスも同様に固定できます。

・古いファームウェアでスクリプトが動かない問題は、以下の様なコードでバージョンチェック
 してしまうことをおすすめします。
 特に新しい関数を使っている場合は読み込み時に落ちるので、
 その場合はブートストラップと本体にスクリプトを分けましょう。

if(fa.md5 ~= nil) then
print("FlashAir firmware version is too old!");
error("error() has been called.");
end;

FlashAirにA/Dコンバータ(MCP3008)をつなぐ

FlashAirにA/Dコンバータをつなぐのは非常に簡単。
MCP3008を用意し、以下のようにつなぐ。
(可変抵抗は観察用で、実際には必要な電圧入力を設定する)



次のようなスクリプトを書く。

fa.spi("init",1000);
fa.spi("cs",0);

fa.spi("write",0x01);
a = fa.spi("write",0x80);
b = fa.spi("write",0xFF);

fa.spi("cs",1);
fa.spi("read");

s = bit32.bor(bit32.lshift(bit32.band(a,0x03),8),b)
v = 3.3*s/1023;

print("CH0:"..v.."[V]");

一応解説を入れておくと、
0x01はスタートビット
0x80は測定指示(シングルエンド・CH0・ダミー)
0xFFはダミーである。

a,bに合計10bitのデータが入るので、sで結合し、vで電圧にしている。
MCP3008は厳密にはSPIではない(8bit刻みではない)のだが、
SPIでも互換で使えるように設計されており、
データシートにあるSPI機能を用いた使い方そのままである。

cs=1の後、空読みしているのは、前回の記事に載せた仕様(orバグ)ためである。

センサー情報をWi-Fiで飛ばしたい、あるいはスマホから読み取りたい時には便利なので
試してみてほしい。

より多くのセンサーを繋ぎたいのであれば、I2C変換機能を持ったボード
(秋月FlashAir DIP IOボードもしくはAirioRP)を使い、
I2Cで繋がるA/DコンバータICを使うことをおすすめする。

(細工をすれば、あまった1ピンで2つのSPIスレーブを制御することもできるが)



2015年12月24日木曜日

fa.spiの挙動(秋月 FlashAir DIP IOボードキットでfa.spiが動かない問題)

※FlashAir本体をアップデートすること!
 W3.00.01にしないと、fa.spi関数は正常に動作しません!
 (W3.00.00にも関数が存在はしていますが、動作不良のようです)

秋月DIPボードの挙動が、fa.spiを使った時だけ妙 + mode切り替えをすると動くことがある
という話を聞いたので、mode切り替えだけで出力が切り替わるのかを調査。


初期状態
SCK  = HIGH(Hi-Z)
MOSI = HIGH(Hi-Z)
INT  = HIGH(Hi-Z)
SS   = HIGH(Hi-Z)
MISO = HIGH(Hi-Z)

fa.spi("init")
SCK  = LOW
MOSI = LOW
INT  = HIGH(Hi-Z)
SS   = HIGH
MISO = HIGH(Hi-Z)

→defaultではモード3とリファレンスに書いてあるが、どうみてもmode3ではない。
 ただ、1度空readをするとmode3らしき挙動になる。
 また、モードを切り替えてもinitするとまた戻される。

fa.spi("mode",0)
SCK  = LOW
MOSI = LOW
INT  = HIGH(Hi-Z)
SS   = HIGH
MISO = HIGH(Hi-Z)

fa.spi("mode",1)
SCK  = LOW
MOSI = LOW
INT  = HIGH(Hi-Z)
SS   = HIGH
MISO = HIGH(Hi-Z)

fa.spi("mode",2)
SCK  = HIGH (時折LOWのまま)
MOSI = LOW
INT  = HIGH(Hi-Z)
SS   = HIGH
MISO = HIGH(Hi-Z)

fa.spi("mode",3)
SCK  = HIGH (時折LOWのまま)
MOSI = LOW
INT  = HIGH(Hi-Z)
SS   = HIGH
MISO = HIGH(Hi-Z)

・結論
切り替わった。

ただし、時折切り替わり不良を起こしているらしき状態がある。
(mode3なのにSCKがLなど。cs切り替えでも発生する)

その場合、いくら切り替えても治らない。(極稀に治る。)
ただ、fa.spi("read")とでもして送信処理をさせれば、必ず切り替わる。


ちなみに、このページの情報を元にどんなに頑張っても
AE-FAIO(秋月 FlashAir DIP IOボードキット)は思い通りに動かないと思います。
サンプル通りソフトSPIで使うことを強くおすすめします。

※動きました。
CSの前、もしくは後に空読みを入れると、不安定さが解消するようです。
CSが反映されないことがあるバグ(or仕様)が影響し、
連続したパケットとして認識されていた可能性が高いです。
ソフトSPIで動いていたのは、このバグ(or仕様)がないためだと考えられます

function write_reg(addr,data)
--fa.spi("read"); --こちらでも良い。
fa.spi("cs",0)
fa.spi("write",0x20)
fa.spi("write",addr)
fa.spi("write",data)
fa.spi("cs",1)
fa.spi("read");
end

if(fa.md5 ~= nil) then
print("FlashAirのファームウェアが古すぎます");
error("error() has been called.");
end;

fa.spi("init",300000);
fa.spi("mode",3);
write_reg(0x00,0xaa) -- IOCONFIG

write_reg(0x01,0x00) -- IOSTATE
sleep(100)
write_reg(0x01,0x0F)
sleep(100)
write_reg(0x01,0x00) -- IOSTATE
sleep(100)


この情報を元に、秋月FlashAir DIP IOボード用のライブラリを作りました。

2015年12月16日水曜日

FlashAirのLuaスクリプトでtarアーカイブを展開する

--展開にはめっちゃ時間かかります(1ファイル最低5秒以上)
--Tiny Tar Extracter.lua
--超簡易版です。ファイル名と、ファイル種別(フォルダorファイル)、サイズ、チェックサムしか見てません。
--(しかもチェックサムおかしくても展開続けます)
--フォルダ作成処理のところを入れ替えればWindows上でも動きます。

MastarPath="/";

f = io.open ("/FlashAir_Tiny_Lua_Editer.tar","rb");
if(f==nil)then
print "Err";
return;
end
print "OPEN";
next_file_head = 0;

while true do
collectgarbage();
now_file_head = next_file_head;
--FileName
f:seek("set",now_file_head);
fname = f:read(100);
--tar末端検出
fname = string.match (fname,"[^\0]+");--null文字削除
if(fname == nil)then break; end;
print("name:"..fname);

--FileSize
f:seek("set",now_file_head+0x7C);
size = f:read(12);--Tar形式は文字列
size = string.match (size,"[^\0]+");--null文字削除
size = tonumber(size,8);--(ただし8進数!)
print("size:"..size);
block = tonumber(string.format ("%d",((size+511)/512))); --ビット演算ダメ
print("blocks:"..block);
next_file_head = now_file_head + ((1+block)*512); -- HEAD+BODY
print("next_file_head:"..next_file_head);

--chksum
f:seek("set",now_file_head+0x94);
chksum = f:read(8);
chksum = string.match (chksum,"[^\0]+");--null文字削除
chksum = tonumber(chksum,8); --これも8進数
print("chksum:"..chksum);

sum=0;
for pos=0,511 do
f:seek("set",now_file_head+pos);
if((pos >= 0x94) and (pos < (0x94+8)))then
sum = sum+0x20;
else
sum = sum+string.byte(f:read(1));
end
end
print("sum:"..sum);

if(sum == chksum)then
print("chksum OK");
else
print("chksum NG");
end

--typeflag
f:seek("set",now_file_head+0x9C);
typeflag = f:read(1);
if(typeflag == "0")then
type = "File";
elseif(typeflag == "5")then
type = "Directory";
else
type = "Unsupported";
end

print("typeflag:"..typeflag.."/"..type);

--出力
if(type == "Directory")then
--windows用
--fname = string.gsub (fname, "/","\\")
--os.execute("mkdir "..fname)
print(string.sub("mkdir:"..MastarPath..fname,1,-2));
    if(lfs.mkdir(string.sub(MastarPath..fname,1,-2)) == true)then --末端/は削る
      print("mkdir OK");
      else
      print("mkdir fail");
      end
end
if(type == "File")then
fo = io.open (MastarPath..fname,"wb");
if(fo==nil)then
print "Output Err";
return;
end

collectgarbage();--メモリのお掃除


    --debug限界:8192+4096+1024+384 = 13696
    --release限界:8192+4096+1024+540 = 13852
Trans_Block =8192; --転送ブロックサイズ。メモリの余裕によって変更。
    pos_save=0;
    if((size-1) > Trans_Block)then --サイズが小さいならループしない
for pos=0,(size-1-Trans_Block),Trans_Block do
collectgarbage();--メモリのお掃除

      --メモリの関係もあるので64バイトづつ書き込む
f:seek("set",now_file_head+512+pos);
fo:write(f:read(Trans_Block));

--if((pos % 10240) == 0)then
io.write("\rputting..."..pos.."/"..size.."    ");
--end
pos_save=pos;
        end
pos_save=pos_save+Trans_Block;--最後の転送分を加算
end
print(size-pos_save)
f:seek("set",now_file_head+512+pos_save);
fo:write(f:read(size-pos_save));

fo:close();
end

print("\n---");
end


f:close()
print "CLOSED";

Copyright (c) 2015, GPS_NMEAAll rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

2015年12月14日月曜日

FlashAirの設定ファイルを自動で書き換えたい人向けLuaスクリプト

FlashAirの設定ファイルを自動で書き換えたい人向けLuaスクリプト。
今までの手抜き置換実装ではなく、本格的にCONFIG丸ごと書き換えられるように作ってみた。
配布Luaスクリプトで設定を自動で書き換えたい場合なんかにどうぞ。
動作確認はしていますが、必ず動作する・設定ファイルを破壊しない保証はありません。

三条項BSDライセンス。

--設定データテーブル。無くてもいいっちゃいい。
ini={
    APPAUTOTIME="",
    APPINFO="",
    APPNAME="",
    APPNETWORKKEY="",
    APPSSID="",
    BRGNETWORKKEY="",
    BRGSSID="",
    CID="",
    CIPATH="",
    DELCGI="",
    LUA_RUN_SCRIPT="",
    LUA_SD_EVENT="",
    MASTERCODE="",
    PRODUCT="",
    STA_RETRY_CT="",
    TIMEZONE="",
    UPDIR="",
    VENDOR="",
    VERSION="",
    IP_Address="",
    Subnet_Mask="",
    Default_Gateway="",
    DNS_Server1="",
    DNS_Server2="",
    Proxy_Name="",
    Port_Number="",
    APPMODE="",
    DNSMODE="",
    IFMODE="",
    LOCK="",
    NOISE_CANCEL="",
    UPLOAD="",
    WEBDAV="",
    DHCP_Enabled="",
    Proxy_Enabled="",
 
--[[
      APPCHANNEL="",
    HTTPDMODE="",
    HTTPDUSER="",
    HTTPDPASS="",
    APPMINTIME="",
    APPMAXTIME="",
    REDIRECT="",
    APPEXT="",
    APPTYPE="",
    AGINGTIME="",
    WLANSTAMODE="",
    DOMAINNAME="",
    COMMAND="",
    APMODE="",
    NOGATEWAYMODE="",
    SCRIPT="",
]]--
}

-- CONFIG READER ---
print "HTTP/1.1 200 OK\nPragma: no-cache\nCache-Control: no-cache\n";

--READ START
f = io.open("/SD_WLAN/CONFIG","r");
if(f==nil) then print ("Not Found.");return; end;

--ANALYZE
for l in f:lines() do
    s = string.gsub(l," ","");
    h = string.sub(s,1,1);
    print(s);
    if (h ~= "[") then
        for key, value in string.gmatch(s, "([^=]+)=([^=]+)") do
            ini[key] = string.gsub(value,"\r","");
        end
    end
end

f:close();

--CONFIG...
--ここで書き換えたい設定を記入する。
--ここで変更しなかったデータはそのまま残る。(順序はシャッフルされるが)

ini["DNSMODE"]="0";

--DELETE VOID KEY
for key,value in pairs(ini) do
    if(value == "")then
        ini[key]=nil;  
    end
end

--OUTPUT a-z
fa.rename("/SD_WLAN/CONFIG","/SD_WLAN/CONFIG.bak");

fo = io.open("/SD_WLAN/CONFIG","w");

print("-----")
print("[Vendor]\n");
fo:write("[Vendor]\r\n\r\n");
for i=0,26,1 do
    n = 0x61 + i;
    for key,value in pairs(ini) do
        h = string.byte(string.lower(key));
        if(h == n) then
            print(key.."="..value.."");
            fo:write(key.."="..value.."\r\n");
          end
    end
end
fo:close();

print "Done";

2015年12月6日日曜日

FlashAirのLuaスクリプトで得たファイル更新時間を文字列に直す

FlashAirデベロッパーフォーラムで「ファイルの更新日時を取得したい」という話があった。
どうせUNIX時間か何かだろう、と思ったが、試してみるとハマったので書いておく。

FlashAirに搭載されたLuaFileSystemライブラリからファイルの更新時間を
取得する(
lfs.attributes→modification)謎の数値が帰ってくる。例えば、1199991528など。

UNIX時間かな?と思い、変換してみると、今さっき作ったファイルなのに
2008/01/11 03:58:48とかになってしまう。

日付がおかしいのならともかく、時間単位でも合わない。

UNIX時間との差をとっても、次のファイルはまたおかしな値になる。

さっき作ったファイルと、今作ったファイルの値を比較してみても、秒単位ではないなにかで

数値が増えていることがわかる。単純に二倍や1\2でもない。

そう、これは秒数ではない。

では正体は何かというと、FATの時間情報そのものだ。
形式は以下。

01000111100001100110101011101000
YYYYYYYMMMMDDDDDHHHHHMMMMMMSSSSS

FATファイル システムのしくみと操作法 - ELM
http://elm-chan.org/docs/fat.html
こちらのサイトで言うところの、

DIR_WrtDateとDIR_WrtTimeの各16bitを結合した32bitのバイナリ情報だ。

ということで、これらを変換して、人間に読める形式に変換するスクリプトを書いた。

Lua5.2にビット演算機能が実装されていて本当に良かったと思った。

ライセンスはWTFPLなので、自由に使って欲しい。


--FAT形式のファイルアクセス時間を分解する
function GetFileModificationTime(Fat_binary_time)
   Year = bit32.band (bit32.rshift(Fat_binary_time, 9+16),0x7F) + 1980
   Month = bit32.band (bit32.rshift(Fat_binary_time, 5+16),0x0F)
   Day = bit32.band (bit32.rshift(Fat_binary_time,0+16),0x1F)

   Hour = bit32.band (bit32.rshift(Fat_binary_time, 11),0x1F)

   min = bit32.band (bit32.rshift(Fat_binary_time, 5),0x3F)
   sec = bit32.band (Fat_binary_time,0x1F)*2; --FAT時間は秒数が2秒刻み

   return Year,Month,Day,Hour,min,sec

end

--yyyymmdd_hhmmss.jpgを生成する

function TimeToFileName(Year,Month,Day,Hour,min,sec)
   return string.format("%04d%02d%02d_%02d%02d%02d.jpg",Year,Month,Day,Hour,min,sec)
end

-------------------サンプル----------------------

--Hello.txtの更新時間を取得する
a = lfs.attributes("Hello.txt")

--文字列で得る

Year,Month,Day,Hour,min,sec = GetFileModificationTime(a.modification)
print(Year.."年"..Month.."月"..Day.."日"..Hour.."時"..min.."分"..sec.."秒")

--文字列で得たものをファイル名形式にする

print(TimeToFileName(GetFileModificationTime(a.modification)))