8pinoでAdafruit Neopixel Ringを光らせる その1
放置していた
8pinoハックですが、このままでは、やらない恐れがあると考え、後戻り出来ないように、モノを買ってみました。
Adafruit Neopixel Ring 16連
何か、かっこ良さそうというのと、ググるとarduinoで動かしている人が沢山いらっしゃったので。
switch science様によると、
フルカラーシリアルLED NeoPixelが16個載ったリング状の基板です。信号線1本でそれぞれのLEDを別々の色に光らすことができます。
とのこと。スゲエ技術だ。
まずは、ハンダ付け
GWの時間を使って、早速ハック。
8pinoも、Neopixel Ringも汎用的に使い回したいので、ピンヘッダつけて、ブレットボード上で扱えるようにします。 まずは、島忠に行ってハンダセットを購入。コテ、台、ハンダで、しめて3000円。
- Amazonだともっと安くて一度シュンとなっています。
プライベートでは、中学校以来のハンダ付け。ピンク色の鉛用石鹸が懐かしいです。
Neopixel Ringは
動作電圧:5V(最大7V)
とあるので、USBのVBusを直接入れてあげなければいけません。 * 8pinoのVCCは3.3V。
悩みながら、よいしょと、巨人の肩の上に乗ってみると(ググると)ソリューションが。
8pinoの裏にVBusが出ているので、これを空いている基板に出します。参照先では、8pino+と呼んでいます。良いですね。
早速、こうして、 こう!
あと、Neopixel Ringも、こう! 今回は、OUT pinは使わないので、V、 Gnd、IN pinの3つだけピンヘッダつけます。
サンプルコードを動かす
githubにNeopixel用のサンプルコードが公開されています。
LEDの数と、ピン番号だけ変えて、焼いてみます。
おぉ!ちゃんと動作しました。 電子工作初心者としては、大きな進歩です。
ただ、良くYouTubeとかで見る動画では、LEDがレインボーカラーに輝いていたりするので、次回は、それにチャレンジしてみます!
IRKitってすばらしい! その3
注意
過去の記憶を遡って記載しています。20150625記
スマホアプリ
Android版、作りました。
仕様
- BD Player、電気、テレビ、オーディオエアコンの全コマンド実行可能。
- タブビュー&縦スクロールで快適な操作性を実現。
- 各種マクロ対応
- おやすみスイッチ
- おでかけスイッチ
- オーディオ向け、簡単Bluetoothペアリング
- (バグ有り)Widget対応
- (未実装)NFCシールにスマホかざしたら任意のコマンド発行。
画面仕様
BD Player
早送りとかは、タップ操作だとキツイです(笑)
電気
楽しくて、わざわざ寝室の布団の中から消す(そして、リビングを覗いて、電気が消えたか確認する)ブームが家族内で発生。
テレビ
家のテレビはネットワーク越しのリモコンに対応していますが、スマホ上での認識に、数秒かかります。IRKitは一瞬でコマンド発行できるので便利。
オーディオ
家のオーディオは、NFC搭載しておらず、スマホやタブレットの音を出したいときは、少し面倒。リモコン経由でBluetoothのペアリングを開始できるだけですごい便利。
エアコン
これだけはクセモノでした。
毎回のコマンド発行時に、温度・湿度・時間設定などをのせているようで、ほんとんど解析出来ず。幾つかの決めうち設定しか出来ませんでした。
マクロ
自分で作っておいてなんですが、めっちゃ便利。
一見、使えそうなNFC機能は未実装です。
Widget
とWidget設定すると、
のようにホーム画面から直接リモコンコマンドを発行出来ます。
詳細
アイコン
このイケているアイコンは全て
さんからダウンロードしました!
iOS向け?のアイコンを多用しています。
つまずいた所
Android上でのBonjour
APIはあります。NsdManager。
言われた通りにやってはいるのですが、OSのせいなのか端末のせいなのか、10回やったら1回は失敗しました。
これに失敗すると、IRKit側がハマる事が多く、電源抜き差しが必要となり、使い物になりません。
というわけで、毎回の機器検索はあきらめて、初回起動時に設定値として覚えることにしました。
凝りに凝った機器検索UIは、ほとんど使われませんでした…
数百msec位表示されますけど…
コードはこちら
さすがに量が多くて、全部はのせられないので、IRKit制御回りのコードを一部だけ。
IRKitManager.java
package IRKit; import android.content.Context; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; import android.util.Log; public class IRKitManager { // For log private final static String TAG = IRKitManager.class.getSimpleName(); // For Bonjour communication private NsdManager.DiscoveryListener discoveryListener; private NsdManager.ResolveListener resolveListener; private NsdManager mNsdManager; private static final String DNS_TYPE = "_irkit._tcp"; // For IRKit private IRKitDiscoveryListner mListner = null; private IRKitDevice mIRKitDevice; // Others private Context mContext; // Constructor public IRKitManager( Context context ){ Log.d(TAG, "IRKitManager constructor"); this.mContext = context; setUpIRKitDiscoveryInfo(); } // Start Discovery IRKit public void startDiscoverIRKit( IRKitDiscoveryListner listner ){ mListner = listner; if(mNsdManager!=null){ mNsdManager.discoverServices(DNS_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener); } } // Stop Discovery IRKit public void stopDiscoverIRKit(){ mListner = null; if(mNsdManager != null){ mNsdManager.stopServiceDiscovery(discoveryListener); } } // Setup IRKit Discovery Information private void setUpIRKitDiscoveryInfo() { Log.d(TAG, "Setting up Bonjour"); discoveryListener = new NsdManager.DiscoveryListener() { @Override public void onStopDiscoveryFailed(String serviceType, int errorCode) { // TODO Auto-generated method stub } @Override public void onStartDiscoveryFailed(String serviceType, int errorCode) { // TODO Auto-generated method stub } @Override public void onServiceLost(NsdServiceInfo serviceInfo) { // TODO Auto-generated method stub } @Override public void onServiceFound(NsdServiceInfo serviceInfo) { Log.d(TAG, "Service resolved: " + serviceInfo.getServiceName() + " host:" + serviceInfo.getHost() + " port:" + serviceInfo.getPort() + " type:" + serviceInfo.getServiceType()); mNsdManager.resolveService(serviceInfo, resolveListener); } @Override public void onDiscoveryStopped(String serviceType) { // TODO Auto-generated method stub } @Override public void onDiscoveryStarted(String serviceType) { // TODO Auto-generated method stub } }; mNsdManager = (NsdManager) mContext.getSystemService(Context.NSD_SERVICE); resolveListener = new NsdManager.ResolveListener() { @Override public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { } @Override public void onServiceResolved(NsdServiceInfo serviceInfo) { Log.d(TAG, "IRKit is detected! IP Address is [" + serviceInfo.getHost().getHostAddress() + "]."); mIRKitDevice = new IRKitDevice( serviceInfo.getHost().getHostAddress(), serviceInfo.getServiceName() ); if( mListner != null ){ mListner.onIRKitDiscovered( mIRKitDevice ); }else{ Log.e(TAG, "mListner is null!"); } } }; } public interface IRKitDiscoveryListner{ public void onIRKitDiscovered( IRKitDevice irkit_dev ); } }
IRKitDevice.java
package IRKit; import java.io.IOException; import android.util.Log; public class IRKitDevice { // For log private final static String TAG = IRKitManager.class.getSimpleName(); private String m_ip_addr = ""; private String m_dev_name = ""; public IRKitDevice( String ip_addr, String dev_name ){ m_ip_addr = ip_addr; m_dev_name = dev_name; } public String learn() { return ""; }; public int send( final String send_data ){ Log.d(TAG, "send is called."); new Thread() { @Override public void run() { String responseJson = "NULL"; try { // Log.d(TAG, "send_data: " + send_data); responseJson = IRKitHttpClient.httpPost("http://" + m_ip_addr + "/messages", send_data); // 読み替えてね。 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } Log.d(TAG, "Response: " + responseJson); } }.start(); return 0; }; public String getIPAddress(){ return m_ip_addr; } public String getDevName(){ return m_dev_name; } }
IRKitってすばらしい! その2
注意
過去の記憶を遡って記載しています。20150624記
何はともあれ、ドキュメント
を確認します。
といっても、全てがIRKitのサイトに書いてある(しかも日本語!)ので、大して時間はかかりません。
15分もあれば、一通り目を通すことができます。
具体的には、IRKit-Device-APIです。
ハックステップ概要
任意のリモコンコマンドを発行するまでのハックステップは、ざっくり下記の通り。
- 機器発見 : IRKitをネットワーク上で発見する。
- 学習 : IRKitにリモコンコマンドを学習させる。
- GET data : 学習した結果(コマンドデータ)をIRKitから取得する。
- POST data : 取得したコマンドデータをIRKitへ送る。
- 発行 : IRKitが2.の赤外線コマンドを発行する。
順を追って記録に残していきます。
ハックステップ詳細
1. 機器発見
IRKitをおうちのネットワークに繋いだ状態からスタートします。
IRKitのAPIはIPアドレスがわかれば即座に叩けるので、まずは、IPアドレスを取得したいです。
この方法は世の中には幾つかありますが、IRKitでは、Appleさん謹製のBonjourという機器発見プロトコルを使って見つけられるそうです。
最近めちゃくちゃ動作の遅いMacMiniのターミナルから、Bonjour!と挨拶してみました。
手順書通りで確かに取得できました。
% dns-sd -B _irkit._tcp (中略) Instance Name 23:12:55.768 Add 2 4 local. _irkit._tcp. irkitd2a4 ^C % dns-sd -G v4 irkitd2a4.local (中略) Hostname Address irkitd2a4.local. 10.0.1.2
この例ですと、IPアドレスとして、 10.0.1.2がとれています。
2. 学習
IRKitは常に学習Readyとなっていますので、IRKitに向かってリモコンを向けて、リモコンのボタンを押すだけです。
簡単。
IRKitのLEDが点滅したら、(たぶん)学習出来ています。
おうちではカウンターの下に貼り付けてます。
3. 学習データのGET
それでは、2.で学習させたリモコンコードを読み出してみたいと思います。
やることは単純です。(手段はともかく)1.で取得したIPアドレス/messagesに対してGET発行するだけです。
GET from http://10.0.1.2/messages
すると、以下のような値(★1)が返ってきます。後でコピペするだけなので、データを理解する必要はありません。
{"format":"raw","freq":38,"data":[18031,(中略), 4400,1190]}
リモコン学習データがとれたので、いよいよ、発行してみます。
4. IRKitへデータを送る
これも簡単です。GETと同じ口に★1のデータをPOSTするだけです。
POST to http://10.0.1.2/messages ★1
5. リモコンコマンド発行(発光)
これで、めでたくIRKitからリモコンコマンドが発行され、おうちの中の何かが動いたと思います。
ツールを作る。
コマンドラインからばっかりだと、データをコピペするのも面倒になってくるので、ツールを作ります。
こだわりはないので、一番速く実現出来そうな、html/jsで書いてみます。
chromeブラウザでは動作確認済み。
単純なプログラムですが、家中の機器をブラウザから制御できるのは、新感覚。 実にスマートホームっぽいです。
これを使って、リモコンコードデータベースも出来上がったので、いよいよスマホアプリに実装してみようかと思います。
その他ノウハウ
- 得られたリモコンコードは、毎回少しずつ違っている事がある。でも、どれも動く。
- 長いデータだな、と思って良く見ると、同じコードの繰り返し(3回位)だったりする。繰り返し部は削除して、最初の部分だけにしても、動作した。
- 窓の近く、お昼過ぎにコウコウと太陽の光が注ぐと、IRKitから発行しても届かない(?)場合がある。
コードはこちら
index.html
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>IRKit JavaScript Test</title> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script> </head> <body> <br> <br> <textarea name="log_textarea" cols="50" rows="5"></textarea> <br> <br> <div id="Rows" ></div> <!--div id="test" > <input type="text" readonly name="command_label_1"> <input type="button" disabled name="btGet_1" value="GET" onclick="onClickedGet( 1 );"> <input type="button" name="btPost_1" value="POST" onclick="onClickedPost( 1 );"> <input type="text" name="gotten_data_1"> <br> <br> <input type="text" readonly name="command_label_2"> <input type="button" disabled name="btGet_2" value="GET" onclick="onClickedGet( 2 );"> <input type="button" name="btPost_2" value="POST" onclick="onClickedPost( 2 );"> <input type="text" name="gotten_data_2"> <br> <br> <input type="text" name="command_label_3"> <input type="button" name="btGet_3" value="GET" onclick="onClickedGet( 3 );"> <br> <br> </div--> <script src="./js/irkit_test.js"></script> </body> </html>
irkit_test.js
var gCurrentRowNum = 0; var gArrayRowCmdName = new Array(20); var gArrayRowData = new Array(20); initialize(); function initialize(){ output_textarea("initialize is called!"); var elm = document.getElementById( "Rows" ); elm.innerHTML = ''; addRow(); }; function output_textarea( text ){ var objTextarea = document.getElementsByName('log_textarea')[0]; objTextarea.value += text; objTextarea.value += "\n"; } function onClickedGet( currentIndex ){ output_textarea( "onClickedGet is called." ); output_textarea( "currentIndex is " + currentIndex ); if( document.getElementsByName('command_label_' + currentIndex)[0].value == "" ){ alert("Please Enter command name."); return; } if( currentIndex > 20 ){ output_textarea( "Fully registrated..." ); alert("Fully registrated... Please Reflesh by F5!" ); return; }else{ addRow(); } preparePost( currentIndex ); getIRData( currentIndex ); } function onClickedPost( currentIndex ){ output_textarea( "onClickedPost is called." ); output_textarea( "currentIndex is " + currentIndex ); sendIRData( currentIndex ); } function addRow(){ var i = 0; var numNextIndex = gCurrentRowNum + 1; var elm = document.getElementById( "Rows" ); var obj = '<div id="Row_' + numNextIndex + '"><input type="text" name="command_label_' + numNextIndex + '">' + '<input type="button" name="btGet_' + numNextIndex + '" value="GET" onclick="onClickedGet( ' + numNextIndex + ' );"></div>' + '<br>'; for( i = 1; i < gCurrentRowNum + 1; i++ ){ gArrayRowCmdName[i-1] = document.getElementsByName('command_label_' + i)[0].value; if( gCurrentRowNum != i ){ gArrayRowData[i-1] = document.getElementsByName('command_data_' + i)[0].value; } } elm.innerHTML += obj; for( i = 1; i < gCurrentRowNum + 1; i++ ){ document.getElementsByName('command_label_' + i)[0].value = gArrayRowCmdName[i-1]; if( gCurrentRowNum != i ){ document.getElementsByName('command_data_' + i)[0].value = gArrayRowData[i-1]; } } gCurrentRowNum++; } function preparePost( currentIndex ){ document.getElementsByName('command_label_' + currentIndex)[0].readOnly = true; document.getElementsByName('btGet_' + currentIndex)[0].disabled = true; var elm = document.getElementById( "Row_" + currentIndex ); var obj = '<input type="button" name="btPost_' + currentIndex + '" value="POST" onclick="onClickedPost( ' + currentIndex + ' );">'; obj += '<input type="text" name="command_data_' + currentIndex + '">' elm.innerHTML += obj; document.getElementsByName('command_label_' + currentIndex)[0].value = gArrayRowCmdName[currentIndex-1]; } /* Constants */ var HTTP_SCHEME_STRING = "http://"; var IRKIT_IP_ADDR = "10.0.1.2"; var API_CONTROL_PORT = "/messages"; function getIRKitURL() { return HTTP_SCHEME_STRING + IRKIT_IP_ADDR + API_CONTROL_PORT; } function postWebAPI( webAPI_command, callback_func ) { var url = getIRKitURL(); jQuery.ajax( url, { crossDomain: true, type: "POST", dataType: 'text', data: webAPI_command , success: function(result){ output_textarea( "Post Success" ); } }); } function sendIRData( currentIndex ) { output_textarea( "sendIRData is called." ); var data = document.getElementsByName('command_data_' + currentIndex)[0].value; var webAPI_command = JSON.stringify( data ); postWebAPI( webAPI_command, sendIRDataCallback ); } // getIRData function getIRData( currentIndex ) { output_textarea( "getIRData is called." ); output_textarea( "currentIndex is " + currentIndex ); var url = getIRKitURL(); jQuery.ajax( url, { crossDomain: true, type: "GET", dataType: 'text', success: function(text){ output_textarea( text ); document.getElementsByName('command_data_' + currentIndex)[0].value = text; output_textarea( "GET method success!" ); }}); } // unused.... function sendIRDataCallback( responseText ) { output_textarea("sendIRDataCallback is called."); output_textarea("responseText is " + responseText); }
IRKitってすばらしい! その1
注意
過去の記憶を遡って記載しています。20150623記
2014の夏
は個人的にIoTブーム、特にスマートホームブームがやって来ていました。
(非公開ではありますが、)私は個人的に、それにむけた指向性無しリモコンを作っていたのですが、困ったのは、そもそもWi-Fiで操作できる・API公開されている機器が圧倒的に少ないこと。
また、あっても価格が高かったり、すぐに買い換えないものであったりします。
※さすがに、これを機にエアコンを買い換えよう…とはなりません。
ただ、既存の機器にも、大抵は赤外線を使った(指向性の)リモコンがついています。 これを利用してスマートホーム化を実現してみようと思いたちました。
ソリューションの検討
世の中には、赤外線⇔Wi-Fiの変換をしてくれて、かつAPI公開してくれる、有難い機器が存在します。
財布と妻の顔色を伺いながら、比較しました。
機器名 | 価格†2 | デザイン | API公開 |
---|---|---|---|
iRemocon | 22,400円 | 好き | ○ |
Pluto | 13,800円 | 普通 | 非Open†1 |
eRemote | 6,700円 | 普通 | 非Open†1 |
IRKit | 7,700円 | 好き | ○ |
†1. 非Openとは、問合せしないとAPI仕様がわからない事を意味します。
†2. 価格はAmazon価格。
以上の結果、(タイトルでネタバレしてますが)ギリギリ予算内で、かつ、要件を満たすIRKitを選択しました!
早速注文
したら、すぐに来ました。
思った以上に小さくてびっくり!
小さすぎて、軽すぎて、逆に本当に動くのかな?と心配になるくらいです。
IRKitの特徴
- Arduino inside. 未使用ピン引き出し済!お好きに改造可能。
- Open Hardware(回路、外装データ)
- Open Source(SDK).
- Open API(JSON).
- おうちの外からも、スマホからリモコン操作できる。
- 個人で作られている。スゲェ。
あぁ、もうお腹いっぱいです。
まずは純正アプリを使ってみる
純正アプリはiOS向けにしかアプリが提供されておらず、私のスマホは、iphoneではないので、妻のiPod touchを使わせていただくことに。
Wi-Fi接続までの導入では、これ以上無いくらい丁寧な説明がありました。
とりあえず、おうちの中にあるものを配置してみました。すぐに画面いっぱいになります。
分かりやすいUIで、難なく使えました。 ただ、日常用途で激しく使うためには、もっとおうち向けにカスタマイズが必要です。
次回から、早速ハック開始。
8pinoがやって来た!
幸運にも
2014 Maker Faire Tokyoにて、運良く8pinoを購入することが出来ました!
ホントに小さくて、これでプログラミングができるなんて、にわかには信じがたい。 パッケージデザインや、取り出しのUXが美しくて、それだけでも嬉しくなります
動かしてみる
当方、数学屋さんのため、電子工作セット等は持ち合わせておりません。 とりあえず、8pinoに実装されているLEDで、Lチカを試みます…
8pinoで、Lチカ 337拍子! レゴのミニフィグと比較して、この小ささ。
なんとも、ビックリしたのは、8pino袋から取り出して、20分足らずで実現できるこの簡単さ!arduino、adafruit 、8pinoの皆様、心から尊敬します。
コードはこちら。
int led = 1; void setup() { pinMode( led, OUTPUT ); } void loop() { // 3 digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(500); // 3 digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(500); // 7 digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); delay(500); }
今冷静に思い返すと、関数化すべきだったが、興奮していて、そこまで気が回らず。
日本語の説明書もしっかり出来ていて、ほぼ戸惑う事なく、導入出来ました。
Lチカが出来たので、はしゃぐ
以下、8pinoの小ささを活かせないか、色々試した(はしゃいだ)記録。
トイ・ストーリーのグリーンアーミーメンに意味もなく持たせた図。
とにかく、レゴのミニフィグとコラボ出来ないか考える。
うん、ゴーストにはぎりぎり入るな。光るのがいい感じ。むしろUSBケーブルのコネクタの方が大きくてひっかかる。
暗いところに行ってみる。最近の妖怪ブームにのって、8pinoお化けの登場。 偶然だが、おまけでついているブレイカブル基板の穴が、目にfit。
Lチカで、火災現場を再現。
こうやってゴミ箱を…
さぁ
年末年始、何作ろう?