2012年8月26日日曜日

AndroidとPC(Winamp)の楽曲同期

AndroidとPC(Winamp)の楽曲プレイリスト同期に手こずった記録です。

前提

  • 自宅では楽曲ファイルの再生とプレイリスト管理をWindows PC上のWinampで行う
  • 楽曲ファイルはすべてUbuntu PCに保存されていて、Windows PCからはsamba経由でアクセス
  • 外出時にはAndroid (NEC N-01D) で音楽を聴く

やりたいこと

  • Winampの任意のプレイリストのファイルをAndroidで同期・聴けるようにしたい
  • 対象のプレイリストは時々更新するので出来る限り手軽に同期したい
  • USBケーブルでPCとAndroid機を繋ぐのはめんどくさいのでWi-Fiで済ませたい

まずはWinampの世界で


Android版Winampを使えばWi-Fi経由で任意のプレイリストを同期できるらしい! 素敵!

  • PCのWinampからAndroidデバイスが見つからない
    1. とりあえずUSBで繋いでみると当然見つかります
    2. 無線LAN接続のノートPCのWinampからもOK(・∀・
    3. 有線LANで接続されたPCから無線LAN接続のAndroid機は見つけられない……?
    4. ルーターが無線LANポートへのマルチキャストパケットの転送を制限していたので解除
    5. 同期できた!(・∀・
  • 文字化け
    • 同期できた楽曲をAndroidで見るといくつか文字化けが見つかる
    • Webで検索したら、ID3タグがUnicode系でないとAndroidでは文字化けするという情報多数
    • ID3Uniで全部Unicode化
    • だいぶ改善されました。が、まだ文字化けしている楽曲が残っています…
  • ファイル名が文字化け or 失われる
    • ファイル名が失われていた
    • Winampは転送時にファイル名の変更をしている?
    • ダメなファイル名の特徴を調べればわかるかもしれませんがここで気力が尽きました
  • Wi-Fiでの転送が不安定
    • 同期中に途中で通信が切れる
    • 症状は、Android側のWinampがクラッシュするか、Wi-Fi接続が切れる
    • どちらが発生するかは不定

…Winampの世界で完結させるのは諦めることにしました。


次の戦略 - rsync backup for android で同期

  1. WinampからプレイリストをM3U形式で出力
    • Winampのプレイリストビューで保存(Ctrl+S)するだけです
  2. Ubuntu PCでM3Uを解釈して必要な楽曲ファイルのシンボリックリンクを同期用フォルダに作る
    • 同期用フォルダには、楽曲フォルダのツリーをそのままコピーして、必要なファイルのみシンボリックリンクを作ります
    • cronで固定のパスのm3uを読み、実行
  3. rsync backup for android で同期
    • シンボリックリンクを解決してコピーする設定

M3Uを解釈して同期用フォルダを構築
  • Winampが吐き出すM3Uは次の通りということにしています。
    • Shift-JIS (CP932)
    • '#' から始まる行以外がファイルのリスト
  • 波ダッシュ問題
  • ファイルパスと正規表現
    • '/' で囲むとわけわかんなくなります。
    • '|' がいいと思います。
#!/bin/bash

if [ ! -f "$1" ]; then
  echo "no playlist is specified."
  exit
fi

WINDIRARR=(S:/sound/ T:/sound2/)
SRCDIRARR=(/home/library/sound1/ /home/library/sound2/)
OUTDIRARR=(/home/users/android-rsync/data/1/ /home/users/android-rsync/data/2/)
TMPFILE=$1.tmp

for (( i = 0; i < ${#OUTDIRARR[*]}; i++ )); do
  find ${OUTDIRARR[$i]} -type l -exec rm {} \;
done

sed -e 's|\\|\/|g' $1 > $TMPFILE

while read LINE; do
  LINE=`echo $LINE | nkf --ic=CP932 --oc=UTF-8 | sed 's|\\s*$||g'`
  
  if test 0 -ne ${#LINE} && ! echo $LINE | grep -sq '^#'; then
    RELPATH=$LINE
    for (( i = 0; i < ${#WINDIRARR[*]}; i++ )); do
      WINDIR=${WINDIRARR[$i]}
      SRCDIR=${SRCDIRARR[$i]}
      OUTDIR=${OUTDIRARR[$i]}
      RELPATH=`echo "$LINE" | perl -pe "s|$WINDIR||i"`
      if [ "$LINE" != "$RELPATH" ]; then break; fi
    done
    
    if [ "$LINE" != "$RELPATH" ]; then
      RELDIR=${RELPATH%/*}
      if [ ! -d "$OUTDIR$RELDIR" ]; then mkdir -p "$OUTDIR$RELDIR"; fi
      ln -s "$SRCDIR$RELPATH" "$OUTDIR$RELPATH"
    else
      echo "ERROR: $RELPATH"
    fi
  fi
done < $TMPFILE

rsync backup for android
  • Ubuntu側にコレ専用のユーザを作り、鍵を登録します
    •  鍵は rsync backup for android で作ったものを使います
  • Additional rsync options
    • 同期方向は Ubuntu→Android への一方通行になるように設定
    • シンボリックリンクを解決してコピー
      • -l    copy symlinks as symlinks    ←初期状態ではこちら
      • -L    transform symlink into referent file/dir    ←これに変更
Rescan Media
  • Androidはメディアストレージサービスというプロセスでプレイリストを管理しています。
  • 通常はOS起動時やストレージのマウント時にスキャンしてプレイリストを構築するらしいです。
  • なので rsync で引いてきただけでは楽曲はプレイリストに入りません。
  • メディアストレージサービスのスキャンを手軽に手動実行できるのがこのアプリ。
  • 初回起動時は "Auto quit?" が off になっています。チェックをONにしたあと手動でアプリケーションを明示的に終了させないと、2度目以降rescanされていないようでした。
  • アプリを起動したタイミングでrescanが実行されるのかな?

他の戦略 - FUSE


シンボリックリンクではなく Filesystem in Userspace(FUSE) がいいのでは? というアドバイスも貰いました。FUSEというものは知らなかったのですが面白そうです。

2012年8月22日水曜日

エアコンのWeb UIを作った

エアコンのリモコンとか作った そのご。

Arduino IDE のシリアルモニタからしか操作出来ないのはつまんないのでUIを作ったの巻。
  1. Arduinoに赤外線LEDと温度センサーを繋げて
  2. Ubuntuに接続して
  3. PHPでWebインターフェイスを作った
という話です。

ハードウェア


  • Arduino UNO R3
  • 赤外線LED : OSI5LA5113A
  • 温度センサー : LM61CIZ
  • UbuntuがインストールされたPC

前回まではWindowsで作業していたのですが、WebサーバはUbuntuで動いているため今回はそちらで。
温度センサーの LM61CIZ は、-30℃ ~ +100℃ を 300mV ~ 1600mV で出力します。

スケッチ


温度を常時出力しながらシリアルの入力 (リモコン信号送信指示) を待ちます。
void loop() {
  digitalWrite(PIN_IR_OUT, LOW);
 
  if (Serial.available()) {
    int c = Serial.read();
    for (int i = 0; i < signalMap[i].chr != '\0'; i++) {
      if (signalMap[i].chr == c) {
        sendSignal(signalMap[i].signal);
        Serial.println("send");
        break;
      }
    }
  } else {
    // analog input value
    float aIn = analogRead(PIN_ANALOG_IN);
   
    // input voltage (mV)
    float vIn = (5000 * aIn) / 1024;
   
    // temperature (C)
    float t = (vIn - 600) / 10;
   
    Serial.println(t);
  }
 
  delay(1000);
}

ArduinoをUbuntuに接続


ドライバとかは不要です。USBケーブルを挿すだけなのでお手軽。
screen /dev/ttyACM0 9600
でシリアル通信を確認。

PHPでWebインターフェイスを作る


はじめに作ったものはこんな感じでした。

やっつけ感が溢れる

機能は、温度を表示し、エアコン ON/OFF の指示を受け付けるだけです。

シリアル通信には php-serial という ライブラリを使いました。
通常 Apache のユーザは /dev/ttyACM0 などへはアクセス出来ないと思います。
サーバの運用ポリシーに合わせて対処を考える話ですね。

さて、これでやってみたかったことは一通り出来ました。
帰宅3分前にスマートフォンで室温を見て「うわー;´д`) 」って思ったらエアコンをつけておく、なんてことも可能です!……が、スマートフォンで見てみると悲惨なことに (左上に小さく表示) なったので jQuery Mobile を使ってみました。
いい感じ

2012年8月20日月曜日

Arduinoから赤外線信号を送信

エアコンのリモコンとか作った そのよん。

ひとつ前のエントリで読み取ったリモコンの信号を、自前で赤外線LEDをコントロールして発信してみます。
受信のときとは異なり、キャリア周波数の変調 (38kHzで点滅させる処理) を自分でプログラムする必要があります。
…受信はセンサーが高機能なお陰で楽できました。

赤外線LEDには OSI5LA5113A を使いました。

送信ビットデータの生成


受信のときの逆処理を行うだけなので詳細は割愛。

信号データはArduino内に持つことにします。
#define SIGNAL_LEN 7

typedef struct _SignalMap {
  char chr;
  byte signal[SIGNAL_LEN];
} SignalMap;

SignalMap signalMap[] = {
  // 電源ON 冷房 風量自動 28℃
  {'C', { 0x28, 0x61, 0x3d, 0x10, 0xef, 0xbc, 0x43 } },
  // 電源OFF 冷房 風量自動 28℃
  {'c', { 0x28, 0x61, 0x3d, 0x10, 0xef, 0xac, 0x53 } },
  // terminator
  {'\0', { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } }
};
シリアルへ送られてきた文字に反応して、対応する信号を発信する仕様です。

キャリア周波数の変調


38kHzで点滅ということは、1周期 26.32マイクロ秒。
そんな厳密ではないと思うのですが、ON/OFFが半々だとしてもそれぞれ13マイクロ秒程度ということです。
unsigned long endTime = micros() + highTimeSpan;
while (endTime > micros()) {
  digitalWrite(PIN_IR_OUT, HIGH);
  delayMicroseconds(9);  // ※a
  digitalWrite(PIN_IR_OUT, LOW);
  delayMicroseconds(6);  // ※b
}
delayMicroseconds(lowTimeSpan);
待ち時間 (delayMicroseconds) は手探り。
各命令がどれぐらいの時間を使うのか調べるのが正しい方法かもしれません。。。
とりあえずこれでうちのエアコンは受け付けてくれました。

なお事前に友人が作った赤外線送信のスケッチを見せてもらっていたのですが、それと同じ時間(上記の a=9, b=7)ではうちのエアコンは反応しませんでした。ここらへん機器によって微妙に差がありそうです。

OSI5LA5113Aの指向性


かなり狭い角度にしか信号が届きません。
データシートを見ると ±3°ぐらい。体感でも 10°ずらすとダメな感じ。
これに気付かずハマったorz

テスト


このままでは実際に発信している信号が元のリモコンと同じものなのか、確認する手段がありません。このまま手探りのテストははっきり言って現実的ではありません。ここまでそれに気付かなかった私って。。。
どうしようもないのでもう一つ Arduino を買って、前回のエントリと同じ受信モジュールを組んでしまいました。

完成


シリアルモニタから 'C' を入力したらエアコンがぴぴっと動き出す!
エアコン斜め横に配置。奥側はつけっぱだった温度センサー

2012年8月19日日曜日

Arduinoで赤外線信号を読取

エアコンのリモコンとか作った そのさん。

室温がわかるようになってもそこから発展しないのはつまんないし、室温をコントロール出来れば面白いなと思ったわけです。
つまり、エアコン操作。

友人がArduinoと赤外線LEDでリモコンを自作しているのを見ていたので、その組み合わせでどうにかなりそうなことは分かっていました。
しかし肝心の送信する信号を知る方法がありません。
PC-OP-RS1 (パソコン用学習リモコン) みたいなので調べればいいじゃないかという意見もありますが、安いものではないしどうせなら自作してみたほうが面白いかな、と。

まず赤外線リモコンの信号とはどんなものなのか検索。
分かりやすく解説してくれているサイトがあったので、この知識を前提に進めます。

ハードウェア

  • Arduino UNO R3
  • PL-IRM-2161-C438
    • 安価で入手可能なセンサーですが、これがなかなか高機能。赤外線リモコン特有の38kHzで点滅する赤外線の有無を出力してくれます。この手の工作では定番ぽい?

赤外線センサーの Vout を、Arduinoのデジタルピンに繋ぎます。残りの電源とGNDも適当に。

ソフトウェア


自宅のエアコンのリモコン信号がどのフォーマットなのかを知るため、赤外線信号のON/OFF時間を取得します。

sketchはGistで公開
Raw IR-signal receiver for Arduino with PL-IRM-2161-C438 — Gist

結果はちょっと見づらいですが、信号のON/OFFが切り替わったタイミングがマイクロ秒単位で取得できます。
リーダー信号の時間から、おそらく家製協フォーマットだろうと推測。

フォーマットが分かれば16進表記にするのは難しくないですね。

IR-signal receiver for Arduino with PL-IRM-2161-C438 — Gist


というわけでリモコン信号の値を受け取ることが出来ました。
ビット順序が昇順か降順かだとかは機器ごとに変わると思いますが、大概の機器はこんな感じでいけそうです。

本当にこれで正しいのかどうかは実際に信号送信してみなければ分からないのですが、それはまた後ほど。

キーボード G510 のLCDアプレット作成

エアコンのリモコンとか作った そのに。

Arduinoが検知した温度をどこに表示させようかと考えて思い立ったのが、Logicool Gaming Keyboard G510 のLCD。

軽く調べてみて分かったことは
  • Logitech/Logicool の G-Series と呼ばれる機器の幾つかはLCD(と幾つかのボタンのセット)を搭載しており、このLCDはWindows上で動くAppletと呼ばれるソフトウェアから制御する。
  • Applet は G-Series のユーティリティソフトウェアから制御する。
  • G-Series のユーティリティソフトウェアのインストール先にある LCDSDK フォルダ内に、AppletのSDKがある

なんだかいい感じです。

Applet開発はどうやるのか


取り敢えずSDKを覗いてみると、プログラミングガイドライン、APIリファレンス、サンプルコードなど情報は揃っていました。開発言語はC/C++, ビルド環境は Visual Studio でいいみたい。

SDKは大きく二つに分かれています。
  • LCDSDK
    • テキスト表示、ビットマップ表示、プログレスバーなど、便利なライブラリが用意されたセット
  • LCDSDKCore
    • LCDとの接続、LCD横のボタン操作イベント、LCDのドット単位操作など、必要最小限のAPIセット

ぱっと見で単純そうだったので LCDSDKCore の方を使うことにしました。

SDKに含まれるサンプル ColorAndMono を参考にします。
このサンプルは、接続されているLCDがカラーかモノクロかを判断して、ランダムなノイズのような画像をLCDに表示するだけのものですが、LCDの接続/切断、LCD横についているボタンのイベント、その他各種イベントについても一通り書かれているので参考にするにはうってつけ。
ソースを見れば分かりますが、WndProcに処理を実装していくタイプの標準的なWin32アプリケーションに、LCD用の初期化処理とイベント処理が追加されたようなものです。

まず、私が持っている G-Series 機器は G510 だけなので、カラー判別処理とカラーLCD用の処理はバッサリ無視。
G510のLCDは、横160 × 縦43 ドット、単色です。


LCDの表示更新処理には lgLcdBitmap160x43x1 というスゴい名前の構造体を使います。
メンバは2つ。
  • hdr … ビットマップのフォーマットを指定。
  • pixels … 表示させる画像データ。unsigned char の配列。
pixels の各Byteがドットを表し、先頭ビットが 1 だと対応するLCDのドットが明るくなる、といった感じ。lgLcdUpdateBitmap 関数に hdr のアドレスを渡すと、ヘッダの後ろに続く pixels が読まれてLCDに反映されるみたいです。

ここまでわかればAppletを作る最低限の情報は揃っています。

製作

こんなアプリケーションを作ることにしました。 

  • G-Seriesのユーティリティソフト起動時に自動起動
  • 1秒ごとにLCDを更新する (WM_TIMER 使用)
  • USBシリアルポートへArduinoが送り続けてくる温度情報を読み取る
  • 温度情報をLCDに表示
  • 日時も表示
  • 余ったスペースに何か絵を表示できればいいな


LCDのための定型処理は ColorAndMono を参考に必要な処理を実装。
ColorAndMonoサンプルのソースを読めば分かることばかりなのでここでは割愛。

絵はモノクロ2値ビットマップを読み込みます。

文字描画関数なんてものは用意されていないのでそこは何とかする必要があります。
素直に LCDSDK を使うか、別途DIBに描いてコピーするか、自前でビットマップフォントを作るか、まぁどれでもいけると思います。
今回は絵の表示処理が使い回せるのでビットマップフォントを作りました。
日時と温度を表示するだけなので数字+αがあればいいのです。


ちょっとハマったところ その1

Windowsでは fopen("COM3", "r") とかやってもシリアルポートからの入力を読めないみたいです。
この制限を書いた正式なドキュメントは見つからなかったのだけれど、とりあえずダメなものはダメなのだと諦めて Win32 API から読むことにしました。

HANDLE hCom = CreateFile(
    port,            // シリアルポート。"COM3" みたいな文字列
    GENERIC_READ | GENERIC_WRITE,
    0,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL
);

if (hCom == INVALID_HANDLE_VALUE) {
    // このへんでエラー処理
    return -1;
}

DCB dcb;
GetCommState(hCom, &dcb);

dcb.BaudRate = 9600;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;

SetCommState(hCom, &dcb);

// あとは ReadFile でごにょごにょ。

ちょっとハマったところ その2

2値ビットマップのピクセルデータは 1bit = 1dot なのですが、横ライン1行が 32bit 境界で揃っています。

こちらの説明が分かりやすかったです。
BMP ファイルフォーマット

MSDNには 16bit 境界みたいに書いてあるけど違うと思うの。。。

こんなの出来ました

LCDでネギを振るミクさん

LCDでネギを振るミクさん

Arduinoで温度検知

エアコンのリモコンとか作った そのいち。

まずはこちらの記事を参考にさせて頂きました。
Arduinoで温度計(LM35DZ) - 思い出はいつまでも

使ったもの

  • Arduino UNO R3
  • LM35DZ
    • マルツパーツ館で購入したのですが、センサーではなくオペアンプのエリアに置いてあって探し回ったり。
  • 作業PCは Windows 7 (x64) 

やったこと


まずハード側。

LM35の3つのピンがそれぞれ何なのか調べます。データシートはこういうパーツを販売している店がWEBで公開してくれていて、すぐに見つかりました。

+Vs を Arduino の 5V へ
Vout を Arduino の A0 へ
GND を Arduino の GND へ
それぞれ繋ぎます。

ハードは以上。 これだけ。

次にArduinoのソフトウェア一式をダウンロード。IDEやドライバが含まれます。
 Arduino - Software

バージョンは 1.0.1 でした。

作業PCにドライバをインストール。
  1. USBケーブルを接続するとドライバのインストールに失敗と表示される
    • デバイス マネージャで見ると「不明なデバイス」
  2. 手動でドライバをインストールすることにする
    • 「不明なデバイス」のドライバの更新
    • "drivers\Arduino UNO REV3.inf" を選択
  3. デバイス マネージャの "ポート(COMとLPT)" に "Arduino UNO R3 (COM3)" みたいなのが追加されました

Arduino IDE の設定は次の2点。
  • マイコンボードの選択で Arduino UNO
  • シリアルポートを選択


スケッチはとりあえず上記の参考記事からそのまんまコピペ。

IDEの「マイコンボードに書き込む」をクリックすると、コンパイルしてArduinoに書き込まれ、Arduino付属のシリアルモニタで温度の表示ができました。



Arduino お手軽すぎる!
知識無しでもこんなことが数千円で出来てしまうなんていい時代です。

2012年8月18日土曜日

エアコンのリモコンとか作った

元々はPC周りの温度を常時確認したいと思って弄り始めたArduino。
モバイル環境から室温確認&エアコンを操作が出来るようになったので一区切り。
しかし節電が叫ばれるこのご時世、全力で活躍するような機会は少なそうです。

いろいろと手を出したので途中でやったことなどを少しずつ書き起こしてみます。

やったことは大まかにいうと
電子工作経験ゼロ、電気回路の知識は中学校レベルの人間がやったことなのでおかしなところが多いかもしれません。

Blogger

日々やったことを纏めて書き残したいときに書いていきます。
月一回ぐらいのゆるい継続を目指して。