備忘録的なもの

Linux で TAP-TST10N の測定データを取り出す

自宅にあるマシンの消費電力を測りたくてサンワサプライのワットチェッカーを買ったが、 その測定結果を Linux から取得するためにやったことをまとめる。

値段は若干お高め(とは言っても 7,000円 とかそこらだが)ではあったが、 ワットチェッカーのモニタでその瞬間の値を目視確認するだけじゃなくて、 ある程度の期間の測定データを溜めて USB 接続経由で PC から取得できる、 ってところに魅力を感じたのでこれにした。

USB 接続して使う場合は付属の Windows 用のソフトを使って測定データの確認や CSV でのエクスポートを行うことが想定されているが、 買う前に軽く下調べをして Linux で使っている先人の記事もあり試してみようと思ったのもある。

USB電力計 サンワサプライ TAP-TST10をLinuxで使ってみた – OSAKANA TAROのメモ帳

TAP-TST10N について

サンワサプライから出ているワットチェッカー。 Amazon のページとか見れば大体分かるが、見た目通りでコンセントと機器の間に挟んだら勝手に計測してくれるもの。 内容物としては以下くらいでまぁ普通。

  • ワットチェッカー本体
  • Windows 用のソフトの入ったミニCD
  • USB A - MicroB のケーブル( 1m くらい)
  • 取り扱い説明書

電力の計測は1秒間隔でされていてそれがモニターに表示される。 また10分間隔で計測値を内部に記録していて USB 接続経由で取り出せるようになっている。 計測値の保存期間は1ヶ月でそれを超えると古いものから消えていく。

自分は現行版の TAP-TST10N を買ったが、よく似たものとして廃版の TAP-TST10 がある。

TAP-TST10の類似製品検索結果|サンワサプライ株式会社

「TAP-TST10N Linux」とかでググると使ってる人はいくらか見つかるが、よく見ると大抵 TAP-TST10の方を使っている。 今回試した範囲では同じ様に使えていて、サンワサプライのページを見ても筐体デザイン以外の違いが分からないが一応別物ではあるっぽい。

ちなみに自分は買って色々弄ってる途中で現行版と廃版があることに気付いた。

測定結果取り出すまでにやったこと

実際測定結果を取り出そうとガチャガチャやってみると色々必要だったのでその辺りの記録

ざっくりとは以下の感じ

  • TAP-TST10N の認識
  • 測定結果の取り出し用 Python コードの取得、少し修正
  • 作業する Linux ユーザに TAP-TST10N アクセス用の権限を設定
  • 物理接続段階で TAP-TST10N が usbhid に掴まれていることの解消

コードは別の方が公開されていたモノをフォークして少し手を加えて使っているのだが、 USB 周りの扱いも Python コードに関してもだいぶ雰囲気でやってるので何か変な事をやっている可能性はある。

2024/05/16 追記) 最終的なコードは以下

TAP-TST10N を PC と繋ぐ

付属品の中にケーブルが入っているので、それで PC と繋ぐ。

TAP-TST10N は基本的にコンセントからの電力で動く 1 が、 一応コンセントから抜いた状態でも USB のバスパワーで動くので認識はする。

実際繋ぐ時は dmesg を見ながら接続するとこんな感じのログが流れるので認識されたことが確認できる。

❯ sudo dmesg --follow-new
[40648.851484] usb 1-1.4: new full-speed USB device number 14 using xhci_hcd
[40648.996528] usb 1-1.4: New USB device found, idVendor=040b, idProduct=2201, bcdDevice= 0.02
[40648.996544] usb 1-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[40648.996549] usb 1-1.4: Product: HID Device
[40648.996553] usb 1-1.4: Manufacturer: Weltrent Semiconductor, Inc.
[40649.004866] hid-generic 0003:040B:2201.0005: hiddev96,hidraw0: USB HID v1.10 Device [Weltrent Semiconductor, Inc. HID Device] on usb-0000:00:14.0-1.4/input0

また認識されていれば lsusb でも確認できる。 lsusb だけだと TAP-TST10N がどれか分かりにくいが、 以下のような感じで前述の ID ( 040b:2201 )と照らすと判別できる。

❯ lsusb -d 040b:2201
Bus 001 Device 013: ID 040b:2201 Weltrend Semiconductor HID Device

この idVendor=040b 2idProduct=2201 は、 USB デバイスを識別するために割り振られているもので後で何度か必要になる。

SANWA SUPPLY TAP-TST10 control tool を実行してみる

TAP-TST10N からデータを取り出すためのプログラムの準備をする。

冒頭に書いた、購入前に見た記事でも使われていたものでコードは GitHub で公開されているのでこれを使わせてもらう。

GitHub - nonakap/taptst10ctl: SANWA SUPPLY TAP-TST10 control tool

