2020年3月11日

[slack]もしかしてRTMはもう使えなくなったのか(2020/03/11)?

私が以前、Pythonで使ったslackbotはこちらだ。

https://github.com/lins05/slackbot

RTMを使っている、と。
まあ、そうなるよねぇ。

ただ、New Slack appsはRTMは使わんだろう、と書いてある。

https://api.slack.com/rtm

では、Newだとどんな感じでイベントを受け取るのだろうか。

https://api.slack.com/authentication/basics#listening

うーん・・・Request URLは必須なのか?

 

おお、ツール一覧があるのか。

https://api.slack.com/tools

しかし、PythonとかJavaScriptとかしかない。。。


うん、RTMを使わないでやる方法はあきらめたよ、私は。
RTMを使おう。

まずは、classic Slack appを選択。

https://api.slack.com/apps?new_classic_app=1

そして、適当にOAuth & Permissionsの設定をして、Bot User OAuth Access Tokenを取得。
どのパーミッションがいるのか分からないので、やりながら調べるのだ。
とりあえず、channels:readを追加しないとchannelsのサンプルが動作しないことは分かった。
まずは、これだけ付けておこう。

  • app_menthins:read
  • channels:read
  • im:read
  • im:write
  • reactions:write

 

まずは、websocketサンプルが良かろう。
というのも、これは主にイベントログを出すためのサンプルのようだったからだ。

slack-bot: 2020/03/11 21:45:18 websocket_managed_conn.go:177: Connecting to RTM
Event Received: slack-bot: 2020/03/11 21:45:19 rtm.go:71: Using URL:
slack-bot: 2020/03/11 21:45:19 websocket_managed_conn.go:181: Failed to start or connect to RTM: not_allowed_token_type
slack-bot: 2020/03/11 21:45:19 websocket_managed_conn.go:147: reconnection 1 failed: not_allowed_token_type reconnecting in 100ms

not_allowed_token_typeね。うんうん、いい調子だ。

https://api.slack.com/methods/rtm.connect

user tokenにはscopeはいらないが、botの方は"bot"というscopeがいるらしい。
が、botというscopeはdeprecatedっていわれたんだよなぁ。
そして、Scopes Updateとかいう画面に遷移してみると、やたらとチェックが入っていたので、適当に外したのが今の状態なのだ。

もしや・・・あのチェックが入った状態が"bot"を選択したのと同じだったのか・・・?
何も読まずに進めたのがまずかったか。。。

 

再確認だ。

まず、別のclassic appを作って"bot" scopeを追加する。

image

update scopesをクリック。

Step1は、bot token scopeの選択だ。
・・・うん、旧来のscopeには全部チェックが入っているな。
気にせず、一番下にあるcontinueをクリック。

Step2は、user token scopeの選択。
rtm.connectには不要らしいので、これはそのまま。

Step3は確認で、Step4はなんかURLとか出てきた。
疲れてるので、今回はスルー。

 

では、Bot Token Scopesを見ておこう。

  • calls:read
  • calls:write
  • channels:read
  • chat:write
  • dnd:read
  • files:read
  • groups:read
  • im:history
  • im:read
  • im:write
  • mpim:history
  • mpim:read
  • mpim:write
  • pins:write
  • reactions:read
  • reactions:write
  • remote_files:read
  • remote_files:share
  • remote_files:write
  • team:read
  • users:read
  • users:read.email
  • users:write

いやあ、こんなにいらんじゃろう。
とは思うものの、さっき失敗したので、まずはこれらのscopesを追加して試そう。

・・・ダメじゃないか。変わらないじゃないか。。。
もしかして、update scopesしたときに変更したのがまずかったのか?
いや、そうでもないようだ。

 

はっ!
もしかして、Update Scopesしたこと自体がまずかったのか??

image

むう、やはりUpdate Scopesしないとダメなのか・・・。

 

と、Update Scopesに飛んだのだが、よく見ると一番上にメッセージが出ていた。

image

いやいや、それができないから困ってるのだよ。

 

もしかして、ドキュメントの更新が遅れているだけで、今からアプリを追加した人はRTMが使えなくなっているんじゃなかろうか。
「今から」と書いたのは、私が以前作ったPythonのアプリは動いているからだ。

 

動くとか動かないとかいう以前に、設定ができないので利用できなくなってるという気がしている。。。
落ち着くために、しばしSlackは忘れよう。

