RIGOL (リゴル) 1.5GHzスペクトラム・アナライザ(トラッキング・ジェネレータ機能付) 【国内正規品】,DSA815-TG - RIGOL

RIGOL

4.0 / 5.0

やっぱスペアナは欲しいよなあと思いはじめて数ヶ月、決心がついたので買いました。技術力が低いので本当に金額に見合うぶんだけの活用ができるかは微妙ですが「見えないものが見えるようになる」というメリットに抗えませんでした。

スペアナといっても現状ではいくつも選択肢があります。USB 接続のものはハードのインターフェイスがいらない分をスペックにコストをかけられるので、スペック的には良さそうです。

DSA815-TG は位相雑音(ゆらぎ/時間ドメインでのジッタ)が多いというのが大きな欠点の一つのようです。高純度の信号の測定ができないことを意味しますが、この欠点がどれぐらい今後に影響してくるのかまだわかっていません。

しかしやはり欠点はあってもスタンドアロン型のもののほうが使い勝手は良さそうです。電源入れれば使えるというのは大事に感じます。USB 接続してアプリを立ちあげてという手順は地味にだるいのです。そういうところを考えると、スペックが良くても使う頻度が少なくなってしまうのは一番損になりますから、スタンドアロンのものにしました。

この機種は数年前までは15万前後で買えたみたいですが、円安進行のためか、かなり値上りしています。悔しいところですが諦めて18万ぐらいで買いました。当然トラッキングジェネレータ付きです。

所感

思ったよりも反応が良くて使いやすいです。

UIを日本語設定にすることができますが、この手の中華製品にありがちなひどい明朝フォントになったりはせず、普通に使えるレベルだと思います。とはいえ英語設定で使ってますが…

他に買ったものや欲しいもの

10dB アッテネータはマトモなやつを1つ買いました。追加でいくつか安いアッテネータを注文しています。

スペクトラム・アナライザによる高周波測定―高調波,不要輻射,変調,ひずみ,位相ノイズ,伝送特 (計測器BASIC) - 高橋 朋仁

高橋 朋仁

4.0 / 5.0

この本は買う前に読んでいました。アマゾンではなく、CQ出版社のサイトから PDF を買ってます

あとはリターンロスブリッジ (VSWR ブリッジ) が欲しいので作るかどうするか、という感じです。

方向性結合器は自分の欲しい周波数帯・耐電力のものは全く売っていないので作るしかありません。一度作っていますが、もっといいのを作りたいところです。

とはいえ50MHzまでならアンテナアナライザーもあるので、そこまで必要でもないかなという気がします。

LAN 経由でスクリーンショットをとる

まず測定画面のスクリーンショット(ハードコピー)ができないとかなり不便なので、ここから解決しました。

できたもの

プログレスを出しつつスクリーンショットをダウンロードしてこれます。ダウンロードしたあとは imagemagick の convertコマンドで png に変換し、optipng コマンドで最適化します。

ダウンロード部分にはライブラリ依存が一切ないので Ruby さえ動けば bmp ダウンロードはどこでも動きますし、LAN 経由なので一切ドライバなどが必要ないのが良いところです。

png 変換時には imagemagick コマンドと optipng コマンドが必要です。

測定器とのコミュニケーションプロトコル

DSA815 には USB 経由での GPIB 通信と、LAN ケーブル経由での VXI-11 (というよりは、LXIの一部)がサポートされています。

このへん、用語がいっぱいあるので整理しとくと

  • GPIB (IEEE488)
    • すごく古くからあるパソコンと計測器を繋ぐバス規格
    • 今は USB 経由に変換して繋ぐのが普通なようだ?
  • VXI-11
    • GPIB をイーサネット上で再現するような規格
  • LXI
    • LAN経由で計測器繋ぐというのをまとめた上位規格
    • Web インターフェイスとか、生TCPソケットとかの定義を含みつつ、VXI-11 も含む。
  • VISA
    • 計測器用プロトコルをひとまとめにした上位規格
    • VISA の裏側で GPIB とか VXI-11 とかが動いているイメージ

