ThinkPad 起動時に出る PowerMgr.exe の 0x0000007E エラーを解決

ThinkPad T495s の起動時に、PowerMgr.exe が「0x0000007E 指定されたモジュールが見つかりません。」というエラーが出るようになりました。正確な時期は定かでないですが、BIOS アップデートか Windows 10 2004 へのアップデートをした後と思われます。

powermgr 0x0000007e error | ノートPCのQ&A【OKWAVE】 によると全世界的にも報告があるとのこと。さらには Lenovo Vantage の「デバイス設定」ページの右側にある「バッテリー状態」にもエラーメッセージが出ているとのこと。見てみると確かに、バッテリー容量表示の下に次のようなメッセージが。

Lenovo Power Manager ドライバーがインストールされていないか、正しく機能していません。Windows Update を実行するか、手動でここからインストールしてください。インストール後に、コンピュータを再起動します。

Lenovo のフォーラムをいくつか見てみると、暫定的な解決法が載っていました。

forums.lenovo.com

 

自分の環境(以下)ではうまくいきました。

  • PC: ThinkPad T495s
  • OS: Windows 10 バージョン 2004 (OSビルド 19041.508)
  • Lenovo Power Manager のドライバーのバージョン: 10.0.127.0

 

保証はできませんが、他の誰かの参考になるよう、やった手順を記録しておきます。上記のフォーラムに書いてあったやり方と同じです。同じバージョンのドライバーをアンインストール→インストールしただけですが、それで治りました。 

  1. Lenovo Settings Power Manager for Windows 10 (Version 1703 or Later) - ThinkPad - GB から Lenovo Settings Power Manager (n1fup87w.exe) と README (n1fup87w.txt) をダウンロード。
  2. README の supported models 一覧に、自分の機種が載っているかどうか確認。載っていなかったらここでやめておく。
  3. Windows キーと X (エックス) を同時に押した後、表示されるリストの中から「デバイス マネージャー」」をクリック。
  4. バイスマネージャーが開く。「システム デバイス」の中の「Lenovo Power Manager」を右クリックして「プロパティ」をクリック。
  5. Lenovo Power Managerのプロパティが開く。「ドライバー」タブを開いて「バージョン」を確認。自分の場合は最新版と同じ 10.0.127.0 でした。
  6. 同じ画面の「デバイスのアンインストール」ボタンをクリック。
  7. バイスのアンインストールが表示される。「このデバイスのドライバーソフトウェアを削除します。」にチェックを付けて、「アンインストール」ボタンをクリック。
  8. Power Manager がアンインストールされる。デバイスマネージャーから Lenovo Power Manager が消える。
  9. ダウンロードしておいた Lenovo Settings Power Manager (n1fup87w.exe) を実行し、表示に従ってインストール。
  10. バイスマネージャーの「操作」>「ハードウェア変更のスキャン」を押して、Lenovo Power Manager が復活したことが確認できます。
  11. PC を再起動する。起動後、一瞬例のエラーメッセージが表示されたが、自動的に消えた。その後は出ていないみたい。Lenovo Vantage の「デバイス設定」>「バッテリー状態」のエラーメッセージも消えていました。 

 

勝手に暗くなる ThinkPad X220t

ThinkPad X220 tablet は最高だ。しかしいくつか欠点もある。

例えば、バッテリー駆動中に画面の輝度が勝手に調整される点。このおせっかい機能が気になってしょうがない。黒背景のターミナル等を全画面表示するとふわーっと輝度(明るさ)が落ち、非常に可読性が下がる。しょうがないので Fn+Home を連打して輝度を上げる。

書きものの途中で調べ物や休憩をするためにブラウザにウィンドウを切り替えると、今度は白ベースの背景に合わせてか、輝度がふわーっと上がって眩しい!Fn+End を連打して輝度を下げる。

もう振り回されるのは嫌だ!以下のようにして解決した。

  1. Win キーを押して「インテル」と入力
  2. インテル(R) コントロール・センター」が表示されるのでクリック
  3. ウィンドウが表示されたら「インテル(R) HD グラフィックス」を選択
  4. 「次のアプリケーションモードのいずれかを選択してください」と出たら「基本モード」を選択
  5. 左側のタブから「電源」を選択
  6. 右上の「電源に接続▼」を押して「バッテリー駆動」に切り替え
  7. 下の方にある「ディスプレイ省電テクノロジ」のチェックを外す

悩めるドイツ単身赴任(1): インターネット

はじめに

