ゆっくり技術ノート!

いつかきっとできるだろうよ

AppleScriptでiTunesのテレビ番組を連続再生する

iTunes のテレビ番組を連続再生するには再生したいテレビ番組をプレイリストに登録してリピート再生するという方法がある。
しかし、毎度毎度プレイリストを作るのは大変なので AppleScript を使って自動で次のエピソードに移動するようにしてみる

仕組み

  1. Scriptを起動したら TV Show が再生されていないか20秒毎にチェックする
  2. 再生されていたらそのEpisode の合計時間と現在の再生位置から残りの再生時間を3分ごとに調べる
  3. 残りの再生時間が3分以下になったら1分ごとにチェック、1分以下になったら1秒ごとにチェック
  4. 残りの再生時間が1秒以下になったら次のEpisode に移動する
  5. 2にもどる

今回使った命令いろいろ

次のEpisode に移動

  • これは他の種類のメディア(音楽とか)にも使えてキーボードについている送りキー[▶︎▶︎]と同じ機能を持つ
tell application "iTunes"
    next track
end tell

再生中のトラックの合計時間を調べる

  • 1:20:30(h:m:s) のように出力される
tell application "iTunes"
    set timeSize to time of current track as text
end tell

再生中のトラックの再生済みの時間を調べる

  • 秒で出力される
  • 再生されていない時は missing value となる
tell application "iTunes"
    set nowPositionSec to player position
end tell

コードは Gist にある

This applescript will automatically play the episode of the next iTunes TV show. · GitHub

  • 右にあるDownload ZIPからダウンロードする
  • Script Editor で開いて.app形式で保存する
  • Spotlight から起動したり、iTunes Scriptフォルダに入れて iTunes からも起動できる
  • iTunes Scriptフォルダは~/Library/iTunes/Scripts

参考ページ

  1. 鳶嶋工房 / AppleScript / Tips / スクリプトの中断
  2. iTunes for Mac まとめ - AppleScript
    • iTunes関連の命令について
  3. life log: AppleScript 最速基本文法マスター
  4. iTunes Scripts の使い方
    • iTunes Scriptフォルダの場所

ありがとうございます

Raspberry Pi で家電を操作する1

目次

今回は長くなるので複数回に分ける
  1. Raspberry Pi で家電を操作する1 (MacにWebサーバを設置して照明を操作する)
  2. Raspberry Pi で家電を操作する2 (Raspberry PiにWebサーバを設置して照明を操作する)[未投稿]

やりたいこと

  1. 今回はブラウザから部屋の照明のスイッチをサーボモータで動かしon/offさせたい
  2. 最初は Mac で作ってからRaspberry Pi に移す

構成と環境

Raspberry PiArduino の通信はシリアル通信を使用した
Raspberry Pi から直接サーボを操れるようだが、慣れているので Arduino を使用した。そのうち変更するかもしれない Gyazo

  1. OS X 10.10.2
  2. Arduino UNO
  3. ruby 2.0.0p598
  4. serialport (1.3.1)
  5. sinatra (1.4.5)

作業の軌跡

  1. Arduino でシリアルを受信してサーボを動かす
  2. Sinatra からシリアル信号を送る
    1. 時間差をつけてシリアル信号を送る
  3. Sinatra にタイマー機能をつける
  4. Raspberry Pi に移す
    1. IPを固定する
    2. ホスト名でリモート接続できるようにする
    3. 作成したSinatraアプリケーションをデーモンにする

Arduino でシリアルを受信してサーボを動かす

シリアル信号に応じてサーボを右もしくは左に少し回して照明などのスイッチをon/offする
スケッチは以下のような感じになった

  1. loop で常時シリアル信号を監視する
  2. 信号は数字になっていてそれぞれに対応したアクションを探す
  3. アクションが決定したら実行する
  4. サーボをデフォルトの位置に戻す

codeはgistにある
Arduino のコード

動作はこんな感じになった Gyazo

Sinatra からシリアル信号を送る

Sinatra でブラウザにon/offボタンを表示させ押せれたらシリアル信号を送るとようにしたい
Sinatra とは Ruby on Rails のような Ruby で Webサーバを立てるためのドメイン特化言語(DSL)です

Sinatra でシリアル通信を扱うためにはgemのserialportが必要なのでinstallする
gem install serialport

コードは以下のようになった

  1. 初期動作
    1. textで保存したシリアルポートの名前(環境によって異なる)を読み込む
    2. シリアルのインスタンスを作成
  2. /がリクエストされたらon/offボタンのあるhtmlを送る
  3. スタンドライトのonのボタン/standOnが押されたらシリアルを送信し/にリダイレクトする