「というよりは、LXIの一部」と書きました。DSA815 はドキュメントには書いてないのですが、生のTCPソケット経由での通信もできるようになっていて、実はこれが一番簡単です。LXI の仕様では生TCPソケットの通信はポート5025を使うような記述がありますが、DSA815 では 5555 ポートです。

DSA815 でスクリーンショットコマンド

:PRIV:SNAP?

を送ると、11バイトのヘッダ(ファイル長を含む)とビットマップファイルが送られてきます。この :PRIV:SNAP? は隠しコマンドのようで、公式の Programming Reference にはありません。PRIV は Private の略なんでしょう。別のクライアントの通信からわかったので微妙なところです。

ref.

  1. トップ
  2. tech
  3. スペクトラムアナライザ DSA815-TG を買いました / LAN経由でスクリーンショットとれるようにしました

  • VIN 2〜24V
  • VOUT 2〜28V
  • 2A
  • 効率最大95%
  • スイッチング周波数1.2MHz

という表示のもの。

スイッチング周波数が高く、かなり小さい。(スイッチング周波数が高いほどコイルが小さくできるため。ただしスイッチング損が増えるので効率が犠牲になりやすい)。裏面に実装はなく、写っている部品で全て。

注意が必要で、普通と逆(左まわり)で電圧があがる。だから安いのか?

5V → 12V 昇圧での効率

2.5W → 2.2W 88%
5.0W → 4.73W → 94.6%
10W → 8.42W → 84.2%

これ以降出力低下

コイルが結構発熱する。

値段の割に効率が良く、小さいので結構汎用的に使えそう。2V〜 昇圧可能なのも嬉しい。NiMH 2本で昇圧したいときはベストといってもいいんじゃないかという気がする。

SX1308 とは

http://www.suosemi.com/ 深圳市硕芯科技有限公司という会社のもの

オン抵抗 80mΩ パワーMOSFETがビルトインされている。

  1. トップ
  2. tech
  3. 超小型 DC/DC ステップアップコンバータ SX1308 2A $0.99

パナソニック 口腔洗浄器 ジェットウォッシャー ドルツ 白 EW-DJ61-W - パナソニック(Panasonic)

パナソニック(Panasonic)

3.0 / 5.0

フロスをどうしてもサボってしまうので、11月ぐらいに買ってみました。だいたい使用感はわかったかなと思うのと、歯科での指導を挟んだのでで感想を書いておきます。

フロスの代わりにはならない

フロスが面倒で買ったのですが、ジェットウォッシャーではフロスの代わりにはなりませんでした。結局歯間の狭いところ(自分はほぼ隣同士の歯がくっついています)の歯垢は落とせないみたいです。かなり念入りにジェットウォッシャーを使ってからでも、フロスを使うとまださらに歯垢がとれます。歯科でのチェックでも、フロス使用時は95%〜ぐらいの歯垢除去率だったのが、ジェットウォッシャーだけの場合80%〜ぐらいまで落ちていました。

歯科のでチェックは歯石(磨き残しが続いて歯垢が固まった状態=恒常的に磨けていない箇所)も見るんですが、サボりがちのフロスでもひどいことにはならないので、サボりがちであってもフロスのほうがマシみたいです。

まぁそりゃそうだろという感じではあります。これは歯間ブラシの代替であって、フロスの代替ではありません。歯間が狭い人には向いてないのでしょう。

食べカスを落とすのは気持ちがいい

食べカスはすごい勢いで落ちてくるので気持ちが良いです。まぁフロスでも落ちるんですが、フロスの場合、糸にヘバりついてくる感じなので使用感が悪いという気持ちの問題があります。

冷い水でやるとつらい

冬の冷い水をすぐに使ってジェットウォッシャーをするとかなりつらいです。知覚過敏の人には苦行になります。

あらかじめて汲んでおいて室温にするとか、お湯でやるとかする必要があります……

今後

とはいえ、やらないよりはマシだと思うので、フロスと併用することにします。結局手順が増えてるだけじゃないかという……

