2019年8月31日土曜日

golang で pid を指定してプロセスを kill する方法

概要

exec.CommandStart した場合、そのまま Kill を呼べれば良いですが処理が終了する場合は呼べません
そんな場合には pid からプロセスを生成し kill しましょう

環境

  • macOS 10.14.6
  • go 1.12.9

サンプルコード

os.FindProcess(pid) で新規にプロセスを生成できます
あとはこれの Kill() 関数をコールするだけです

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    cmd := exec.Command("sleep", "100")
    err := cmd.Start()
    pid := cmd.Process.Pid
    fmt.Println(pid)
    if err != nil {
        fmt.Println(err)
        return
    }
    process, err := os.FindProcess(pid)
    if err != nil {
        fmt.Println(err)
        return
    }
    err = process.Kill()
    if err != nil {
        fmt.Println(err)
        return
    }
    cmd.Wait()
}

2019年8月30日金曜日

json: cannot unmarshal object into Go struct field Profile.error of type error

概要

error タイプは json.Marshal すると {} に変換されます
このまま json.Unmarshal すると {} は error タイプでないと言われてエラーになります
しかし JSON でエラーを管理したい場合があります
そんな場合はエラーメッセージだけを JSON で管理するようにしましょう

環境

  • macOS 10.14.6
  • go 1.11.5

エラーが発生するコード

package main

import (
    "encoding/json"
    "errors"
    "fmt"
)

type Profile struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    Err  error  `json:"error"`
}

func main() {
    p := Profile{
        Name: "hawksnowlog",
        Age:  99,
        Err:  errors.New("test"),
    }
    j, err := json.Marshal(p)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(j))

    p2 := Profile{}
    err = json.Unmarshal(j, &p2)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(p2.Name)
    fmt.Println(p2.Age)
    fmt.Println(p2.Err)
}

=> {"name":"hawksnowlog","age":99,"error":{}}

素直に実行するとこんな感じで JSON が表示されそのあとパースエラーが表示されます

対応したコード

今回は ErrMsg というフィールドを構造体に一つ追加しそのフィールドを JSON 用のフィールドとして使います
そして MarshalJSONUnmarshalJSON を実装することで JSON <-> struct の相互変換時にエラーの情報を復元します

また json:"-" をフィールドに追加することでそのフィールドを JSON に含めないようにします

package main

import (
    "encoding/json"
    "errors"
    "fmt"
)

type Profile struct {
    Name   string `json:"name"`
    Age    int    `json:"age"`
    Err    error  `json:"-"`
    ErrMsg string `json:"error"`
}

func (p *Profile) UnmarshalJSON(b []byte) error {
    type Alias Profile
    p2 := Alias{}
    err := json.Unmarshal(b, &p2)
    if err != nil {
        return err
    }
    p.Name = p2.Name
    p.Age = p2.Age
    p.Err = errors.New(p2.ErrMsg)
    return nil
}

func (p Profile) MarshalJSON() ([]byte, error) {
    type Alias Profile
    p2 := Alias{
        Name:   p.Name,
        Age:    p.Age,
        Err:    p.Err,
        ErrMsg: p.Err.Error(),
    }
    ret, err := json.Marshal(p2)
    if err != nil {
        return nil, err
    }
    return ret, nil
}

func main() {
    p := Profile{
        Name: "hawksnowlog",
        Age:  99,
        Err:  errors.New("test"),
    }
    j, err := json.Marshal(p)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(j))

    p2 := Profile{}
    err = json.Unmarshal(j, &p2)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(p2.Name)
    fmt.Println(p2.Age)
    fmt.Println(p2.Err)
}

=> {"name":"hawksnowlog","age":99,"error":"test"}

となり構造体もちゃんと復元されます
また type をエイリアスしてあげることも大事でこれをしないと json.Marshaljson.Unmarshal をコールしたときに無限ループになるので注意しましょう

参考サイト

2019年8月29日木曜日

redigo でネストな構造体を扱う方法

概要

前回 redigo の基本的な使い方を紹介しました
redis を使っていると複雑な構造を持つデータを格納したくなります
今回は redigo で階層的な構造体を扱う方法を紹介します 

