バーコードリーダーでバーコードを読むと発話するGolangサンプル

 バーコードリーダーでバーコードを読むと発話するPythonサンプルと同じ、バーコードを読むと登録した名前を呼んで挨拶するサンプルアプリをGolangでも作成してみました。

GolangでUSBバーコードリーダーを読む方法

 Python版と同じ様に、USBバーコードリーダーをPaPeRo i 本体に接続されると作られる/dev/input/以下のデバイスファイルを直接オープンしてデータを読み取ります。linux/input.hで定義されているstruct input_eventのフォーマットとしてデータを読み込むにはGolangでは標準パッケージencoding/binaryを利用します。

package main

import (
    "encoding/binary"
    "fmt"
    "os"
    "path/filepath"
)

type Input struct {
    TvSec  int32
    TvUsec int32
    Type   int16
    Code   int16
    Value  int32
}

func main() {
    var fd *os.File
    var err error

    alldev, _ := filepath.Glob("/dev/input/by-id/*-event-kbd")
    if alldev == nil {
        return
    }
    for _, path := range alldev {
        fd, err = os.Open(path)
        if err == nil {
            break
        }
    }
    if fd == nil {
        return
    }

    dat := Input{}
    for {
        err := binary.Read(fd, binary.LittleEndian, &dat)
        if err != nil {
            return
        }
        typ, value := dat.Type, dat.Value
        code := int32(dat.Code)
        fmt.Printf("%d %d %d\n", typ, code, value)
        // この後Shiftキーの状態を考慮して入力文字判定
        // 略
    }
}

type, code, valueについてはPython版の説明をご参照ください。Shiftキーも含めてキーが押された、放されたというイベントが送られてくるのでShiftキーの状態を保持して入力された文字が何かを判定します。

キーコードから文字への変換

 キーコードから文字へ辞書を使って変換するコードはGolangでは以下の様になりました。

const (
    KEY_RESERVED         = 0
    KEY_ESC              = 1
    KEY_1                = 2
    KEY_2                = 3
    KEY_3                = 4
    KEY_4                = 5
    KEY_5                = 6
    KEY_6                = 7
    KEY_7                = 8
    KEY_8                = 9
    KEY_9                = 10
    KEY_0                = 11
    KEY_MINUS            = 12
    KEY_EQUAL            = 13
    KEY_BACKSPACE        = 14
    KEY_TAB              = 15
    KEY_Q                = 16
    // 略
)

var usKeyCharDic [](map[int]string) = [](map[int]string){
    map[int]string{  // Shiftが押されていない場合
        KEY_1:          "1",
        KEY_2:          "2",
        KEY_3:          "3",
        KEY_4:          "4",
        KEY_5:          "5",
        KEY_6:          "6",
        KEY_7:          "7",
        KEY_8:          "8",
        KEY_9:          "9",
        KEY_0:          "0",
        KEY_MINUS:      "-",
        KEY_EQUAL:      "=",
        KEY_TAB:        "\t",
        KEY_Q:          "q",
        // 略
    },
    map[int]string{  // Shiftが押されている場合
        KEY_1:          "!",
        KEY_2:          "@",
        KEY_3:          "#",
        KEY_4:          "$",
        KEY_5:          "%",
        KEY_6:          "^",
        KEY_7:          "&",
        KEY_8:          "*",
        KEY_9:          "(",
        KEY_0:          ")",
        KEY_MINUS:      "_",
        KEY_EQUAL:      "+",
        KEY_TAB:        "\t",
        KEY_Q:          "Q",
        // 略
    },
}

// GetUsKeyboardChar usキーボードのキーコードから文字を取得する
func GetUsKeyboardChar(code int, shift bool) string {
    idx := 0
    if shift {
        idx = 1
    }
    if val, found := usKeyCharDic[idx][code]; found {
        return val
    }
    return ""
}

入力監視とアプリメイン処理

 Golangには非同期APIがなくバーコードのデータを読むbinary.Read()もデータ待ちでブロックします。このため入力監視処理はGoroutineとして動かしchannelでアプリメイン処理へデータを送る構成にします。
 アプリメイン処理はchannelからの受信を契機として動作する状態遷移プログラムとして構成しました。ブラウザからバーコードと名前の組を設定できる様にし、バーコードを読むと対応する名前を発話させています。

