ああ、ゴミが。否、ゴミでなくとも不要なものは全て。しかし、不要なものをゴミというなら、たとえほんのちょっと前まで部屋のすみで偉そうにしていた、それであっても。


承前。

 

センサーノードで生じた問題というのは、実行時に生じるOut of memory エラー。Stationモードで通常動作していても、APモードに切り替えた途端、起動しなかったりする。
しなかったりする、というがこれまた微妙なところで、一見うまくいったように見えても、電波出して端末と接続して(手製の)HTTPサーバーまで動いているのに、突然PANICで終わる。

 

コールバックスタイルなので、プログラム動作中でもシリアルコンソールは動きます。なので、ここからヒープやグローバルの内容を調べて原因を探ることになりますが、まあ、どこをどう攻めていいやら、手がかりがつかめず難儀しました。

開発者向けのFAQ(https://github.com/TerryE/nodemcu-firmware/blob/b094e382ee23e8fa74a6c8d4a129c1ef14905c8b/docs/en/lua-developer-faq.md)があって、英文なのでGoogle翻訳にかけて(こちらも容量オーバーで一括翻訳できないので、適当に区切ってコピペ)熟読してようやく解決の兆しが見えてきました。

 

話は逸れますが、Google翻訳は最近は本当に良くなって驚くほど的確な訳を出してくれるのですが、それでも時々おかしい訳がでてくることがあります。また、「知っていて当然」な成句なんかもそのままで、これはワタシの語学力の無さもあって何かの呪文のような文章になることもあります。
今回はそこまで酷くなかったのですが、それでも"catch22"と"close the barn door"については改めて調べなくてはなりませんでした。
それでも、コピペ作業のおかげもあって完全に内容を理解することができたので良しとしましょう。機械翻訳などという便利な世の中になりましたが、手間をかけるに越したことはないということで。

 

話を戻します。

 

Luaのガベージコレクタは徹底していて、使われなくなったものは何でも回収してしまいます。ガベコレ、というと8ビットパソコン世代の悲しさで変数しか思い浮かばないのですが、Luaでは関数やモジュールも対象になります。
最初FAQにこれを見つけた時ピンとこなかったのですが、すぐにこの仕組みでsoライブラリやDLLなんかに近い動作を実現していることに気づきました。
実はLuaのドキュメント(http://milkpot.sakura.ne.jp/lua/lua51_manual_ja.html)にはガベージコレクションについて詳細に書かれています。丁寧な日本語訳がアップされているにも関わらず斜め読みで済ましてきたツケを払わされた気分ですね。ワタシが悪いんですが。

 

具体的には、ガベージコレクタはpackage.loadedに登録されているモジュールは使用中と見なして回収しないので、任意のタイミングで登録を解除しておいて、後はガベコレにまかせる、というものです。簡単ですね。
注意しなくてはならないのは登録を解除するタイミングで、解除したいモジュールや関数の外部からは解除できません。解除するモジュールの実行コードの先頭でやります。これでモジュールを抜けて呼び出し側に戻った後にガベコレが回収してくれるという訳です。

 

モジュールのアクセスはテーブルを介して行うのが普通ですが、テーブルの取り込みが無駄(何せ直後に不要になる)になるので、関数を返す方法もあります。

書き方は 

conn:on("receive", function(sck,data) require("httpserver")(sck,data) end)

等とします。最初、requireしたhttpserver(自作)が関数を返すようにしたのでonの第2引数にそのまま書いたのですが動きませんでしたので、functionの中に書いています。
しかし、コードの見た目が個人的には地味にインパクトあります。今までrequireをCのinclude の親戚くらいにしか考えてこなかったので。

 

ちなみにこのhttpserverですが、APモードで端末側に送るhtmlがそこそこの量あります。APモード自体、初期設定の為に1回くらいしか動かさないので、使用後に抜去してしまうのは合理的といえます。

 

 

ワタシはこういう芸の細かいのは大好きなので多少手間がかかっても苦になりません。nodemcu(と、esp8266)はソフト屋的にもかなり遊べるデバイスではないでしょうか。

 

 

esp8266(EP-WROOM-02)ではLua(eLua,nodemcu)、データを受けとるPC側ではPython、そして今度はSQL(SQLite3)も扱わなくてはなりません。


また、日頃はシェルスクリプトも書く(書き流す)し、使っているOSがLinuxなので何か問題があれば延々と他人の書いたCやC++のソースを読まなくてはならず、最悪の場合自分で改変することもあります。

 

そんな訳で時々頭がコンガラガッてしまうのですね。例えばLuaで do done とか書いてみたり、Cのifで条件式に()つけなかったり。どの言語使ってもやることは一緒なんですが。

 

ミクスドランゲージプログラミング、というのは例えばCで書いていてハードを直接叩きたい時にアセンブラを使う、とかPythonで書いたら遅すぎてものにならないので部分的にCで書きなおすとか、そういった形態を指すと思うのですが、一つの仕掛けを得るのにあれやこれや言語を動員してプログラムする、というのも広い意味でそのように呼んで良いのかも知れません。

 

まあ、今回はハードウェア要件が単純ですし、socket使えて且つファイアウォールなんかは然程考慮しないで済んでいるので全体としては楽な部類でしょう。

 

ワタシはアマチュアなのでこんなふうにマイペースでやれますが、同年代のプロも少なからずいる訳で、本当にお疲れ様ですと心底申し上げたいですね。

しつこく、esp8266(ESP-WROOM-02)とnodemcuに取り組んでいます。

 

アプリケーションは相変わらず温度計(と気圧計)ですが、ようやくAPモードになって端末からLAN情報をもらって、その後STAモードに遷移する、という一連の動作ができるようになりました。途中、メモリエラーを吐きまくるようになり、どうしたものかと悩みました。node.heap()でチェックすると10kBくらい残っているのですが、APモードでLAN情報をもらう際に正規表現使ったりしていますのでその為かも知れません。htmlも当初ヒアテキストにしていたのですが、listen時にoutofmemoryを吐きましたので、ドキュメントのサンプルにあるように細切れにしてtableにしました。

 

さらに、APモードの設定にてwifi.WPA_WPA2_PSK としていたところをwifi.WPA2_PSKとしてみました。

 

これらはsdkやeLuaの挙動を完全に理解している訳ではなく、当て推量がほとんどなのですが、どうにかエラーを吐かなくなりましたので効果はあったようです。

 

 

データを受け取るPC側のCGIも修正し、CSVファイルに追加するのを止めてsqlite3(pythonモジュール)に変えました。sqlは以前仕事(一般事務)でデータ処理するのに少し弄ったことがあるくらいで完全に忘れています。そもそも、MSAccessが動くのでGUI頼みで真面目に覚えようとしませんでしたから。

 

データベースにした理由は、センサーノードが増えた(といっても2,3つ)場合、抽出の手間が増えるのとデータが蓄積された後の集計や解析のことを考えたことによります。

 

 

それからテーブルやクエリを対話操作で確認したいので、ツールとしてPalemoonのアドオンをインストールしました。

 

データベースを導入したのでグラフの作成プログラムも最初から書き直しです。こうなったら、これもCGIにしてリモートからはWeb経由で確認できるようにしようと考えています。

 

何かどんどん大げさになってきたような。sqlの参考書を探してこなくては。

 

sntpもrtcも自前で何とかなったのですが、今ひとつスッキリしません。というのも、Luaで書くとどうしてもプログラムが長くなるのからなのですね。

 

長くなる原因はいろいろありますが、型を定義できない為、というのもその一つ。byteやらsubやらtonumberやらを駆使して延々と綴っていくしかないのですね。

 

稿末に示したコードは今回書いた「かなり簡略化した」sntpクライアントなんですが、貰ってきた秒時を年月日時分秒に変換し、さらにrtc向けのBCDに直すというのが、如何にも、なんですね。

 

実行速度稼ぎたいのでmath.floorをローカル変数floorに入れたりとかしてます。が、nodemcuの場合、メモリ上の制約もあってグローバル推奨みたい。悩ましいところではあります。

 

ということで、nodemcuで用意されているモジュール(sntp,rtctime他)を追加してみることにしました。手順はドキュメントに書かれているのでここには書きませんが、ヘッダファイルのコメントを外して、再makeするだけです。ESP-WROOM-02に書き込む際は、erase_flashしてからのほうが無難でしょう。

 

nodemcuのRTCも普通に時刻を見るだけなら問題ないのですが、今回のように割り込み源として使おうとするとちょっと不足のような気がします。というか、直接的な方法だとできないのではなかろうか。

 

思いついたのは、スリープして所定の時刻(今回は1分間隔)で再起動し、データを送信してまたスリープする、というもの。手順としては定石ですが、時刻の精度に難あり。再起動するごとにsntpで時刻修正することになろうかと思いますが、wifiの再起動もやるとなると結構時間かかりますですよ。

 

rtc(RX-8025)はそのまま続投かも知れません。

 

sntp={}
sntp.host="jp.pool.ntp.org"
sntp.port=123
sntp.request=string.char(0x0b)..string.rep(string.char(0x00),47)
sntp.ip=nil
sntp.us=nil

 

function sntp.register(userfunction)
    sntp.us=net.createUDPSocket()
    -- function(s, data, p, i)
    sntp.us:on("receive", userfunction )
end

 

function sntp.sync()
    sntp.us:dns(sntp.host, function(sck,i)
        sntp.ip = i
        sntp.us:send(sntp.port, sntp.ip, sntp.request)
        end
    )
end

 

function sntp.close()
    sntp.us:close()
end

 

-- Utility
function sntp.ntp2bcd(ts)
    local m, h, d, y = 60, 3600, 86400, 31536000
    local unixofset=2208988800
    local jstofset=9*h
    local floor=math.floor
    local days={31,28,31,30,31,30,31,31,30,31,30,31}

    local function bin2bcd(b)
        return floor(b/10)*16 + b % 10
    end
    t = tonumber(ts)
    t = t - unixofset + jstofset
    local year = floor(t / y)
    if year % 4 == 0 then
        days[2] = 29
    end
    local leap = floor((year-2)/4)
    t = t % y
    t = t - leap * d

    local day = floor(t / d)
    local month=0
    for i=1, #days, 1 do
        if day <= days[i] then
            month = i
            t = t - day*d
            break
        end
        day = day - days[i]    
        t = t - days[i]*d
    end

    local hour = floor(t / h)
    t = t % h

    local minutes = floor(t / m)
    local seconds = t % m
    year = (year + 1970) % 100
    return
        bin2bcd(year),
        bin2bcd(month),
        bin2bcd(day),
        bin2bcd(hour),
        bin2bcd(minutes),
        bin2bcd(seconds)
end

 

あくまでも暦の上での話であって、実際は夏本番な訳ですが。

 

終日、esp8266と格闘していました。一応、紛いなりにもsntpで時刻情報を取ってこれるようになりましたので時刻合わせは自動になりました。紛いなりにも、というのは遅延時間を考慮していないからで電波時計と比較して1秒ほど遅れて見えます。まあ、実用上の問題はないでしょう。

 

IPのやりとりについては、ネットを漁ってみたところ、起動直後はwifiをAPモードにしてwebサーバーを動かし、端末からssidとpwdを設定する、というのが普通なようです。いくつか事例を見つけたのですが、例によってarduino互換環境での事例ばかり。Lua(nodemcu)でやっている、というのは圧倒的少数派のようです。

 

nodemcuでもAPモードは当然使えます。が、STATIONモードでは動いていたサーバールーチンが動作しない。

 

なぜ?

 

 

 


代わり映えしない見た目ですが中身は結構進歩しました。自動で時刻補正して、分刻みで気温と気圧をデータ収集サーバに送っています。消費電力がアレですが(LPS25HのLEDが余計なんだよなあ)。

8月6日早朝の室温記録。明け方の冷え込み、というのが見られない。7時半位から上昇するのは光線が差し込むようになるからで、8時台にグラフの傾きが変わっているのは起床して窓を開けた為。その後の急降下の気配はエアコンを動かしたことによります。

 

地味にesp8266を弄っています。nodemcu既製のsntpモジュールがデフォルトで組み込まれていないので、自分で書いて、rtcの時刻設定ができるようになりました。なんせ不慣れなので動かすまでまる1日かかりましたが、おかげでLuaを使うコツみたいなものがわかってきました。何でもそうですが、自分自身で手と頭を使って試すのが結局は一番の近道になると思います。(尚、納期は無視された模様)

 

 

 

自分宛てメモ

 

Lua5.1は実数しか扱わない。ので、符号なし32ビット整数のつもりでいると、負数にされたりする。

関数に渡す場合は、16進文字列(0xnnnnnnnn)で渡して受け取り側でtonumberをかける。

 

 

nodemcu単独での計時が難しいので、RTCを接続することにしました。

 

RTCには、世間での使用例が多いことからEPSONのRX-8025を試してみました。インターフェースはI2Cです。既にLCDや温度センサーを接続しているので、問題は少ないだろう、という判断でした。

 

しかし。

 

まあ、ろくにマニュアルを読まずに始めたのでハマるハマる。全く設定ができなくて、慌ててマニュアルを読み直したら、レジスタアドレスの与え方が間違っていたのが原因でした。また、ものが「時計」ということもあり、通信条件にちょっと注意が必要なところもありましたので、アプリケーションマニュアルの23ページ以降はしっかり読み込んだほうがいいと思います。

 

Luaレベルで注意しなくてはならないのは、通信開始から終了まで(i2c.start から i2c.stopを実行するまで)を0.5秒以内に収めなくてはならない、という部分です。これは、通信中にカウントが進んでデータ不整合になるのを防ぐ為、カウンターを一時停止し通信終了後に補正して再開することによるものです。

 

このRTCは割り込みを2通りの方法で発生できますが、今回は定時割り込み機能を使って1分毎に割り込みを行うことにしました。これでタイムスタンプ付きのデータをPCに飛ばすことができます。

 

プログラムをざっと書いて動かしてみたのですが、目論見通り動作してくれました。後はIPの自動取得や時刻の設定、エラー処理等細かいところを詰めて行くだけです。

 

esp8266(以下センサーノード) と データ収集サーバー(以下PC)との接続について。


両方共LANに接続しているのでIPアドレスの参照は比較的容易だけど、それなりに手順を踏まなくてはならない。

 

以下、手順。


PC側

  1. PCでnmapか何かを使ってLAN上のアドレスを得る。
  2. 上記で得られたアドレスに「お前はセンサーか?」と問い合わせる。その際、PCのアドレスも送る。
  3. センサーノードが「俺はセンサーだ」と返してきたら、それを登録して、データが流れてくるのを待つ。

 

 

センサーノード側

  1. 初期化して、ルータがアドレスを割り当ててくれるのを待つ。
  2. サーバを作って、PC側からの問い合わせを待つ。
  3. PCに「俺はセンサーだ」を返信する。
  4. センシングして適宜PC側にデータを送る。

 

さて、ここで問題になるのはセンサーノード側の手順に待ちが多いこと。コールバック内での待ちループが使えないのは勿論、フォアグランド側の待ちも無駄なので差し控えたい。
また、手続き型のつもりでだらだらプログラムを書いていて、コールバックが動いているのを忘れて挙動不審に陥ったりするのも回避したい。

 

ということで、処理を細切れにしてスイッチャで繋ぐことにしました。コードは嵩みますがプログラムの見通しは良くなるはずです。(そうか?)

 

スイッチャは次の通り。全体として繰り返し処理する訳ではないので、処理を単純にするため起動と同時にそのタスクはテーブルから抜去してしまいます。もし、繰り返しが必要なら抜去した要素をテーブルの末尾に追加すれば良いと思います。タスク参照用の添字の管理が省けるでしょう。

 

function task_switch()
    tm:stop()
    tm:unregister()
    if #task > 0 then
        tm:register(10,tmr.ALARM_SEMI,table.remove(task,1))
        tm:start()
    end
end

タスクテーブルはLuaのテーブルそのまま。

task = {ipcheck, startmes, getready_for_dataserver, showserverip,
        sensor_start,showsensor
}

 

起動はタイマ(tmr)をSEMIモードで使います。このモードでは所定時間(ここでは10ms)後にコールバックを起動したら、そこで一時停止します。再開するにはtmr.start()を使います。

 

例えば上記テーブルの最初の要素 ipcheck はルータからのIP割当を「待って」いるのですが、wifi.sta.getip() の結果が nil ならtmr.start()を呼んで次のタイミング(10ms後)を待ちます。もし、IPが得られたならtask_switch()を呼んでコールバックに次のタスクをセットします。これで10msに次のタスクに遷移します。

 

SEMIモードで一時停止するのがミソで、これがAUTOモードだと次のタイミングまでに全ての処理を終えなくてはなりません。終わらない場合は一旦コンテキストを退避してルーチンを途中で抜けるという、面倒な処理を書かなくてはなりません。コルーチンが使えるので多少は楽できると思いますが。

 

その代わり、同時進行型のいわゆるマルチタスクにはなり得ません。もし、このままマルチっぽくしたいなら、別ルーチンでタスクの処理時間を計測して、タスクが遷移した時に不公平がないよう、呼び出し回数で調整するしかないでしょう。

 

管理するセンサーやデバイス(LCDとかブザーとか)が多くて、順番に見て回りたい時とか、タスクの挿抜が容易で使えそうと思うのですが。nodemcuはコールバックが充実しているので出番は少ないかも知れませんけど。

 

esp8266、より厳密に言えばESP-WROOM-02(以下ESP)に環境センサー(気温と気圧)を繋いでデータ取りをやっています。

 

計測時間の管理は収集するPC側でやっています。手順としては、所定の時刻(現在は2分刻み)になったらESPにリクエストを出してデータを返してもらう、この繰り返しです。

 

この方法の問題点は、ESPが常時起動していなければならない点。消費電力的にバッテリー駆動するのが難しくなります。
また、ESPを複数用意してあちらこちらのデータを収集しようとすると、PC側の処理時間の都合で必ずしも時刻的に正確なデータにならない可能性が出てきます。まあ、今回は2分間隔でデータも気温と気圧なので実際(10個位なら)は大丈夫ですが。

 

解決方法としては、ESP側のタイミングでPCにデータを送信すれば良いのですが、PC側の応答待ち(最悪ダウンしている可能性もある)により時刻が不正確になるため、計測時刻の管理をESP側でやらなくてはなりません。要するに、送信データにタイムスタンプを付す訳ですね。

 

 

nodemcuのドキュメントを呼んでいると一応、rtctimeというそれらしいモジュールが出てきます。が、読んでみるとどうも「なんちゃってrtc」らしい。スリープ時の実行速度は不安定だし、そもそもMPUの動作クロックが可変だし、厳密な時刻の管理は覚束ない模様。このためsntpを併用して随時時刻を補正して使うようです。まあ、ネットに接続しているのだからntpはどこかにあるでしょう。

 

ということで、早速動かしてみようとしたのですが動きません。またですか、と思ったのですが、今回はちょっと様子が違います。


print(sntp)
nil
print(rtctime)
nil
ROMにモジュールが存在しません。

 

ドキュメントに記載されている全てのモジュールが組み込まれている訳ではないようです。ROMサイズの制約によるものらしい。デフォルトでは512KBシステムでも動くようにしてある、とか何とか。

 

この辺、昔のパソコンの16KB-BASICとか32KB-BASICとかいうのと同じノリですね。

 

 

詳細は (nodemcuを展開したディレクトリ)/app/include/user_modules.h とかに書かれています。尚、これを書き換えれば所望のモジュールが組み込まれる、のかは試していません。というのも、時刻管理をrtctimeとsntpの組み合わせでやるのは結構煩わしいからです。I2Cが動いているので、ここにハードを追加したほうが余程スマートだと思います。

 

モジュールを追加して面白そうなのはpwm関係ではないでしょうか。モーター回したり音出したり(データをどこに置くのか、という問題はあるが)いろいろ遊べそうです。

 


Search

Calendar

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
2728293031  
<< August 2017 >>

Archive

Mobile

qrcode

Selected Entry

Link

Profile

Search

Other

Powered

無料ブログ作成サービス JUGEM