2020年3月9日

[golang]slackのClassic app

golangでslackのbotを作りたいので検索すると、出てきた。

Go で slack bot - Kohei Yoshida - Medium
https://medium.com/@yukihira/go-%E3%81%A7-slack-bot-b3711d8ae7bd

 

重要な情報は多いが、Event Subscriptionsの設定が必要というのは大きかった。
ソースコードもあるし、動かせばわかるは・・・

 

あれ、Event Subscriptionsの"Save Changes"がクリックできない。。
ここで入力が必要そうなものはRequest URLか。
イベントが起きたらHTTP POSTで通知するよ、といっているように見えるのだが、気のせいだろうか。

 

もしそうなら、グローバルでアクセスできるサーバを持たない状態では使えないはずだ。
じゃあ、やっぱりWebSocketでやるしかないの?
RTMになるの?
でも、最近はRTMは使わないらしいやん。。。

あれ、でも「Create a Slack App (Classic) 」を選択して作っても、普通に作ったのと同じような設定画面にしか見えない。

Slack has improved how we handle permissions for apps, so you can now request only the specific scopes your app needs. We recommend updating your app’s scopes. Beginning February 21, all new app submissions to the App Directory must use granular permissions. Read our blog post.

これがClassicの方は出てくるのだ。
2月21日を過ぎたから、もう見分けが付かなくなったのだろうか?

2020年3月8日

slackのtokenを取得する(2020/03)

せっかくgolangの勉強をしているのだから、他で作っているものもときどきgolangで書いてみよう。

というわけで、slack botだ。


検索したら出てきた。

https://github.com/slack-go/slack

 

コードはサンプルをまねするとして、token idをどうやって取得するのか忘れた。。。

ここでいいと思う。

https://api.slack.com/authentication/oauth-v2

が、たぶんclassic という方を使うんじゃなかろうか。

https://api.slack.com/apps?new_classic_app=1

ここでCreateするといろいろ出てくるのだが、この画面に出てくるのはVerification Tokenだから別物だ。
左側の"OAuth & Permissions"をクリックすると、たぶん今までのtoken idが取得できる。

 

しかし、これもすんなり取得できるわけではなく、まず"Scopes"の追加をする。
これが、たくさん項目があるのだ・・・。
コンボボックスで1つずつ追加することもできるし、ボタンの"Update Scopes"をクリックすると権限一覧画面に遷移するので、そこでチェックボックスによって同時に設定できるのだろう。
だろうが、設定すると"Update Scopes"のボタンが出てこないのが残念だ。

 

channel一覧を取得するサンプルは動いたのだが、私としてはapp宛のメンションを受け取ったら返事をする、みたいなシンプルなものを試したい。
試したいのだが、permissionがわからん。
eventsapiサンプルが期待するやつかと思うのだが、動かしてもnot_allowed_token_typeになるのだ。

うーむ。。。

[golang]エラーの返し方

C言語の時はあまり悩まなかった・・・いや、どうするかは悩むけど言語的には悩まなかったエラーの返し方。
golangの場合は、だいたいerrorsというものを使っているようだ。

 

Pythonもそうだけど、戻り値を簡単に複数返すことができるというのは楽ですな。
私の場合、C言語ではboolを戻り値にして「成功かエラー」だけにすることが多い。
多いのだが、あまり考えたくないというのと、組み込みだと「エラーが起きる=リセットするしかない or バグ」ということがほとんどだったので、戻り値を用意してスタックを使ってしまうくらいだったらvoidでいいや、みたいな気持ちだったからだ。
ほら、エラーが起きたことを知らせるにしても、LEDの点滅とかブザー鳴動だし。

しかし、最近は組み込みじゃないプログラムを書くことが増え、「エラーをユーザに見せる」という技がいるようになってきた。そうするとboolではとても足りず、「intにするか・・・でも戻り値の変更はつらいので引数増やすか・・・でもそれも・・・」とさらに悩むということになってしまっている。

 

その点、複数の戻り値を同時に返すことができるし、戻り値の2番目にerrorsを返しがち、というルールもあるようなので、それに載っかるのが無難だろう。

ただ・・・個人的には暗黙のルールってのがどうも苦手で。
プロジェクトごととか個人のルールはあってしかるべきだが、言語でのルールは仕様に含めてしまってもいいのでは・・・。