ただこれを単純に取ってきて自分の環境で動かすとこんな感じのエラーになる。

❯ git clone https://github.com/nonakap/taptst10ctl.git
Cloning into 'taptst10ctl'...
remote: Enumerating objects: 7, done.
remote: Total 7 (delta 0), reused 0 (delta 0), pack-reused 7
Receiving objects: 100% (7/7), done.
Resolving deltas: 100% (1/1), done.

❯ cd taptst10ctl/

❯ python -m venv .venv

❯ source .venv/bin/activate

❯ pip install pyusb
Collecting pyusb
  Using cached pyusb-1.2.1-py3-none-any.whl (58 kB)
Installing collected packages: pyusb
Successfully installed pyusb-1.2.1

[notice] A new release of pip is available: 23.2.1 -> 23.3.2
[notice] To update, run: pip install --upgrade pip

❯ python taptst10ctl.py 
  File "/home/shida/src/github.com/nonakap/taptst10ctl/taptst10ctl.py", line 118
    print "No.,DateTime,Watt,kWh"
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?

これは単純に Python2 用のコードを Python3 で実行したことによるものなので括弧を付ければ良い。

@@ -115,11 +115,11 @@ while True:
     if data[16] == 0xfe or size <= 0:
         break
 
-print "No.,DateTime,Watt,kWh"
+print("No.,DateTime,Watt,kWh")
 if count > 0:
     watt_unit = datetime.timedelta(minutes=10)
     start_time = now - watt_unit * (count - 1)
     for x in range(count):
         t = start_time.timetuple()
-        print "%d,%d/%02d/%02d %02d:%02d,%.1f,%.2f" % (x + 1, t[0], t[1], t[2], t[3], (t[4] / 10) * 10, watts[x], kWhs[x])
+        print("%d,%d/%02d/%02d %02d:%02d,%.1f,%.2f" % (x + 1, t[0], t[1], t[2], t[3], (t[4] / 10) * 10, watts[x], kWhs[x]))
         start_time = start_time + watt_unit

TAP-TST10N のデバイスのアクセス権限を設定

で実行すると、今度は以下の様な感じで USB デバイスへのアクセス権でのエラーになるのでこれの対応をする。