「独自拡張のような形でIEEE802.11のフレームを飛ばして」と書きましたが、実際のWiFiパケットは見ておらず、真偽に疑問があったので、キャプチャして確認しました。

結論からいうとその通りのようで、Management frame (ビーコンとかと一緒) を Vendor-specific な形式で使っているようです。Wireshark だと Malformed Packet となってしまうのが謎ですが…


Management frame

Action field

を Vendor-specific で使っている

ただ、Vendor Specific Content にあたる部分に、esp_now_send で渡したバイト列以外にもヘッダみたいなのがついています。これはよくわかりません。

  1. トップ
  2. tech
  3. ESP-NOW はどのように IEEE802.11 フレームを使っているか?

OS X はビルトインで WiFI のパケットをスニフする機能が入っているので、とりあえずキャプチャして保存するだけならとても簡単にできます。

sudo /usr/libexec/airportd en0 sniff 1

これで WiFi のチャンネル1 (2.5GHz帯) のスニフができ、/tmp 以下に適当なファイルができて保存されます。

キャプチャはこれで良いのですが、別途解析ソフトウェアが必要です。

Wireshark

https://www.wireshark.org/

airportd sniff での結果は Wireshark で解析することができます。.cap ファイルを Wireshark の File → Open… から選択するだけです。

接続先が接続元の Mac アドレスでフィルタする場合、wlan.da == ff:ff:ff:ff:ff:ff や wlan.sa == ff:ff:ff:ff:ff:ff でできます。

該当するようなパケットが見つかっているなら、右クリックでフィルタとして適用とできるようになっています。

WPA2-PSK の解析

WiFi の経路は暗号化されているので、そのままだとIEEE802.11 フレームは読めますが、イーサネットフレームは読めません。しかしこれも Wireshark に適切に設定をすることで解析可能になります。

http://jorisvr.nl/wpapsk.html ここにSSIDとPassphrase を入れてでてくる16進数文字列をコピーしておきます。

Preferences… → Protocols → IEEE802.11 → Decryption Keys

type を wpa-psk として、Key にコピーした16進数文字列を入れます。

これで OK を押すと既存の解析結果も更新され、デコード可能ならデコードされます。

  1. トップ
  2. tech
  3. OS X での WiFi スニファ

オムロン 【自動電源ON】【4秒測定】【体重50g単位表示】【PC/スマホ対応 Wi-Fi通信機能搭載】体重体組成計 カラダスキャン HBF-253W-BK - オムロン(OMRON)

オムロン(OMRON)

5.0 / 5.0

なんとなく欲しいなとは思いつつ高いから買ってませんでしたが、妻が買ってきました。とりあえず結構いい感じです。

セットアップ時にスマートフォンのオーディオ出力とマイク入力を使ってやるようになってました。どういうタイミングで音を出してるのか調べなかったのですが、ヘルプだと「ピポパと聞こえたら…」という記述があるので、DTMF でやってるのかもしれません。このオーディオ出力とマイク入力を使う方法ですが、Zenfone2 ではうまくいった一方で、HTC J One ではうまくいきませんでした (beat audio などは切ってみたんですが…)

それはともかく、WiFi 経由ということですが、実際どのような通信が行われているのか知りたくなってきました。できれば接続先サーバをうまく騙して (ないし透過プロキシを通して) データをインターネットに出る前に自分で処理したいという気持ちもあります。

そんなわけでIEEE802.11フレームのパケットスニファを行って少しだけ追跡してみました。

接続先は vdu.wellnesslink.jp

WiFi パケットの解析をしてみたところ、ちゃんと TLS を使っているようでした。なので透過プロキシやら何やらを挟むことはできません。

体重データは人によってはセンシティブな個人情報ですから、TLS を使うのは当然でしょう。

一方、サーバサイドのサービスが終了してしまったとき、このデバイスの WiFi 機能は完全にゴミになります。自分の所有するデバイスの管理を自分でできないわけですからちょっと気持ち悪いところがあります。

  1. トップ
  2. tech
  3. オムロンのWiFi対応体重計 HBF-253W の通信傍受 (失敗)