いや、そうしたらしたで「なんでここまで縛られるんじゃー」って思うのは間違いないな。


それはともかく、errorsの使い方も調べる。

errors - GoDoc
https://godoc.org/github.com/pkg/errors

発生時はNew()かErrorf()で新規に作るか、伝えられたerrorsに情報を付加して返すかだろう。
WithMessage()とWrap()は同じもののような気がするが、実装はどうなんだろう?

01: func Wrap(err error, message string) error {
02:     if err == nil {
03:         return nil
04:     }
05:     err = &withMessage{
06:         cause: err,
07:         msg:   message,
08:     }
09:     return &withStack{
10:         err,
11:         callers(),
12:     }
13: }
  
01: func WithMessage(err error, message string) error {
02:     if err == nil {
03:         return nil
04:     }
05:     return &withMessage{
06:         cause: err,
07:         msg:   message,
08:     }
09: }
  

Wrap()は、WithMessage()+コールスタック、というところだろうか。

 

普段ならコードを書いて検証するところなのだが、今日は気分が乗らない・・・。

[golang] testを書こう

単体テストというかユニットテストというか、とにかくそういうのを序盤から書いていかないと、後からまとめて書くのはつらい。
テストを作っておくと、書き換えたいときも精神的負担が少ないしね。

 

testing - The Go Programming Language
https://golang.org/pkg/testing/

テストって言うと、どうしても夢のような機構が存在することを期待してしまう。
テストコードを書かなくてもそれっぽいテストコードを自動で書いてくれないだろうか、とか。
とかというか、それしかないが。

もちろん、そういうものはgolangにもない。
あるのは、テストコードがしくみとして考慮されている、ということか。

  • テストしたいファイルの名前は、後ろに"_test.go"にする
  • テストのsignatureは、TestXxx(t *testing.Test)
  • NGルートはt.Errorf()みたいな感じで終わらせる
    • Skip()などもあるらしい

 

raw_test.go

01: package pcd_test
02: 
03: import (
04:     "bytes"
05:     "testing"
06: 
07:     "github.com/hirokuma/go_pasori370/pcd"
08: )
09: 
10: func TestRawEncode(t *testing.T) {
11:     var msg pcd.RawMsg
12: 
13:     msg.Cmd = 0x18
14:     msg.Data = []uint8{0x01}
15:     data := pcd.RawEncode(&msg)
16: 
17:     model := []uint8{
18:         0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD4, 0x18, 0x01, 0x13, 0x00,
19:     }
20:     if bytes.Compare(model, data) != 0 {
21:         t.Fatalf("not same")
22:     }
23: }
  

src/github.com/hirokuma/go_pasori370/pcd$ go test .
ok      github.com/hirokuma/go_pasori370/pcd    0.002s
src/github.com/hirokuma/go_pasori370/pcd$ go test .
ok      github.com/hirokuma/go_pasori370/pcd    (cached)
src/github.com/hirokuma/go_pasori370/pcd$ go clean -testcache
src/github.com/hirokuma/go_pasori370/pcd$ go test .
ok      github.com/hirokuma/go_pasori370/pcd    0.003s

1つ上のディレクトリからだと、こう。

src/github.com/hirokuma/go_pasori370$ go test ./...
?       github.com/hirokuma/go_pasori370/cmd    [no test files]
?       github.com/hirokuma/go_pasori370/dev    [no test files]
ok      github.com/hirokuma/go_pasori370/pcd    0.003s

ちなみに、"./..."じゃなくて"..."とすると、golangの大元からテストするようだ。

 

いくつかわかったこと。

  • _test.goのパッケージ名は、オリジナルと同じか、後ろに_testをつけるのが一般的か
    • オリジナルと同じだとimportがいらん
    • 外部から使ったのと同じようにしたいなら、_testを付けるのがいいのかな
  • _test.goのオリジナルにある関数を全部テスト関数にしなくても怒られない
    • -vつけてリストを出すしかないか
    • -coverで漏れたところを感じとるのがよいのかなぁ
  • テストの結果はキャッシュされる
    • go clean -testcache
  • テストとは関係ないが、配列やsliceの固定値は定義できない
    • 全部RAMに展開するということか

2020年3月7日

[golang]RC-S956 encode部分だけ

golangで、とりあえずRC-S956に送信するデータだけ作ることにした。