ドイツへの単身赴任で困ったこと悩んだことを記録していくことにした。いつか何かの役に立つかもしれない。まずはインターネット接続の悩みから。

ドイツのインターネット事情

ドイツではブロードバンド契約のほとんどに最低契約期間が設けられている。たいていの場合、二年以内に解約した場合にはペナルティ(違約金)が発生する。僕の滞在予定期間ははじめから一年を切っていたのでペナルティ覚悟で契約せざるを得ないのだが、このペナルティの具体的な内容がよく分からなかった。契約後の工事に時間を要するとの忠告もあり、店舗まで足を運んだものの結局契約を思いとどまることになった。#問題はドイツ語の不自由な僕自身にもあるのだけど…

プリペイドなモバイルインターネット

同僚に相談したところ、代替手段としてプリペイド SIM を使ったモバイルインターネットを薦められた。これはプリペイドの名の通り、あらかじめお金をチャージしておくことで、チャージされた金額分だけ利用可能、それ以上は課金されないという種類のサービスだ。チャージ済の SIM カードを USB モデムやモバイルルータに挿すことで利用する。有線接続に比べると速度は出ないが、いつ止めても違約金が発生しないし、工事も不要でお手軽だ。

複数のキャリア (T-mobile, vodafone, O2, E-plus) と MVNO (FONIC, Blau, Congstar などなど) から、それぞれ複数のプリペイドプランが提供されており、少々分かりにくい。これまでに2つ試してみたのでここにメモしておく。

まとめ

FONIC Internet-Tagesflatrate T-mobile Xtra web'n'walk DayFlat
一日の支払い 2.50 ユーロ 4.95 ユーロ
一月の支払い上限 25 ユーロ 上限なし
通信量の上限 500MB/日, 5GB/月 1GB,2GB/日
上限超過時の制限 64kbps 384kbps (1GB超), 64kbps (2GB超)
VoIP(Skype等)の利用 可能 契約上禁止
使用回線 O2 T-mobile
参考(一月31日間利用時の支払い額) 25 ユーロ 153.45 ユーロ

ここ3ヶ月ほど FONIC を使い続けてきたが、速度が安定しない日が多くなってきたため最近は T-mobile をメインに使っている。でも一日5ユーロは厳しい…

その1: FONIC Surf-Stick (FONIC Internet-Tagesflatrate)

http://www.fonic.de/html/prepaid-surf-stick-fuer-mobiles-internet.html

FONIC は O2 の回線を利用した MVNO
プリペイド SIM カードが付いた USB モデムをSurf-Stick という名のパッケージで提供している。
家電量販店はもちろん、スーパーマーケットでも売られている。
パッケージの値段は 29.95 ユーロ。1日分の無料通信料が含まれている。

利用開始のためにはインターネットでアクティベート作業が必要。
https://www.fonic.de/selfcare/servlet/ActivationInfo に接続して SIM の番号や個人情報の入力をしなければならない。

付属のプリペイド SIM は Internet-Tagesflatrate (一日定額インターネット) プランにセットされている。一日の使用料は 2.50 ユーロ。接続を開始するとチャージから 2.50 ユーロが引かれ、その日の 23:59 までは追加金なしで利用可能になる。
ただし一日の通信量が 500 MB を超えると、それ以降は日付が変わるまで帯域制限がかかって最大 64 kbps でしか通信できなくなる。

FONIC が使っている O2 の回線では、下の T-mobile とは違って VoIP 等の利用を禁止していない。

その2: T-mobile Xtra web'n'walk DayFlat

http://www.t-mobile.de/tarife/0,10821,17773-_1081,00.html

T-mobile はドイツの大手通信キャリア。カバー範囲が広く、O2 に比べて安定しているという噂を聞いて試しに購入してみた。
T-mobile の店舗に行ってモバイルルータ用のプリペイド SIM が欲しいと伝えて出てきたものが Xtra web'n'walk DayFlat プラン。
一日の使用料は 4.95 ユーロ。FONIC と同様、0:00-23:59 の間 4.95 ユーロぽっきりで利用可能。
一日の通信料が 1 GB を超えると通信速度が 384 kbps に制限され、2GB を超えた時点で 64 kbps に絞られる。制限はその日限り。

ただし T-mobileVoIP, P2P, テザリングなどの利用を許可していない(注意書きに明記されている)。
特に利用ポートやプロトコルに制限がかかっているわけではないので使おうと思えば使えてしまうが…

補足: チャージの方法

プリペイド SIM のチャージ方法はいくつかあって、それぞれ必要なものが異なる。

