2020年7月14日火曜日

配列への追加についてグダる

時事ネタは突っ込み所が多すぎて全然関係のない話題が続いていますが、今回もそんなネタ。

最近のスクリプトや言語は型や配列に関してものすごい自由度が高いのですが、その代償として何かしらの犠牲を伴っています。

私が言語に最初にどっぷりと漬かった言語はturbo-pascalなのですが、その原因はどうしても細かい所まで見て実装を確認しないと処理が上手く回らなかったり、処理時間を短くするために内部でどのように動いているのかまで見ないといけなかったからだと思います。
あと付属するマニュアルが結構読み応え満点で実際にそうなっているかどうかは別にして、結構突っ込んだ事まで解説されてたりしました。日本語版なので訳者の思い入れが加味されていたのかもしれませんが。

基本的な考え方として、配列は単なるメモリ空間なので、最初に領域を決めたら拡張することができません。動的に変動させるためにはどうするか?というところで実装の仕方は色々な考え方でうまく乗り越えているだけのこと。
追加に対して代償を減らすには領域確保で指定された領域以上に予め確保することで時間的リソースの節約を行います。これはどんな形の配列でも変わりません。繰り返すことでメモリー空間に分断された状態になると、どうしてもアクセス効率が落ちるので、最終的にはガーベージコレクトや再生成するなどして綺麗な状態に保つ必要があるかもしれません。とはいっても、すでにCPUキャッシュやらなんちゃらキャッシュやら、広大なメモリー空間のおかげで実害は出てこないのも事実。ただおまかせ状態だとそれなりに何らかのリソースの浪費はしていると思います。

一番非効率なやり方は、新たに配列全体の領域+追加分の領域を確保して、配列の内容のコピーを行って不要になった領域を解放するという方法ではないでしょうか?これの代償としては配列に対してメモリアドレスなどで直接参照しているものがある場合、参照している部分も変更する必要があるので、結構な処理負荷が出てきたり、放置すると簡単に処理が落ちることになります。
これを結構積極的に行っている言語というかシェルというか物が、おそらくAndroidだと思います。基本的な考え方として、すべてのオブジェクトインスタンスは最悪破棄して再生成すれば元通りという発想のもと、リソース不足になると積極的に破棄したり、どこかに追いやられてしまいます。最近のバージョンではかなりうまい仕組みが導入されて改善されていますが、その辺がどうしても好きになれない場合は苦痛しかないかも。
とはいえ、非効率なやり方の利点として、追加しても常に新規に領域確保された状態になるので配列へのアクセスでまずおかしなことは発生しません。配列の要素へのアクセス速度も常に変わらない状態でアクセスできるため、タイミングの影響を受けるようなこともまず発生しません。

で、なんでこんなたかが配列への要素の追加というくだらない話をだらだら書いているのかといえば、pythonスクリプトで結構安易に .append()で要素の追加を行っている部分を見て、最近気にしたことはなかったけどこの辺どうなのかな?とちょっと突っ込んでみたところ、やはり結構処理的に割高な処理なんだと再認識したのでグダりましたw
たしかに便利なんですけどねw処理時間がμ秒単位の場合はやめた方が良いかも。どうしてもという場合はpythonの場合はarray.array('b')とか、要素の追加を減らす処理に変更するとか、そもそも要素の追加をさせない形にするとか、仕様を見直した方がいいんじゃないかな?

