ハードウェアエンジニアの備忘録

電子工学(半導体物性)→応用光学・半導体プロセス→アナログ回路→C/C++→C#/.NETと低レイヤーから順調に(?)キャリアを登ってきているハードウェアエンジニアの備忘録。ブログ開始時点でiOSやサーバーサイドはほぼ素人です。IoTがマイブーム。

XilinxのJTAGケーブルが届いた

f:id:tosh419:20160605074744j:plain

Xilinx社製のFPGAにファイルを書き込む場合、USB接続のJTAGケーブルを使うことになる。しかしながら、Xilinx社公式のJTAGケーブルは高い。例えば、プラットフォームケーブルUSBⅡはXilinx公式HPで$225する!

 

これで、他社製のFPGAにも使えればまあしょうがないかな、という感じもするのだが、AlteraやLatticeのFPGAには使用できない。

 

こんな状況も手伝い、Googleで「Xilinx JTAG」とうつと、関連ワードに「自作」などと出てくる。

 

しかし、iMPACTで認識しないなどのトラブルを抱えているとの話も聞く。うーん、と悩んでいるときに、eBayでJTAGケーブルが出品されているのを見つけた。発送元は、中国のシリコンバレーと名高い?深センである。そしてそのお値段、$29.53(約\3,000)、激安

 

f:id:tosh419:20160605085145j:plain

↑こんな感じで、複数のJTAGの間口に対応する。「上」という漢字はこういう意味だった。ちなみに、使用方法に対する説明書などは一切なく、試行錯誤しこういう使用法だと言うことに気付いたw。

 

ちゃんと使えるのか、、?今後レポしていく予定。

Socket.IOを使ってクライアントからRaspberry Pi 3のストリーム配信を開始する

ここに従う形で、クライアントPCからSocket.IO通信を使って、Raspberry Pi 3に通信しストリーミング配信を開始することを試みる。

 

Raspberry Pi 3 (サーバー)側の準備

まずは適当なディレクトリに移動し、git cloneする。済んだらcdで移動しておく。

$ git clone https://github.com/irisSchaffer/rpi-socket-streaming.git
$ cd rpi-socket-streaming

次に、Node.jsのパッケージ関係をそろえる。

$ npm install

また、Githubのリンク先には記述されていないが、忘れずにstreamディレクトリをrpi-socket-streamingの下に作成しておく。

$ mkdir stream

各自の環境に合わせて、IPアドレスやPort番号を変更する。まずは、config.json内の赤い部分を修正する。クライアントサイドのIPアドレスなので、WindowsPCで見るのであれば、cmd.exe→ipconfigを打ったときに表示されるアドレスが該当のアドレスだ。Macであればifconfigをターミナルで打ったときに表示されるアドレスを入れれば良い。

{
  "port": 9999,
  "client-host": "192.168.10.100",
  "client-port": 80,
  "capture-rate": 200,
  "capture-time": 100,
  "image-path": "stream",
  "image-name": "image.jpg",
  "image-width": 640,
  "image-height": 480
}

 

Windows PC (クライアント)側の準備

次にクライアント側の準備だが、Githubのリンク先に置いてあるclient.htmlをダウンロードし、54行目と56行目の赤字になっている部分を自分のRaspberry PiのIPアドレスに変更する。

<script src="http://192.168.0.102:9999/socket.io/socket.io.js"></script>
var socket = io.connect('http://192.168.0.102:9999');

これで、晴れて準備は終わり。

いざ、試す

まず、Raspberry Pi 3のターミナルからnodeコマンド。

$ node index.js
listening on *:9999

次に、Chromeなどでclient.htmlを開いて、Start Streamボタンを押す。

… 何も写らない

ChromeのConsoleを見ると、

f:id:tosh419:20160504183435p:plain

socket.ioが400 Bad Requestになっている。解決策はここに書いてあった。client.htmlを以下のように変更する。

// 古いやつ
// var socket = io.connect('http://192.168.10.102:9999');

// 新しいやつ
var socket = io.connect('http://192.168.10.102:9999',{transports:['websocket']});

