AlternoteでEvernoteに保存したMarkdownを,Markedでプレビューするやつを作った話.

概要

軽量EvernoteクライアントであるAlternoteを使ってMarkdownでメモをとり,
それをMarkdownビューワであるMarkedでプレビューするものを作った.

これは,設定さえしておけば,いつものメモ動作にHotkeyを一発加えるだけで実行できる.
また,最終的にEvernoteにデータが保持されるのであれば,エディタはなんでも良い.
以下の図のような感じでプレビューできる.右の黒いアプリがAlternoteで,左側のMarkdownビューワがMarkedである.

f:id:concre_shiitake:20150726170959p:plain

Markdownが好きだ

Markdownはいい感じだ.
メモをMarkdownで取ると,構造化されたメモがわりと簡単に取りやすい.
だからみんなMarkdownのこと結構好きだと思う. そんなこともあってオンラインストレージを使ったMarkdownメモツールというのがいくつかあって,ぼくもこれまで色々試してきた.

例えば,Vim用にはEverVimがあったり,
ブラウザから利用可能なwri.peがあったり,
MaciPhone用のアプリではDayOneがあったりする.

github.com

wri.pe

dayoneapp.com

と,色々試してきたし,どれも好きだったんだけどメモがとりにくかったり,シンタックスハイライトが微妙だったり,見返しづらかったりして結局定着しなかった(個人の感想です.)

で,最近はAlternoteというMac用の軽量Evernoteクライアントを使っていた.
Evernote,クライアントは重いしメモとるのめんどくさかったりと個人的にはだいぶ好きじゃないんだけど,Alternoteはそのへんがけっこうよかった.
ただ,EvernoteはなぜかMarkdownに対応してないので,そこをどうにかする必要があった.

Evernoteに書いたMarkdownをプレビューしてきた先人たち

まあこれは色々いらっしゃる.
個人的には,MacでMarkdown書くときは,Vimで書いてvim-quickrunでMarkedを呼び出してプレビューしていたので, 同じようにMarkedを使っている人を探した.

以下の方はEvernoteで最も最近更新されたメモをMarkedで表示するシェルスクリプトを書かれている.
ただEvernoteがバージョンアップしてディレクトリ構造が変わったのでこのスクリプトはもう動かなかった.

turubee.hatenablog.com

今回作ったものを利用する流れ

というわけで,先人のプログラムをForkして,以下のような流れで動くようにした.
今回新たに作成したプログラムは以下の2つ.

  • Evernote更新取得アプリ

    • Evernoteを開いて同期を行い,他のエディタで行ったEvernoterメモへの変更をクラウドからローカルに取り込むプログラム
  • Evernote更新検知アプリ

    • ローカルに保存されているEvernoteデータの更新を検知して,プレビューアプリを起動するプログラム

これらを利用する流れは以下のようになる.

  1. Evernote更新検知アプリを起動しておく.
  2. Alternoteでメモを書いて保存・同期する.
  3. Hotkeyを叩いて,Evernote更新取得アプリを起動.
  4. Evernote更新検知アプリが更新を検知して,Markedでプレビュー.

つまり,人が行う動作としては,

  1. Evernoteの更新を検知するアプリの起動(初回のみ)
  2. Alternoteでメモを書く
  3. 保存
  4. Hotkey押下

のみであり,これまでのメモ動作にHotkeyを加える形になる.
すると,以下の図のような感じでMarkedでプレビューが実行される.

f:id:concre_shiitake:20150726170959p:plain

プログラムの作成

Evernote更新検知アプリ

シェルスクリプトAutomatorを使ってアプリに固めたものを利用する.

turuさんのシェルスクリプトを基に最新版のEvernoteに対応させたプログラムを保存しておく.ID(ユーザ名ではなく数字7桁のID)は自分の環境に合わせたものをシェルスクリプトに記述する.
保存したら,Automatorを開き,アプリケーションの作成を選択.
以下の図のようにシェルスクリプトを登録しておく.ディレクトリは各自の環境に合わせて変更して欲しい.
あとは保存の際に保存形式をアプリケーション(.app)にして,/Applicationディレクトリとかに入れておく.デフォルトだと裏で動き続けるプログラムなので,ログイン時に起動させたり,Alternote起動させるときにいっしょに起動するなどして欲しい.

f:id:concre_shiitake:20150726173224p:plain

Evernote更新取得アプリ

Hotkeyで呼び出すプログラムを,サービスとしてAutomatorで作る.

EvernoteクライアントはAppleScirptをサポートしているので,以下のAppleScriptEvernoteの同期を実行することができる.

on run {input, parameters}
        tell application "Evernote"
    
            synchronize
    
            repeat until isSynchronizing is false
            end repeat
        end tell
    return input