環境

  • CentOS 7.6.1810
  • go 1.12.1
    • redigo d3876d43bbbeb98b4d67309b4d3eef49438ef74c

結論

ハッシュを使って ScanStruct で構造体にマッピングすることはできないようです
https://github.com/gomodule/redigo/issues/52

結論としては JSON 文字列を set し取り出すときは get でまず文字列として取り出しそれを構造体にマッピングします

以下サンプルコードです

サンプルコード

package main

import (
    "encoding/json"
    "fmt"
    "github.com/gomodule/redigo/redis"
    "math"
    "time"
)

type Profile struct {
    Name      string    `json:"name"`
    Age       int       `json:"age"`
    Address   Address   `json:"address"`
    Test      Test      `json:"test"`
    CreatedAt time.Time `json:"created_at"`
}

type Address struct {
    Zip  string `json:"zip"`
    City string `json:"city"`
}

type Test struct {
    I64 int64   `json:"i64"`
    F64 float64 `json:"f64"`
}

func main() {
    c, err := redis.Dial("tcp", "localhost:6379")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer c.Close()
    // nested struct
    p := Profile{
        Name: "hawksnowlog",
        Age:  99,
        Address: Address{
            Zip:  "123-4567",
            City: "123-4567",
        },
        Test: Test{
            I64: math.MaxInt64,
            F64: math.MaxFloat64,
        },
        CreatedAt: time.Now(),
    }
    // to json
    j, err := json.Marshal(p)
    if err != nil {
        fmt.Println(err)
        return
    }
    // debug print a json string
    fmt.Println(string(j))
    // set a json string
    _, err = c.Do("set", "profile", j)
    if err != nil {
        fmt.Println(err)
        return
    }

    // get a json string as bytes
    j2, err := redis.Bytes(c.Do("get", "profile"))
    if err != nil {
        fmt.Println(err)
        return
    }
    // bytes json to struct
    p2 := Profile{}
    err = json.Unmarshal(j2, &p2)
    if err != nil {
        fmt.Println(err)
        return
    }
    // debug print struct fields
    fmt.Println(p2.Name)
    fmt.Println(p2.Age)
    fmt.Println(p2.Address.Zip)
    fmt.Println(p2.Address.City)
    fmt.Println(p2.Test.I64)
    fmt.Println(p2.Test.F64)
    fmt.Println(p2.CreatedAt.Day())
}

json.Marshal は結構賢いようで time や int64, float64 もちゃんと構造体に合わせてキャストしてくれました

2019年8月28日水曜日

go-kit の kit/log を使ってみた

概要

go-kit/kit/log は golang 標準のログパッケージにはない強力な機能を提供してくれます
また標準の log パッケージとの親和性もあるため導入がシームレスにできます
今回は基本的な使い方やログのフォーマットの設定方法など紹介したいと思います

環境

  • CentOS 7.6.1810
  • go 1.12.1
    • go-kit/kit dc489b75b9cdbf29c739534c2aa777cabb034954

基本的なロギング方法

まずは基本的なロギング方法です
デフォルトのフォーマットはイコールでつなぎ合わせるフォーマットになっています

  • cd $GOPATH/src/github.com/hawksnowlog/a
  • vim main.go
package main

import (
        "github.com/go-kit/kit/log"
        "os"
)

func main() {
        w := log.NewSyncWriter(os.Stdout)
        logger := log.NewLogfmtLogger(w)
        name := "hawksnowlog"
        age := 99
        logger.Log("name", name, "age", age)
}
  • go fmt github.com/hawksnowlog/a
  • go build github.com/hawksnowlog/a
  • ./a

=> name=hawksnowlog age=99

上記は os.Stdout を使っているため標準出力に出力されます
logger を作成してあとは Log 関数を使えばイコールつなぎのログになります
この logger に対していろいろと設定することで様々なロギングを行うことができます

常に同じ情報をログに含める

例えばリクエスト ID などは常に表示しておきたい情報です
そんな場合には With を使うことでロギング時に常に同じ情報を含めることができます

package main

import (
        "github.com/go-kit/kit/log"
        "os"
)

func main() {
        w := log.NewSyncWriter(os.Stdout)
        logger := log.NewLogfmtLogger(w)
        logger = log.With(logger, "host", "localhost")

        name := "hawksnowlog"
        logger.Log("name", name)
}