polling→websocketにしてやるということでした。これで自分の場合はうまくいった。

 

 

Node.jsを使ってshellコマンドを叩く

Raspberry Piに入れたNode.jsを使って、シェルを叩く方法のメモ。

参考にしたのは、以下のブログ。

Node.jsでshellコマンドを叩く - PolyPeaceLight

Node.jsからSlackに投稿するPythonスクリプトを動かしてみる。

まずは、適当なディレクトリに移動し、jsファイルを作る。

$ vi shelltest.js

 以下のサンプルコードを書く。

var exec = require('child_process').exec;
var child;

child = exec("python slacktest.py", function (error, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if (error !== null {
console.log('exec error: ' + error);
}
});

ちなみに、slacktest.pyは以下のようなコードになっている。

#Slack Post Test
from slacker import Slacker
token = "Your Token 自分のトークンに変えてね"
slacker = Slacker(token)
channel_name = "#" + "general"
message = "API test by Script"
slacker.chat.post_message(channel_name, message, username='test')

これをターミナルから

$ node shelltest.js

とうつと、

f:id:tosh419:20160430080824j:plain

投稿できることが確認できた。

Node.jsからRaspberry Pi 3のカメラを動かしてみる。

お次はもう少し複雑なスクリプトを動かすことに挑戦する。参考にしたのはこのサイト。

Raspberry Pi, Camera and Node.js - Live Streaming with Websockets #IoT | The Jackal of Javascript

適当なファイル名のjsファイルを作る。

$ vi raspistilltest.js

 以下のようなコードを書いた

var spawn = require('child_process').spawn;
var proc;

var args = ["-w","640","-h","480","-o","/home/pi/Pictures/image_stream.jpg"]
proc = spawn('raspistill',args);

raspistillコマンドを実行するだけのファイルになる。/home/pi/Picutres/image_stream.jpgができていれば成功になる。

$ node raspistilltest.js

 実行すると、、

f:id:tosh419:20160430100503p:plain

できた!image_stream.jpgが確認できる。

raspistillで撮影→それが何かをCloud Visionで識別する

これらを組み合わせることで、Node.jsを用いて、raspistillで撮影を行い、その画像をGoogle Cloud Visionで判別するプログラムを作った。

まず、cloudvisionのディレクトリに移動し、適当な名前のjsファイルを作る。

$ cd /home/pi/projects/cloudvision
$ vi cameratest.js

次にコードを書いていく。内容としては、上記raspistillの項で説明したコードと、CloudVisionの記事で紹介したコードの組み合わせだ。

var spawn = require('child_process').spawn;
var proc;

var args = ["-w", "640", "-h", "480","-o","/home/pi/Pictures/image_stream.jpg"]
proc = spawn('raspistill', args);

'use strict'
const vision = require('node-cloud-vision-api')

// init with auth
vision.init({auth: 'Your API Key'})

// construct parameters
const req = new vision.Request({
  image: new vision.Image('/home/pi/Pictures/image_stream.jpg'),
  features: [
    //new vision.Feature('FACE_DETECTION', 4),
    new vision.Feature('LABEL_DETECTION', 10),
  ]
})

// send single request
vision.annotate(req).then((res) => {
  // handling response for each request
  console.log(JSON.stringify(res.responses))
}, (e) => {
  console.log('Error: ', e)
})

これを保存し、

$ node cameratest.js

として実行すると、下のような写真が撮影され、

f:id:tosh419:20160430110207j:plain

コンソールには以下が出力される。

f:id:tosh419:20160430110343p:plain

見にくいので、いくつか結果を書き出してみると、

  • meal (ご飯)
  • breakfast (朝食)
  • furniture (家具)

などの結果が確認できる。ただ、

  • sink (シンク)
  • plumbing fixture (配管の部品)
  • swimming pool (スイミングプール)

との結果も出ている。

しかし、この写真まずそうに撮れている笑

 

 

 

Apple Developers Program登録方法

XCode7.0からは無料で実機ビルドができるようになった!やった!とか言っていたのも過去の話。APNs(Apple Push Notification service)のAPIキーを取得するためには、結局Apple Developers Programへの登録が必要という、、

 

という事で、今回はApple Developers Programへの登録方法の備忘録。

(無料のアカウントだと何もできないよ↓)

f:id:tosh419:20160424190304j:plain

Enrollします!↓

f:id:tosh419:20160424190502j:plain

次に、Entity Typeを選択する。自分の場合はIndividualにあたる。

f:id:tosh419:20160424190835j:plainちなみにPlease note以下をざっと訳すと、

App Storeで販売者としてリストされるために、あなたの会社はあなたの国で法的な組織体でなければいけませんよ。個人事業主や、社員が一人しかいなくて国 によって法的な組織体と認められない場合、登録した人個人の法的な名前で登録され、App Storeで表示されますよ

って感じ。

個人情報を入れていって、最後に、、\12,744支払いでおしまい。

f:id:tosh419:20160424192238j:plain

この時点でアカウントはペンディング状態。48時間以内にAppleから連絡が来て正式に承認となる。

登録したのは日曜日だったが、火曜日に以下のメールが来た。

f:id:tosh419:20160429195809j:plain

Apple Developers Programにログインした画面は以下のような感じ。

f:id:tosh419:20160429200737j:plain

MembershipやCertificates, IDs & Profilesなど無料アカウントの時にはなかった項目が確認できる。

 

 

 

motion-mmalをストリーミングで見るときにパスワード認証をかける

今はまだローカルでRaspberry Piが閉じている環境なので、セキュリティに関してシビアではないが、いずれ外部ネットワークからも観れるようにする予定である。

それに備え、motion-mmalcam-both.confを書き換えてID認証を導入する。

①motion-mmalcam-both.confの書き換え

$ cd /opt/motion-mmal/
$
sudo vi motion-mmalcam-both.conf

ファイルを開いたら下記箇所を変更する。viで/stream_authと打つとすぐ見つかる。

stream_auth_method 1
stream_authentication newid:newpassword

これで、次回からストリーミング配信を見ようとすると、ID認証を求められるようになる。

f:id:tosh419:20160417202644p:plain

iPhone側の実装も変更

ほぼここのコピペだが、、、FirstViewControler.swiftを変更する。

import UIKit

class FirstViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // loadCamView()
        loadCamViewWithAuth()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    @IBOutlet weak var MonitorWebView: UIWebView!
    
    func loadCamView(){
        var url: String = "http://192.168.10.102:8081/?action=snapshot"
        let requestURL = NSURL(string: url)
        let req = NSURLRequest(URL: requestURL!)
        
        MonitorWebView.loadRequest(req)
    }
    
    func loadCamViewWithAuth(){
        var url_with_basic_auth = "http://192.168.10.102:8081/?action=snapshot"
        var url = NSURL(string: url_with_basic_auth)
        var req = NSMutableURLRequest(URL: url!)
        
        var username = "newid"
        var password = "newpassword"
        var authStr = "\(username):\(password)"
        var data = authStr.dataUsingEncoding(NSUTF8StringEncoding)
        
        var authData = data!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions())
        var authValue = "Basic \(authData)"
        
        req.setValue(authValue, forHTTPHeaderField: "Authorization")
        
        MonitorWebView.loadRequest(req)
    }
    
    @IBOutlet weak var ReloadWebView: UIButton!
    
    @IBAction func ReloadWebView_pressed(sender: UIButton) {
        // loadCamView()
        loadCamViewWithAuth()
    }    
}

 loadCamViewWithAuth関数を追加し、そちらに実装を書いた。NSDataBase64EncodingOptions.allZerosは現在のSwiftのバージョンではなくなったようだ。かわりにNSDataBase64EncodingOptions.allZeros()としておいて問題なかった。

 