end run

このスクリプトを組み込んだ以下の図のようなAutomatorサービスを作成し保存する.これは,Evernoteを起動し,同期を実行したらAlternoteを起動する,というプログラム.

f:id:concre_shiitake:20150726173442p:plain

サービスを保存したら,Macのシステム環境設定から,ショートカットキーで作成したサービスを呼び出すように,以下のように設定する.
(名前は自由に設定すれば良い.ここではAlternote_viewerという名前をつけていた.) Hotkeyは好みなので自由に変えて良い.

f:id:concre_shiitake:20150726173456p:plain

補足

今回,AlternoteをEvernoteエディタとして利用しているが,ここはVimでもEmacsでも,公式Evernoteクライアントでもなんでもいい.
(でもVimとかだったらquickrunからMarked呼び出したほうがラク)

公式クライアントを使う場合は Evernote更新検知アプリのみが必要になり,Evernote更新取得アプリは必要ない.

iPhoneについて

個人的にはiPhoneみたいなスマホでmarkdownをプレビューする必要はあんまりないと思ってる.
それよりも,markdownを使って出先でサクッとメモをとって,家のMacで読み返すっていう使い方をしてる.
ただ,iPhone向けのEvernoteクライアントはなんかしっくりくるのが見つかってないので,このへんもなんか考えたい.

Android wearをiPhoneで使えるようにした話 - いきさつなどと,仕組み編その1 -

いきさつと整理

だいぶ前のエントリで書いたように,iPhoneでAndroidWearを使えるようにした.

github, APKのリンクと,Playストアリンク

そもそも何に貢献してるのか

Android Wear端末をAndroid OSの載ってるスマホ以外で使えるようにした.
AndroidWear端末のRootを取ることも,iPhoneを脱獄する必要もない. (Root取ると,iPhoneだけで時刻同期ができるようになる.)

Android WearはAndroid用のスマートウォッチなのでそのままだとiOSみたいな異なるOSを使ってるスマホの通知を受け取ったりすることができない.
つまり,iPhoneならApple Watch以外選択肢が無い!という状況を変えることができる.

できること

  • 通知の確認,削除
  • 電話着信の確認,応答/拒否
  • 音楽とか動画の操作とタイトル表示
  • Androidスマホ無しでの時刻合わせ(これだけAndroidWear端末のRoot化が必要)

  • 最近の動画

アップデートする度に動画をYoutubeに上げてる.これは一番最近あげた動画.

www.youtube.com

他にもぼくのチャンネルにいろいろあげてる. www.youtube.com

なぜこれまでこのようなアプリがでてこなかったのか

Android Wearの主眼はNotificationを中心としたユーザとのインタラクションだと思う. そのためAndroidが提供しているフレームワークでは,Notificationを抽象化したAPIを提供することで,スマホ側の通知を簡単にWear側に出したり,それに対するアクションを行うことができる.よってAndroidWearの活用としてはこれが主眼となっており,そもそもこのような提供されたフレームワークから逸脱し,iOSに対処するという考えがあまりなかったのかと思う.(そもそも持ってる人少ないという問題がある……)

この資料がサクッとつかみやすいと思う.

ではどうすればiPhoneに対応できるのか

方法としては以下の2つが考られる.

  • iOS側で対応する

    • iOS側での対処としては例えば,ANCSをAndroid Wearが解釈できるIntentに変換する,
  • Wear側で対応する

    • Wear側での対処はANCSをWearが解釈するメソッドを実装する.

先のSlideshareにもあったように,AppleはANCS(Apple Notification Center Service)というものを利用することを推薦していますので,Wear側でANCSに対応できればよいことになります.
また,Apple watchではBLEを利用するためのAPIが提供されていませんが,Android wearでは提供されているため,実装がしやすいです.
Appleの開発者によると,WatchOS2でも提供していないとのこと……)

次回

Android wearでANCSを解釈するメソッドを実装するために,その仕様について解説します.
ANCSの概要とか,とっかかりについてはいくつかのブログ記事が見られますが,実際に欲しい通知を受け取ったり,通知に対するアクションを送信したりといったところを触れている資料がないのでそのへんを中心に書きたいと思います.

おまけ