01: package pcd
02: 
03: // RawMsg raw data
04: type RawMsg struct {
05:     Cmd  uint8
06:     Data []uint8
07: }
08: 
09: // RawEncode encode RC-S956 format
10: func RawEncode(msg *RawMsg) []uint8 {
11:     data := make([]uint8, 3+2+1+1+len(msg.Data)+2)
12:     data[0] = 0x00
13:     data[1] = 0x00
14:     data[2] = 0xff
15:     data[3] = (uint8)(1 + 1 + len(msg.Data))
16:     data[4] = (uint8)(-data[3])
17:     data[5] = 0xd4
18:     data[6] = msg.Cmd
19:     copy(data[7:], msg.Data[:])
20: 
21:     sum := uint(0xd4 + msg.Cmd)
22:     for _, v := range msg.Data {
23:         sum += uint(v)
24:     }
25:     data[7+len(msg.Data)] = uint8(-sum)
26:     data[7+len(msg.Data)+1] = 0x00
27: 
28:     return data
29: }
30: 
31: // RawDecode decode RC-S956 format
32: func RawDecode(data []uint8) *RawMsg {
33:     return new(RawMsg)
34: }
  

たったこれだけ(しかもDecodeはやってない)のだが、けっこう時間がかかった。

 

structの形を決める

私はどうも、汎用的に作るのが苦手だし、拡張とか考えるのも苦手なようだ。

今回はgolangなので、メモリの確保については悩まずに済むし、ステップ数を減らす努力もしなくてよいだろう。
0x00の代入は省略しようかどうかと多少悩んだのだが、そこはコンパイラに任せよう。
データ構造としては、書いていた方がわかりやすいから、今回は書いた。

 

0xD4がメインコマンドなのだが、それはもう取り込んだ形にした。
わざわざメインコマンドとサブコマンドをユーザに指定させる必要はあるまい。
そもそも、0xD4以外で始まるパターンってよく知らんし。

 

というわけで、structはサブコマンドとそのパラメータのみになった。
パラメータはmakeして渡すイメージだ。

 

packageパス

またしてもpackageパスに悩まされた。

今回はmainパッケージは呼ぶだけにするつもりなので、github.com/hirokuma/go_pasori370/cmdの下にmain.goを置くことにした。
それ以外はgithub.com/hirokuma/go_pasori370/pcdに。

main.goでimportするのは"github.com/hirokuma/go_pasori370/pcd"でよかったのだが、go runするパスがよくわからなくなった。

# これはOK
src/github.com/hirokuma/go_pasori370/cmd $ go run .
src/github.com/hirokuma/go_pasori370/cmd $ go run github.com/hirokuma/go_pasori370/cmd

# これはNG
src/github.com/hirokuma/go_pasori370 $ go run cmd
src/github.com/hirokuma/go_pasori370 $ go run cmd/.

カレントディレクトリはいいけど、パス名が入った途端にsrcからの絶対パスと判定されるのか。

 

 

rangeの戻り値

作ったのは良かったのだが、DCSの値があわなかった。
forでぐるぐる回して足しているところだ。

for v := range msg.Data {
    sum += uint(v)
}

これは、ダメなのね。
正しくは、こう。

for _, v := range msg.Data {
    sum += uint(v)
}

あー、あるある~、って言ってほしいところであった。

 

01: package main
02: 
03: import (
04:     "encoding/hex"
05:     "log"
06: 
07:     "github.com/hirokuma/go_pasori370/pcd"
08: )
09: 
10: func main() {
11:     var msg pcd.RawMsg
12: 
13:     msg.Cmd = 0x18
14:     msg.Data = []uint8{0x01}
15:     data := pcd.RawEncode(&msg)
16:     log.Printf("data: %s\n", hex.Dump(data))
17: }
  

$ go run github.com/hirokuma/go_pasori370/cmd
2020/03/07 20:52:12 data: 00000000  00 00 ff 03 fd d4 18 01  13 00                    |..........|

久々すぎて値に自信がないのだが、まあ実際に流して見れば分かるだろう。

[golang][gousb]デバイスの認識

では、gousbを使ってみよう。

 

exampleを使う。

https://github.com/google/gousb/blob/master/example_test.go

まあ、まずはデバイスを認識できるかどうかからだ。
libusb-1.0がいるから、事前にaptなりなんなりでインストールしておく。

