神道の文献は、たとえ最古の古事記(712年)であっても、仏教伝来(遅くとも6世紀半ば)後に書かれたものであるので、そもそも「仏教に影響されていない神道」というものを記録している文献はない。すなわち仏教抜きで神道を考えるというというのはそもそも現代では無理。
「復古神道」は名前に反して江戸時代の新興宗教であって、儒教・仏教色を排すという意味ではかえってそれらの影響をうけている。
神道の文献は、たとえ最古の古事記(712年)であっても、仏教伝来(遅くとも6世紀半ば)後に書かれたものであるので、そもそも「仏教に影響されていない神道」というものを記録している文献はない。すなわち仏教抜きで神道を考えるというというのはそもそも現代では無理。
「復古神道」は名前に反して江戸時代の新興宗教であって、儒教・仏教色を排すという意味ではかえってそれらの影響をうけている。
C++ はながいこと食わず嫌いだった。とにかく「難しい」というイメージだけ先行していた。
しかし、あくまで better C として使う限りでは難しくないし、可読性が上がるので、 C++ を使わない手はないという気持ちになった。
C を書いていると、だいたいは書いたコードがそのままバイナリに翻訳されている感があり、安心感がある。struct はメモリ上の配置そのままだし、関数だって単に処理のまとまりに名前がついてて、呼び出しもとに自動的に戻ってくるラベルなだけだ。
C++ になるとオブジェクト指向に概念が入ってきて、これが実際どのようにコンパイルされるかに不安が出てくる。クラスはメモリ上でどう表現されているのか、メソッドディスパッチはどのように行われているか?
もちろん知っている人にとっては簡単な話で
ということで、virtual を使わない限りでは、C++ の class は C で struct 定義と、その第一引数にその sturct ポインタをとる関数郡でしかなく、これは C でよくやるオブジェクト指向のプリミティブな実装とよく似ている。
このようなCのコードは
#include <stdio.h>
typedef struct {
unsigned counter;
} my_counter;
void my_counter_init(my_counter* this) {
this->counter = 0;
}
void my_counter_incr(my_counter* this) {
this->counter++;
}
unsigned my_counter_get_count(my_counter* this) {
return this->counter;
}
int main() {
my_counter counter;
my_counter_init(&counter);
printf("%d\n", my_counter_get_count(&counter));
my_counter_incr(&counter);
printf("%d\n", my_counter_get_count(&counter));
} このようなC++のコードとほとんど同じバイナリが出力される
#include <cstdio>
class my_counter {
unsigned counter;
public:
my_counter(); // void init();
void incr();
unsigned get_count();
};
my_counter::my_counter() {
this->counter = 0;
}
void my_counter::incr() {
this->counter++;
}
unsigned my_counter::get_count() {
return this->counter;
}
int main() {
my_counter counter;
printf("%d\n", counter.get_count());
counter.incr();
printf("%d\n", counter.get_count());
} テンプレートは、複数の型に対する処理を1回でまとめて書けるという仕組みなので、原理的には、扱う型が増えるほど、出力バイナリに関数本体が増えていくことになる。
しかし一方で、テンプレートをコンパイル時計算のために使うような場合は、コンパイル時に解決されるコードがほとんどになり、出力バイナリは書いたコードの見た目の多さに反してかなり少なくなることもある
ということで、テンプレートが使われているからといってバイナリサイズが肥大化するというわけではない。
この書きかたで本当に最適化されたコードに出てくるの? という不安がある。これは難しくて、コンパイラが優秀でも、当然プログラマがちゃんと const を付けてコンパイラに意図を伝えないと、完全に最適化されたコードにはならない (インライン化されないとか)。
C にはない概念がある分、const の付けかたが複雑で、ベターCとして使おうと思うと一番ハマる。
一方で、テンプレートメタプログラミングでコンパイル時に計算することができるので、コンパイラが判断できないような高度な最適化を自力でやることができる。
組込みのコードはどうしてもマジックナンバーが多くなりやすく、C で書くとマクロだらけになる。Cのマクロは文字列展開なので型がなく、当然マジックナンバーにも型を付与できない。組込みは実行時デバッグのコストが高いので、コンパイル時に見つけることができるエラーは全てコンパイル時に見付けたいが、Cではそこまでのことができない。
浮動小数点まわりについてはMCUでは非常に重い処理になるので、できるだけ事前計算した係数をつかってMCU上では整数演算にしてしまうなどの最適化をしたくなる。こういうときCのマクロだけで書くのはとても厳しい。C++ であればコンパイル時に浮動小数点計算を行って定数展開できるので、あきらかに有利になる。
浮動小数点に限らず、高いレイヤーでの最適化はコンパイラは知るよしもないので手でやる必要がある。その際C++のTMPは非常に強力に使える。
測定中、10MHz 付近に急激な落ちこみが観測されることがあって気になっていました。そういうものなのかなとも思ったのですが、調べてみたら同じような問題にあたっている人がいて、まぁよく考えたらそういうものではないよなという感じです。
これは簡単に再現することができます。TGとSAを同軸で直結させて、TGを有効にし、10MHz 付近を拡大することで、変な落ちこみを見ることができます。
以下の画像がそうですが、明かに1dB 落ちこみがあるのがわかると思います。
あるいは、掲示板中にもありますが 10μH と 33pF ぐらいを直列に接続し、バンドパスフィルタを構成した上で 10MHz 付近を見ると、スカートが不自然になっているので非常に目立ちます。 冒頭の画像はこの方法での再現です。直結よりもわかりやすくおかしいことがわかります。
広いスパンで見ているときや、広いダイナミックレンジで見ているときには気付きにくいかもしれませんが、フィルタのようなものを測定するときはかなり気になる挙動だと思います。
10MHz 付近はアマチュア無線でも使う帯域ですし、基準信号付近ですから余計気になります。位相雑音がどうとかよりも致命的な問題でしょう。
ファームウェアの 1.15 が1月22日にリリースされており、これにある程度の修正が含まれています。
最新のファームウェアは http://int.rigol.com/Support/SoftDownload/3 からダウンロードできます。
Product Series → Spectrum Analyzer → DSA815 Firmware_00.01.15.01.00 というのをダウンロードします。DSA815(DSP)update.rar というやつです。
DSA815update.sys を USB メモリのルートディレクトリにコピーして、DSA815 のフロントUSBポートに接続し、Storage の画面でこのファイルを選択したあと、Sys Update を選択すると、アップデートが開始されます。数分で終わり、再起動すると最新のファームウェアで起動します。
↑ 使ったUSBメモリです。自宅にUSBメモリがなく、SDカードリーダーとSDカードで代用しようと思いましたが、DSA815 では認識されませんでした。純粋な USB メモリじゃないとダメなようです。ググってみると、メーカー的にはできるだけ容量が少ないほうが信頼性が高いということになっているみたいです。とはいえ現時点で新品はもはら8GB未満は売ってません。これは USB 3.0 メモリですが、DSA815 は USB 3.0 に対応してないので、古くてもなんでもいいはずです (信頼性は別として)。
見ての通りです。直結の場合では不連続部分はわかりません。一方バンドパスフィルタで見たときはすこし不連続な部分が残っているようにみえます。
ということで、完全には解決しません。挙動的にはまるめ誤差みたいなのが発生しているような感じますが実装がどうなっているかはわかりません。
とはいえ、このように修正が入るので、現状の状態は Rigol のエンジニアは把握しており、直すつもりはあるようです。ファームウェアアップデートだけですめばいいんですが……
DSA815-TG には3年保証がついているのですが、ファームウェアアップデート時にどのような扱いになるか不安になりました。(そもそも国内販売版のものが国際版と完全に同一であるかの確証もない)
国内のリゴルジャパンへメールで確認をとったところ、すぐに返信していただけました。
とのことで、アップグレードしても大丈夫そうです。
購入直後スクリーンショットをLAN経由でとれるようにしましたが 1.15 になって、TCPソケット経由でのコマンド解釈がすこし厳密になって動かなくなったので直しました。
あと、スクリーンショットをとるコマンドは最後にリモートモードを解除するコードを入れていたのですが、動かなくなりました。リモートからコマンドを実行すると必ずリモートモードになってしまって若干鬱陶しいのでなんとかしたいのですが、こちらの原因はよくわかってません。
どうやらスリープを挟まないとダメな模様
また、デフォルトで mDNS での DNS-SD の機能が無効になってみたいです。HTTP 経由でアクセスして設定しないと DNS-SD のクエリに応答しません。
アップグレード後は 00.01.15 です。
スペアナがあったらやってみたかったことの1つとしてコモンモードフィルタ(RFチョーク)の評価というのがあります。だいたいトロイダルコアを使って作っており、トロイダルコアは再現性が高いので計算通りにやれば実用性能はでるはずなのですが、ちゃんと作れているか?はやはりはかってみなければわかりません。
スペアナがなくてもハムバンドでダミーロードを使いつつ高周波電流を頑張って測るという方法で一応検証はできるのですが、面倒くさすぎるので結局グラフ化したりまでしたことはありません。面倒くささが勝りすぎます。
その点スペアナがあれば一瞬で…… という気持ちがありました (実際は適切な治具がないと正確に測るのはやはり難しいのですが……)
大地経由での電流がコモンモード電流なので、フィルタのホットとコールドをショートして、TG のホットをフィルタを通してスペアナの入力にし、TG とスペアナのコールド側をショートさせれば計れそうです。要は単にRFチョークとして測定します。
なおコモンモードフィルタはだいたい-30dB ぐらいあれば十分な性能といえるらしいです。
まずワニグチクリップをトラッキングジェネレータとスペアナ入力に直付けし、ホットコールドをそれぞれショートさせます。
この状態でTGの出力レベルを0dBmに設定し、ノーマライズします。(なおビビリなので外付けのアッテネータは入れてませんが内蔵アッテネータは10dB入れたままです) それなりに出力がないとノイズに測定結果が隠れてしまうので気をつけます。
ノーマライズされた状態でホット側をはずしてアイソレーションを確認します。-30dB 以上なければ測定の意味がないので、アイソレーションがとれてなさそうなら諦めてスパンを狭くして再度ノーマライズからやりなおします (意味がない結果が見えていると混乱するため)。
TG 側にワニグチクリップをつけたりして配線長を増やすと50MHz〜は容易にアイソレーションがとれなくなるので注意します。以下はオープンの状態のアイソレーションです。
この状態でフィルタに接続して測ります。フィルタの入力側(TG側)の線長が長いとこれまたアイソレーションがとれないので誤差の原因になりますが、排除するのが難しいです。シールドケースに入れて測るしかない気がしますが、今回は用意できてないので多少飛びこみがあります。
今回計ったのはキットものの http://www.ddd-daishin.sakura.ne.jp/ddd/dcf/dcf-rf-29l3/kit-dcf-rf-29l3-ver2.htm これです。スペック的には 50MHz ぐらいまでは -30dB 余裕でとれるみたいなグラフがあります。
一応このように雑に測った感じでも 50MHz ぐらいまでは -30dB ぐらいになっているように見えます。(0〜100MHzのスパンなので真ん中が50MHzです)。とはいえ -40dB までとれている範囲はそれほどないです。
ついでに挿入損失も測りました。
入出力インピーダンス50Ωとして、例えばインピーダンスZを直列挿入した場合、入力側にかかる電圧は出力電圧を、挿入インピーダンス と入力インピーダンスで分圧した形になる。
なので、入力電圧は になる。dB で表わすと
RIGOL (リゴル) 1.5GHzスペクトラム・アナライザ(トラッキング・ジェネレータ機能付) 【国内正規品】,DSA815-TG cho45
やっぱスペアナは欲しいよなあと思いはじめて数ヶ月、決心がついたので買いました。技術力が低いので本当に金額に見合うぶんだけの活用ができるかは微妙ですが「見えないものが見えるようになる」というメリットに抗えませんでした。
スペアナといっても現状ではいくつも選択肢があります。USB 接続のものはハードのインターフェイスがいらない分をスペックにコストをかけられるので、スペック的には良さそうです。
DSA815-TG は位相雑音(ゆらぎ/時間ドメインでのジッタ)が多いというのが大きな欠点の一つのようです。高純度の信号の測定ができないことを意味しますが、この欠点がどれぐらい今後に影響してくるのかまだわかっていません。
しかしやはり欠点はあってもスタンドアロン型のもののほうが使い勝手は良さそうです。電源入れれば使えるというのは大事に感じます。USB 接続してアプリを立ちあげてという手順は地味にだるいのです。そういうところを考えると、スペックが良くても使う頻度が少なくなってしまうのは一番損になりますから、スタンドアロンのものにしました。
この機種は数年前までは15万前後で買えたみたいですが、円安進行のためか、かなり値上りしています。悔しいところですが諦めて18万ぐらいで買いました。当然トラッキングジェネレータ付きです。
思ったよりも反応が良くて使いやすいです。
UIを日本語設定にすることができますが、この手の中華製品にありがちなひどい明朝フォントになったりはせず、普通に使えるレベルだと思います。とはいえ英語設定で使ってますが…
10dB アッテネータはマトモなやつを1つ買いました。追加でいくつか安いアッテネータを注文しています。
スペクトラム・アナライザによる高周波測定―高調波,不要輻射,変調,ひずみ,位相ノイズ,伝送特 (計測器BASIC) cho45
この本は買う前に読んでいました。アマゾンではなく、CQ出版社のサイトから PDF を買ってます。
あとはリターンロスブリッジ (VSWR ブリッジ) が欲しいので作るかどうするか、という感じです。
方向性結合器は自分の欲しい周波数帯・耐電力のものは全く売っていないので作るしかありません。一度作っていますが、もっといいのを作りたいところです。
とはいえ50MHzまでならアンテナアナライザーもあるので、そこまで必要でもないかなという気がします。
まず測定画面のスクリーンショット(ハードコピー)ができないとかなり不便なので、ここから解決しました。
プログレスを出しつつスクリーンショットをダウンロードしてこれます。ダウンロードしたあとは imagemagick の convertコマンドで png に変換し、optipng コマンドで最適化します。
ダウンロード部分にはライブラリ依存が一切ないので Ruby さえ動けば bmp ダウンロードはどこでも動きますし、LAN 経由なので一切ドライバなどが必要ないのが良いところです。
png 変換時には imagemagick コマンドと optipng コマンドが必要です。
DSA815 には USB 経由での GPIB 通信と、LAN ケーブル経由での VXI-11 (というよりは、LXIの一部)がサポートされています。
このへん、用語がいっぱいあるので整理しとくと
「というよりは、LXIの一部」と書きました。DSA815 はドキュメントには書いてないのですが、生のTCPソケット経由での通信もできるようになっていて、実はこれが一番簡単です。LXI の仕様では生TCPソケットの通信はポート5025を使うような記述がありますが、DSA815 では 5555 ポートです。
:PRIV:SNAP?
を送ると、11バイトのヘッダ(ファイル長を含む)とビットマップファイルが送られてきます。この :PRIV:SNAP? は隠しコマンドのようで、公式の Programming Reference にはありません。PRIV は Private の略なんでしょう。別のクライアントの通信からわかったので微妙なところです。
という表示のもの。
スイッチング周波数が高く、かなり小さい。(スイッチング周波数が高いほどコイルが小さくできるため。ただしスイッチング損が増えるので効率が犠牲になりやすい)。裏面に実装はなく、写っている部品で全て。
注意が必要で、普通と逆(左まわり)で電圧があがる。だから安いのか?
5V → 12V 昇圧での効率
2.5W → 2.2W 88%
5.0W → 4.73W → 94.6%
10W → 8.42W → 84.2%
これ以降出力低下
コイルが結構発熱する。
値段の割に効率が良く、小さいので結構汎用的に使えそう。2V〜 昇圧可能なのも嬉しい。NiMH 2本で昇圧したいときはベストといってもいいんじゃないかという気がする。
http://www.suosemi.com/ 深圳市硕芯科技有限公司という会社のもの
オン抵抗 80mΩ パワーMOSFETがビルトインされている。
フロスをどうしてもサボってしまうので、11月ぐらいに買ってみました。だいたい使用感はわかったかなと思うのと、歯科での指導を挟んだのでで感想を書いておきます。
フロスが面倒で買ったのですが、ジェットウォッシャーではフロスの代わりにはなりませんでした。結局歯間の狭いところ(自分はほぼ隣同士の歯がくっついています)の歯垢は落とせないみたいです。かなり念入りにジェットウォッシャーを使ってからでも、フロスを使うとまださらに歯垢がとれます。歯科でのチェックでも、フロス使用時は95%〜ぐらいの歯垢除去率だったのが、ジェットウォッシャーだけの場合80%〜ぐらいまで落ちていました。
歯科のでチェックは歯石(磨き残しが続いて歯垢が固まった状態=恒常的に磨けていない箇所)も見るんですが、サボりがちのフロスでもひどいことにはならないので、サボりがちであってもフロスのほうがマシみたいです。
まぁそりゃそうだろという感じではあります。これは歯間ブラシの代替であって、フロスの代替ではありません。歯間が狭い人には向いてないのでしょう。
食べカスはすごい勢いで落ちてくるので気持ちが良いです。まぁフロスでも落ちるんですが、フロスの場合、糸にヘバりついてくる感じなので使用感が悪いという気持ちの問題があります。
冬の冷い水をすぐに使ってジェットウォッシャーをするとかなりつらいです。知覚過敏の人には苦行になります。
あらかじめて汲んでおいて室温にするとか、お湯でやるとかする必要があります……
とはいえ、やらないよりはマシだと思うので、フロスと併用することにします。結局手順が増えてるだけじゃないかという……
「独自拡張のような形でIEEE802.11のフレームを飛ばして」と書きましたが、実際のWiFiパケットは見ておらず、真偽に疑問があったので、キャプチャして確認しました。
結論からいうとその通りのようで、Management frame (ビーコンとかと一緒) を Vendor-specific な形式で使っているようです。Wireshark だと Malformed Packet となってしまうのが謎ですが…
ただ、Vendor Specific Content にあたる部分に、esp_now_send で渡したバイト列以外にもヘッダみたいなのがついています。これはよくわかりません。
OS X はビルトインで WiFI のパケットをスニフする機能が入っているので、とりあえずキャプチャして保存するだけならとても簡単にできます。
sudo /usr/libexec/airportd en0 sniff 1
これで WiFi のチャンネル1 (2.5GHz帯) のスニフができ、/tmp 以下に適当なファイルができて保存されます。
キャプチャはこれで良いのですが、別途解析ソフトウェアが必要です。
airportd sniff での結果は Wireshark で解析することができます。.cap ファイルを Wireshark の File → Open… から選択するだけです。
接続先が接続元の Mac アドレスでフィルタする場合、wlan.da == ff:ff:ff:ff:ff:ff や wlan.sa == ff:ff:ff:ff:ff:ff でできます。
該当するようなパケットが見つかっているなら、右クリックでフィルタとして適用とできるようになっています。
WiFi の経路は暗号化されているので、そのままだとIEEE802.11 フレームは読めますが、イーサネットフレームは読めません。しかしこれも Wireshark に適切に設定をすることで解析可能になります。
http://jorisvr.nl/wpapsk.html ここにSSIDとPassphrase を入れてでてくる16進数文字列をコピーしておきます。
Preferences… → Protocols → IEEE802.11 → Decryption Keys
type を wpa-psk として、Key にコピーした16進数文字列を入れます。
これで OK を押すと既存の解析結果も更新され、デコード可能ならデコードされます。
オムロン 【自動電源ON】【4秒測定】【体重50g単位表示】【PC/スマホ対応 Wi-Fi通信機能搭載】体重体組成計 カラダスキャン HBF-253W-BK cho45
なんとなく欲しいなとは思いつつ高いから買ってませんでしたが、妻が買ってきました。とりあえず結構いい感じです。
セットアップ時にスマートフォンのオーディオ出力とマイク入力を使ってやるようになってました。どういうタイミングで音を出してるのか調べなかったのですが、ヘルプだと「ピポパと聞こえたら…」という記述があるので、DTMF でやってるのかもしれません。このオーディオ出力とマイク入力を使う方法ですが、Zenfone2 ではうまくいった一方で、HTC J One ではうまくいきませんでした (beat audio などは切ってみたんですが…)
それはともかく、WiFi 経由ということですが、実際どのような通信が行われているのか知りたくなってきました。できれば接続先サーバをうまく騙して (ないし透過プロキシを通して) データをインターネットに出る前に自分で処理したいという気持ちもあります。
そんなわけでIEEE802.11フレームのパケットスニファを行って少しだけ追跡してみました。
WiFi パケットの解析をしてみたところ、ちゃんと TLS を使っているようでした。なので透過プロキシやら何やらを挟むことはできません。
体重データは人によってはセンシティブな個人情報ですから、TLS を使うのは当然でしょう。
一方、サーバサイドのサービスが終了してしまったとき、このデバイスの WiFi 機能は完全にゴミになります。自分の所有するデバイスの管理を自分でできないわけですからちょっと気持ち悪いところがあります。
pack のテンプレート文字列から、それを使ってパックした結果のサイズを求めたいということはありませんか。つまりやりたいことは sizeof(struct x) です。
pack テンプレート文字列は割と複雑で、任意長や文字列やポインタなどがあり、全てにおいてうまく動作するものを作るのは無理ですが、だいたいうまくいくのは実装できそうです。
def sizeof(template)
x = Class.new(Numeric) do
def to_str; ""; end
def to_int; 0; end
# implicit to_f is called only with Numeric subclass
def to_f; 0.0; end
end.new
size = template.scan(/([a-zA-Z][_!]?[<>]?)([0-9]*)/).reduce(0) {|r,(_,n)|
r + (n.empty?? 1 : n.to_i)
}
# p [template, size]
Array.new(size) { x }.pack(template).size
end やってることは「とりあえず pack してみる」ですが、pack するためには、まずテンプレート文字列に応じて、適当な型と適当な長さの配列が必要になります。
適当な長さという点では多い分には問題ないので、適当に数えています。
適当な型というのは、厳密にやると結局テンプレート文字列をパースするのと同じぐらい面倒なので、pack が要求するデータ型すべてに暗黙的に変換可能なオブジェクトを作っています。
pack が要求する型は整数・文字列・浮動小数点数があります。整数は to_int、文字列は to_str を実装すると、Ruby はそのオブジェクトについて整数・文字列と同等に扱う(必要なら暗黙的に変換される) ことになっています。
浮動小数点数にはそういったどんなオブジェクトも浮動小数点数として扱えるようにする、というメソッド名がないのですが、Numeric のサブクラスであって to_f が実装されている場合には暗黙的に変換されるというルールがあるので、ベースクラスを Numeric にしています。
[
"C",
"S",
"L",
"Q",
"c",
"s",
"l",
"q",
"S_", "S!",
"I", "I_", "I!",
"L_", "L!",
"Q_", "Q!",
"s_", "s!",
"i", "i_", "i!",
"l_", "l!",
"q_", "q!",
"S>", "L>", "Q>",
"s>", "l>", "q>",
"S!>", "I!>",
"L!>", "Q!>",
"s!>", "i!>",
"l!>", "q!>",
"S<", "L<", "Q<",
"s<", "l<", "q<",
"S!<", "I!<",
"L!<", "Q!<",
"s!<", "i!<",
"l!<", "q!<",
"n",
"N",
"v",
"V",
"U",
"w",
"D", "d",
"F", "f",
"E",
"e",
"g",
"G",
"A",
"a",
"Z",
"B",
"b",
"H",
"h",
"u",
"M",
"m",
# "P",
# "p",
# "@",
# "X",
# "x",
"C255",
"i!2s!2",
"i!i!s!s!",
].each do |tmpl|
next if tmpl =~ /q/i
puts "sizeof(%p) = %d" % [tmpl, sizeof(tmpl)]
end sizeof("C") = 1
sizeof("S") = 2
sizeof("L") = 4
sizeof("c") = 1
sizeof("s") = 2
sizeof("l") = 4
sizeof("S_") = 2
sizeof("S!") = 2
sizeof("I") = 4
sizeof("I_") = 4
sizeof("I!") = 4
sizeof("L_") = 8
sizeof("L!") = 8
sizeof("s_") = 2
sizeof("s!") = 2
sizeof("i") = 4
sizeof("i_") = 4
sizeof("i!") = 4
sizeof("l_") = 8
sizeof("l!") = 8
sizeof("S>") = 2
sizeof("L>") = 4
sizeof("s>") = 2
sizeof("l>") = 4
sizeof("S!>") = 2
sizeof("I!>") = 4
sizeof("L!>") = 8
sizeof("s!>") = 2
sizeof("i!>") = 4
sizeof("l!>") = 8
sizeof("S<") = 2
sizeof("L<") = 4
sizeof("s<") = 2
sizeof("l<") = 4
sizeof("S!<") = 2
sizeof("I!<") = 4
sizeof("L!<") = 8
sizeof("s!<") = 2
sizeof("i!<") = 4
sizeof("l!<") = 8
sizeof("n") = 2
sizeof("N") = 4
sizeof("v") = 2
sizeof("V") = 4
sizeof("U") = 1
sizeof("w") = 1
sizeof("D") = 8
sizeof("d") = 8
sizeof("F") = 4
sizeof("f") = 4
sizeof("E") = 8
sizeof("e") = 4
sizeof("g") = 4
sizeof("G") = 8
sizeof("A") = 1
sizeof("a") = 1
sizeof("Z") = 1
sizeof("B") = 1
sizeof("b") = 1
sizeof("H") = 1
sizeof("h") = 1
sizeof("u") = 0
sizeof("M") = 47
sizeof("m") = 0
sizeof("C255") = 255
sizeof("i!2s!2") = 12
sizeof("i!i!s!s!") = 12 Cの構造体とかだと、構造体の中に他の構造体ということは普通にあります。
こういった構造体の文字列を unpack すると、全部フラットな配列になってしまうので、Ruby レベルのオブジェクト構造として再構成しようと思うと面倒なことになります。
ということで、入れ子に対応した unpack というのを実装してみました。
以下のような感じで {} (ブレース) で入れ子を表現するようにテンプレート文字列を拡張します。
struct_foo_t = %{
I!
I!
Z16
}
struct_bar_t = %{
I!
I!
{
#{struct_foo_t}
}
I!
} #=> "I!I!{I!I!Z16}I!"
original = [
0xffff,
0xfeff,
[
0x11,
0x22,
"foobar"
],
0x10,
]
packed = original.pack_deeply(struct_bar_t)
p packed
#=> "\xFF\xFF\x00\x00\xFF\xFE\x00\x00\x11\x00\x00\x00\"\x00\x00\x00foobar\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00"
unpacked = packed.unpack_deeply(struct_bar_t)
p unpacked
#=> [65535, 65279, [17, 34, "foobar"], 16]
p original == unpacked
#=> true 先頭から unpack して入れ子を処理していくというイメージです。処理済みのバイト長が欲しいので、クソみたいですが一旦 unpack したものをもう一度 pack しています。
pack_deeply は単にフラットにして pack するだけなので簡単です。
class String
def unpack_deeply(template)
ret = []
tree = [ ret ]
n = 0
tmpl = ""
# unpack partial and add to result
unpack = lambda {
unpacked = self.slice(n..-1).unpack(tmpl)
# re-pack to get byte length
size = unpacked.pack(tmpl).size
n += size
tmpl = ""
tree.last.concat(unpacked)
}
template.each_char do |chr|
case chr
when "{"
unpack.call
ary = []
tree.last << ary
tree << ary
when "}"
unpack.call
tree.pop
else
tmpl << chr
end
end
unpack.call
ret
end
end
class Array
def pack_deeply(template)
flatten.pack(template.gsub(/[{}]/, ''))
end
end markdown で HTML にして wkhtmltopdf で PDF 化する汎用 Makefile です。
Markdown で書いてプレビューするのはいいんですが、画像がちらばってしまって面倒です。PDF にしておけば1ファイルにまとまるのと、だいたいどこでも見ることができるので便利です。
# brew install markdown
# http://wkhtmltopdf.org/downloads.html
.SUFFIXES: .md .html
.SUFFIXES: .html .pdf
SRCS = ${wildcard *.md}
HTMLS = $(SRCS:.md=.html)
PDFS = $(SRCS:.md=.pdf)
define header
<!DOCTYPE html>
<meta charset="utf-8">
<title>MD</title>
<style>
body { font-family: sans-serif; line-height: 1.66 }
img { max-width: 800px }
</style>
endef
export header
.PHONY: all
all: $(PDFS)
$(HTMLS): Makefile
.html.pdf:
wkhtmltopdf $< $@
.md.html:
(echo "$$header"; markdown $<) > $@
ioctl に構造体のポインタを渡して値を返してもらうような場合があると思います。
このような場合、文字列つくってそのまま渡せば struct のポインタになるようでした。
つまり pack/unpack("P") を使ってこう書くのは
Termios2::FORMAT = "I!I!I!I!CC19I!I!"
Termios2::FORMAT_POINTER = "P44"
# バッファ
v = tio.values.flatten.pack(Termios2::FORMAT)
# pack("P") でポインタを取得
pointer = [v].pack(Termios2::FORMAT_POINTER)
# ポインタを数値として渡す
self.ioctl(TCGETS2, pointer.unpack("L!")[0])
# unpack("P") でポインタから値を取得し、構造体を unpack
tio = Termios2.new(*pointer.unpack(Termios2::FORMAT_POINTER)[0].unpack(Termios2::FORMAT)) 単に文字列を引数に渡す方法で簡単に書ける
# バッファ
v = tio.values.flatten.pack(Termios2::FORMAT)
self.ioctl(TCGETS2, v) # v が ioctl 内で書き変わる
tio = Termios2.new(*v.unpack(Termios2::FORMAT)) v の長さが構造のサイズに足りてないと死にます
リファレンスの IO#ioctl には以下のように書いてあるんだけど、意味がわからなかった……
If it is a string, it is interpreted as a binary sequence of bytes.
グラフ化した消費電力値。5:00 ぐらいにエアコンの電源が自動的に入るようになっていて急激に消費電力が増える。起床後は食洗機や洗濯乾燥機などが稼動してさらに増えていく。40A契約だが、一瞬ピークでそこまでいってる (これは原因不明)
節電モニター cho45
(国内だと節電モニター「はやわかり」という商品名のもの)
500 Can't connect to lowreal.net:443 (certificate verify failed) のとき買って、設置をしてはいたものの、十分に活用していたとはいえない状態でした。電気使いすぎのときにアラートを鳴らしていて、ブレーカーが落ちる1歩手前で気付けるというのが現状では最大の活用でした。
しかし、せっかく USB 端子があってデータが読み出せるのだから、自動的にグラフ化したくなってきました。ちなみにこの製品には Windows 版のかなりデキの悪いソフトウェアが付属していますが、正直常用できるようなものではありません。
この製品はどうやら、以下のものと同一のようです。
USB 接続してシステムプロファイラでベンダIDなどを調べて検索すると、Silicon Labs の USB シリアル変換のチップのようでした。
実際プロトコルダンプして読みだしたりしている人がいます。
思ったより情報があったので簡単そうです。
ドライバの対応の関係上、Linux で読み出すのが一番簡単です。通常のドライバだと製品IDが登録されていないのですが、Linux の場合はこの製品のIDについても対応するドライバを使うようになっているので、特に苦労せずにシリアル通信ができます。
Raspberry Pi の場合、CP210x のドライバは最初から入っているようなので、特にインストールすることもなく、USB ケーブルを繋ぐだけでシリアルポートとして認識します。
ボーレート 250000 8bit stopbit 1 parity 0 となっており、ボーレートが特殊なので、場合によって工夫が必要です。
Ruby を使って読みだすことにしましたが、これのせいで自力で ioctl を呼ぶハメになっています。
送られてくるのは常に、11バイト固定長で、こちらから送るのは \xA5 か \x5A だけです。解析結果のコードがあるので、その通り実装したらいいだけです。
ただ、フローコントロールをオフにせずに試していたところ、デフォルトだとソフトウェアフローコントロールが有効なようですが、データ欠けが発生して微妙に読みだせず4〜5時間ハマりました。どうやら必ずフローコントロールは完全にオフにする必要があるようです (ハードウェアフローコントロールだとデータ欠けは発生しないが、そもそも応答しなくなりました)
という感じのを Ruby で実装しました。といっても、プロトコル解析はされているし、 Python 実装もあったのでたいしたコードにはなっていません。
https://github.com/cho45/CM160-rb
とにかくフローコントロールが最大のハマリどころでした。
この製品は 315MHz帯の微弱電波機器 (免許・技適不要) となっていて、かなり電波が弱いです。うちはかなり狭い家(マンション)で、玄関上にある分電盤からリビングまで、10mもないと思いますが、これで電波がぎりぎり届きません。
なので、機器同士は比較的近くにおくようにして、Raspberry Pi などでネットに中継するほうが安定して便利そうです。広い家だとむしろ中継が必須な気がします。
とはいえ、そろそろスマートメータの設置もすすむので、こういうのも必要なくなるのかもしれません。
分電盤にセンサーをつけて家庭内全体の消費電力を測るというのはやっていますが、実際のところ何がどう電力を食っているのは大本で計測してもわかりにくいわけです。エアコンや洗濯機などの大きなものはともかく、待機電力の支配率はコンセントごとに電力を常時測定しなければわかりません。
みたいなデバイスが欲しくなります。
検討はあとに書きますがまず現段階の回路図です (といってもたいして見所はありませんが…)
ただし、
交流回路に微小抵抗を入れて直接測定するという手もありますが、これは危険が伴いますし、ちょうどいい微小抵抗の入手が難しいという問題があります。10mΩでも30A流したら抵抗だけで9W消費しますし、15Aでも2.25W消費します。
そうなると、やはりカレントトランスを使うほうが筋が良さそうです。ただカレントトランスもあまり入手性は良くないですし、価格もカットコアのものだとそれなりにします。
参考:http://akizukidenshi.com/catalog/g/gP-08960/
しかし ebay で検索してみると、ちょうど良さそうなカレントトランスが100円程度で売られています。カットコアではありませんが、実際のところカットコアのカレントランスが必要なのは分電盤のように絶対切断できない箇所かつホットとコールドが最初から分かれて配線されている部分だけなので、実質的にはあまり問題にならないでしょう。最終的にはこれを使えばよさそうです。
ただ、とりあえずは2000:1のカットコアのカレントトランスが手元にあるので、これを使います。
カレントセンサーが発生させる微小な電流を電圧に変換してデジタル化します。
通常コンセントは1ポートあたり15Aまでですが、実効値で15Aだと、ピークでは21.2A流れていますし、実際の負荷によってはパルス的に倍以上の電流が流れることもあります。30Aぐらいの負荷まではいけるようにしておきたいところですが、そもそも電力契約上で40Aとか60Aが上限なので、このへんをフルスケールにして計算してもよさそうです。
分解能としては1Wあれば十分すぎるぐらいに感じます。100Vだと0.01Aです。結構なダイナミックレンジになります。
センサーの2次側の出力電流は巻線比によって決まります。1:2000 なら2次側に1次側の2000分の1の電流が流れます。すなわち 30A で 15mA、0.01A で 5uA です。
発生する電流は交流なので、整流する必要があります。整流した電流値を負荷抵抗で電圧に変換し、ローパスフィルタで平均化します。
負荷抵抗は10Ωぐらいにすることが多いみたいです。小さければ小さいほどリニアリティが改善します。これにより、1次側が 30A なら抵抗に最大 212mV(30 / 2000 * 10 * sqrt(2))の電圧がかかる全波整流後の波形になります。
後段にローパスフィルタをつけ、平滑します。このフィルタは整流済み信号の平滑と、ADC 入力前の折り返しノイズ防止フィルタを兼ねています。これにより電圧は元の交流電圧の平均値となります。1次側が 30A なら出力は135mV (30 / 2000 * 10 * 0.9)の直流になります。
0.01A なら45uVです。
今回は MCP3425 という8倍までのPGAがあるI2C ADCデバイスを使ってみました。16bit、LSB=62.5uV の分解能があり、8倍のPGAがついているので、8倍時にはLSB=7.8125uVとなります。16bit をしっかり生かせるなら、十分な精度となります (が実際のところこのぐらい微小な電圧を測定するのは難しい)
このADCデバイスのフルスケールは±2.048Vですので、60Aで270mVぐらいは余裕です。
本来だと微小な電流検出にはオペアンプを使ったI-V変換回路をつけるのが筋だと思われます。ただ、低オフセット入力なオペアンプが必要になります。低オフセット電圧のオペアンプは高価ですし、今回はこのように ADC デバイスの PGA で8倍までは増幅できるので、まぁなんとかなるだろうという目論みです。
入力はアナログスイッチで複数いけるようにしたい思いがあります。
今回は ESP8266 のみを使うことを考えました。
というのが大きなメリットだと感じました。
コード的には MCP3425 から読みだして送信するだけです。省電力のため ESP-NOW という ESP8266 独自のAPIで通信を行っています。
ESP-NOW は受信側が別途必要なので、受信用のデバイスのコードもあります。こちらは STA+AP で起動して、ESP-NOW で受けとったデータをグラフAPIに投げなおしています。
ただ、実際 ESP-NOW を使ってみると、少し離すと、想像以上にフレームが届かないことが多いです。WiFi バンドが混んでいるせいというのもあると思いますが、なかなか厳しく、再送を実装するか、もしくは諦めて普通にWiFi接続したほうがいい気がします。電源次第です…
ここが難しくて悩むところです。
もともとは電池駆動しようと思っていましたが、いろいろ実験した結果、電池駆動で半年程度もたせるにはかなりの低頻度のやりとりになることがわかりました。なので、この用途ではあまり筋が良くなさそうです。
一方でAC電源からとってくるのも割と面倒です。200mA 3.3V 程度だけとれる小型のAC/DCスイッチング電源なんて滅多に見ません。ACアダプタを改造するにせよ、そもそもACアダプタがそこそこ高価です。
ということで今のところ電源は保留にしています (モバイルバッテリ駆動しています)
ebay で探すと 3.3V 600mA の AC/DC 電源はすこしヒットしますので、買ってみて使えそうならこれでいい気がします。
ブレッドボードで作業デスク付近のコンセントの電流を計ってみています。まぁまぁ面白いのですが、一箇所だけ計ってもあんまり面白くないです。
電源まわりをうまく解決する方法を考え中です。
本来用途として WiFi は適切なプロトコルとはいえないですから、Bluetooth LE や ZigBee の安価なデバイスがあればそのほうが良いと思います。とはい現状 ZigBee 最安の TWE-Lite ですら ESP-WROOM-02 の3倍ぐらいの値段です。
ruby serialport は、このあたりで設定できるボーレートを列挙していて、これら以外のボーレートを設定できないようです (unknown baud rate 例外)。
Linux だと ioctl 経由で設定すれば任意の設定ができるので、以下のようにして無理矢理設定します。(Raspberry Pi でテスト済み)
完全に任意ではなく、近いボーレートに丸められることがあるようです。
#!/usr/bin/env ruby
require "serialport"
SerialPort.class_eval do
TCGETS2 = 0x802c542a
TCSETS2 = 0x402c542b
CBAUD = 0x100f
BOTHER = 0x1000
# struct termios2 {
Termios2 = Struct.new(*[
:c_iflag,
:c_oflag,
:c_cflag,
:c_lflag,
:c_line,
(0...19).map {|n| # c_cc[NCCS] NCCS = 19
"c_cc_#{n}".to_sym
},
:c_ispeed,
:c_ospeed
].flatten);
Termios2::FORMAT = "I!I!I!I!CC19I!I!"
Termios2::FORMAT_POINTER = "P44"
# }
def set_custom_baudrate(baud)
tio = Termios2.new
tio.each_pair {|m,_| tio[m] = 0 }
# read
v = tio.values.flatten.pack(Termios2::FORMAT)
self.ioctl(TCGETS2, v)
tio = Termios2.new(*v.unpack(Termios2::FORMAT))
# write
tio.c_cflag &= ~CBAUD
tio.c_cflag |= BOTHER
tio.c_ispeed = baud
tio.c_ospeed = baud
v = tio.values.flatten.pack(Termios2::FORMAT)
self.ioctl(TCSETS2, v)
# read
v = tio.values.flatten.pack(Termios2::FORMAT)
self.ioctl(TCGETS2, v)
tio = Termios2.new(*v.unpack(Termios2::FORMAT))
if tio.c_ispeed == baud && tio.c_ospeed == baud
true
else
raise "failed to set baudrate expected:#{baud} but set:#{tio.c_ispeed}/#{tio.c_ospeed}"
end
end
end
@port = SerialPort.new(
"/dev/ttyUSB0",
230400,
8,
1,
0
)
@port.set_encoding(Encoding::BINARY)
@port.set_custom_baudrate(250000)
sleep 0.1
# @port << "\xA5"
loop do
data = @port.sysread(4096)
p [data, data.size]
@port << "\x5A"
end