銀行 ATM でチャージ

必要なもの: ドイツ国内の銀行口座

もしドイツ内に銀行口座を持っていれば、ATM を使って振込をするように SIM への入金(チャージ)が可能。やったことはないけど多分他人の SIM にも入金できる。

オンラインでチャージ

必要なもの: クレジットカード

各キャリアのサイトでユーザ登録すると、オンラインでのチャージが可能になる。クレジットカードかデビットカードの登録が必要。

スーパーやガソリンスタンドでチャージ

必要なもの: SIMロックフリーの携帯電話

スーパーマーケットやガソリンスタンドには、各キャリアのロゴとチャージ金額、バーコードの書かれたカードが売られている。これを購入すると、レジにてチャージ用の電話番号と暗証番号が記載されたレシートを発行してくれる。プリペイドSIMを、通話可能な携帯電話に挿し、レシートに記載された番号に電話をかけて、指示(英語orドイツ語)に従って暗証番号を入力するとチャージが実行される。

EP121 と firefox4

ASUS のスレートPC である Eee Slate EP121 を購入。
ブラウザとして firefox 4 を入れてみたが、デフォルト設定のままだとタッチ操作が不便極まりない。あれこれググった結果、以下の設定で多少快適になった。


アドレスバーに about:config と入力し、設定値の一部を以下のように変更する。

browser.gesture.pinch.out.shift=cmd_fullZoomReset
browser.gesture.pinch.out=cmd_fullZoomEnlarge
browser.gesture.pinch.in.shift=cmd_fullZoomReset
browser.gesture.pinch.in=cmd_fullZoomReduce
browser.gesture.twist.threshold=15
browser.gesture.twist.right=Browser:ForwardOrForwardDuplicate
browser.gesture.twist.left=Browser:BackOrBackDuplicate

上記の設定で、
二本指を広げたり狭めたりするとズームイン・アウトされ、
二本指を左右に(15度以上)回転させるとページを戻ったり進んだりできるようになった。

本当は macbook みたく三本指のスワイプで戻る進むを実現したかったが、うまくいかなかったので回転操作で代用した。windows では三本指スワイプって使えないんだっけ?

Eee note EA800 と Eee Note Sync の対話をキャプチャしてみた (2)

日本アジアカップ優勝おめでとう!

EA800 の解析事情

ここで外からちまちま TCP でつっついている間に、海外では EA800 を開腹して中の Micro SD を取り出すなどして解析が進められているらしい。

EA800のfirmwareが解析中 その1 | OSAKANA TAROのメモ帳

そのうち独自ファームウェアまで作られそうな勢いだ。熱い。

ファイルのダウンロード

あいにく手元に工具や SD カードリーダーが無いので、とりあえず外からつっつくのを続けてみたいと思う。色々試してみた結果、EA800 内のファイルをダウンロードする方法は概ね理解できた。大まかな流れとしては、xmlfile コマンドを使って EA800 内にあるファイルのリストを取得し、リストに載っている各ファイルを downloadfile コマンドでダウンロード、とすれば良さそうだ。

python スクリプト

そこで python で簡単なダウンローダを作ってみた。