=> host=localhost name=hawksnowlog

こんな感じのログになります
この With を使うことでタイムスタンプなどの表示に応用することができます (後述)

JSON フォーマットでロギング

JSON にする場合は NewLogfmtLogger -> NewJSONLogger にすれば OK です

package main

import (
        "github.com/go-kit/kit/log"
        "os"
)

func main() {
        w := log.NewSyncWriter(os.Stdout)
        logger := log.NewJSONLogger(w)
        logger = log.With(logger, "host", "localhost")

        name := "hawksnowlog"
        logger.Log("name", name)
}

=> {"host":"localhost","name":"hawksnowlog"}

タイムスタンプと実行しているプログラムのファイル名を表示する

With を使ってタイムスタンプとファイル名を表示する方法です
デフォルトは UTC なので JST の場合は少し工夫が必要です

package main

import (
        "github.com/go-kit/kit/log"
        "os"
        "time"
)

func main() {
        w := log.NewSyncWriter(os.Stdout)
        logger := log.NewLogfmtLogger(w)
        format := log.TimestampFormat(
                func() time.Time { return time.Now().UTC() },
                time.RFC3339Nano,
                // func() time.Time { return time.Now().UTC().In(time.FixedZone("Asia/Tokyo", 9*60*60)) },
                // time.RFC3339,
        )
        logger = log.With(logger, "timestamp", format, "caller", log.DefaultCaller)

        name := "hawksnowlog"
        logger.Log("name", name)
}

=> timestamp=2019-08-27T20:34:05.1738304758Z00:00 caller=main.go name=hawksnowlog

log.TimestampFormat でフォーマットを指定できます
時刻のフォーマットもいろいろと選択できます
JST の場合はコメントされているほうを使えば OK dえす

ログレベルを設定する

ログレベルを設定することもできます
その場合は github.com/go-kit/kit/log/level と組み合わせます

package main

import (
        "github.com/go-kit/kit/log"
        "github.com/go-kit/kit/log/level"
        "os"
)

func main() {
        w := log.NewSyncWriter(os.Stdout)
        logger := log.NewLogfmtLogger(w)
        // logger = level.NewFilter(logger, level.AllowInfo())
        logger = level.NewFilter(logger, level.AllowDebug())
        // logger = level.NewFilter(logger, level.AllowWarn())
        // logger = level.NewFilter(logger, level.AllowError())

        logger.Log("msg", "no level always show")
        level.Info(logger).Log("msg", "info level")
        level.Debug(logger).Log("msg", "debug level")
        level.Warn(logger).Log("msg", "warn level")
        level.Error(logger).Log("msg", "error level")
}

=> msg="no level alyways show"
=> level=info msg="info level"
=> level=debug msg="debug level"
=> level=warn msg="warn level"
=> level=error msg="error level"

level.NewFilter でログレベルを設定します
上記のように level.AllowDebug() の場合はデバッグレベルのログまででるので詳細なログになります
エラーレベルのログだけで良いのであれば level.AllowError() を設定します
また設定したログレベルに沿ったログを出力したい場合は level.XXX(logger).Log 関数を使ってログを出力するようにしましょう
logger から直接 Log 関数をコールした場合は level に関係なくログが表示されます

最後に

go-kit の kit/log を使ってみました
ロギングのフォーマットやコンテキストロギングなどいろいろと強力な機能が備わっているので便利です
また golang のデフォルトの log パッケージとも親和性があるので互いに干渉しないようにできます

参考サイト

2019年8月27日火曜日

redigo 超入門

概要

gomodule/redigo は golang から Redis にアクセスするためのライブラリです
Sentinel や Redis Cluster を使う場合に別のライブラリと組み合わせる必要があります
今回は基本的な使い方を紹介します

環境

  • CentOS 7.6.1810
  • go 1.12.1
    • redigo d3876d43bbbeb98b4d67309b4d3eef49438ef74c

基本

まずは基本です
Redis Server に接続して info server コマンドを実行してみます

  • cd $GOPATH/src/github.com/hawksnowlog/a
  • vim main.go
package main

import (
        "fmt"
        "github.com/gomodule/redigo/redis"
)

