Golang(Gobot+Firmata+Arduino)でアナログメーターを作ってみる
Golang(Gobot+Firmata+Arduino)でアナログメーターを作ってみる
こんにちは、最近は色々検索しまくっていて、ブラウザのタブを開きすぎて、フリーズしがちな毎日です。
今回は PC の状態を素早く確認できる CPU,Memory,Disk の使用率を可視化するアナログメーターを作ってみました。
前から気になっていた Gobot 等を使って実践してみました。
成果物
Gobot セットアップ
Gobot とは
Next generation robotics/IoT framework with support for 35 different platforms
次世代 robotics/IoT フレームワークですね、
35のプラットフォームをサポートしている強力なライブラリです。
- Golang をインストールする
- Gobot をインストールする
$ go get -d -u gobot.io/x/gobot/...
具体的な使い方としては、import
部分で各種使用したいGobot
のライブラリを読み込ませると良いです。
package main
import (
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/gpio"
"gobot.io/x/gobot/platforms/firmata"
)
- Firmata を Arduino にインストール
Firmata とは
FirmataはPC - マイコン間でやり取りするためのプロトコルです。 汎用入出力の値の取得・書き込みその他の操作をPC側からArduinoのようなマイコンに対して行う為に使用されます。
https://gist.github.com/hiroeorz/7868628
とあるように PC -マイコン間のやりとりに関して難しいことを考えることなく Golang から操作できます。
インストール方法はとても簡単で Arduino IDE のスケッチ例から選択して書き込むだけです。
今回は StandardFirmata
を使用します。
詳しくは本家のサイトで確認してください。
Firmata is a generic protocol for communicating with microcontrollers from software on a host computer. It is intended to work with any host computer software package. To download a host software package, please click on the following link to open the list of Firmata client libraries in your default browser.
- Gobotを使ってプログラムを書く
今回はメーターの駆動部分にサーボモーターを使います。
またArduinoの種類についても詳しくはGitHubに準備した成果物中に記載しているので確認してみてください。
また今回PCのCPU,MEM,DISKの使用状況をモニタリングするためgopsutil
というライブラリを使用しています。
Goの公式documentにも記載があります。
https://github.com/Iovesophy/analog-meter-go/blob/master/hardware/Parts.md
package main
import (
"fmt"
"log"
"time"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/mem"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/gpio"
"gobot.io/x/gobot/platforms/firmata"
"gobot.io/x/gobot/platforms/keyboard"
)
まずは必要なライブラリをimport
します。
次にリテラルで持っておきたい値をconst
で定義しておきます。
const (
MeterMax uint8 = 165
MeterMin = 15
)
今回はサーボモーターの駆動範囲には±15
度遊びを持たせています。
最大角度をMeterMaxとしてリテラルに165
を設定します。
最小角度をMeterMinとしてリテラルに15
を設定します。
次に構造体として使用する電子パーツ等を宣言しておきます。
せっかくGoで書くので今回はパーツとパーツの制御に関わるコンポーネントをdevice
という構造体に持たせて利用することにしました。
このようにデバイスとして括ってあげることで*loop
関数内で見通しがつくやすくなります。
また、Gobot
の大変素晴らしい点なのですが、LedDriver
やServoDriver
などドライバーが標準で用意されているので、こちらを宣言するだけで、簡単にデバイスを制御できます。
type device struct {
keys *keyboard.Driver
keyflag string
ledGreen *gpio.LedDriver
ledBlue *gpio.LedDriver
ledYellow *gpio.LedDriver
servoMotor *gpio.ServoDriver
angleBuf uint8
}
次にデバイスの設定を行います。
ここではレシーバとしてc *device
を定義して先程宣言したdevice
に設定値を読み込ませていきます。
firmataAdaptor
とPIN番号
を各種Gobotのgpio.New*
関数に渡してあげることで設定完了です。
func (c *device) deviceSettings(firmataAdaptor *firmata.Adaptor) {
c.servoMotor = gpio.NewServoDriver(firmataAdaptor, "5")
c.ledYellow = gpio.NewLedDriver(firmataAdaptor, "3")
c.ledGreen = gpio.NewLedDriver(firmataAdaptor, "11")
c.ledBlue = gpio.NewLedDriver(firmataAdaptor, "13")
}
次に起動時やキー入力受信時などの制御内容をinitMotion
として記述します。
Toggle
というのはトグルスイッチのイメージでこのような形でGobot
を利用することによってより読みやすく記述でき、大変直感的にデバイス操作を実現できます。
func (c *device) initMotion() {
c.angleBuf = MeterMax
c.servoMotor.Move(c.angleBuf)
for i := 0; i < 5; i++ {
c.ledBlue.Toggle()
c.ledYellow.Toggle()
c.ledGreen.Toggle()
time.Sleep(time.Millisecond * 100)
}
}
次にせっかくGoを使っているのでマルチスレッドにシングルコアしか持たないArduinoを操作してみましょう。
mainLoop
関数とsubLoop
関数を準備してそれぞれの *Loop
をマルチスレッドに実行させることでキー入力の割り込み処理を実現します。
subLoop関数
func (c *device) subLoop() {
for {
time.Sleep(time.Second)
if c.keyflag == "cpu" {
p, err := cpu.Percent(0, false)
if err != nil {
log.Fatal(err)
}
angleRaw := calcAngleRaw(p[0])
angle := MeterMax - angleRaw
if c.angleBuf <= angle {
for i := c.angleBuf; i < angle; i++ {
c.servoMotor.Move(i)
time.Sleep(time.Millisecond * 50)
}
} else if c.angleBuf >= angle {
for i := c.angleBuf; i > angle; i-- {
c.servoMotor.Move(i)
time.Sleep(time.Millisecond * 50)
}
}
c.angleBuf = angle
} else if c.keyflag == "mem" {
m, err := mem.VirtualMemory()
if err != nil {
log.Fatal(err)
}
angleRaw := calcAngleRaw(m.UsedPercent)
c.servoMotor.Move(MeterMax - angleRaw)
} else if c.keyflag == "disk" {
d, err := disk.Usage("/Volumes")
if err != nil {
log.Fatal(err)
}
angleRaw := calcAngleRaw(d.UsedPercent)
c.servoMotor.Move(MeterMax - angleRaw)
}
}
}
func calcAngleRaw(d float64) uint8 {
return uint8(d / 100 * float64(MeterMax-MeterMin))
}
gopsutil
では次のように例えばCPU使用率を取得したい場合
cpu.Percent(0, false)
と記述すればCPU使用率をパーセントで取得できます。
これらの値をサーボモータで設定している駆動領域に正規化させるためにcalcAngleRaw
関数を定義して計算しています。
func calcAngleRaw(d float64) uint8 {
return uint8(d / 100 * float64(MeterMax-MeterMin))
}
また、CPU使用率に関しては変動が大きいので、サーボモーターの駆動音がうるさい場合があります。
これを解消するために1度ずつその時点でのCPU使用率の割合までwaitをかけながら制御しています。
p, err := cpu.Percent(0, false)
if err != nil {
log.Fatal(err)
}
angleRaw := calcAngleRaw(p[0])
angle := MeterMax - angleRaw
if c.angleBuf <= angle {
for i := c.angleBuf; i < angle; i++ {
c.servoMotor.Move(i)
time.Sleep(time.Millisecond * 50)
}
} else if c.angleBuf >= angle {
for i := c.angleBuf; i > angle; i-- {
c.servoMotor.Move(i)
time.Sleep(time.Millisecond * 50)
}
}
c.angleBuf = angle
mainLoop関数
func (c *device) mainLoop() {
firmataAdaptor := firmata.NewAdaptor("/dev/tty.usbmodem142101")
c.deviceSettings(firmataAdaptor)
c.keys = keyboard.NewDriver()
c.keys.On(keyboard.Key, func(keydata interface{}) {
key := keydata.(keyboard.KeyEvent)
c.initMotion()
if key.Key == keyboard.P {
c.keyflag = "cpu"
c.ledBlue.Off()
c.ledYellow.Off()
c.ledGreen.On()
} else if key.Key == keyboard.M {
c.keyflag = "mem"
c.ledBlue.Off()
c.ledYellow.On()
c.ledGreen.Off()
} else if key.Key == keyboard.D {
c.keyflag = "disk"
c.ledBlue.On()
c.ledYellow.Off()
c.ledGreen.Off()
} else if key.Key >= 97 && key.Key <= 122 {
c.keyflag = "unknownkey"
c.ledBlue.Off()
c.ledYellow.Off()
c.ledGreen.Off()
c.servoMotor.Move(165)
}
fmt.Println(c.keyflag)
})
robot := gobot.NewRobot("bot",
[]gobot.Connection{firmataAdaptor},
[]gobot.Device{
c.keys,
c.servoMotor,
c.ledBlue,
c.ledYellow,
c.ledGreen,
},
c.keys, //set workspace
)
robot.Start()
}
mainLoop
関数では、キー入力を受け付ける部分を書いています。
また、ハンドラにキー入力とsubLoop
で使用するフラグを定義するために制御内容を記述してc.keys
にバインドさせて、Gobot
のNewRobot
関数に渡します。
その際、必ずsubLoop
で使用するデバイスに関しての設定等も[]gobot.Device
に渡しておきます。
最後に変数robot
にバインドさせていたNewRobot
関数をrobot.Start()
で実行します。
最後にmain
関数でsubLoop
をgo routine
で実行したのちに、mainLoop
を実行します。
レシーバを有効にするためdevice
構造体をmain
関数内で宣言し、各種 *Loop
関数をレシーバ経由で呼び出します。
func main() {
c := device{}
c.keyflag = "init"
go c.subLoop()
c.mainLoop()
}
ケースを取り付けた様子
今回はケースと文字盤土台、針は自らモデリングし、3Dプリンターで印刷しました。
所感
今回はアナログメーターを作るだけのとても簡単な例でしたが、Gobot
という名称からもイメージできると思いますがロボティクス開発やIoT開発において利用されることが想定されています。
Next generation robotics/IoT framework
今後も積極的にお家IoT開発等に使っていきたいなと思いました。
参考
- Gobot: https://gobot.io/
- gopstil: https://pkg.go.dev/github.com/shirou/gopsutil
- 「CPUやメモリなどの情報を取得するgopsutilのご紹介」 http://tdoc.info/blog/2015/12/16/gopsutil.html
- Arduino: https://store-usa.arduino.cc/products/arduino-uno-rev3/?selectedStore=us
- ServoMotor: http://www.kumantech.com/kuman-mg90s-9g-size-metal-gear-micro-servo-for-plane-helicopter-boat-car-robot-ky61_p0228.html
Discussion