LoginSignup
1
0

More than 1 year has passed since last update.

Golang(Gobot+Firmata+Arduino)でアナログメーターを作ってみる

Posted at

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のプラットフォームをサポートしている強力なライブラリです。

  1. Golang をインストールする

  1. 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"
)
  1. Firmata を Arduino にインストール

Firmata とは

FirmataはPC - マイコン間でやり取りするためのプロトコルです。 汎用入出力の値の取得・書き込みその他の操作をPC側からArduinoのようなマイコンに対して行う為に使用されます。
https://gist.github.com/hiroeorz/7868628

とあるように PC -マイコン間のやりとりに関して難しいことを考えることなく Golang から操作できます。

インストール方法はとても簡単で Arduino IDE のスケッチ例から選択して書き込むだけです。

スクリーンショット 2021-10-07 11.20.24.png

今回は 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.

  1. 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の大変素晴らしい点なのですが、LedDriverServoDriverなどドライバーが標準で用意されているので、こちらを宣言するだけで、簡単にデバイスを制御できます。

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に設定値を読み込ませていきます。
firmataAdaptorPIN番号を各種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にバインドさせて、GobotNewRobot関数に渡します。

その際、必ずsubLoopで使用するデバイスに関しての設定等も[]gobot.Deviceに渡しておきます。

最後に変数robotにバインドさせていたNewRobot関数をrobot.Start()で実行します。

最後にmain関数でsubLoopgo routineで実行したのちに、mainLoopを実行します。
レシーバを有効にするためdevice構造体をmain関数内で宣言し、各種*Loop関数をレシーバ経由で呼び出します。

func main() {
    c := device{}
    c.keyflag = "init"
    go c.subLoop()
    c.mainLoop()
}

所感

今回はアナログメーターを作るだけの簡単な例でした、
Gobotという名称からもイメージできると思いますがロボティクス開発やIoT開発において利用されることが想定されています。

Next generation robotics/IoT framework

今後も積極的にお家IoT開発等に使っていきたいなと思いました。

参考

1
0
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
1
0