func main() {
        c, err := redis.Dial("tcp", "localhost:6379")
        if err != nil {
                fmt.Println(err)
                return
        }
        defer c.Close()
        fmt.Println(redis.String(c.Do("info", "server")))
}
  • go fmt github.com/hawksnowlog/a
  • go build github.com/hawksnowlog/a
  • ./a

Redis Server の情報が文字列として表示されます
Do 関数を使って redis-cli で使うコマンドをそのまま指定します
サブコマンドがある場合は複数の引数として指定します

set/get

次に set/get コマンドを実行してみます
Redis 上では文字列として管理されますが golang 上で扱う場合には適切な型に変更して使います

  • vim main.go
package main

import (
        "fmt"
        "github.com/gomodule/redigo/redis"
)

func main() {
        c, err := redis.Dial("tcp", "localhost:6379")
        if err != nil {
                fmt.Println(err)
                return
        }
        defer c.Close()
        fmt.Println(c.Do("set", "key", "value"))
        fmt.Println(redis.String(c.Do("get", "key")))
        fmt.Println(c.Do("set", "key2", 0))
        fmt.Println(redis.Int(c.Do("get", "key2")))
}

=> value
=> 0

Do 関数で get したあとで redis.Stringredis.Int を使って golang 上で扱える型に変換します
このあと紹介しますがリストやハッシュなども同じように変換してから使います

リスト型

rpush や lset, lrange の使い方です
ポイントは redis.Strings を使って文字列のスライスに変換してあげる点です

  • vim main.go
package main

import (
        "fmt"
        "github.com/gomodule/redigo/redis"
)

func main() {
        c, err := redis.Dial("tcp", "localhost:6379")
        if err != nil {
                fmt.Println(err)
                return
        }
        defer c.Close()
        fmt.Println(c.Do("rpush", "my_list", "a"))
        fmt.Println(c.Do("rpush", "my_list", "b"))
        fmt.Println(c.Do("lset", "my_list", 0, "A"))
        fmt.Println(c.Do("lset", "my_list", 1, "B"))
        myList, err := redis.Strings(c.Do("lrange", "my_list", 0, -1))
        if err != nil {
                fmt.Println(err)
                return
        }
        for i, v := range myList {
                fmt.Printf("%d -> %s\n", i, v)
        }
}

=> 0 -> A
=> 1 -> B

lset コマンドはキーで指定したリストが存在しない場合エラーになるのであらかじめ rpushlpush を使ってリストを作成しておきます
あとは range 関数を使ってスライスをループして値を取得すれば OK です

ハッシュ型

あとは代表的な型としてハッシュ型の使い方を紹介します
これは hgetall コマンドを使って取り出すことで golang 上では map として扱うことができます

  • vim main.go
package main

import (
        "fmt"
        "github.com/gomodule/redigo/redis"
)

func main() {
        c, err := redis.Dial("tcp", "localhost:6379")
        if err != nil {
                fmt.Println(err)
                return
        }
        defer c.Close()
        fmt.Println(c.Do("hset", "my_hash", "key1", "value1"))
        fmt.Println(c.Do("hset", "my_hash", "key2", "value2"))
        fmt.Println(redis.String(c.Do("hget", "my_hash", "key1")))
        myMap, err := redis.StringMap(c.Do("hgetall", "my_hash"))
        if err != nil {
                fmt.Println(err)
                return
        }
        for k, v := range myMap {
                fmt.Printf("%s -> %s\n", k, v)
        }
}

=> key1 -> value1
=> key2 -> value2

redis.StringMap を使ってコンバートすることで golang 上で map として扱うことができます
リストのときもそうでしたが要素は String になっているので数値を扱う場合にはキャストなどの型変換が必要です

ハッシュ型+構造体

最後に構造体を扱う方法を紹介します
ハッシュとして登録したデータを golang の構造体に marshall して golang 上で扱えるようにすることができます

  • vim main.go
package main

import (
        "fmt"
        "github.com/gomodule/redigo/redis"
)

type Profile struct {
        Name string `redis:"name"`
        Age  int    `redis:"age"`
}