pack のテンプレート文字列から、それを使ってパックした結果のサイズを求めたいということはありませんか。つまりやりたいことは sizeof(struct x) です。

pack テンプレート文字列は割と複雑で、任意長や文字列やポインタなどがあり、全てにおいてうまく動作するものを作るのは無理ですが、だいたいうまくいくのは実装できそうです。

sizeof(template)

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
  1. トップ
  2. tech
  3. Ruby の pack テンプレート文字列からそのデータサイズを求める
  1. トップ
  2. ruby
  3. Ruby の pack テンプレート文字列からそのデータサイズを求める

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
  1. トップ
  2. tech
  3. Ruby でネストした構造体文字列をネストした配列として unpack する
  1. トップ
  2. ruby
  3. Ruby でネストした構造体文字列をネストした配列として unpack する

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 $<) > $@
  1. トップ
  2. tech
  3. Markdown → PDF にする Makefile

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

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

500 Can't connect to lowreal.net:443 (certificate verify failed) のとき買って、設置をしてはいたものの、十分に活用していたとはいえない状態でした。電気使いすぎのときにアラートを鳴らしていて、ブレーカーが落ちる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

コード

以下のようなコードで試した。概要としては

  • 2.5秒に1度起動する (それ以外は deep sleep)
  • 起動時にデータをRTCメモリに書きむ (センサーデータ取得を想定)
  • 4回に1度 WiFi に接続し、UDP でセンサーデータを送信する

というもの

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "rtc_memory.hpp"
#include "config.h"
WiFiClient wifiClient;
struct {
char SSID[255] = WIFI_SSID;
char Password[255] = WIFI_PASS;
} wifi_config;
bool startWifi(int timeout) {
WiFi.disconnect();
WiFi.mode(WIFI_STA);
Serial.println("Reading wifi_config");
Serial.println("wifi_config:");
Serial.print("SSID: ");
Serial.print(wifi_config.SSID);
Serial.print("\n");
if (strlen(wifi_config.SSID) == 0) {
Serial.println("SSID is not configured");
return false;
}
WiFi.begin(wifi_config.SSID, wifi_config.Password);
int time = 0;
for (;;) {
switch (WiFi.status()) {
case WL_CONNECTED:
Serial.println("connected!");
WiFi.printDiag(Serial);
Serial.print("IPAddress: ");
Serial.println(WiFi.localIP());
return true;
case WL_CONNECT_FAILED:
Serial.println("connect failed");
return false;
}
delay(500);
Serial.print(".");
time++;
if (time >= timeout) {
break;
}
}
return false;
}
struct deep_sleep_data_t {
uint16_t count = 0;
uint8_t send = 0;
uint16_t data[12];
void add_data(uint16_t n) {
data[count] = n;
}
template <class T>
void run_every_count(uint16_t n, T func) {
count++;
if (!send) {
send = count % (n - 1) == 0;
} else {
send = 0;
count = 0;
func();
}
}
};
rtc_memory<deep_sleep_data_t> deep_sleep_data;
void post_sensor_data();
void setup() {
pinMode(13, OUTPUT);
Serial.begin(74880);
Serial.println("Initializing...");
// データ読みこみ
if (!deep_sleep_data.read()) {
Serial.println("system_rtc_mem_read failed");
}
Serial.print("deep_sleep_data->count = ");
Serial.println(deep_sleep_data->count);
// データの変更処理(任意)
deep_sleep_data->add_data(deep_sleep_data->count);
deep_sleep_data->run_every_count(4, [&]{
Serial.println("send data");
// なんか定期的に書きこみたい処理
post_sensor_data();
});
if (!deep_sleep_data.write()) {
Serial.print("system_rtc_mem_write failed");
}
if (deep_sleep_data->send) {
ESP.deepSleep(2.5e6, WAKE_RF_DEFAULT);
} else {
// sendしない場合は WIFI をオフで起動させる
ESP.deepSleep(2.5e6, WAKE_RF_DISABLED);
}
}
void loop() {
}
// dummy
void post_sensor_data() {
if (startWifi(30)) {
// do http etc...
WiFiUDP udp;
IPAddress serverIP(192, 168, 0, 5);
udp.beginPacket(serverIP, 5432);
udp.write((uint8_t*)deep_sleep_data->data, sizeof(deep_sleep_data->data));
udp.endPacket();
// wait for sending packet
delay(1000);
} else {
Serial.println("failed to start wifi");
ESP.restart();
}
}
view raw main.cpp hosted with ❤ by GitHub
extern "C" {
#include <user_interface.h>
};
template <class T>
class rtc_memory {
// 4 bytes aligned memory block address in rtc memory.
// user data must use 64 or larger block.
// but system_rtc_mem_read() is failed for 64 so use 65.
static constexpr uint32_t USER_DATA_ADDR = 65;
static constexpr uint32_t USER_DATA_SIZE = 512 - ((USER_DATA_ADDR - 64) * 4);
static uint32_t fnv_1_hash_32(uint8_t *bytes, size_t length) {
static const uint32_t FNV_OFFSET_BASIS_32 = 2166136261U;
static const uint32_t FNV_PRIME_32 = 16777619U;
uint32_t hash = FNV_OFFSET_BASIS_32;
for (size_t i = 0 ; i < length ; ++i) hash = (FNV_PRIME_32 * hash) ^ (bytes[i]);
return hash;
}
uint32_t calc_hash(T& data) const {
return fnv_1_hash_32((uint8_t*)&data, sizeof(data));
}
public:
uint32_t hash;
T data;
static_assert(sizeof(T) <= (USER_DATA_SIZE - sizeof(hash)), "sizeof(T) it too big");
bool read() {
// Read memory to temporary variable to retain initial values in struct T.
rtc_memory<T> read;
// An initial rtc memory is random.
bool ok = system_rtc_mem_read(USER_DATA_ADDR, &read, sizeof(read));
if (ok) {
// Only hashes are matched and copy to struct.
if (read.hash == calc_hash(read.data)) {
memcpy(this, &read, sizeof(read));
}
}
return ok;
}
bool write() {
hash = calc_hash(data);
return system_rtc_mem_write(USER_DATA_ADDR, this, sizeof(*this));
}
T* operator->() {
return &data;
}
};
view raw rtc_memory.cpp hosted with ❤ by GitHub

