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.

  1. トップ
  2. tech
  3. Ruby で IO#ioctl の引数に構造体 (struct) へのポインタを渡したいとき

グラフ化した消費電力値。5:00 ぐらいにエアコンの電源が自動的に入るようになっていて急激に消費電力が増える。起床後は食洗機や洗濯乾燥機などが稼動してさらに増えていく。40A契約だが、一瞬ピークでそこまでいってる (これは原因不明)

節電モニター

節電モニター -

5.0 / 5.0

(国内だと節電モニター「はやわかり」という商品名のもの)

分電盤にクランプつけて使用中の電力がわかるようにするやつ | Fri, Apr 19. 2013 - 氾濫原 のとき買って、設置をしてはいたものの、十分に活用していたとはいえない状態でした。電気使いすぎのときにアラートを鳴らしていて、ブレーカーが落ちる1歩手前で気付けるというのが現状では最大の活用でした。

しかし、せっかく USB 端子があってデータが読み出せるのだから、自動的にグラフ化したくなってきました。ちなみにこの製品には Windows 版のかなりデキの悪いソフトウェアが付属していますが、正直常用できるようなものではありません。

製品の詳細を調べる

この製品はどうやら、以下のものと同一のようです。

USB 接続してシステムプロファイラでベンダIDなどを調べて検索すると、Silicon Labs の USB シリアル変換のチップのようでした。


実際プロトコルダンプして読みだしたりしている人がいます。

思ったより情報があったので簡単そうです。

Raspberry Pi で読みだす

ドライバの対応の関係上、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 などでネットに中継するほうが安定して便利そうです。広い家だとむしろ中継が必須な気がします。

とはいえ、そろそろスマートメータの設置もすすむので、こういうのも必要なくなるのかもしれません。

  1. トップ
  2. tech
  3. 市販の節電モニターから値を読みだしてグラフ化

分電盤にセンサーをつけて家庭内全体の消費電力を測るというのはやっていますが、実際のところ何がどう電力を食っているのは大本で計測してもわかりにくいわけです。エアコンや洗濯機などの大きなものはともかく、待機電力の支配率はコンセントごとに電力を常時測定しなければわかりません。

  • コンセントごとにセンサーをつけても十分に安い
  • データを無線で集約できる

みたいなデバイスが欲しくなります。

試作品の回路図

検討はあとに書きますがまず現段階の回路図です (といってもたいして見所はありませんが…)

ただし、

  • ブレッドボードに実装しているのは 1ch のみでアナログスイッチなし
  • 電源回路が回路図だと昇圧回路になっているが消費電力が多すぎるのでやめている

カレントセンサー

交流回路に微小抵抗を入れて直接測定するという手もありますが、これは危険が伴いますし、ちょうどいい微小抵抗の入手が難しいという問題があります。10mΩでも30A流したら抵抗だけで9W消費しますし、15Aでも2.25W消費します。

そうなると、やはりカレントトランスを使うほうが筋が良さそうです。ただカレントトランスもあまり入手性は良くないですし、価格もカットコアのものだとそれなりにします。

参考:http://akizukidenshi.com/catalog/g/gP-08960/

しかし ebay で検索してみると、ちょうど良さそうなカレントトランスが100円程度で売られています。カットコアではありませんが、実際のところカットコアのカレントランスが必要なのは分電盤のように絶対切断できない箇所かつホットとコールドが最初から分かれて配線されている部分だけなので、実質的にはあまり問題にならないでしょう。最終的にはこれを使えばよさそうです。

ただ、とりあえずは2000:1のカットコアのカレントトランスが手元にあるので、これを使います。

ADC部分

カレントセンサーが発生させる微小な電流を電圧に変換してデジタル化します。

センサーの出力電流

通常コンセントは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です。

ADC

今回は 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倍までは増幅できるので、まぁなんとかなるだろうという目論みです。

入力はアナログスイッチで複数いけるようにしたい思いがあります。

MCU部分

今回は ESP8266 のみを使うことを考えました。

  • ESP8266 は500円ぐらいで激安
  • WiFi 接続できる MCU として単体でいける

というのが大きなメリットだと感じました。

コード的には 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倍ぐらいの値段です。

  1. トップ
  2. tech
  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

  1. トップ
  2. tech
  3. ruby-serialport で任意のボーレートを設定するには (ただし Linux に限る)

ebay で IIC/I2C/TWI 1602 Serial Blue Backlight LCD Display という名称で売られている、激安の16x2のLCDモジュール ($3.19) をためしに動かしてみました。

商品説明ページに一切インターフェイスについての記述はなく、I2Cアドレスから調べる必要があります。とはいえ 16x2 はだいたいみんな同じプロトコルなのと、見た目的に普通のパラレルなLCDをI2C変換しているだけなようなので、簡単に動くだろうと踏みました。

コード

https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library そのままでいけました。アドレスも一緒だったので想定している仕様が完全に一緒なのでしょう。

#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
	Serial.begin(9600);

	pinMode(13, OUTPUT);

	Serial.println("Initializing...");

	Wire.begin();

	lcd.begin();
	lcd.backlight();
	lcd.setCursor(0, 0);
	lcd.print("Hello, World");
	lcd.setCursor(0, 1);
	lcd.print("TEST");
}

void loop() {
	delay(1000);
	lcd.setCursor(0, 1);
	lcd.print(millis() / 1000);
}

備考

買ったものは、一旦動かして記録にしておかないと、あとあと「あれを使おう」と思ったとき二度手間です。

なお、16x2 でバックライト付きなのは国内でも aitendo とかがかなり安く売っているので、あんまり ebay 経由で買う必要はありません。

  1. トップ
  2. tech
  3. Use IIC/I2C/TWI 1602 Serial Blue Backlight LCD Display