func main() {
        c, err := redis.Dial("tcp", "localhost:6379")
        if err != nil {
                fmt.Println(err)
                return
        }
        defer c.Close()
        fmt.Println(c.Do("hset", "profile", "name", "hawksnowlog"))
        fmt.Println(c.Do("hset", "profile", "age", 99))
        pMap, err := redis.Values(c.Do("hgetall", "profile"))
        if err != nil {
                fmt.Println(err)
                return
        }
        p := Profile{}
        err = redis.ScanStruct(pMap, &p)
        if err != nil {
                fmt.Println(err)
                return
        }
        fmt.Println(p.Name)
        fmt.Println(p.Age)
}

=> hawksnowlog, 99

ハッシュにマッピングする構造体 (ここでは Profile) を作成しておきます
hgetall するところまでは同じです
データを取り出したあとで構造体にマッピングするための redis.ScanStruct を呼び出します
マッピング対象の構造体を引数の二番目に参照として渡します
あとは普通に構造体を扱うようにデータを参照できます

keys からのデータ取得

これもよくあるパターンかなと思います
あるプレフィックスで取得したキーに対してデータを取得する方法です

  • vim main.go
package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
)

const prefix = "prefix::"

func main() {
    c, err := redis.Dial("tcp", "localhost:6379")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer c.Close()
    keys, err := redis.Strings(c.Do("keys", prefix+"*"))
    if err != nil {
        fmt.Println(err)
        return
    }
    m := make(map[string][]byte)
    for _, k := range keys {
        v, err := redis.Bytes(c.Do("get", k))
        if err != nil {
            fmt.Println(err)
            return
        }
        m[k] = v
    }
    for k, v := range m {
        fmt.Printf("%s -> %s\n", k, string(v))
    }
}

最後に

redigo の基本的な使い方を紹介しました
今回は Do 関数を使ってコマンドを実行しましたが Send 関数を使えばパイプライン実行することもできます
ポイントはコマンドの実行結果をちゃんと golang で扱えるようにしてあげる点かなと思います
Do 自体は interface{} を返すので interface の扱いに慣れている人は直接 Do 関数の返り値を扱っても OK だと思います
慣れていない場合には redis.xxx のコンバート用の関数を使うことをおすすめします

参考サイト

2019年8月23日金曜日

Ruby Silver に合格しました

概要

勉強期間や勉強方法を紹介します

点数

92/100

90 分ありますが 30 分ほどで終了しました
もう少し冷静に見直せていれば、、

合格証書は後日送られくるのでその場ではもらえません
簡易的なレポートもその場ではもらえません
プロメトリックに登録しているメールアドレスにレポートを参照するための ID が送られてくるのでそれを使って成績を参照すると上記のようなレポートが確認できます

勉強期間

1 ヶ月ほどです
普段はほとんど Ruby は書いておらず趣味で Web アプリや Bot、バッチの開発をしています

勉強方法

教科書は合格教本を使いました

基本はこれの第 6 章以外をすべて読んで理解します
第 6 章も読んでも問題ないですが Silver では出題範囲外の解説が多いのでテスト対策だけなら読まなくて OK です

あとは重要な過去問です
教本にも模擬試験があるので当然やりましょう
あとは以下の Web サービスの過去問を使いました

これらを 100% 正解できるようにしましょう
他のサイトでも紹介していますがやはり過去問に似た問題が多く出題されている印象はありました
全く同じではなく少しテイストを変えて出題している感じです
なので過去問をやることが一番対策になると思いました

あとは String, Array, Hash のリファレンスをしっかり覚えましょう
特にメソッドは重要です
Array, Hash は Enumerable モジュールとも関係が強いので Enumerable モジュールもリファレンスを見て各メソッドの詳細な挙動を確認しましょう
それ以外だと File, IO あたりで特に r,w,a,r+,w+,a+ のモードは覚えておいて損はないです

irb

教本、過去問、リファレンスを見て覚えても良いですが irb で動かしながらやると更に理解が深まります
全部を irb で動かすのは正直面倒なので「覚えられない」「自信がない」「詳細な挙動がわからない」というものだけ使いましょう

合格体験記を読む

一応見ておくと良いと思います
試験のバージョンが v2.1 なのであれば参考になると思います
試験の雰囲気や傾向を掴むのには良い情報だと思います