しかし、C#C++などの古い言語しか触ってない人からみると、Swiftなどの新しい言語の仕様はすごく変化が早い。開発者は大変だな。

Raspberry Pi 3でGoogle Cloud Visionを使う

Googleの画像認識エンジンである、Cloud VisionをRaspberry Pi 3で使ってみたので、その備忘録。参考にしたのは、以下。

①Node.jsをRaspberry Pi 3に導入する

まずはnvm(Node Version Manager)の導入。

$ git clone https://github.com/creationix/nvm.git ~/.nvm

 次にパスを通す

$ source ~/.nvm/nvm.sh

 これでnvmコマンドが使えるようになったので、Node.js公式を見て、最新バージョンのNode.jsをインストールする。最新バージョンの確認は下記nvm ls-remoteコマンドでもわかる。

$ nvm ls-remote
$ nvm install v5.10.1

次にNode.jsの使用バージョンのデフォルト設定を行う。

$ nvm alias default v5.10.1

バージョンを確認してみる。

$ node -v
v5.10.1

 動いていそうだ。

.bash_profileにNode.js周りの設定を書いておく。

$ vi .bash_profile

ここを参考にした。

# nvm設定
[[ -s ~/.nvm/nvm.sh ]] && . ~/.nvm/nvm.sh
nvm use default
npm_dir=${NVM_PATH}_modules
export NODE_PATH=$npm_dir

 次に以下コマンドを打ち設定を反映させる。

