LoginSignup
6
5

More than 5 years have passed since last update.

タスクランナーgodoをEdison+Gobot開発に導入してみた

Last updated at Posted at 2016-12-01

追記:2017/03/06
いろいろあってソースを読んだところ、GobotのEdison用ドライバーはsysfsを直接叩きに行ってることがわかりました
特にPWM周りで不満が大きくて、Gobotからはduty比しかいじくれないけど、Edisonの仕様でduty比0でも僅かに出力が出るので
結局最後はライブラリのソースを改変しながら使っていました(それでも不具合が出る、sysfsをプログラムから叩くのが原因ぽい?)
あまりそれらがよろしくないと思ったので、今度はgolangでmraaのCライブラリを直接触ってみたいところです

golangのクロスコンパイルを何かに使ってみたかった矢先、Gobotというものを知ったので、
Edison開発にgolangで挑戦してみることにした
それにあたって、開発環境作りに精を出した話

できたこと

ソースを保存すると、
1. ビルドして
2. Edisonに転送され
3. 今までのプロセスを殺して
4. 新たなプログラムが走ってくれます
ソースの監視を切って、手動で上記を実行するのもできます

godoとは

godo is a task runner and file watcher for golang in the spirit of rake, gulp.

golang製のタスクランナー
gulpみたいなもの

やりたいこと

  1. ソースからEdison向けにビルド
  2. Edisonへ転送
  3. Edison上のプロセスのkill
  4. Edison上でプログラムの実行
  5. 上記の集約

下準備

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になっています

Gododir/main.go
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

で起動しておけば、ソースを保存する毎に実行してくれる

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5