おそらく間違えた問題

あとから問題を見直すことはできないので記憶の範囲です
Array 内の単語の出現回数をカウントする問題で複数回答の問題でした

a = ["a", "b", "c", "a", "a", "b"]
p a.grep(/a/).count # => 3
p a.count("a") # => 3
p a.inject(0) { |x, y|
  y == "a" ? x+1 : x
} # => 3

所感

Ruby は少し書けるレベルでしたがおそらく勉強なしでは合格できなかったと思います
普段 Ruby を書いている人でも勉強なしでは厳しいかなと感じました
その理由としては問題が引っ掛け問題っぽいのが多く試験の傾向などをつかめんでいないと厳しいと感じたからです
あとは自分の場合だけかもしれませんが普段は使わないメソッドがたくさん出てきていたのでそれらは勉強して理解しなければ解けなかったと思います

アプリなどを開発するときはサードパーティのライブラリばかり使ってしまうのでそれらの使い方は覚えてるのですが、そうなると普段から組み込みのクラスやモジュールなどを使わないのでそういうケースの方は勉強が必要かなと思います

あと受験の際の注意点ですが Silver の試験はプロメトリックを使っています
試験会場によるのかもしれませんが時間前に行っても試験は受けさせてくれませんでした
また CBT によるパソコンによる回答なのですが見直しの機能というか警告機能みたいなのがなく言われるがままにポチポチ進んでいると見直しするせずに終了しちゃってることがあるので注意してください
また問題の直後にアンケートがあります
なので見直しはアンケートまで行ったら自分で前の問題に戻って見直ししましょう
アンケートも回答したら「終了」を押せば試験が採点されすぐに合否がわかります
終了を押す際はドキドキしながら押しましょう

2019年8月22日木曜日

RaspberryPi の起動ボタンを作成する

概要

RaspberryPi Zero を毎日自動で停止しているのですが起動するのに毎回アダプタを抜く or USB を抜くのが面倒なのでスイッチを作成しました

環境

  • Raspberry Pi Zero 512MB
  • Raspbian 10.0 (Buster)
    • Linux raspberrypi 4.19.57+

ブレッドボードでテスト

GPIO3 と GND をタクトスイッチにつなぐだけです

これでタクトスイッチを 3 秒くらい長押ししていると RaspberryPi が起動します

ユニバーサル基板化

ブレッドボードだと邪魔なのでユニバーサル基板を使ってシールド化しました

接続完了

RaspberryPi とシールドを接続すると以下のようになります
こっちのほうがスリムで良いです

参考サイト

2019年8月21日水曜日

docker の nginx で簡単リバースプロキシを構築する

概要

ふとしたときにリバースプロキシ経由でアプリケーションにアクセスしたくなることがあると思います
そんなときには nginx コンテナを使ってリバースプロキシをサクっと構築することができます

環境

  • macOS 10.14.6
  • docker 19.03.1

アプリ作成

  • bundle init
  • vim Gemfile
gem "sinatra"
  • bundle install --path vendor
  • vim app.rb
require 'sinatra'

set :bind, '0.0.0.0'

get '/' do
  'ok'
end
  • bundle exec ruby app.rb
  • curl localhost:4567

=> ok

というアプリを nginx のリバースプロキシ経由でアクセスできるようにします

nginx コンテナ作成 

  • vim default.conf
server {
  listen 80;
  location / {
    proxy_pass http://docker.for.mac.host.internal:4567;
  }
}
  • docker run -d -p 80:80 -v $(pwd)/default.conf:/etc/nginx/conf.d/default.conf --name web nginx
  • curl localhost

=> ok

という感じで簡単にリバースプロキシ経由でアプリにアクセスできるようになります

解説

default.conf にプロキシするアプリの情報を記載するだけです
今回はコンテナからホストマシンのポートに対してアクセスするのでアプリケーションがバインドしている IP を 0.0.0.0 などにしてコンテナからアクセスできるようにする必要があります

また docker for Mac の場合 docker.for.mac.host.internal という名前でホストマシン側にアクセスできますが Ubuntu などの場合は名前でアクセスできないのでホストマシンの IP などを指定するようにしましょう

2019年8月20日火曜日

RaspberryPi の Wifi ドングルが安定しないときの対策