01: // sudo apt install libusb-1.0
02: 
03: package main
04: 
05: import (
06:     "fmt"
07:     "log"
08: 
09:     "github.com/google/gousb"
10: )
11: 
12: const (
13:     VID = 0x054c
14:     PID = 0x02e1
15: )
16: 
17: // https://github.com/google/gousb/blob/master/example_test.go
18: func exampleSimple() {
19:     // Initialize a new Context.
20:     ctx := gousb.NewContext()
21:     defer ctx.Close()
22: 
23:     // Open any device with a given VID/PID using a convenience function.
24:     dev, err := ctx.OpenDeviceWithVIDPID(VID, PID)
25:     if err != nil {
26:         log.Fatalf("Could not open a device: %v", err)
27:     }
28:     defer dev.Close()
29: 
30:     // Claim the default interface using a convenience function.
31:     // The default interface is always #0 alt #0 in the currently active
32:     // config.
33:     intf, done, err := dev.DefaultInterface()
34:     if err != nil {
35:         log.Fatalf("%s.DefaultInterface(): %v", dev, err)
36:     }
37:     defer done()
38: 
39:     // Open an OUT endpoint.
40:     ep, err := intf.OutEndpoint(7)
41:     if err != nil {
42:         log.Fatalf("%s.OutEndpoint(7): %v", intf, err)
43:     }
44: 
45:     // Generate some data to write.
46:     data := make([]byte, 5)
47:     for i := range data {
48:         data[i] = byte(i)
49:     }
50: 
51:     // Write data to the USB device.
52:     numBytes, err := ep.Write(data)
53:     if numBytes != 5 {
54:         log.Fatalf("%s.Write([5]): only %d bytes written, returned error is %v", ep, numBytes, err)
55:     }
56:     fmt.Println("5 bytes successfully sent to the endpoint")
57: }
58: 
59: func main() {
60:     exampleSimple()
61: }
  

$ go run .
2020/03/07 11:04:01 Could not open a device: libusb: bad access [code -3]
exit status 1

まあ、いきなりは動かんか。

 

デバイスを使う場合、sudoとかしないといけなさそうな気がするのだが、lsusbなんかはsudoしなくても使える。
wiresharkなんかはグループを作っておけばsudoせずにいけたのだが、libusbはどうだったか。

L.24のctx.OpenDeviceWithVIDPID(VID, PID) で起きている。
C言語で書くとこんな感じだった。

https://github.com/hirokuma/libhknfcrw_c/blob/master/examples/cyg_devaccess_pasori.c#L109

sudoしてみる。

$ sudo ./go_pasori1
2020/03/07 11:11:44 vid=054c,pid=02e1,bus=2,addr=5.DefaultInterface(): failed to select interface #0 alternate setting 0 of config 1 of device vid=054c,pid=02e1,bus=2,addr=5: failed to claim interface 0 on vid=054c,pid=02e1,bus=2,addr=5,config=1: libusb: device or resource busy [code -6]

これはL.35のメッセージだから、権限の問題ということか。

udevで設定を書くことで回避できるようだが、しばらくはsudoでよかろう。


では、次はL.35のエラーについてだ。
Busyっぽいのだが、私はRC-S370をVirtualBoxのデバイス指定で使えるようにしただけだ。

dmesgを見てみる。

[15145.225082] usb 2-2: new full-speed USB device number 6 using ohci-pci
[15146.112505] usb 2-2: New USB device found, idVendor=054c, idProduct=02e1
[15146.112507] usb 2-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[15146.112509] usb 2-2: Product: RC-S370/P
[15146.112511] usb 2-2: Manufacturer: Sony
[15146.124738] usb 2-2: NFC: NXP PN533 firmware ver 1.48 now attached

OSがRC-S370をデバイスとして認めてしまっている・・・?
そういえば、いつからかLinuxはNFCデバイス(たぶんReader/Writerだろう)を識別するようになったという記事を見かけた気がする。

RC-S380(Pasori)とLinux NFC Subsystem | 何かできる気がする
https://dekirukigasuru.com/blog/2019/01/05/linux-nfc-subsystem/

Linux NFC Subsystem、ね。
できれば、今回は新しいことは試さず、USBとして動かしたいのだ。

 

そういえばC言語で書いていたときも、default interfaceみたいなものは使えなかった気がする。

