追記:2017/03/06
いろいろあってソースを読んだところ、GobotのEdison用ドライバーはsysfsを直接叩きに行ってることがわかりました
特にPWM周りで不満が大きくて、Gobotからはduty比しかいじくれないけど、Edisonの仕様でduty比0でも僅かに出力が出るので
結局最後はライブラリのソースを改変しながら使っていました(それでも不具合が出る、sysfsをプログラムから叩くのが原因ぽい?)
あまりそれらがよろしくないと思ったので、今度はgolangでmraaのCライブラリを直接触ってみたいところです
golangのクロスコンパイルを何かに使ってみたかった矢先、Gobotというものを知ったので、
Edison開発にgolangで挑戦してみることにした
それにあたって、開発環境作りに精を出した話
できたこと
ソースを保存すると、
- ビルドして
- Edisonに転送され
- 今までのプロセスを殺して
- 新たなプログラムが走ってくれます
ソースの監視を切って、手動で上記を実行するのもできます
godoとは
godo is a task runner and file watcher for golang in the spirit of rake, gulp.
golang製のタスクランナー
gulpみたいなもの
やりたいこと
- ソースからEdison向けにビルド
- Edisonへ転送
- Edison上のプロセスのkill
- Edison上でプログラムの実行
- 上記の集約
下準備
Edisonの設定
Edisonにはsshでアクセスします。
事前にEdisonにsshの鍵を渡しておいてください
Edisonにscreenで接続して、
# mkdir .ssh
# echo "公開鍵をコピペ" >> .ssh/authorized_keys
godoの導入
go get
最高ありがとう
go get -u gopkg.in/godo.v2/cmd/godo
godo用の領域の作成
godoはワーキングディレクトリ下にGododirというディレクトリを作って
その下にmain.goを作って設定をします
mkdir Gododir
Edison開発用の枠組み
Edison開発のための枠組みを設定しておきます
まず、自分のEdisonのドメイン([エジソンの名前].local
)を確認しておきます
僕の場合は初期設定どおりのedison.local
です
また、ユーザーはroot
で動かします
godoのTaskは、標準ライブラリのtext/template
のように、
Taskに渡すコマンド内の"{{.option}}"みたいな領域にテンプレート処理ができます
options do.M
は、そのためのmapになっています
package main
import (
"strings"
do "gopkg.in/godo.v2"
)
// EdisonHost is host name of edison
var EdisonHost = "edison.local"
// EdisonUser is user name of edison
var EdisonUser = "root"
// AppName is Application Name
var AppName = "EdisonProgram"
var options = do.M{
"app_name": AppName,
"edison_host": EdisonUser + "@" + EdisonHost,
"edison_user": EdisonUser,
}
func tasks(p *do.Project) {
// ここにTaskを並べていく
}
func main() {
do.Godo(tasks)
}
ソースからEdison向けにビルド
念願のクロスコンパイルを実行します
p.Task("build", nil, func(c *do.Context) {
c.Run("GOOS=linux GOARCH=386 go build")
})
Edisonへ転送
scpを使います
p.Task("copy_to_edison", nil, func(c *do.Context) {
c.Run("scp {{.app_name}} {{.edison_host}}:/home/{{.edison_user}}", options)
})
Edison上のプロセスのkill
トリッキーなことをしています
psをgrepしてgrep以外のプロセスが走っていたらのidを抜き出してkillします
killallで殺すとプロセスが走ってないときにステータスコードが1で帰ってきてエラーで止まってしまった
エラーも標準入力に逃がすようにすれば動くのかもしれない
p.Task("killall_process", nil, func(c *do.Context) {
// もしかしてこれで動く?
// c.Run("ssh {{.edison_host}} killall -q {{.app_name}} 2>&1", options)
ps := c.RunOutput("ssh {{.edison_host}} 'ps | grep {{.app_name}}'", options)
rows := strings.Split(ps, "\n")
ids := []string{}
for _, row := range rows {
if strings.Contains(row, AppName) && !strings.Contains(row, "grep") {
ids = append(ids, strings.Split(strings.Trim(row, " \n"), " ")[0])
}
}
if len(ids) != 0 {
addOptions := options
addOptions["ps_ids"] = strings.Join(ids, " ")
c.Run("ssh {{.edison_host}} 'kill {{.ps_ids}}'", addOptions)
}
})
Edison上でプログラムの実行
sshでコマンドを送信するのに、適当に&をつけてもバックグラウンド実行できなくてハマりまくった
いろいろ試した結果これで動いた
p.Task("exec_process", nil, func(c *do.Context) {
c.Run("ssh {{.edison_host}} 'nohup /home/{{.edison_user}}/{{.app_name}} > /home/{{.edison_user}}/output.log 2>&1 &'", options)
})
上記の集約
デフォルトのTaskを設定します
do.SでTaskを渡すと、SerialにTaskを実行してくれます
do.Pだと、Prallelです
do.S{"task1", do.P{"task2-1", "task2-2"}, "task3"}
とかもできるっぽい
今回は並列に動かす必要はないので、do.Sだけです(buildとkillall_processは並列でもいいかな)
Taskに対してSrc()を設定しておくと、監視対象を設定してくれる
defaultTask := do.S{
"build",
"killall_process",
"copy_to_edison",
"exec_process",
}
p.Task("default", defaultTask, nil).Src("**/*.go")
実行
godo
で、デフォルトのタスクが実行される
godo --watch
で起動しておけば、ソースを保存する毎に実行してくれる