iPhoneAndroid wearを使えるようにするアプリのこれまで.

  • @MohamadAGがプロトタイプを作成,動画がバズる.未だに公開されていない.
  • @Shiitakeo(ぼく)がMohammadAGの動画を見て触発され,アプリをフルスクラッチで作り,github,PlayStoreで公開する.日本で全然バズんないけど外国の方には結構人気.
  • @Shiitakeoが,iPhoneAndroid wearの接続が切れた後に自動再接続を行うためのiOSアプリを作成.AppStoreでリジェクトされたため,Cydiaで公開.
  • @Skytek65が@shiitakeoのコードをフォークして,アイコンや,利用方法の動画を加えたアプリを公開.その後のアップデートはなし.
  • AppStoreで公開されているBLEUtilityでも自動再接続が可能なことに気づき,iPhone側で利用するアプリをLightBlueから変更.
  • @alialmahdi が自作したとTwitter,ブログで発言.リジェクトされたという発言の後,特に言及,アプリ公開はない.
  • @GuiyeCがShiitakeoのアプリをフォークし,アプリアイコンやアプリ名の変更,通信シーケンスの改善等を行う.マーケット公開済み.

ぼくは,こんなことできるかなっていうのをプロトタイピングしながら確かめていくのが好きで,今はこのアプリより他のことやりたい(というか他のことしかしてない)ので,熱意のありそうな@GuiyeCさんが引き継いでいい感じのアプリに成長させていってもらえるとうれしいなあと思ってます.OSS感あって新鮮な経験でした.

Apple Media Service(AMS)を使ってAndroid WearからiPhoneのメディアを操作する話

1. 概要

iPhoneAndroid wearを使う話をしてきた.
で,その仕組み編として,前回の[通知に関するANCS話]に続いて, BLE経由でのメディアの操作や情報取得に関する Apple Media Service(AMS) についての話をする.
アプリを作った当初はこれに気づかずに,iPhone側にBLEで音楽を操作するアプリを仕込んだりしていた.

ぼくみたいなにわかiOSデベロッパが気づきにくい理由としては,AMSに関する解説記事とかブログみたいなものが全くなくて,公式のDeveloper Libraryでひっそりと公開されているだけだからだと思う.

iOS x BLEの参考書として有名な,堤さん(@shu223), 松村さん(@reo_matsumura)の,iOS×BLE Core Bluetoothプログラミング 本にもAMSのことは載ってなかったと思う.

www.amazon.co.jp

なので,メディア操作,楽曲情報取得に絞ってではあるが,簡単な解説記事を書こうと思う.

2. AMS: Apple Media Service の概要

これを利用して,BLE経由でiPhoneの音楽やビデオといったメディアが制御できるようになる. iPhoneの画面を下からスワイプした時にでてくるドロワーに音楽操作したり音量変えたりできるGUIがあるけど,あれをBLE経由で操作できると考えるといいと思う.なので,音楽だけでなくて,例えばビデオなんかも制御できる.

だから例えば,スマホ経由での操作だけじゃなくて,
BLEと慣性センサが載ったオリジナルデバイスからiPhoneのメディアを操作したりといったことがお手軽にできるようになる.

制御としては,次のようなことができる.

  • 再生/停止
  • 前の曲に戻る,次の曲に進む
  • 音量Up/Down
  • 曲情報や,プレイヤーアプリ状態の取得

補足: AMS用語

  • MS: Media Source
    • iOSデバイス等のメディアを保持,再生するデバイス
  • MR: Media Remote
    • MSのメディアを遠隔から操作するアクセサリデバイス

3. AMSのもつCharacteristicsについて

AMSは以下のUUIDのService,Characteristicsを持つ.これらのCharacteristicsの概要と利用方法は以降で述べる.

名前 UUID プロパティ 機能
Service Apple Media Service 89D3502B-0F36-433A-8EF4-C502AD55F8DC
Characteristics Remote Command 9B3C81D8-57B1-4A8A-B8DF-0E56F7CA51C2 write メディア,音量の操作
Entity Update 2F7CABCE-808D-411F-9A0C-BB92BA96C102 read, write, notify メディアや,プレイヤーアプリ情報のやりとり
Entity Attribute C6B2F38C-23AB-46D8-A6AB-A3A870BBD5D7 read, write EntityUpdateでは大きすぎるデータのやりとり

3.1. Remote Command Characteristic

このCharacteristicにコマンドを書き込むことで,メディアの操作(再生停止,前,次)や音量の操作を行う.

  • コマンド

以下のような,命令したい1Byteのコマンド(RemoteCommand)を書き込む.

Byte 1
Value RemoteCommendID
  • Remote Command IDの種類

現在は以下の11種類が提供されている.それぞれの機能はコマンド名そのまんま.

コマンド名 Value
RemoteCommandIDPlay 0
RemoteCommandIDPause 1
RemoteCommandIDTogglePlayPause 2
RemoteCommandIDNextTrack 3
RemoteCommandIDPreviousTrack 4
RemoteCommandIDVolumeUp 5
RemoteCommandIDVolumeDown 6
RemoteCommandIDAdvanceRepeatMode 7
RemoteCommandIDAdvanceShuffleMode 8
RemoteCommandIDSkipForward 9
RemoteCommandIDSkipBackward 10