実測

説明をいれると

約5秒でAPに接続できている。その後UDPで送信し、送信が終わるのをちょっと無駄に1秒待っている。

V: 31.72mA となっているのが画面内の平均だけど、WiFi 接続が2回起きているので、1サイクルの平均ではない。

1サイクル(約16秒)あたりは、WiFi に 80mA 6秒、スリープ 20uA 10秒とし平均30mA。

もし単三 NiMH 2400mAh 3本(公称1.2V * 3 = 3.6V)をLDOで3.3Vにして電源にしたとすると、バッテリ容量は2400mAh * 1.2V * 3 = 8640mWh。このケースのESP8266の平均消費電力は 3.6V * 30mA = 108mW (LDO損失こみ)。8640 / 108 = 80時間。

支配的なのは圧倒的にWiFi接続時間なので、とにかく周期を長くとれば、それだけ電池寿命は伸びる。実際のところ電池3本で実用的なのは5分以上かなあという気がする (5分間隔でも2ヶ月ぐらい)。

これよりも短い間隔でデータのやりとりをする場合、電池駆動は考えないほうがよさそう。どうしても電池駆動したい場合WiFi 使うのが間違っているので ZigBee とか使うべきなんだと思う。とはいえ ESP8266 が圧倒的に安すぎるので、なんでも WiFi でやりたくなってしまう。

  1. トップ
  2. tech
  3. メモ: ESP8266 での実測電流

経緯

メモ: ESP8266 での実測電流 | tech - 氾濫原 というのを書いたあと、WiFi のコネクションなしでもデータを適当に送信できたらいいのではないか?と考えました。

具体的には SSID などをビーコン情報としてコネクションレスでブロードキャストしているのだから、これにセンサーデータをつけてしまえばいいだろう、というアイデアです。