https://github.com/hirokuma/libhknfcrw_c/blob/master/examples/cyg_devaccess_pasori.c#L133-L156

おそらく、この辺りはend pointを探しているところだろう。

なので、便利関数を使うサンプルではなく、その下にある方のサンプルを使おう。

https://github.com/google/gousb/blob/master/example_test.go#L76

ここから下を貼り付けただけなので、コードは省略だ。

2020/03/07 11:47:56 vid=054c,pid=02e1,bus=2,addr=6.Config(2): device vid=054c,pid=02e1,bus=2,addr=6: configuration id 2 not found in the descriptor of the device. Available config ids: [1]

"Config(2)"なので、ここのエラーか。

https://github.com/google/gousb/blob/master/example_test.go#L105

そういえば、なんとなくコードを貼り付けて動かしているのだが・・・この2とか3とかってなんなのだ??
エラーログの一番最後に"[1]"となっているから、2じゃなくて1ならconfigがあるということか。

・・・1にすると進んだ。。。
まあいい、最後まで通してから考えよう。

 

次のエラーはこれ。

2020/03/07 11:53:06 vid=054c,pid=02e1,bus=2,addr=6,config=1.Interface(3, 0): descriptor of interface (3, 0) in vid=054c,pid=02e1,bus=2,addr=6,config=1: interface 3 not found, available interfaces 0..0

3じゃなくて0ってことか。
が、0にしたらしたで別のエラーになった。

2020/03/07 11:54:51 vid=054c,pid=02e1,bus=2,addr=6,config=1.Interface(3, 0): failed to claim interface 0 on vid=054c,pid=02e1,bus=2,addr=6,config=1: libusb: device or resource busy [code -6]

ダメだ、このやり方じゃダメだ。。。


C言語で書いていたときのやり方がいいだろう。
調べられるリストを作って、先頭から順に調べて使えるものを探す、と。

改めて、openする流れをおさらいしよう。

https://github.com/hirokuma/libhknfcrw_c/blob/master/examples/cyg_devaccess_pasori.c#L47

  1. libusb_open_device_with_vid_pid()でハンドル取得
  2. libusb_get_device()でデバイス取得
  3. libusb_get_config_descriptor()でコンフィグ取得
  4. コンフィグからinterfaceの数だけEnd Pointを探す?
  5. libusb_claim_interface()でclaim

自分で書いておきながら理解していないが、当時もlibusbのサンプルから持ってきたやり方を使っただけだろう。
コンフィグ中をぐるぐる探すところも、本当はbreakとかして抜けるべき何じゃなかろうかね。。。

01: // sudo apt install libusb-1.0
02: 
03: package main
04: 
05: import (
06:     "fmt"
07:     "log"
08: 
09:     "github.com/google/gousb"
10: )
11: 
12: const (
13:     VID = 0x054c
14:     PID = 0x02e1
15: )
16: 
17: // https://github.com/google/gousb/blob/master/example_test.go
18: func openUsb() {
19:     // Initialize a new Context.
20:     ctx := gousb.NewContext()
21:     defer ctx.Close()
22: 
23:     // Open any device with a given VID/PID using a convenience function.
24:     dev, err := ctx.OpenDeviceWithVIDPID(VID, PID)
25:     if err != nil {
26:         log.Fatalf("Could not open a device: %v", err)
27:     }
28:     defer dev.Close()
29: 
30:     for _, cfg := range dev.Desc.Configs {
31:         for _, inf := range cfg.Interfaces {
32:             for _, alt := range inf.AltSettings {
33:                 for _, endPnt := range alt.Endpoints {
34:                     fmt.Printf("endpoint=%v\n", endPnt)
35:                 }
36:             }
37:         }
38:     }
39: }
40: 
41: func main() {
42:     openUsb()
43: }
  

$ sudo ./go_pasori1
endpoint=ep #4 OUT (address 0x04) bulk [64 bytes]
endpoint=ep #4 IN (address 0x84) bulk [64 bytes]

 

最初、for文でinterface数なんかをがんばって取得してぐるぐる回そうとしていたのだが、lsusbサンプルを見てrangeというものがあったことを思い出す。


おそらくこれでUSBデバイスはオープンできたと思う。

だが、C言語でlibusbを使ったときは最後にlibusb_claim_interface()を呼んでいた。
これは何をする関数なのだろうか?