実際にどんな処理でこの辺の処理時間が気になっかと言えば、DHTxxのライブラリっぽいやつで、エラーが発生したときにどんな状態で発生しているのか気になったので色々と書き換えて気になりました。
どのくらいのインパクトがあるかといえば…raspberry pi 3b+でループでGPIOピンの状態をサンプリングしている処理があるのですが、そこでループのタイミングで配列に要素を追加してサンプリングしていたときは、最大25回ぐらいだったのですが、これを信号が変動した時だけにした場合、最大30回ぐらいになりました。「ただし」が付くのですが、raspberry piで使用しているCPUはマルチコアとなっていてメインとなるプロセッサー0とその他とで処理能力の差があるようで、25回→10回、30回→10回ぐらいに簡単に落ち込みました。処理が落ちてる状態なら変わりないということではなく、おそらく不利になるのがGPIOなどのIO周りのウェイトがプロセッサーによって異なっているためだと思います。逆に言えば、現状ではメモリアクセスの時間リソースへの影響が小さくなったという結果なのかもしれませんw(結局何が言いたいんだ(;´д`)

2020年7月13日月曜日

DHT11とDHT22

色々と思うところがあってセンサーの選定をしていましたが、安価で簡単に手に入りそうなDHT22をポチって見ました。2個セットで1400円。一個700円とか3個セットのBME280より高価ですね…。

で早速手持ちのソースをデータシートをみつつ変更したところ、いきなりうまく行かず…すこし試行錯誤した結果、開始時の手順はDHT11の時と同じにするのが安定しました。データシート上だと細かく記載されていて手順が増えているようにも思えたのですが、結果としてpull upされた状態からダウン状態にした後、アップして待機するだけでデータが送られてきました。

それなりにDHTの共通ライブラリっぽいソースも見てみましたが、DHT11とDHT22とではデータの加工方法が違うだけのようでした。

今回DHT22を選んだ理由として、小数点以下の数字が取得できるっていうのがポイントだったのですが、これは今日発覚したDHT11でも温度の小数点が取得できたということで半減してしまいました…。(解像度が16bitで温度が整数のみってどう考えてもおかしいだろという突っ込みはさておきw)

気を取り直してスペックを見てみましょう。

DHT11DHT22
温度範囲0~50℃
-20~60℃
-40~80℃
温度解像度16bit16bit
温度レスポンス10s10s未満
湿度範囲20~90%RH
5~95%RH
0~99.9%RH
湿度解像度16bit0.1
湿度レスポンス6s5s未満
電源電圧3.5~5.5V3.5~5.5V
消費電力0.3mA8mA
サスペンド時60μA10~20μA
スリープまでの時間2s2s
上記は各データシートからの抜粋値で、レスポンスに関してはデータシート上に前提条件として1/e(63%)とか25℃でとか書いてあるので、センサー素子から値を得るのに何度も試行する必要があるもののようです。範囲が広がっているDHT22の方が消費電力が上がっているのもこの辺が影響してそうです。

正直、アップデート後のDHT11なら一般使用は問題ないんじゃというのはさておき、気になる違いはとしてDHT22の優れている点は温度、湿度の範囲が少し広いところでしょうか。DHTの仕様特性として、ほとんどの時間をサスペンド状態で放置されるはずなので、サスペンド時の消費電力が低くなっているのもいい感じです。
デメリットとして消費電力が数字上結構上がっているところ。データシート上はturn onと書いてありましたが、絶えず値をとり続けるのであれば、DHT11は結構優位ですね。

見直せば見直すほど、DHT22を買った利点がないわけですがw



関係ないところで、Raspberry piの放熱やセンサー自体の放熱もあるようで、DHTシリーズはエラー時のリトライで取得できる温度が少し上ってしまう感じがします。手で連続で値を取得すると温度がすこし上がることが多いのもおそらくセンサー自体の発熱があるためのような。

DHT11の温度は小数点まで持っていたw

錯乱状態が少し解消され様子を見たところ、
16時過ぎぐらいからグラフの形が矩形からいい感じになりました。
今までは小数点以下は無いと思っていたので、ここで100均の基準温度計に表示を合わせてみます。
小数以下がカットされていたという感じの重なり具合に。

すでにDHT22を買ってしまったんですが、温度の小数部があるなら、基準センサーはDHT11でもよかったかも…(==;

仕様を呑み込めなく錯乱へ…

昨日電車の中で、スマホでデータシートやソースを見ながら頭の中を整理してかなりの部分がすっきりしたのですが、困ったことにDHT11の仕様で勘違いしていた部分があったようで…

そもそもの起点に戻って整理してみようかと。

まず元となったpythonのソースがどこだったのか謎だったのですが、おそらく
ここだったと思われ。再度アクセスしたらgitサーバーが落ちたのかアクセスがエラーに。
で、経緯としてはその後、修正が加わって大きな違いは
「温度と湿度ともに小数部を加味するようになった。」
という点。
で、DHT11自体は仕様がアップグレードされセンサーの感度範囲が広がったこと。
湿度は 20-90%RHから5-95%RH
温度は 0 to 50 ℃ to - 20 to 60 ℃

大きな違いは温度がマイナス温度から検知できるということ。

で、アップデート後のドキュメントで温度に関しての説明で
The temperature is high as part of the data of the temperature.
The temperature is low as part of the data of the temperature, and the temperature low bit 8 is the negative temperature, otherwise the temperature is positive
と書かれていて、あんまり意識していなかったのですが、これをグーグル翻訳にかけてみると…
気温のデータとして気温が高い。
温度データの一部として温度が低く、温度の低いビット8が負の温度で、それ以外の場合は温度が正
意味不明すぎなのですが、原文を見直せば見直すほど意味が理解できなくなりました(*´Д`)
もともと英語力なんて無いのであれなんですけどw
英語圏の人はこれで理解できるのかな?

で、色々と考えた結果、アップデート後のv1.3では「温度がマイナスの場合は、温度の下位バイトの8ビット目が1になる」というとらえ方でいいのかな?どのタイプのDHT11を手にしているのかもわからないのであれなんですが、、、現状の上記のgitのソースを見ると、
humidity = the_bytes[0] + float(the_bytes[1]) / 10
となっていて、pythonで 0b10000000ってなっててもマイナスの扱いなんてしないし、そもそも足しこんでいるのですっげーやばいと思うんですよね。
そもそも「/10」っていう部分が気になる人には気になるかもw
pi@rasp4b4g:~ $ python3
Python 3.7.3 (default, Dec 20 2019, 18:57:59)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0b10000000
128
>>> float(0b10000000)
128.0

こんな感じでfloatしても、こんなことに。 仕様上は下位バイトは0~9までしか格納されないのですが、マイナスの場合は符号ビットを取り外さないとイケないわけで。
humidity = (the_bytes[0] + (the_bytes[1] & 0x7F)*0.1)*(-1if(the_bytes[1] & 0x80)else(1))
とか、そんな感じにする必要があるような?

v1.3のセンサーも無いし、データシートのデータサンプルに気温4024℃とパリティーエラーしか載ってないので正確なことは言えませんが。

他にもなんか色々といままで見かけた仕様も矛盾してしまってたりしてほんとに錯乱状態だったので綺麗さっぱりと忘れることにしよう。

この辺が気になりだした原因は、そもそもDHT11の仕様で湿度、温度ともに整数値しかないと思っていたところ、受け取った4バイトのデータを実際に表示して気づいたという。
しかも、decimalを10進と翻訳されて、データフォーマットが回路用に設計されてるのかな?とか勘違いしてたのが原因でした。実際のデータを表示させてその勘違いに気付いた次第です。




そもそもなところで、センサー誤差を拾うために結局DHT22も入手して只今思案中w

2020年7月11日土曜日

再起動後、録画直後にmotionが落ちる

再起動した後、気づいたらカメラの動作ランプが消えていて、サービス無効化したっけか?とsudo service motion statusで確認すると、Activeの表示。(topとかでプロセスの状態を確認すればよかったかも…)問題がなさそうなので、ログを見てみることに。
/var/log/motion/motion.log
が存在していたので、起動したのは確実。理由はこのraspberry piはほとんどのlogはtmpなので再起動で消えてしまいます。
中身を見ると、確かに[ERR]がいくつかでててそこで終わっています。

特に考えもせずにsudo service motion restartで、motionを再起動。
そしてカメラを動作させて様子を見ると…普通に動いてます。ですよねぇ…今まで動いていたし。
思い起こせば、マスクをかけていたのを外したからムービーファイルが巨大になって落ちたのかな?とか。でもそんなことはないと思うのでちょっと納得いかない。

しばらく放置しても落ちそうになかったので、再度再起動してみる。
カメラの動作ランプが点灯して、motionを反応させて録画させてみると、保存のタイミングでカメラのランプが消えてしまいました。落ちたか…。
sudo service motion restartで再起動させて、しばらく様子を見ると普通に動作している感じです。
原因がさっぱりわかりません。

再度ログをみてみると、
[1:ml1:F] [NTC] [EVT] [Jul 11 01:38:03] event_newfile: File of type 8 saved to: /tmp/20200711013802F-01.avi
[1:ml1:F] [NTC] [ALL] [Jul 11 01:38:03] motion_detected: Motion detected - starting event 1
[1:ml1:F] [ERR] [ENC] [Jul 11 01:42:20] ffmpeg_avcodec_log: Too large number of skipped frames 235812 > 60000
[1:ml1:F] [ERR] [ENC] [Jul 11 01:42:20] ffmpeg_put_frame: Error while writing video frame: No such file or directory
[1:ml1:F] [NTC] [ALL] [Jul 11 01:42:20] preview_save: different filename or picture only!
[1:ml1:F] [NTC] [EVT] [Jul 11 01:42:20] event_newfile: File of type 1 saved to: /tmp/20200711013809F-01-01.jpg
[0:motion] [NTC] [ALL] [Jul 11 01:56:05] motion_startup: Using log type (ALL) log level (NTC)
[0:motion] [NTC] [ALL] [Jul 11 01:56:05] become_daemon: Motion going to daemon mode
設定は結構変なことをしていて、一旦tmpに動画を作らせてからそのあと保存用のディレクトリに移動させてたりするので、出力が/tmpになってたりします。

原因の一端にその辺もありそうですが、エラーメッセージで検索をかけたところ、
を見てみると、timelapse設定をオフにしてみると対処できるらしい。が、そんな機能使ってないんですが。
まぁそれはそれとしてmotion.confを見てみると、timelapse関連の設定は、

ffmpeg_timelapse 0
ffmpeg_timelapse_mode daily

の2つありました。ffmpeg_timelapseが0なのでオフのはずなのですが、エラーメッセージから推測すると、ファイル名を変更しようとしているのでffmpeg_timelapse_modeも影響がありそうなので、
ffmpeg_timelapse_mode manual
と設定変更してみました。

変更後、とりあえず落ちることは無くなりました。





motion -hで表示されたバージョン情報
motion Version 4.0, Copyright 2000-2016 Jeroen Vreeken/Folkert van Heusden/Kenneth Lavrsen/Motion-Project maintainers

パッケージバージョン
4.0-1