$ source ~/.bash_profile

これでNode.jsの準備は完了。

Google Cloud Visionの設定

https://cloud.google.com/vision/にアクセスして、登録をすませる。

f:id:tosh419:20160417060030p:plain

左上のハンバーガーメニューから、API Managerを選択し、新しい認証情報→APIキー

f:id:tosh419:20160417060209p:plain

その後サーバーキーを選択して、作成。

そうそう、無料試用期間とはいえ、万が一が恐いので、請求のアラート設定をしておく。例えば、APIキーが漏れてしまった時などでも、被害を最小限にとどめられる。お支払い→予算とアラート→予算を作成から適当な金額を設定する。AWS破産とか冗談にならないからね。

f:id:tosh419:20160417060445p:plain

③node-cloud-vision-apiをRaspberry Pi 3に導入する

適当なディレクトリに移動し、フォルダを作る

$ cd home/pi/projects/
$
mkdir cloudvision

npm initして、node-cloud-vision-apiをインストールする

$ npm init
$
npm install --save node-cloud-vision-api

JavaScriptを実行してみる

'use strict'
const vision = require('node-cloud-vision-api')

// init with auth
// Your API Keyのところを②で取得したAPIキーに変える。
// これは公開すると他人に使われる恐れがあるので公開しないように!!
vision.init({auth: 'Your API Key'})

// construct parameters
const req1 = new vision.Request({
// ローカルにあるイメージを指定。
image: new vision.Image('/home/pi/Pictures/summerfruits.jpg'),
features: [
new vision.Feature('FACE_DETECTION', 4),
new vision.Feature('LABEL_DETECTION', 10),
]
})

// 2nd image of request is load from Web
const req2 = new vision.Request({
image: new vision.Image({
// インターネット上のイメージを指定。
url: 'https://scontent-nrt1-1.cdninstagram.com/hphotos-xap1/t51.2885-15/e35/12353236_1220803437936662_68557852_n.jpg'
}),
features: [
new vision.Feature('FACE_DETECTION', 1),
new vision.Feature('LABEL_DETECTION', 10),
]
})

// send multi requests by one API call
vision.annotate([req1, req2]).then((res) => {
// handling response for each request
console.log(JSON.stringify(res.responses))
}, (e) => {
console.log('Error: ', e)
})

上記コードをcloudvision_samplecode.jsとして保存する。

$ node cloudvision_samplecode.js

 として実行すると、下記がターミナルに出力される。(見やすくするためにインデントをつけている)

{
"labelAnnotations":
[
{
"mid":"/m/036qh8",
"description":"produce",
"score":0.97685134
},
{
"mid":"/m/02wbm",
"description":"food",
"score":0.94573361
},
{
"mid":"/m/05s2s",
"description":"plant",
"score":0.94254869
},
{
"mid":"/m/02xwb",
"description":"fruit",
"score":0.90469909
},
{
"mid":"/m/0dxb5",
"description":"berry",
"score":0.88049006
},
{
"mid":"/m/0gqbt",
"description":"shrub",
"score":0.53203881
},
{
"mid":"/m/0463nlw",
"description":"west indian raspberry",
"score":0.50897217
}
]
},