http://libusb.sourceforge.net/api-1.0/group__dev.html#ga32fabedf5f13fdecf1cb33acdb19b57a

制御するデバイスを主張する、なのか?

C言語版では固定で0を使っていたのだが、果たしてうまくいっていたのだろうか・・・。

 

それはともかく、gousbだとConfig()がそれなのか。
しかし、libusb_bulk_transfer()は無く、代わりにReadStream, WriteStreamを取得してアクセスするようだ。
その辺はサンプルを見た方が早いだろう。

https://github.com/google/gousb/blob/master/example_test.go#L52

 

Closeするのはdevだけでよさそうな気配がする。
これはまだ確信を得られないので、後で考えよう。

2020年3月6日

[golang]USBデバイスを操作できるのか

なんとなくgolangで書いていけるんじゃないかというところまでやってきたと思うので、何か作ることにする。
何かしら1つでも動くものを作れば、見えてくるものがあるんじゃなかろうか。

 

というわけで、今回はUSBデバイスが操作できるのか調べよう。


出てきたのは、これ。

google/gousb: gousb provides low-level interface for accessing USB devices
https://github.com/google/gousb

googleの下にあるってことは、golangの標準ライブラリかと思ったのだが、そうではないようだ。
まあ、普通はUSBデバイスを操作しようとは思わんよな。

 

libusb-1.0を使うらしく、先にインストールがいるそうだ。
ということは、かなりローレベルなところを扱うようにできているのか。

そのおかげか、Windowsでも使えるらしい。
まあ、Windowsでlibusbということは、zadigなんかも使うことになるだろう。

だが、今はgolangをVirtualBox上のLubuntu18.04で動かしているので、USBデバイスもVirtualBox経由になる。
WSLではまだUSBデバイスが扱えなかったと思うし、しばらくはそれでよかろう。


うまくいくかわからないが、昔書いたUSBを制御するアプリをgolangに移植していこう。

C言語でつくり、C++、C#、Javaに持っていき、Androidでも確か動かした気がする。
最初に書いてから何年も経つので、今から書き直すともうちょっとましになるかもしれん(ならない気もするが・・・)。

ともあれ、課題にはほどよいんじゃなかろうか。

2020年3月4日

[golang]defer

関数を抜けるときに実行してくれる処理を指定することができるそうだ。

それがdefer。

Defer, Panic, and Recover - The Go Blog
https://blog.golang.org/defer-panic-and-recover

なぜか知らんが、deferとpanicとrecoverは並べて比べるもののようだ。
まあ、後者2つは知らんのだがね。


deferキーワードを付けた関数はリストに載せられ、リストに載ったものは関数がreturnする際に呼び出されるそうだ。リストは上に積んでいくタイプで、呼ばれるときも上から順番だ。

 

src/ex7/main.go

01: package main
02: 
03: import "fmt"
04: 
05: func main() {
06: 	defer fmt.Printf("Yoshio.\n")
07: 	defer fmt.Printf(" is ")
08: 	defer fmt.Printf("My name")
09: }
  

src$ go run ex7
My name is Yoshio.

ふむ。
では、こうしたらどうなるだろう?

src/ex7/main.go

01: package main
02: 
03: import "fmt"
04: 
05: func main() {
06: 	{
07: 		defer fmt.Printf("Yoshio.\n")
08: 		defer fmt.Printf(" is ")
09: 		defer fmt.Printf("My name")
10: 	}
11: 	defer fmt.Printf("Mao.\n")
12: 	defer fmt.Printf(" are ")
13: 	defer fmt.Printf("You")
14: }
  

src$ go run ex7
You are Mao.
My name is Yoshio.

もしかしたらスコープを抜けるタイミングで実行されるんじゃないかと思ったのだが、returnするところまでリストに積んでから実行しているな。


deferは関数をリストに載せるものなら、こんなのでもよいのだろうか。

01: package main
02: 
03: import "fmt"
04: 
05: func main() {
06:     defer func() {
07:         fmt.Printf("My name")
08:         fmt.Printf(" is ")
09:         fmt.Printf("Yoshio.\n")
10:     }
11: }
  

無名関数、というやつかね(anonymous function、らしい)。

function must be invoked in defer statement

うーん、無名関数は関数ではないのだろうか?