2010-02-05 追記: フォルダIDに対応するパスが判明したのでソース先頭のコメント部に追記した

  #!/usr/bin/env python
  #-*- coding: utf-8 -*-
  
  #
  # ea800_downloader.py
  #
  #   Usage: python ea800_downloader.py FOLDER_ID OUT_DIR
  #   
  #   FOLDER_ID: ダウンロードしたいフォルダの ID (下記参照)
  #
  #    | ID | folder path                        |
  #    |----+------------------------------------|
  #    |  0 | /eTablet/var/ebook                 |
  #    |  2 | /eTablet/var/photos                |
  #    |  4 | /eTablet/var/enotes                |
  #    |  8 | ?                                  |
  #    | 12 | /eTablet/etc/db                    |
  #    | 14 | /eTablet/etc/template              |
  #    | 31 | /eTablet                           |
  #    | 32 | /usr/local/eTablet/bin             |
  #    | 33 | /eTablet/etc                       |
  #    | 34 | /eTablet/var                       |
  #    | 35 | /usr/local/eTablet/lib             |
  #    | 37 | /usr/local/eTablet/bin/ebookreader |
  #
  #   OUT_DIR: ダウンロードしたフォルダの格納先ディレクトリ
  #            OUT_DIR ディレクトリの直下に files と xmlfiles の 2 つのディレクトリを作り、
  #            files ディレクトリにはダウンロードしたファイルを格納し、
  #            xmlfiles ディレクトリにはファイルのリスト(XML形式)を格納する。
  
  
  import sys
  import os
  import socket
  import xml.etree.ElementTree as etree
  import hashlib
  
  
  BUF_SIZE = 8192
  
  
  class Connection(object):
      """
       Simple wrapper class for Raw TCP connection.
       Refer to: http://docs.python.org/howto/sockets.html
  
      Attributes:
       - _sock
      """
      
      def __init__(self, address):
          self._sock = socket.socket()
          self._sock.connect(address)
  
      def send(self, op):
          totalsent = 0
          op = op.encode("utf-8")
          op_size = len(op)
          while totalsent < op_size:
              sent = self._sock.send(op)
              if sent == 0:
                  raise RuntimeError, \
                      "socket connection broken"
              totalsent = totalsent + sent
  
      def recv(self, size=1):
          read = 0
          buf = []
          while read < size:
              data = self._sock.recv(BUF_SIZE)
              if data == "":
                  raise RuntimeError, \
                      "socket connection broken"
              read = read + len(data)
              buf.append(data)
              
          return "".join(buf)
          
      def close(self):
          self._sock.close()
  
  
  class Downloader(object):
      """
       File downloader for Eee Note EA800.
  
      Attributes:
       - _cs
       - _ds
       - _files_folder
       - _xmlfiles_folder
      """
      
      def __init__(self, out_dir):
          # connect to command socket
          self._cs = Connection(("169.254.2.1", 20000))
          # recieve welcome message
          self._cs.recv()
          
          # connect to data socket
          self._ds = Connection(("169.254.2.1", 20001))
          # recieve welcome message
          self._cs.recv()
  
          self._files_folder = os.path.join(out_dir, "files")
          self._xmlfiles_folder = os.path.join(out_dir, "xmlfiles")
  
      def close(self):
          # close both connections
          self._ds.close()
          self._cs.close()
  
      def _md5sum(self, data):
          m = hashlib.md5()
          m.update(data)
          return m.hexdigest()
  
      def downloadfile(self, folder_id, path):
          """execute 'downloadfile' command"""
          
          # send "downloadfile" request
          op = "downloadfile\n%d\nPath=%s\n\\n::\\n" % (folder_id, path)
          self._cs.send(op)
  
          # receive "downloadfilereturnvalue" response
          res = self._cs.recv()
          res_list = res.split(b"\n")
  
          assert res_list[0] == "downloadfilereturnvalue", "not correct response"
  
          # extract status and file_size from "downloadfilereturnvalue" response
          status = res_list[1]
          file_size = int(res_list[2])
          md5 = res_list[3][4:]  # drop first 4 character "md5="
  
          assert file_size > 0, "file_size is not positive integer"
  
          # prepare file and directory to output data
          outfile_path = os.path.join(self._files_folder,
                                      "%02d" % folder_id,
                                      path.lstrip("/"))
          outfile_dir = os.path.dirname(outfile_path)
          try:
              os.makedirs(outfile_dir)
          except OSError:
              pass
  
          # output data to local directory
          data = self._ds.recv(file_size)
          assert self._md5sum(data) == md5, "MD5 check sum invalid"
          outfile = open(outfile_path, "wb")
          outfile.write(data)
          outfile.close()
  
          print "downloaded: %s" % path
  
      def listfile(self, folder_id):
          """execute 'listfile' command"""
  
          # send "listfile" request
          op = "listfile\n%d\n\\n::\\n" % folder_id
          self._cs.send(op)
  
          # receive "listfilereturnvalue" response
          res = self._cs.recv()
          res_list = res.split(b"\n")
  
          assert res_list[0] == "listfilereturnvalue"
  
          # extract status and file_size from "listfilereturnvalue" response
          status = res_list[1]
          file_size = res_list[2]
          if file_size.endswith("\\n::\\n"):
              file_size = file_size[:-6]
          file_size = int(file_size)
  
          assert file_size > 0
  
          # output data to console
          data = self._ds.recv(file_size)
          print data
  
      def xmlfile(self, folder_id):
          """execute 'xmlfile' command"""
  
          # send "xmlfile" request
          op = "xmlfile\n%d\n\\n::\\n" % folder_id
          self._cs.send(op)
  
          # receive "xmlfilereturnvalue" response
          res = self._cs.recv()
          res_list = res.split(b"\n")
  
          assert res_list[0] == "xmlfilereturnvalue"
  
          # extract status and file_size from "xmlfilereturnvalue" response
          status = res_list[1]
          file_size = res_list[2]
          file_size = int(file_size)
  
          assert file_size > 0
  
          # prepare file and directory to output data
          outfile_path = os.path.join(self._xmlfiles_folder, "%02d.xml" % folder_id)
          try:
              os.makedirs(self._xmlfiles_folder)
          except OSError:
              pass
  
          # output data to local directory
          outfile = open(outfile_path, "wb")
          data = self._ds.recv(file_size)
          outfile.write(data)
          outfile.close()
  
          # parse xmlfile
          tree = etree.parse(outfile_path)
  
          return tree
              
      def downloadfolder(self, folder_id):
          """recursive download files in a folder"""
  
          tree = self.xmlfile(folder_id)
          self._traversal_and_download(tree.getroot(), u"", folder_id)
  
      def _traversal_and_download(self, elm, folder_name, folder_id):
          if elm.tag == "file":
              path = folder_name + u"/" + elm.get("name")
              try:
                  self.downloadfile(folder_id, path)
              except AssertionError, e:
                  print e
          else:
              for child in elm.getchildren():
                  self._traversal_and_download(child, elm.get("name", u""), folder_id)
  
  
  if __name__ == "__main__":
      folder_id = int(sys.argv[1])
      out_dir = sys.argv[2]
      
      d = Downloader(out_dir)
      d.downloadfolder(folder_id)
      d.close()
  