見た目も良い感じになったと思う Gyazo

嵌ったところ1 逆引きDNS

localhost:4567でアクセルするとロードが100ms程度で終わるのに対しIPアドレスを直接指定すると1分以上かかってしまった
どうやらSinatra側がアクセスしてきたクライアントのIPアドレスをホスト名に変換するときに hosts が設定されていないとタイムアウトするまで時間がかかってしまうらしい (この解釈であってるかわからない)
DNS逆引きを止めるコードがネット上にあったので使わせてもらうことにした
sinatraでDNS逆引きを止める | TechRacho

嵌ったところ2 inline-block の隙間

今回 inline-block を使ったがリストタグ<ul>内に改行があると改行が半角スペースとなって表示されるらしい
その影響で各ボタンの間に隙間ができてしまって結果的にレイアウトが崩れてしまった

CSS で inline-block の文字間を 0em にして inline-block中 のulの文字間を xx-large とすることで解決した
一部を抜粋

ul{
    list-style:none;
    font-size: 0em;           /* inline-block 隙間対応*/
}

li p, .on, .off, .timer{
    padding: 30px 0px;
    margin: 0px;

    display: inline-block;
    font-size: xx-large;  /* inline-block 隙間対応*/

    color: #FFFFFF;

}

rerun

余談だが今回始めてrerunを使ってみた
rerun は関連するコードが更新されるとsinatraを再起動してくれる sinatra-contribだとイマイチ動こかないことがあったが rerun はいい感じに再起動してくれる
gem install rerunしてrerun 'ruby app.rb'する
Sinatra: Frequently Asked Questions

タイマー機能を作る

今回は簡単に指定時間 sleep させたあと任意のアクションのアドレス(例えばlocalhost:4567/standOn)にアクセスするようにする
コードはこんな感じになった

require "open-uri"

Hour = 3600

#Create mode
print("Select Mode\n1. Stand Light On\n2. Stand Light Off\n3. Room Light On\n4. Room Light Off\n--> ")
mode = gets.to_i

case mode
when 1 then mode = "standOn"
when 2 then mode = "standOff"
when 3 then mode = "roomOn"
when 4 then mode = "roomOff"
else
    print("Mode error\n")
    return 0
end
print("Select #{mode}\n\n")

#Create timelimit
print("Select Time(hour)\n--> ")
limit = gets.to_i
print("Select #{limit}hour\n\n")

#Wait
print("Waiting #{limit}hour...")
sleep(Hour * limit)
print("\n\n")

#Acsess
open("http://localhost:4567/#{mode}").read
print("Done\n")

Sinatra にタイマー機能をつける

上記のタイマだとターミナルからしか使えないのですこし面倒 しかもターミナルウインドウを閉じてしまうとプロセスも閉じてしまうので困ってしまう なので Sinatra と同じプロセスで動かして尚且つブラウザからも操作できるようにしてみる

コードはこんな感じになった

  1. '/'でタイマのボタンT(/timer/standOn)を押す
  2. 2つめのパスに記したアクション(例えばstandOn)を引数に/timerが開く
  3. /timer/standOnで時間を入力して時間とアクションをpostで送る
  4. app.rbがpostを受けて別スレッドでsleepさせておき本スレッドではすぐに/にリダイレクトする
  5. 別スレッドが時間になったら任意のアクションのアドレス(例えばlocalhost:4567/standOn) にアクセスする

このように直接時刻(6:30)を指定する方法と数時間後(4時間30分後)を指定する方法を用意した Gyazo

雑感

  1. タイマの時間指定をするフォームでinput type="tell"にするとiPhone等で数字入力するときにテンキーキーボードが出てくるのでカッコイイ
  2. 将来的にはタイマボタンT/timer/standOnに画面遷移するのではなくon/offボタンの長押しで画面遷移したい
  3. iosのブラウザでinput type="time"を表示するとinputの親要素ごと上下に数ピクセルずれてしまったのでなんとかしたい
  4. MaciPhone でアクセスできるとお布団から出なくても部屋の明かりをon,offできて寝る時に便利
  5. 照明のタイマー機能でやすやす起きれると思っていたら、布団に頭まで潜っているので意味がなかった(特に冬は)

コードはGithubとgistにある

  1. Sinatra のコード Coro365/home-control
  2. Arduino のコード It receives the serial signals to control the switches.

次回予告

次回は今回のコードを Raspberry Pi に移します
Raspberry Pi で家電を操作する2 (Raspberry PiにWebサーバを設置して照明を操作する)[未投稿]です