ということで ESP8266 の API を調べてみましたが、どうやら WiFi のフレームを生で扱うようなAPIはないようでした。じゃあSSID にデータを入れて softap としてビーコンを発射すればとも思いましたが、さすがに邪悪すぎます。

もうちょっとAPIを眺めていると ESP-NOW というのがあることに気付きました。独自拡張のような形でIEEE802.11のフレームを飛ばしてWiFiコネクションレスで送受信できるもののようです。これを使えば前述のような邪悪なことをしなくても同じようなことができそうです。

ESP-NOW の概念

常時データを受けとるスレーブと、必要なときにデータを送信するコントローラに分かれます (送受信はどちらもできる)。スレーブは常時チャンネルを受信していなければならないので、基本的に固定の電源が必要です。コントローラはデータを投げるだけなのでバッテリ駆動できます。

送受信するまえにお互いの Mac アドレスを知っている必要があります。ESP-NOW としては、お互いに一旦 softap となって BSSID (Macアドレス) を公開してペアリングすることを想定しているようです。

なお、(おそらく)フレームをそのまま送信しているだけなので、確実に到達する保証はなく、もし要求されるならACKみたいなものは自力で実装する必要もあります。

暗号化のサポートもあるようですがそこまでとりあえずは調べていません。

実装

スレーブとコントローラいずれも ESP8266 である必要があるので (ローレベルなIEEE802.11のフレームを扱えるなら互換デバイスは作れそうですが)、ESP8266 は2台必要です。

ペアリング部分はめんどうなので、肝心のところだけ試しに実装して試してみました。

スレーブ

スレーブはデータを受けとって表示するだけです。現実的なアプリケーションなら、STATION+AP モードにして、別のホストに中継するように動作させると思いますが、今回はAPモードで動作させています。

esp_now_register_send_cb してますが send してないので呼ばれません。

ピアのMacアドレスは、STATION_IF のMacアドレスです。

#include <Arduino.h>
#include <ESP8266WiFi.h>
extern "C" {
	#include <espnow.h>
	#include <user_interface.h>
}

#define WIFI_DEFAULT_CHANNEL 1

uint8_t mac[] = {0x5C,0xCF,0x7F,0x8,0x37,0xC7};

void printMacAddress(uint8_t* macaddr) {
	Serial.print("{");
	for (int i = 0; i < 6; i++) {
		Serial.print("0x");
		Serial.print(macaddr[i], HEX);
		if (i < 5) Serial.print(',');
	}
	Serial.println("}");
}

void setup() {
	pinMode(13, OUTPUT);

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

	WiFi.mode(WIFI_AP);
	WiFi.softAP("foobar", "12345678", 1, 0);

	uint8_t macaddr[6];
	wifi_get_macaddr(STATION_IF, macaddr);
	Serial.print("mac address (STATION_IF): ");
	printMacAddress(macaddr);

	wifi_get_macaddr(SOFTAP_IF, macaddr);
	Serial.print("mac address (SOFTAP_IF): ");
	printMacAddress(macaddr);

	if (esp_now_init() == 0) {
		Serial.println("init");
	} else {
		Serial.println("init failed");
		ESP.restart();
		return;
	}

	esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
	esp_now_register_recv_cb([](uint8_t *macaddr, uint8_t *data, uint8_t len) {
		Serial.println("recv_cb");

		Serial.print("mac address: ");
		printMacAddress(macaddr);

		Serial.print("data: ");
		for (int i = 0; i < len; i++) {
			Serial.print(" 0x");
			Serial.print(data[i], HEX);
		}
		Serial.println("");
	});
	esp_now_register_send_cb([](uint8_t* macaddr, uint8_t status) {
		Serial.println("send_cb");

		Serial.print("mac address: ");
		printMacAddress(macaddr);

		Serial.print("status = "); Serial.println(status);
	});

	int res = esp_now_add_peer(mac, (uint8_t)ESP_NOW_ROLE_CONTROLLER,(uint8_t)WIFI_DEFAULT_CHANNEL, NULL, 0);

//	esp_now_unregister_recv_cb();
//	esp_now_deinit();
}