概要

RaspberryPi を無線 LAN にする際に欠かせない Wifi ドングルですがルータなどの相性もあるようで頻繁に接続が切れたりします
そんなときに極力安定させる方法を紹介します

環境

  • Raspberry Pi Zero 512MB
  • Raspbian 10.0 (Buster)
    • Linux raspberrypi 4.19.57+

まずは原因切り分け

自分が経験した感じ原因は

  • ルータ側
  • RaspberryPi 側

のどちらかになります
まずはこれのどちらかを切り分けしましょう

具体的なやり方としては「ルータ (アクセスポイント) を変えて安定するか確かめる」になります
例えばモバイルルータ (WiMax2 やどんなときも Wifi など) を契約しているのであれば、まずそのルータと RaspberryPi を無線接続して安定するか確かめます
そして次に別のルータやアクセスポイントと接続して安定して確かめます
簡単に用意できるのはスマホのテザリングかなと思います

この結果として

  • RaspberryPi との無線接続が不安定になるルータがある
  • どのルータでも無線接続が不安定になる

のであればルータが原因の場合があります
ルータの dhcp や Mac アドレスフィルタリングなどの設定を確認してみましょう
ただこの場合でも RaspberryPi 側に原因がある可能性があるので次に RaspberryPi 側で取れる対策を紹介します

Wifi ドングルが対応しているか確認する

自分は RaspberryPi で動作しない Wifi ドングルに出会ったことがあります (参考)

基本的には Raspbian にドライバがインストールされていれば動作します
が、ドライバがあっても動作しないケースもあるようです
自分が遭遇したのは Buffalo の WLI-UC-GNM2S というドングルでこれはドライバがあってもうまく動作しませんでした

ではどれを使うのが良いのかという話になるのですがそれはこのあと紹介します

Wifi ドングルの熱暴走を止める

これが一番の原因かもしれません
「再起動直後は安定しているけどしばらくすると接続が切れる」という症状の場合はおそらく熱暴走が原因です

対策としては

  • Wifi ドングルが複数ある場合はドングル同士を離す
  • 他のドングルがある場合はそれらのドングルを極力使用しない or ドングルとの距離を離す
  • ファンを取り付ける (おすすめ)
  • ヒートシンク (できればファンにしたい)

かなと思います
ドングル同士を離すのは単純にドングル同士が近いほうがお互いに発熱して更に熱暴走の可能性が上がるからです

あとは物理的に冷やしましょう
自分の経験上一番効果があったのがファンなのでファンの取り付けをおすすめします
「どうやって Wifi ドングルにファンを取り付けるの?」と思う方いると思いますが自分は単純に小型の扇風機あてています
ただファンのデメリットはファンの音がうるさいというのがあります
あとはヒートシンクですがこれはそこまで効果がないと思います
付け方とヒートシンクの数にもよると思いますがファンほど冷却効果はないかなと思います
ただヒートシンクのほうが安価で済みます

Bluetooth ドングルと干渉しないようにした

これは電波干渉の話です
Bluetooth ドングルが近くにある場合は少し離してみましょう
あとは電子レンジです
電子レンジと Wifi の 2.4GHz は互いに干渉し合うのでなるべく電子レンジの近くにルータおよび RaspberryPi を置かないようにしましょう

最適な Wifi ドングルに買い換える

自分が持ってる Baffalo の WLI シリーズは正直おすすめしません
他のサイトでいろいろと紹介しているのでそれを参考にするのが一番かなと思います

あとは正直ルータとの相性もあるので試してみないと分からないというのが一番です
ただ、少なくとも自分が持っている WLI シリーズはとても安価ですか RaspberryPi で使用するのはおすすめしません

RaspberryPi3 の内蔵 Wifi ドングルでも同じことが言えるかもしれない

「RaspberryPi Wifi 不安定」あたりでググるとよく RPi3 の内蔵 Wifi ドングルで不安定になるという記事が出てきます
実はこれも熱暴走をしている可能性はありそうです
自分は RPi3 を持っていないので試せていませんが試す価値はあるかなと思います

最後に