01: package main
02: 
03: import "fmt"
04: 
05: func main() {
06:     defer func() {
07:         fmt.Printf("My name")
08:         fmt.Printf(" is ")
09:         fmt.Printf("Yoshio.\n")
10:     }()
11: }
  

ああ、括弧がこんなところに必要なのね!

src$ go run ex7
My name is Yoshio.

 

括弧に気付いたのはこちらのおかげだ。

go - Multiple defers vs deferred anonymous function - Stack Overflow
https://stackoverflow.com/posts/32541870/edit

そう、私もどっちがいいのか気になって、そもそも書けるのかどうかを試していたのだった。
順番が大切なら、anonymous functionにした方が意図が分かりやすいだろう。

2020年3月3日

[golang]class関数っぽいやつはメソッド

気を抜くと・・・実装をまったくやっていない・・・。
意味が無くても良いので、何か手を動かさねば。

 

というわけで、今回はgolangのclass関数っぽいやつをやってみる。


classっぽい関数というのは、あれだ。

  • 実装としてはstruct
  • 関数名の前に括弧してstructの変数を書いておくと、その変数があたかもグローバル変数であるかのように使用できる

 

名前が分からんのだ・・・。
正式名が分かれば検索できるのだが、どうにも覚える気力が湧かない。

https://golang.org/ref/spec#Function_declarations

あれ・・・funcの直後に関数名が来ているぞ?

https://golang.org/ref/spec#Method_declarations

こっちだ。
そうか、functionとmethodは別物だったのか。
class変数っぽいやつは、Receiverという名前ね。うん、どこかで聞いた覚えはある。

 

というわけで、今回はメソッドをやってみるのだ。


src/ex6/cmd/main.go

01: package main
02: 
03: import "ex6/gogo6"
04: 
05: func main() {
06: 	var da gogo6.Data6
07: 
08: 	da.PrintGogo()
09: }
  

 

src/ex6/gogo6/print_gogo.go

01: package gogo6
02: 
03: import "fmt"
04: 
05: type Data6 struct {
06: 	name string
07: 	age  int
08: }
09: 
10: func (data *Data6) PrintGogo() {
11: 	fmt.Printf("name: %s\n", data.name)
12: 	fmt.Printf("age: %d\n", data.age)
13: }
  

src/ex6$ go run ex6/cmd
name:
age: 0

初期化していないので、デフォルト値が出力されている。
C言語の人としては未初期化の変数を使うのは心が痛むのだが、まあ動くからよかろう。

 

未初期化が嫌なら初期値を設定すれば良いのだが、Data6のメンバは小文字で始まっているので外部からはアクセスできない(undefinedになる)。

この手の構造体は、Newするメソッドも用意するのが一般的だったと思う。
なので、print_gogo.goにこういうメソッドを追加しようとした。

01: func (data *Data6) NewData6() {
02: 	data.name = ""
03: 	data.age = 0
04: }
05: 
06: func (data *Data6) NewData6(name string, age int) {
07: 	data.name = name
08: 	data.age = age
09: }
  

はい、ダメでした。
オーバーロードはないのね。

https://golang.org/doc/effective_go.html#composite_literals

うーん、Newは値の初期化というよりは、普通にメモリを確保する方がよさそうだ。
初期化だけだったらInitがいいのかな?

メモリ確保も、golangだとnewとmakeがあるようだ。
眺めた感じでは、newは1つ、makeはmapとかsliceとかかな。

 

src/ex6/cmd/main.go

01: package main
02: 
03: import "ex6/gogo6"
04: 
05: func main() {
06: 	da := gogo6.NewData6("yoshio", 95)
07: 
08: 	da.PrintGogo()
09: }
  

src/ex6/gogo6/print_gogo.go

01: package gogo6
02: 
03: import "fmt"
04: 
05: type Data6 struct {
06: 	name string
07: 	age  int
08: }
09: 
10: func NewData6(name string, age int) *Data6 {
11: 	data := new(Data6)
12: 	data.name = name
13: 	data.age = age
14: 	return data
15: }
16: 
17: func (data *Data6) PrintGogo() {
18: 	fmt.Printf("name: %s\n", data.name)
19: 	fmt.Printf("age: %d\n", data.age)
20: }
  

src/ex6$ go run ex6/cmd
name: yoshio
age: 95

 

newは解放もあるかもしれんが、今日はここまで。