{
"faceAnnotations":
[
{
"boundingPoly":
{
"vertices":
[
{"x":112},
{"x":724},
{"x":724,"y":510},
{"x":112,"y":510}
]
},
"fdBoundingPoly":
{
"vertices":
[
{"x":193},
{"x":638},
{"x":638,"y":399},
{"x":193,"y":399}
]
},
"landmarks":
[
{
"type":"LEFT_EYE",
"position":{"x":342.14697,"y":77.257805,"z":0.0010066124}
},
{
"type":"RIGHT_EYE",
"position":{"x":524.90961,"y":100.77812,"z":0.84962881}
},
{
"type":"LEFT_OF_LEFT_EYEBROW",
"position":{"x":285.53159,"y":30.352997,"z":25.532389}
},
{
"type":"RIGHT_OF_LEFT_EYEBROW",
"position":{"x":395.71207,"y":32.727783,"z":-25.809153}
},
{
"type":"LEFT_OF_RIGHT_EYEBROW",
"position":{"x":484.55322,"y":44.141739,"z":-25.38883}
},
{
"type":"RIGHT_OF_RIGHT_EYEBROW",
"position":{"x":591.29803,"y":69.55114,"z":26.930927}
},
{
"type":"MIDPOINT_BETWEEN_EYES",
"position":{"x":434.99573,"y":73.941544,"z":-36.170986}
},
{
"type":"NOSE_TIP",
"position":{"x":423.93985,"y":167.91109,"z":-111.36714}
},
{
"type":"UPPER_LIP",
"position":{"x":411.80557,"y":241.09036,"z":-82.791382}
},
{
"type":"LOWER_LIP",
"position":{"x":405.9841,"y":306.68048,"z":-82.744064}
},
{
"type":"MOUTH_LEFT",
"position":{"x":337.35049,"y":267.53821,"z":-39.726524}
},
{
"type":"MOUTH_RIGHT",
"position":{"x":487.37915,"y":290.97012,"z":-37.928776}
},
{
"type":"MOUTH_CENTER",
"position":{"x":410.22937,"y":273.30365,"z":-75.321}
},
{
"type":"NOSE_BOTTOM_RIGHT",
"position":{"x":471.14438,"y":202.10455,"z":-44.104683}
},
{
"type":"NOSE_BOTTOM_LEFT",
"position":{"x":371.49542,"y":186.17801,"z":-44.648823}
},
{
"type":"NOSE_BOTTOM_CENTER",
"position":{"x":419.42804,"y":201.44179,"z":-75.345909}
},
{
"type":"LEFT_EYE_TOP_BOUNDARY",
"position":{"x":343.91556,"y":62.291443,"z":-9.4704609}
},
{
"type":"LEFT_EYE_RIGHT_CORNER",
"position":{"x":378.49484,"y":83.426445,"z":0.63221854}
},
{
"type":"LEFT_EYE_BOTTOM_BOUNDARY",
"position":{"x":340.5495,"y":88.847572,"z":-4.0131183}
},
{
"type":"LEFT_EYE_LEFT_CORNER",
"position":{"x":304.99127,"y":76.559814,"z":16.381186}
},
{
"type":"LEFT_EYE_PUPIL",
"position":{"x":339.22885,"y":75.820984,"z":-4.330039}
},
{
"type":"RIGHT_EYE_TOP_BOUNDARY",
"position":{"x":526.98669,"y":85.809883,"z":-8.6181784}
},
{
"type":"RIGHT_EYE_RIGHT_CORNER",
"position":{"x":560.83966,"y":109.43663,"z":17.566591}
},
{
"type":"RIGHT_EYE_BOTTOM_BOUNDARY",
"position":{"x":523.4292,"y":112.47009,"z":-3.1689837}
},
{
"type":"RIGHT_EYE_LEFT_CORNER",
"position":{"x":485.92252,"y":100.39104,"z":0.7664215}
},
{
"type":"RIGHT_EYE_PUPIL",
"position":{"x":527.13208,"y":100.01988,"z":-3.736815}
},
{
"type":"LEFT_EYEBROW_UPPER_MIDPOINT",
"position":{"x":343.1142,"y":7.1320877,"z":-7.4442763}
},
{
"type":"RIGHT_EYEBROW_UPPER_MIDPOINT",
"position":{"x":541.75824,"y":32.65345,"z":-6.5013323}
},
{
"type":"LEFT_EAR_TRAGION",
"position":{"x":207.46692,"y":196.30023,"z":203.98279}
},
{
"type":"RIGHT_EAR_TRAGION",
"position":{"x":623.50317,"y":249.52588,"z":205.91609}
},
{
"type":"FOREHEAD_GLABELLA",
"position":{"x":440.61343,"y":34.804077,"z":-31.940067}
},
{
"type":"CHIN_GNATHION",
"position":{"x":395.04126,"y":391.9465,"z":-74.228394}
},
{
"type":"CHIN_LEFT_GONION",
"position":{"x":215.1674,"y":298.48297,"z":105.92882}
},
{
"type":"CHIN_RIGHT_GONION",
"position":{"x":590.98389,"y":346.83713,"z":107.62403}
}
],
"rollAngle":7.2499251,
"panAngle":0.27318951,
"tiltAngle":11.967749,
"detectionConfidence":0.577574,
"landmarkingConfidence":0.19900249,
"joyLikelihood":"POSSIBLE",
"sorrowLikelihood":"VERY_UNLIKELY",
"angerLikelihood":"VERY_UNLIKELY",
"surpriseLikelihood":"VERY_UNLIKELY",
"underExposedLikelihood":"VERY_UNLIKELY",
"blurredLikelihood":"VERY_UNLIKELY",
"headwearLikelihood":"VERY_UNLIKELY"}],
"labelAnnotations":
[
{"mid":"/m/01k74n","description":"facial expression","score":0.86115927},
{"mid":"/m/068hy","description":"pet","score":0.80557323},
{"mid":"/m/017ftj","description":"sunglasses","score":0.72584444},
{"mid":"/m/0sgh53y","description":"selfie","score":0.71132851}
]
}
]

 