❯ python taptst10ctl.py 
Traceback (most recent call last):
  File "/home/shida/src/github.com/nonakap/taptst10ctl/taptst10ctl.py", line 54, in <module>
    dev.set_configuration()
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/core.py", line 915, in set_configuration
    self._ctx.managed_set_configuration(self, configuration)
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/core.py", line 113, in wrapper
    return f(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/core.py", line 158, in managed_set_configuration
    self.managed_open()
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/core.py", line 113, in wrapper
    return f(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/core.py", line 131, in managed_open
    self.handle = self.backend.open_device(self.dev)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/backend/libusb1.py", line 804, in open_device
    return _DeviceHandle(dev)
           ^^^^^^^^^^^^^^^^^^
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/backend/libusb1.py", line 652, in __init__
    _check(_lib.libusb_open(self.devid, byref(self.handle)))
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/backend/libusb1.py", line 604, in _check
    raise USBError(_strerror(ret), ret, _libusb_errno[ret])
usb.core.USBError: [Errno 13] Access denied (insufficient permissions)

ディストリビューションというか udev の設定によるのだと思うが、 TAP-TST10N を USB で繋いだだけだと root からのみデバイスにアクセスできるようになっていて一般ユーザからはアクセスできない。 sudo 等を使って述の python コードを root で実行することで回避はできるが、 それも微妙なので udev 経由で USB デバイスの権限設定を行って一般ユーザからもアクセスできるようにする。

まず USB デバイスにアクセスする用のグループ( uaccess 3 )を作成して、自分( shida )をグループに追加する

❯ sudo groupadd uaccess

❯ sudo gpasswd -a shida uaccess 
ユーザ shida をグループ uaccess に追加

❯ id
uid=1002(shida) gid=1002(shida) groups=1002(shida),993(input),998(wheel),1003(uaccess)

TAP-TST10N 用の udev ルールを以下の様に作成し、 udev に読み込ませる。

❯ ls -lh /etc/udev/rules.d/10-tap-tst10n.rules
-rw-r--r-- 1 root root 99 12月 21 16:30 /etc/udev/rules.d/10-tap-tst10n.rules

❯ cat /etc/udev/rules.d/10-tap-tst10n.rules
SUBSYSTEMS=="usb", ATTRS{idVendor}=="040b", ATTRS{idProduct}=="2201", MODE="0660", GROUP="uaccess"

❯ sudo udevadm control --reload-rules

で、所属するユーザから TAP-TST10N にアクセスできるようになる。 USB を抜き差ししても udev がルールを適用して権限設定してくれる。

TAP-TST10N を usbhid ドライバから unbind する

ユーザで実行した python から TAP-TST10N へのアクセスを行えるようになったが、 次はこんな感じのエラーが出る。

❯ python taptst10ctl.py 
Traceback (most recent call last):
  File "/home/shida/src/github.com/nonakap/taptst10ctl/taptst10ctl.py", line 54, in <module>
    dev.set_configuration()
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/core.py", line 915, in set_configuration
    self._ctx.managed_set_configuration(self, configuration)
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/core.py", line 113, in wrapper
    return f(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/core.py", line 159, in managed_set_configuration
    self.backend.set_configuration(self.handle, cfg.bConfigurationValue)
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/backend/libusb1.py", line 812, in set_configuration
    _check(self.lib.libusb_set_configuration(dev_handle.handle, config_value))
  File "/home/shida/src/github.com/nonakap/taptst10ctl/venv/lib/python3.11/site-packages/usb/backend/libusb1.py", line 604, in _check
    raise USBError(_strerror(ret), ret, _libusb_errno[ret])
usb.core.USBError: [Errno 16] Resource busy

USB デバイスが別のプロセスか何かに掴まれてるっぽい感じ。

調べた感じだと、 TAP-TST10N を繋いだ段階で udev が usbhid というドライバを自動でロードして、 usbhid ドライバが USBデバイス(ファイル)を先に掴んでおり、 その横から前述のプログラムで USB デバイスへのアクセスをしようとしたので前述の Resource busy で弾かれた、ということっぽい。 (理解度がかなり怪しい)

udev が bind した usbhid ドライバは手動で unbind もできるようで、 先人の記事でもそうしていたので今回もそれに習った。 自分が実行したのはこんな感じ。

まず usbhid ドライバに bind されている USB デバイスを確認。

❯ ls -lh /sys/bus/usb/drivers/usbhid/
合計 0
lrwxrwxrwx 1 root root    0 12月 22 01:50 1-1.4:1.0 -> ../../../../devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.4/1-1.4:1.0
--w------- 1 root root 4.0K 12月 21 11:17 bind
lrwxrwxrwx 1 root root    0 12月 21 11:17 module -> ../../../../module/usbhid
-rw-r--r-- 1 root root 4.0K 12月 21 11:17 new_id
-rw-r--r-- 1 root root 4.0K 12月 21 11:17 remove_id
--w------- 1 root root 4.0K 12月 21 11:17 uevent
--w------- 1 root root 4.0K 12月 21 15:44 unbind

この 1-1.4:1.0 が正しく TAP-TST10N かどうかを確認。

❯ lsusb -tv
/:  Bus 001.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/12p, 480M
    ID 1d6b:0002 Linux Foundation 2.0 root hub
    |__ Port 001: Dev 015, If 0, Class=Hub, Driver=hub/5p, 480M
        ID 291a:2817  
        |__ Port 004: Dev 019, If 0, Class=Human Interface Device, Driver=usbhid, 12M
            ID 040b:2201 Weltrend Semiconductor 
        |__ Port 005: Dev 018, If 0, Class=Billboard, Driver=[none], 480M
            ID 291a:8352  
    |__ Port 005: Dev 003, If 0, Class=Video, Driver=uvcvideo, 480M
        ID 0c45:6723 Microdia 
    |__ Port 005: Dev 003, If 1, Class=Video, Driver=uvcvideo, 480M
        ID 0c45:6723 Microdia 
    |__ Port 007: Dev 008, If 0, Class=Wireless, Driver=btusb, 12M
        ID 8087:0029 Intel Corp. AX200 Bluetooth
    |__ Port 007: Dev 008, If 1, Class=Wireless, Driver=btusb, 12M
        ID 8087:0029 Intel Corp. AX200 Bluetooth
    |__ Port 010: Dev 006, If 0, Class=Communications, Driver=cdc_acm, 12M
        ID 27c6:5385 Shenzhen Goodix Technology Co.,Ltd. Fingerprint Reader
    |__ Port 010: Dev 006, If 1, Class=CDC Data, Driver=cdc_acm, 12M
        ID 27c6:5385 Shenzhen Goodix Technology Co.,Ltd. Fingerprint Reader
〜(後略)〜

(Bus)1-(Port)1:(Port)4: を指しているので 040b:2201 の ID を持った TAP-TST10N であることが確認できる。

ってことで以下で unbind する。

❯ sudo sh -c 'echo "1-1.4:1.0" > /sys/bus/usb/drivers/usbhid/unbind'

❯ ls -lh /sys/bus/usb/drivers/usbhid/
合計 0
--w------- 1 root root 4.0K 12月 21 11:17 bind
lrwxrwxrwx 1 root root    0 12月 21 11:17 module -> ../../../../module/usbhid
-rw-r--r-- 1 root root 4.0K 12月 21 11:17 new_id
-rw-r--r-- 1 root root 4.0K 12月 21 11:17 remove_id
--w------- 1 root root 4.0K 12月 21 11:17 uevent
--w------- 1 root root 4.0K 12月 22 02:28 unbind

この辺りの制御は USB デバイスのアクセス権限の設定と同じ様に、 udev のルールを良い感じに設定することでも回避できそうな気もしたが、 udev の経験値が少なくて時間かかりそう(実際できるかどうかも分からない)だったので今回は諦めた。

pyusb のバージョン依存と思われる部分の修正

で、最後にこんな感じのエラーが出た。

❯ python taptst10ctl.py 
Traceback (most recent call last):
  File "/home/shida/src/github.com/nonakap/taptst10ctl/taptst10ctl.py", line 86, in <module>
    data = dev.read(ENDPOINT, 17, intf, 1000)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Device.read() takes from 3 to 4 positional arguments but 5 were given

引数 5 つと認識されている理由はよく分からないが、 PyUSB のチュートリアルを見た感じ USB デバイスのエンドポイントを指定していればインタフェースの指定は要らなそうだったので以下の様な感じで修正。 (このコードが書かれたのが結構前なので、それから今までの間に仕様変更があったとかだろうか)

@@ -83,7 +83,7 @@ watts = []
 kWhs = []
 outep.write('\x02\x18\x0a')
 while True:
-    data = dev.read(ENDPOINT, 17, intf, 1000)
+    data = dev.read(ENDPOINT, 17, 1000)
     # dump raw data
     #print(" ".join("{:02x}".format(x) for x in data))

データの取得

で、実行すると以下の様な感じでデータが取れる。

❯ python taptst10ctl.py
No.,DateTime,Watt,kWh
1,2023/12/18 07:11,196.0,0.02
2,2023/12/18 07:21,0.0,0.05
3,2023/12/18 07:31,1.3,0.05
4,2023/12/18 07:41,1.3,0.05
5,2023/12/18 07:51,1.3,0.05
6,2023/12/18 08:01,1.3,0.05
7,2023/12/18 08:11,1.3,0.06
8,2023/12/18 08:21,1.3,0.06
9,2023/12/18 08:31,1.3,0.06
10,2023/12/18 08:41,1.3,0.06

〜(中略)〜

548,2023/12/22 02:21,0.0,0.17
549,2023/12/22 02:31,0.0,0.17
550,2023/12/22 02:41,0.0,0.17
551,2023/12/22 02:51,0.0,0.17
552,2023/12/22 03:01,0.0,0.17
553,2023/12/22 03:11,181.0,0.19
554,2023/12/22 03:21,190.0,0.23
555,2023/12/22 03:31,1.2,0.25
556,2023/12/22 03:41,1.3,0.25
557,2023/12/22 03:51,1.3,0.25

おわりに

ということで、色々分からないなりに楽しく(?)ガチャガチャやっていたのだが、 途中で SwitchBot のスマートプラグをワットチェッカーとして使っている記事をいくつか見つけて、 「そっちの方が良さそうだな?」となってしまいちょっと冷めてしまった。

良さそうと思ったのは、負荷で消費電力が大きく上下する PC の様なものの消費電力を見るならおそらく実際に計測するのはアイドル時と高負荷時になり、

  • 今回買ったワットチェッカーの10分間隔での測定結果記録を使うより時間的な解像度を上げられそう
  • PC の電力計測だと大抵奥まった場所に設置することになるので、 bluetooth なりの無線で値取れた方が作業しやすそう

の2点が主な理由。

今回買ったサンワサプライのヤツはそれよりも白物家電みたいに消費電力がもう少し安定してて、 ずっと動かしてる様なヤツの消費電力を数週間から数ヶ月単位で計測して何かする 4 みたいなことの方が向いてるのかも、と感じた。

自分の下調べのガバ具合に少々げんなりしたが、 雰囲気でやっていた Linux での USB 周辺(udev)についての知識が少し付いたのでまぁ良し、 と思うことにした。

参考


  1. そもそもワットチェッカーはそう使うものという説はある ↩︎

  2. USB.org で調べてみるとサンワサプライの VenderID は 0x040b = 1035 ではなく 0x0D9D = 3485 だった、 OEM品 ってことなんだろうか ↩︎

  3. ここでは uaccess という名前を使ったが、調べたら他にも plugdev という名前を使っていたり、専用のグループは作らず wheel からアクセスできるようにしているケースもあった。どれがベターとかは無く割と自由っぽい。 ↩︎

  4. 消費電力や使用頻度を可視化して電気代節約の参考にするとか ↩︎