void loop() {
}

コントローラ

スレーブとほぼ同じですが ROLE が変わっています。適当なバイト列を esp_now_send() しています。

ピアのMacアドレスは、SOFTAP_IF のMacアドレスです。

#include <Arduino.h>
#include <ESP8266WiFi.h>
extern "C" {
	#include <espnow.h>
	#include <user_interface.h>
}

#define WIFI_DEFAULT_CHANNEL 1

uint8_t mac[] = {0x1A,0xFE,0x34,0xEE,0x84,0x88};

void printMacAddress(uint8_t* macaddr) {
	Serial.print("{");
	for (int i = 0; i < 6; i++) {
		Serial.print("0x");
		Serial.print(macaddr[i], HEX);
		if (i < 5) Serial.print(',');
	}
	Serial.println("}");
}

void setup() {
	pinMode(13, OUTPUT);

	Serial.begin(74880);
	Serial.println("Initializing...");
	WiFi.mode(WIFI_STA);

	uint8_t macaddr[6];
	wifi_get_macaddr(STATION_IF, macaddr);
	Serial.print("mac address (STATION_IF): ");
	printMacAddress(macaddr);

	wifi_get_macaddr(SOFTAP_IF, macaddr);
	Serial.print("mac address (SOFTAP_IF): ");
	printMacAddress(macaddr);

	if (esp_now_init()==0) {
		Serial.println("direct link  init ok");
	} else {
		Serial.println("dl init failed");
		ESP.restart();
		return;
	}

	esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
	esp_now_register_recv_cb([](uint8_t *macaddr, uint8_t *data, uint8_t len) {
		Serial.println("recv_cb");

		Serial.print("mac address: ");
		printMacAddress(macaddr);

		Serial.print("data: ");
		for (int i = 0; i < len; i++) {
			Serial.print(data[i], HEX);
		}
		Serial.println("");
	});
	esp_now_register_send_cb([](uint8_t* macaddr, uint8_t status) {
		Serial.println("send_cb");

		Serial.print("mac address: ");
		printMacAddress(macaddr);

		Serial.print("status = "); Serial.println(status);
	});

	int res = esp_now_add_peer(mac, (uint8_t)ESP_NOW_ROLE_SLAVE,(uint8_t)WIFI_DEFAULT_CHANNEL, NULL, 0);

	uint8_t message[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08 };
	esp_now_send(mac, message, sizeof(message));
	ESP.deepSleep(2.5e6, WAKE_RF_DEFAULT);

//	esp_now_unregister_recv_cb();
//	esp_now_deinit();
}

void loop() {
}

動作と消費電力

こんな普通に簡単な実装を書いてみたら普通に動きました。

スレーブ側のシリアル出力

コントローラの電流値の推移

この実装だと 2.5秒ごとに毎回送信しているので割と平均消費も大きいです。しかしそれでも、前回の16秒ごとのWiFi接続の30mAの半分以下です。

コントローラの別実装

ということで、2.5秒おきに deep sleep から復帰して値を RTC メモリ書きつつ、4回に1度はWiFiで送信するようなサンプルも書いてみました。これは前回のWiFiコネクション+UDPの部分をESP-NOWを使うように置き換えたものになります。

エントリ冒頭の画像はこれの横軸を広げたものです。

平均で6.8mAぐらいです。

備考

この実装ではフレームの再送などはしていないので、データが欠落する可能性があります。とはいえ UDP の実装でも同じことがいえるので、これが原因だからダメということにはならないでしょう。

しかしこの実装だとMacアドレス以外一切何も確認していないので、もし悪意のある人が近くにいれば変なデータを送りこむのは容易です。

ESP8266 を使ってできる無線通信ではこれ以上の省電力化は難しそうな気がします。あとは送信出力を下げるぐらいしかなさそうです。

  1. トップ
  2. tech
  3. ESP8266 の低消費電力の限界をさぐる (ESP-NOWを使ってみる)