3.2. Entity Update Characteristic

再生するメディアの情報やプレイヤーアプリの状態をやりとりする.Notifyを申し込んだ後に,欲しい情報を以下のコマンドを利用してCharacteristicに送信することで,その情報がNotifyされるようになる.

  • コマンド

以下のように,1Byte目でエンティティを選択,2Byte目以降でそのエンティティの中で取得したいアトリビュートを指定する.アトリビュート複数指定することができる.

Byte 1 2 3
Value EntityID Attribute1 Attribute2
  • Entity IDの種類

Entityは以下のようなものがある.例えば,曲情報が欲しい場合には "2"というEntityIDを使う.

名前 Value 説明
EntityIDPlayer 0 プレイヤーアプリ名や再生状態,音量に関するエンティティ
EntityIDQueue 1 メディアQueueに関するEntity
EntityIDTrack 2 曲等のTrackに関するEntity
  • TrackAttributeの種類(EntityIDTrackの場合)

EntityIDごとのAttributeについては公式資料を確認して欲しい.ここでは,例としてEntityIDTrackを指定した場合に選択可能なAttributeについて説明する.

名前 Value 説明
TrackAttributeIDArtist 0 アーティスト名
TrackAttributeIDAlbum 1 アルバム名
TrackAttributeIDTitle 2 曲名
TrackAttributeIDDuration 3 曲の長さ(秒単位)
  • コマンドを送信するコードの例

Notifyを申請した後に,その成功/失敗がonDescriptorWriteを通して通知される.成功だった場合に,以下のようにコマンドを送信して欲しい情報を申請する.この例では,EntityIDTrackのうち,曲名とアーティスト名に関する情報を取得する.

 @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            Log.d(TAG_LOG, " onDescriptorWrite:: " + status);

            // Notification source
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(TAG_LOG, "status: write success ");

                //find AMS
                BluetoothGattService service = bluetooth_gatt.getService(UUID.fromString(service_ams));
                if(service != null) {
                    BluetoothGattCharacteristic chara = service.getCharacteristic(UUID.fromString(characteristics_entity_update));
                    if(chara != null) {
                        chara.setValue(new byte[]{(byte) 0x02, (byte) 0x00, (byte) 0x02});
                        bluetooth_gatt.writeCharacteristic(chara);
                    }
                }
            }
        }

Notifyされてくるデータ

  • パケット構造

1Byte目にエンティティを示すIDが入っており,2Byte目にAttributeを示すIDが入っており,4Byte目以降は実際の情報が入っている.

  @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            Log.d(TAG_LOG, "onCharacteristicChanged:: " + characteristic.getUuid().toString());

            if (characteristics_entity_update.toString().equals(characteristic.getUuid().toString())) {
                Log.d(TAG_LOG, "entity update ");

                byte[] get_data = characteristic.getValue();
                String music_info = characteristic.getStringValue(3);
                Log.d(TAG_LOG, "new music info: " + music_info);

                if(get_data[1] == (byte)0x00) {
                    Log.d(TAG_LOG, "get artist info: " + music_info);
                }else if(get_data[1] == (byte)0x02) {
                    Log.d(TAG_LOG, "get title info: " + music_info);
                }
            }
        }

Entity Attribute

Entity Updateでは,データが大きすぎて伝えられない情報をやりとりする.
ちなみに作成したアプリでは,Android wearで表示する文字が多くなると文字が小さくなって見づらくなるのでこれを利用していない.

  • パケット

取得したい情報のEntity,AttributeのIDを指定する.

Byte 1 2
Value EntityID AttributeID

3. シーケンス

3-1. 準備

AMSのEntityUpdateCharacteristicを探してほしい情報を登録すると,曲が変わったタイミングなどでNotifyされてくる.

fig2-2

3-2. メディアの操作

RemoteCommandCharacteristicを探して,操作したいコマンドを送信すると,MS側でその操作が実行される.

Fig 2-3

3-3. メディア情報の取得

3-1. 準備 の手順で曲名の一部(曲名が長い場合は先頭のみ)が送られてくるが,正式なタイトル情報が知りたい場合はEntityAttributeへコマンドを書き込み情報を読み出す.

Fig2-4

4. まとめと実際のコード

音楽のアーティスト名と曲名を取得するという例にそって,AMSの使い方を簡単に説明した.
シンプルなので,BLE Centralなプログラムが書ける人には難しくないと思う.
実際にアプリの中では,MusicControlActivityの中で利用している.