画像の一枚目は下のフルーツの画像で、

f:id:tosh419:20160417211241j:plain

   {
  "mid":"/m/036qh8",
"description":"produce",
"score":0.97685134
},
{
"mid":"/m/02wbm",
"description":"food",
"score":0.94573361
},
{
"mid":"/m/05s2s",
"description":"plant",
"score":0.94254869
},
{
"mid":"/m/02xwb",
"description":"fruit",
"score":0.90469909
},
{
"mid":"/m/0dxb5",
"description":"berry",
"score":0.88049006
},
{
"mid":"/m/0gqbt",
"description":"shrub",
"score":0.53203881
},
{
"mid":"/m/0463nlw",
"description":"west indian raspberry",
"score":0.50897217

とあるので、大体は合っていそう。

 

画像の2枚目はTaylor Swiftである。

f:id:tosh419:20160417211328j:plain

        "joyLikelihood":"POSSIBLE",
"sorrowLikelihood":"VERY_UNLIKELY",
"angerLikelihood":"VERY_UNLIKELY",
"surpriseLikelihood":"VERY_UNLIKELY",
"underExposedLikelihood":"VERY_UNLIKELY",
"blurredLikelihood":"VERY_UNLIKELY",
"headwearLikelihood":"VERY_UNLIKELY"}],
"labelAnnotations":
[
{"mid":"/m/01k74n","description":"facial expression","score":0.86115927},
{"mid":"/m/068hy","description":"pet","score":0.80557323},
{"mid":"/m/017ftj","description":"sunglasses","score":0.72584444},
{"mid":"/m/0sgh53y","description":"selfie","score":0.71132851}
]

sorrow,anger,などがunlikelyなのであっている。サングラスや自撮りであることも認識していそうである。

 

結構面白い。月に1000回ぐらいしか無料のAPIコール枠はないのでやりすぎないように注意。