RaspberryPi の Wifi ドングルの無線接続を安定させる方法を紹介しました
ルータとの相性もあるので正解を探すのが正直難しいです
最大のポイントは「相性の良い Wifi ドングルを購入する」のが一番かなと思います
更に発熱が小さいドングルを選択すればなお良いと思います

ただ小型の Wifi ドングルはどうしても発熱してしまうのでやはり別途冷却する仕組みがないと厳しいかなと思います

参考サイト

2019年8月19日月曜日

どんなときも Wifi を契約しました

概要

StarWifi を解約して「どんなときも Wifi」に移行しました
契約時の内容をメモしておきます

環境

  • どんなときも Wifi (2019/08/14 時点)

申し込み

ここから申し込めば OK です
決済はクレカにしましょう
選択するのは特にないと思います
端末のカラーくらいでしょうか
端末はレンタルになります

申込時には事務手数料で 3,000 円かかります

端末発送の連絡

「【どんなときもWiFi】発送完了のお知らせ」というメールが来ます
問い合わせて確認してみたのですがどうやら発送と同時に課金が発生しているようです (謎)
契約当月は発送日からの日割りで請求されるようです

セットアップ

こんな感じで梱包されてきました

開封すると更に箱がありここにルータなどが入っています

入っているのは以下の通り

  • 本体
  • microUSB ケーブル
  • 説明書
  • ストラップ

これらは解約時に返却する必要があるので大切に保管しましょう

サイドに電源ボタンがあるので 3 秒間長押しします
するといろいろと点滅を始めるので待ちます
最終的に電波のボタンが点灯すれば OK です
電波を探してる最中は点滅しています
それだとまだ使えないので点灯するまで待ちましょう

電波を掴むまでは少し時間がかかりました

SSID 変更

ルータの管理画面は 192.168.43.1 にブラウザでアクセスします
スマホのものすごい最適化されたサイトになっています
管理者用のパスワードはデフォルトで admin/admin になっています
ログイン後変更もできるので適宜変更してください

SSID もデフォルトだと 「Donnatokimo_xxx」という名前になっているので適当に変更しましょう

ブラウザだと操作がわかりにくいですが以下の画面がテキストフォームになっているので変更して保存すれば OK です
ボタンは名前を変更したりすると表示されます
一点注意点としてはパスワードに記号が使えませんでした
(前の SSID のパスワードに記号が入っておりそれと全く同じものが使えなかったので各デバイスの Wifi の再設定を余儀なくされました、、)

SSID を変更するとルータは一旦再起動するようなので待ちましょう

契約内容の確認

https://support.donnatokimo-wifi.jp にアクセスすると契約内容など確認できます
同封されている「ご契約の内容」という紙の裏面にパスワードが記載されているので確認しましょう

サポートページでは初月の日割りの料金や契約内容の確認などができます
契約解除もこのページから可能です

速度

mac の speedtest-cli を使って測定してみました

Retrieving speedtest.net configuration… Testing from Softbank BB (126.204.161.252)… Retrieving speedtest.net server list… Selecting best server based on ping… Hosted by OPEN Project (via 20G SINET) (Tokyo) [0.96 km]: 38.386 ms Testing download speed…………………………………………………………………….. Download: 29.78 Mbit/s Testing upload speed…………………………………………………………………………………… Upload: 9.89 Mbit/s

まぁこんなもんでしょうか
あとはこれが制限されないかどうか気になるところです

その他・注意事項

  • 2 年縛りがあります
  • d1 というクラウド SIM を使ったルータなのですがこれが同時接続数が「5」しかありません
    • 他のモバイルルータと比べるとかなり少ないのでこれまで 5 台以上接続していた人は注意が必要です
  • ラズパイ+ USB ドングルとの相性が悪い? (要調査)
    • 自分がやった対処としては Bluetooth ドングルを外すのと Wifi ドングルを複数接続している場合はできる限り離すという対処で何とか安定しました
    • おそらく d1 が電波干渉に弱いのとドングルの発熱が接続を不安定にしていたんだと思います

最後に

どんなときも Wifi に申し込んでみました
ルータが届いてから初回セットアップまでの方法も紹介しました
セットアップは他のモバイルルータ系のサービス同様にかなり簡単にできます
あとは d1 の使い勝手や制限が本当にないのかを使いながら確かめたいと思います