参考ページ

  1. Sinatraについての質問です Sinatraのサーバにlocalhost:4567で… - 人力検索はてな
    • ありがとうございました
  2. sinatraでDNS逆引きを止める | TechRacho
    • DNS逆引きキャンセル
  3. Ruby - rerunでSinatraのファイル変更時にリロード - Qiita
    • rerunについて
  4. inline-blockを並べた場合に発生する「隙間」を消去するCSS » INSPIRE TECH
    • inline-blockの隙間

ありがとうございます

Ruby で Pushbullet を使う

PushbulletAPI を使って各デバイスに通知を送ってみる

Pushbullet

Pushbullet はブラウザ又は Mac/Windows/iOS/Android のアプリからアクセスできて特定のデバイスにテキストや地図、ファイル等を送信できる。送信を受けたデバイスには通知センターに表示できる
今回はこれを使って Mac から任意のタイミングで iPhone にpush通知 を送るようにしたい

Gem インストール

Ruby で扱うための gem が公開されていたのでこれをインストール

gem install washbullet

自分のアクセストークンを Pushbullet の Setting から入手

32桁の文字列がアクセストークンになっている

アクセストークンを使って API から自分のid とデバイスのid を調べる

ターミナルで以下のコマンドを実行する

自分のid

curl -u Your_Token: https://api.pushbullet.com/v2/users/me

バイスのid

curl -u Your_Token: https://api.pushbullet.com/v2/devices

JSON が帰ってる。 "iden":に続く文字列がidになる

取得したidで通知を送る

require 'washbullet'

client = Washbullet::Client.new('Your_Token')

client.push_note('Your_Device_Id', 'title', 'messege')

これを実行するとで送信された

f:id:Coro:20150201222227p:plain

id を Ruby から調べる

手動でidを読むのは面倒なのでアクセストークンを入力すると自動で自分のid と Pushbullet に登録されたデバイスのid を返すようにしてみた

require "json"

def getUserId (token)

    api_response = `curl -s -u #{token}: https://api.pushbullet.com/v2/users/me`
    userId = JSON.parse(api_response)

    return userId["iden"]
end

def getDeviceId (token)

    api_response = `curl -s -u #{token}: https://api.pushbullet.com/v2/devices`
    deviceInfo = JSON.parse(api_response)

    deviceIds = Array.new
    deviceInfo["devices"].size.times do |i|
        deviceId = deviceInfo["devices"][i]["iden"]
        deviceName = deviceInfo["devices"][i]["nickname"]
        deviceModel = deviceInfo["devices"][i]["model"]

        deviceIds.push([deviceId, deviceName, deviceModel])
    end

    return deviceIds
end


token = "Your_Token"

p getUserId (token)
p getDeviceId (token)

実行すると自分のidとデバイスid,デバイスのニックネーム,デバイスモデルが表示される

参考ページ

  1. Pushbullet API Documentation
    • 公式ドキュメント
  2. hrysd/washbullet
    • 今回使ったgem、お世話になります
  3. Ruby 1.9 以降で JSON を扱う - 見上げれば、空

ありがとうございます。

TerminalでAppleScriptを実行した時のlogコマンド挙動

追記 この情報は古くなりました。

  • OS X 10.10 Yosimete ではtell application内のlogも表示されるようです

Ruby 経由でAppleScriptを実行する
test.rb

result = `osascript test.scpt`

test.scpt

on run {}
    funcA()
end run

on funcA()
    log "in funcA"
    funcB()
    funcC()
end funcA

on funcB()
    log "in funcB"
end funcB

on funcC()
    log "in funcC"
    tell application "Finder"
        log "in tell application Finder"
        
        tell application "Terminal"
            log "in tell application Terminal"
        end tell
    end tell
end funcC

すると
結果

$ ruby test.rb
in funcA
in funcB
in funcC

他のハンドラ内のlogコマンドを表示されるのに、tell application内では表示されなかった
そこで表示用のハンドラを作りtell application内から呼ぶことで解決した
test.rb

on run {}
    funcA()
end run

on funcA()
    log "in funcA"
    funcB()
    funcC()
end funcA

on funcB()
    log "in funcB"
end funcB

on funcC()
    log "in funcC"
    tell application "Finder"
        display("in tell application Finder") of me
        
        tell application "Terminal"
            display("in tell application Terminal") of me
        end tell
    end tell
end funcC

on display(message)
    log message
end display

結果

$ ruby test.rb
in funcA
in funcB
in funcC
in tell application Finder
in tell application Terminal