利用例

例えばコマンドプロンプト

python ea800_downloader.py 4 C:\tmp\eeenote

とすれば、フォルダID = 4 のフォルダ (/eTablet/etc/enotes) 内のファイルが C:\tmp\eeenote\files\12 内にダウンロードされます。

ただし自分用に適当に作ったものですので、何が起こっても責任は取れません。ファイルを削除してしまうようなことは無いと思いますが、ご利用は自己責任でお願いします。

次はアップロード

データのアップロードは、uploadfile と noteimportbook でインポートするか、restoreadd か restoredel を使ってリストアするか、のどちらかでいけそう。あと内蔵の evernote クライアントがうまく動いてくれないので、PC 経由で evernote 連携もできるようにしたい。

org-mode はやっぱり便利

Emacs の org-mode はやはり便利だ。構造化文書を書くための機能が充実している。今回久しぶりに はてな でブログを書くにあたっては、下記サイトを参考に org-mode で下書きを作った。

org-modeからはてなダイアリーに投稿するelispを書いた
http://d.hatena.ne.jp/r_takaishi/20100211/1265888107


simple-hatena-mode を導入していなかったのと、個人的な都合もあって以下のように変更して使った。

(defvar org-export-hatena-notation-section '("^\\* \\([^\t\n\r\f]*\\)\n\\[.*$" "* \\1"))
(defvar org-export-hatena-notation-subsection '("^\\*\\* \\([^\t\n\r\f]*\\)$" "** \\1"))
(defvar org-export-hatena-notation-subsubsection '("^\\*\\*\\* \\([^\t\n\r\f]*\\)$" "*** \\1"))
(defvar org-export-hatena-notation-quote '("[ ]*#\\+BEGIN_QUOTE[ \t]*" ">>" "[ ]*#\\+END_QUOTE" "<<"))
(defvar org-export-hatena-notation-super-pre '("[ ]*#\\+BEGIN_EXAMPLE[ \t]*" ">||" "[ ]*#\\+END_EXAMPLE" "||<"))
(defvar org-export-hatena-notation-src '("[ ]*#\\+BEGIN_SRC[ \t]*\\([^\t\n\r\f]*\\)" ">|\\1|" "[ ]*#\\+END_SRC" "||<"))
(defvar org-export-hatena-temp-buffer-name "*Org Hatena Export*")

(defun org-export-hatena-section (notation)
  (goto-char (point-min))
  (while (re-search-forward (nth 0 notation) nil t)
    (replace-match (nth 1 notation))))

(defun org-export-hatena-begin-to-end (notation)
  (goto-char (point-min))
  (while (re-search-forward (nth 0 notation) nil t)
    (replace-match (nth 1 notation)))
  (goto-char (point-min))
  (while (re-search-forward (nth 2 notation) nil t)
    (replace-match (nth 3 notation))))

(defun org-export-hatena (beg end)
  (interactive "r")
  (let ((diary (buffer-substring beg end))
        (begin-to-end `(,org-export-hatena-notation-quote
                        ,org-export-hatena-notation-super-pre
                        ,org-export-hatena-notation-src))
        (section `(,org-export-hatena-notation-section
                   ,org-export-hatena-notation-subsection
                   ,org-export-hatena-notation-subsubsection))
        (buffer (get-buffer-create
                 (generate-new-buffer-name
                  org-export-hatena-temp-buffer-name))))
    (set-buffer buffer)
    (insert diary)
    (mapc (function org-export-hatena-begin-to-end) begin-to-end)
    (mapc (function org-export-hatena-section) section)
    (switch-to-buffer-other-window buffer)))

上記を .emacs に追記した。変換リージョンを選択して M-x org-export-hatena とすれば、はてな記法に変換されたものが *Org Hatena Export* というテンポラリバッファに格納される。これをコピペしてブラウザからはてなへ投稿。ほんとは simple-hatena-mode を導入して投稿まで自動化させるべきですね。

Eee note EA800 と Eee Note Sync の対話をキャプチャしてみた (1)

前置き

Eee Note EA800 とは

台湾から ASUS 社の Eee note EA800 (以後 EA800 と呼ぶ)を輸入して三週間ほど経過した。EA800 はモノクロ液晶を使った電子ノート端末である。付属のスタイラスペンを使って、こんな具合にノートを取ることができるすぐれものだ。




Eee Note Sync とは

Eee Note Sync は、EA800 に付属する Windows PC との同期用アプリケーションである。Eee Note Sync を使うことで、電子書籍や電子ノートなどのデータを PC と EA800 との間で送受信できる。しかしその機能は十分とは言い難い。特に電子ノートに関しては、できるのはページデータ(gif)とメタデータ(xml)を CAB で圧縮したもの(.nte という拡張子のファイルになる)を PC へエクスポートして、同 .nte ファイルを EA800 へインポートすることだけ。PC上での閲覧や、ページの差し替え並べ替え、ノートの分割合併など、個人的に欲しかった機能はことごとく無い。せめて .nte ファイルが zip だったらもう少し取り回しが楽なんだけど。

EA800 と直接データのやりとりはできないだろうか

Eee Note Sync に頼ることなく、EA800 との間で自由にノートの送受信がしたい!あわよくば Eee Note Sync に代わるクライアントを自作できないものかと思い、Eee Note Sync と EA800 間の通信プロトコルを調査してみた。幸い、EA800 と PC の間の通信は Ethernet over USB (RNDIS) で行われるようなので Wireshark を使ってキャプチャが可能だ。

EA800 を PC へ接続した際の通信内容

EA800 を PC へ接続した際に、PC (Eee Note Sync) と EA800 の間でどのような通信が行われるかを調べてみた。

EA800 を PC へ接続する

付属の USB ケーブルを使って EA800 を PC へ接続すると、以下のようにして Ethernet ネットワークが構築される。

  • EA800 と Windows XP マシン (以下 PC と呼ぶ) を USB 接続
  • EA800 の画面上に選択肢が表示されるので 「Eee Note Sync同歩模式」(Eee Note Sync との同期)を選択する
  • 初回接続時であれば、ここでドライバや Eee Note Sync が PC へインストールされる (※ドライバや Eee Note Sync のインストールは「安装Eee Note Sync以及…」を選んだときのみ実行されるのでこれは嘘でした)
  • PC が EA800 を RNDIS device として認識する
  • PC と EA800 が(仮想的な) Ethernet ネットワークで接続される
  • EA800 は IP アドレス 169.254.2.1 を名乗る。EA800 内では DHCP サーバが起動している
  • PC が EA800 内の DHCP サーバへ IP アドレスの払出しを要求。EA800 に IP アドレス 169.254.2.3 が与えられる
Eee Note Sync が EA800 を検知する

EA800 が PC に接続されたことを Eee Note Sync が検知する (おそらく Ping を使って)

コネクションの確立開始

Eee Note Sync は EA800 の接続を検知すると、「制御用コネクション」と「データ用コネクション」2つのコネクションの確立を試みる。後述するように、Eee Note Sync と EA800 との通信は「制御用」と「データ用」の2つのコネクションを使って行われる。FTP のようである。

制御用コネクションの確立

まず、制御用コネクション確立までの通信を以下に示す。なお、以降、[->] で PC (Eee Note Sync) から EA800 へのメッセージ送信を示し、[<-] で、EA800 から PC へのメッセージ送信を表すこととする。

  • [->] Eee Note Sync が適当な空きポート(ここでは TCP 1331 とする)から、EA800 の TCP 20000 ポートへ接続を試みる (普通の TCP 3-way Handshake)
  • [<-] EA800 の TCP 20000 ポートから、Eee Note Sync の TCP 1331 ポートへレスポンスが返る
commandconnectreturnvalue
welcome to connect the server. server version 1.0(2010-07-16 10:00:00) version 3.0(2010-7-28 18:00)\n::\n

改行文字には 0x0a (LF) が使われている。また、メッセージの終端を示すための記号には "\n::\n" が使われているようだ。ここで "\n" は文字通り 0x5c ("\") と 0x6e ("n") であって、改行文字 (0x0a) を表すものではないので注意。また、メッセージの末尾には 0x0a (LF) は付かない。

データ用コネクションの確立

続いてデータ用のコネクションが確立される。

  • [->] Eee Note Sync が適当な空きポート(ここでは TCP 1332 とする)から、EA800 の TCP 20001 ポートへ接続を試みる (普通の TCP 3-way Handshake)
  • [<-] EA800 の TCP 20001 ポートから、Eee Note Sync の TCP 1332 ポートへレスポンスが返る

dataconnectreturnvalue
welcome to connect the server data socket. server version 1.0(2010-07-16 10:00:00) version 3.0(2010-7-28 18:00)\n::\n
ログイン

2つのコネクション確立が終わると、制御用のコネクションを使って、Eee Note Sync から EA800 へログイン要求が送信される。

  • [->] Eee Note Sync が制御用コネクション上で EA800 へログイン要求メッセージを送信
login
1
\n::\n

"1" の意味は不明。

  • [<-] EA800 が Eee Note Sync へ応答メッセージを送信
loginreturnvalue
0
username=ASUS EeeNote
passwd=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
version=1.0.1.36
\n::\n

なんとこちらからパスワードを入力する前に、EA800 からパスワードが送られてきた。さすがに平文では無いようだが。

Eee Note Sync 上にパスワードダイアログが表示される

このタイミングで、Eee Note Sync 上にパスワードの入力を促すダイアログが表示される。正しいパスワードを入力すると、特に通信は発生せず次のステップへ進む。このことから察するに、パスワードの検証は EA800 内で行われるのではなく、EA800 からパスワードに関する情報を受けとった Eee Note Sync が代理で検証を行っているようだ。

ちなみに、EA800 のセキュリティ設定でパスワードによる保護を有効にしていない場合は、EA800 から送信される passwd は空となり、Eee Note Sync 上でのパスワードダイアログも表示されない。

システム情報の取得

ログインが成功すると、Eee Note Sync から EA800 に対してシステム情報の取得要求が送信される。

  • [->] Eee Note Sync が制御用コネクション上でシステム情報要求メッセージを送信
systeminfo
-1
\n::\n


ここも "-1" の意味は不明。

  • [<-] EA800 からシステム情報が送信される。
systeminforeturnvalue
Internal SD
181 3049 6%
External SD
N/A 
Device Name
ASUS EeeNote
Device UUID
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
fwboot
 V1.0.1
kernelrom
 V1.0.1.71 TW
0000
\n::\n


key, value, ..., key, value が改行で区切られているだけのようだ。ここで得られた情報が Eee Note Sync の "System Info" タブで表示されるのだろう。

ファイルリストの取得

続いて、EA800 内の DB ファイルリスト(ファイル一覧)が取得される。DB ファイルというのは、ノートとページの関係や作成日時などのメタデータを管理するファイルであり、実体は sqlite の .db ファイルである。このことから EA800 内のデータは sqlite で管理されていると考えられる。

  • [->] Eee Note Sync が制御用コネクション上で DB ファイルリストの取得要求を送信する。
listfile
12
\n::\n

"12" の意味は不明。 "12" はリストの取得対象となるフォルダを示すIDである。後述するように、EA800 内のフォルダには ID が振られていて、"12" は DB ファイルリストを格納するフォルダの ID となっている。イメージ的には shell 上で "ls 12" をしてファイルリストを取得しているのに近いかな。

  • [<-] EA800 から制御用コネクションを使って応答メッセージが返る。このメッセージに DB ファイルリスト自体は含まれない。
listfilereturnvalue
1
257\n::\n


"1" も "257" も何を指すものかは分からない。 "1" はおそらくステータスコード(成功を示す)"。"257" はファイルリストのサイズ。

  • [<-] ファイルリスト自体は *データ用* コネクションを使って EA800 から Eee Note Sync へ送信される。
4096
12
stickymemodata.db

798084\n10240
12
voicedata.db

798084\n285
12
DBVerionInfo.inf

798084\n368640
12
ebookdata.db

798084\n16384
12
phonebookdata.db

798084\n6144
12
tagdata.db

798084\n1046528
12
notedata.db

798084\n62464
12
photodata.db

798084\n

メッセージの中身(ファイルリスト)は、ファイルサイズ、謎の数字(12)、ファイル名、謎の数字(798084)、改行(0x0a)、区切り文字("\n")、の 6 つの値を 1 セットとして、ファイルの数だけ(ここでは 8 つ)のセットが結合されて送られる。

ここで取得されるリストには、以下の 8 つのファイルが掲載された。

  • stickymemodata.db
  • voicedata.db
  • DBVerionInfo.inf
  • ebookdata.db
  • phonebookdata.db
  • tagdata.db
  • notedata.db
  • photodata.db
DBファイルのダウンロード

Eee Note Sync はリストに載っている 8 つの DB ファイルそれぞれについて、ダウンロードを実行する。

  • [->] まず、Eee Note Sync は制御用コネクションを使って、stickymemodata.db のダウンロードを要求する。
downloadfile
12
Path=stickymemodata.db
\n::\n

"12" の意味は不明。 "12" は前述した通り DB フォルダの ID。

  • [<-] EA800 は制御用コネクションを使って応答する。
downloadfilereturnvalue
1
4096
md5=66c9a2dbf2041239700795efe2448c87
\n::\n

ファイル stickymemodata.db のサイズ(4096)とハッシュ値(66c9a...)が含まれている。"1" の意味は不明。 "1" はおそらく成功を示すステータスコード

  • [<-] EA800 はデータ用コネクションを使って、stickymemodata.db の内容を送信する。
    SQLite format 3... [stickymemodata.db ファイルの実体]
  • Eee Note Sync は受信したデータの MD5 ハッシュを計算し、制御用コネクションから受信済みの MD5 値と比較して、ファイルの正当性を確認する(多分)

  • 以下、同様の処理が、残りの 7 つのファイルそれぞれについて繰り返される。
  • voicedata.db のダウンロード
downloadfile
12
Path=voicedata.db
\n::\n

downloadfilereturnvalue
1
10240
md5=7ab5ea8cd345f06f7b44ccf177e970d9
\n::\n

  • DBVerionInfo.inf のダウンロード
downloadfile
12
Path=DBVerionInfo.inf
\n::\n
downloadfilereturnvalue
1
285
md5=b12b4341a96dd901bf5867568a09f5f1
\n::\n
  • ebookdata.db のダウンロード
downloadfile
12
Path=ebookdata.db
\n::\n
downloadfilereturnvalue
1
368640
md5=3199fa4a8e270a352805c3123a7a5aea
\n::\n
  • phonebookdata.db のダウンロード
downloadfile
12
Path=phonebookdata.db
\n::\n
downloadfilereturnvalue
1
16384
md5=488502c85dafbff66cea58dceebb2e76
\n::\n
  • tagdata.db のダウンロード
downloadfile
12
Path=tagdata.db
\n::\n
downloadfilereturnvalue
1
6144
md5=43a5f7851af89ffd1d637c02af4dbc5e
\n::\n
  • notedata.db のダウンロード
downloadfile
12
Path=notedata.db
\n::\n
downloadfilereturnvalue
1
1046528
md5=39202016cf5ee1b30f5ba0f04cf03edd
\n::\n
  • photodata.db のダウンロード
downloadfile
12
Path=photodata.db
\n::\n
downloadfilereturnvalue
1
62464
md5=07156c4dfef0bb9e9d51c1b56b20d4fa
\n::\n
ファイルダウンロード完了

8 つのファイル全てのダウンロードが終わると、Eee Note Sync から EA800 へダウンロード完了を示すメッセージが送信される。

  • [->] Eee Note Sync は制御用コネクションを使って、EA800 へダウンロード完了を示すメッセージを送信する。
downloaddbcompleted
0
13

0001-01-01 00:00:00
-1\n\n::\n

"0", "13", "-1" の意味は不明。日付も謎。

ここまでで、PC 内の C:\Documents and Settings\[ユーザ名]\My Documents\EeeNote\[UUID下12桁]\tempdatabase フォルダに、上記の 8 つのファイルがダウンロードされている。Eee Note Sync も内部で sqlite を使ってこれらのファイルに含まれるメタデータを参照するのだと思う。

とりあえずここまで

引き続き、電子ノートデータのダウンロードとアップロード部分を調べてみたい。