2013年5月13日月曜日

アクリルLEDパネルお試し

とあるイベントでアクリルLEDパネルの展示&作成をやってたのを見て、自分もやってみたくなったので挑戦してみました。

とりあえず雰囲気を掴むため、出来るだけあり合わせの道具で。
アクリル板だけ東急ハンズで買ってきました。
  • 削る道具: ダイソールーター
  • 光る仕組み: Arduino + フルカラーLED (OSTA5131A)
  • アクリル板: 透明 2mm厚
  • パネルの固定: 洗濯バサミ:)
パネルの作成はシンプルに
  1. 紙に絵を下書き
  2. アクリル板を紙に乗せて下絵をペンでトレース
  3. アクリル板に書いた線をなぞってルーターで軽く削る
削る作業だけは集中力が要求されますが、あとは適当。

完成したパネルをLEDの上に垂直に立てて置いただけですが、ちゃんとLEDパネルっぽくなりました。
LEDひとつだと光量が足りないかも?とか、ちゃんと絵が光るのかな?とか不安でしたが杞憂だったみたい。

お手軽でなおかつ面白い。
道具を揃えてパネル台も含めてきちんと作るのもいいかなー。

シンプルな絵ということで、Minecraftで人気のCreeperさんにご登場願いました:)

2013年1月14日月曜日

one-liner時報

終日一人で自宅に引き篭っていると、時間の感覚が無くなってアレな感じです。
最近、職場で時報が鳴るようになったら時間が分かりやすくてよかったので、自宅でも導入してみました。

その辺で公開されているソフトを使ってもいいのですが、折角なので自前で。

サウンドの再生自体は.NETでお手軽にできるので、PowerShellから一発。
(New-Object Media.SoundPlayer("C:\time-signal\signal.wav")).PlaySync()
これをタスクスケジューラで毎時59分56秒とかに実行するよう登録すると、一応時報になります。

ですがこのままではタスク実行時にコマンドプロンプトウィンドウが表示されてちょっと残念。

こういうときはWshShellオブジェクトからRun。
第2引数IntWindowStyleを0にすればウィンドウは表示されません。

こんな.vbsファイルをタスクスケジューラに登録すれば目的達成。
CreateObject("WScript.Shell").Run "powershell (New-Object Media.SoundPlayer(\""C:\time-signal\signal.wav\"")).PlaySync()",0
one-linerといいつつ .vbsファイルをタスクスケジューラから呼ぶという邪道な感じになってしまいました。

サウンドデータは好きなものを.WAV形式で調達すれば良いです。

2013年1月4日金曜日

P/Invokeの規定の呼び出し規約

自前のunmanagedなdllを、.NETなアプリケーションからP/Invokeで使おうとした際に  pInvokeStackImbalance で躓いたメモです。
環境は Visual Studio 2010 / .NET Framework 4.0

unmanaged DLL 側はC++でこんな感じ。
extern "C" {
 __declspec(dllexport) bool
 ReadValue(wchar_t* buffer, unsigned int bufferLength) {
  wcscpy_s(buffer, bufferLength, L"hogehoge");
  return true;
 }
}
それをこんなC#コードで呼び出すと、デバッグ実行時に pInvokeStackImbalance で怒られます。
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public static extern bool ReadValue(StringBuilder buffer, uint bufferLength);
MSDN: DllImportAttribute.CallingConvention フィールド によると
DllImportAttribute.CallingConvention の規定値は Winapi。
これはプラットフォーム標準の呼び出し規約を使用することを示す。Windows なら StdCall。
よくP/InvokeされるWin32APIの場合はすべてStdCallなので規定値でOKですね。

というわけで今回は次ように呼び出し規約をきちんと指定してあげないといけない。
[DllImport("foo.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern bool ReadValue(
    StringBuilder buffer, uint bufferLength);
ちなみにこのエラーを検出している MDA (Managed Debugging Assistant) は、.NET Framework 3.5 では規定値で無効になっているらしいです。

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


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

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