// 画面定義
func appConfDef(param *wc.ConfDefPrm, app *App) *wc.ConfDefSt {
    usersTxtVal := "UPKG00645:佐藤,歩\n5060214370165:鈴木,千晴\n1373331:田中,晶"
    callFmtTxtVal := "こんにちは%[1]s%[2]sさん"

    // 画面/データ定義
    genitems := func() []wc.ItemPtr {
        return []wc.ItemPtr{
            wc.Textarea(&wc.ItemPrm{ID: idUsersTxt, Title: "ユーザ定義(バーコード:名字,名前)", Val: usersTxtVal, Size: 50, Rows: 3, Eot: " "}),
            wc.Textarea(&wc.ItemPrm{ID: idCallFmtTxt, Title: "発話文字列(%[1]sが名字、%[2]sが名前に置換されます)", Val: callFmtTxtVal, Size: 50, Rows: 3, Eot: " "}),
            wc.Button(&wc.ItemPrm{ID: idSaveBtn, Title: "保存", Eol: " ", Handler: func(s *wc.ConfDefSt, item *wc.ItemSt, val string, dic map[string]string) bool {
                s.Dump("")
                return true
            }}),
            wc.Button(&wc.ItemPrm{ID: idReloadBtn, Title: "ファイルから読み直す", Eol: " ", Handler: func(s *wc.ConfDefSt, item *wc.ItemSt, val string, dic map[string]string) bool {
                s.Load("")
                return true
            }}),
            wc.Button(&wc.ItemPrm{ID: idDefaultBtn, Title: "デフォルトに戻す", Handler: func(s *wc.ConfDefSt, item *wc.ItemSt, val string, dic map[string]string) bool {
                s.SetDefault()
                return true
            }}),
            wc.Span(&wc.ItemPrm{ID: idVersion, Val: "Ver." + Version, Nosave: true}),
        }
    }
    handler := &wc.ConfDefHandler{
        OnEvent: func(s *wc.ConfDefSt, idstr string, typ string, val string, dic map[string]string, handled bool) {
        },
    }
    return wc.NewConfDefSt(param, handler, genitems)
}

// アプリケーション開始
func (app *App) Start() {
    logger.Info().Msg("app start.")

    var addr, papeurl *string
    addr = flag.String("addr", ":8866", "http service address")
    papeurl = flag.String("wssvr", "ws://localhost:8088/papero", "papero ws address")
    flag.Parse()

    // papero
    app.papeUrl = *papeurl
    app.papero = yp.NewPapero(*papeurl, "", "")
    app.papero.Start()

    // バーコードリーダーコールバック
    barcodeCallback := func(text string) {
        // channelでデータを送る
        app.papero.Ch <- yp.NewUserEvent(MID_BARCODE, text)
    }
    br := NewBarcodeReader(barcodeCallback)
    go br.Run()

    // 設定画面
    conf := appConfDef(&wc.ConfDefPrm{Filename: "config.json"}, app)
    path := "/config"
    title := "設定"
    wc.CreateConfPage(conf, path, title, ``)
    go conf.StartAll(addr, nil)

    // start app loop
    app.setConf(conf)
    app.loop()
}

// アイドル状態イベント処理
func (app *App) InIdle(msg yp.MsgIf) bool {
    switch {
    case msg.IsUserEvent(MID_BARCODE, ""):
        // バーコードリーダーイベント
        bcd := msg.GetDat().(string)
        // バーコードに対応する名前
        txt, ok := app.userDic[bcd]
        if ok {
            // 苗字と名前に分割
            pair := strings.Split(txt, ",")
            if 2 == len(pair) {
                cf, err := app.conf.GetVal(idCallFmtTxt)
                if err == nil {
                    // 設定のフォーマットに名前を埋め込む
                    txt = fmt.Sprintf(cf, pair[0], pair[1])
                }
            }
            // 名前を発話
            app.papero.Speech(txt, nil)
        } else {
            // 名前が見つからなかったのでバーコード値を発話
            app.papero.Speech(bcd, nil)
        }
        app.TransState(msg, app.StateSpeech)
    }
    return true
}

// 略

ソースコードはこちらからダウンロードできます。


0