Nature Remo mini 使って寝ている間に暑すぎればクーラーON、寒すぎればOFFしています。
買ってすぐの頃は温度設定がイマイチでうまく行ってなくて暑いのにONされなかったり、夜中に寒すぎて手動でOFFしたりしてました。
そこで室温データを分析してうまくやりたいと思ってNature Remo Cloud APIを叩いて室温を取得してからGoogle Spreadsheetに書き込むというのを書いたことがありました。
ただ設定を試行錯誤しているうちにいい感じになってきたのでデータを使うことはありませんでした・・・
最近InfluxDBを触っていたのでSpreadsheetの代わりにDBへ入れたほうがいいかなと思って修正してみたらむしろ簡単になったのでこちらを記録に残しておくことにします。
前提としてMacで作業していてGoでプログラム書いてます。
(常時動かすときはLinuxで動かすつもりですけど)
室温データの取得
Nature Remo Cloud API
アクセストークンが必要です。トーク無しでAPIを叩くとUnauthorizedになります。
トークンを公開しないように気をつけましょう。
デバイスの情報を取りたいので /1/devices
というAPIを使ってみます。
Goで書いてるのでまずはJSONが欲しいということでトークンを使って取得してみます。(${Token}
を置き換える)
$ curl -X GET "https://api.nature.global/1/devices" -H "accept: application/json" -k --header "Authorization: Bearer ${Token}" [{"name":"3F_Remo","id":"xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","created_at":"2018-08-06T13:12:11Z","updated_at":"2019-09-30T14:12:13Z","mac_address":"xx:xx:xx:xx:xx:xx","serial_number":"xxxxxxxxx","firmware_version":"Remo-mini/1.0.87-g8b06f0e","temperature_offset":-2,"humidity_offset":0,"users":[{"id":"xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx","nickname":"GOZU Kenichiro","superuser":true}],"newest_events":{"te":{"val":27.2,"created_at":"2019-10-01T00:13:52Z"}}}]
こんな感じで取れました。
見ずらいので整形したものを貼っておきます。
[ { "name": "3F_Remo", "id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "created_at": "2018-08-06T13:12:11Z", "updated_at": "2019-09-30T14:12:13Z", "mac_address": "xx:xx:xx:xx:xx:xx", "serial_number": "xxxxxxxxx", "firmware_version": "Remo-mini/1.0.87-g8b06f0e", "temperature_offset": -2, "humidity_offset": 0, "users": [ { "id": "xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", "nickname": "GOZU Kenichiro", "superuser": true } ], "newest_events": { "te": { "val": 27.2, "created_at": "2019-10-01T00:13:52Z" } } } ]
newest_events.te.val が室温ですね。27.2度でした。
GoでJSON扱うために struct を書くのが鬱陶しいのでjson2goサイトでサクッと変換します。
type RemoDevices []struct { Name string `json:"name"` ID string `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` MacAddress string `json:"mac_address"` SerialNumber string `json:"serial_number"` FirmwareVersion string `json:"firmware_version"` TemperatureOffset int `json:"temperature_offset"` HumidityOffset int `json:"humidity_offset"` Users []struct { ID string `json:"id"` Nickname string `json:"nickname"` Superuser bool `json:"superuser"` } `json:"users"` NewestEvents struct { Te struct { Val float64 `json:"val"` CreatedAt time.Time `json:"created_at"` } `json:"te"` } `json:"newest_events"` }
配列になってます。うちにはRemoは1つしかないから常に1しか返ってこないけどね。。。
あとは API 叩いてレスポンスのJSONからteを取得します。
まずは net/httpを使ってhttp clientの準備。
import ( "fmt" "log" "net/http" "net/http/httputil" ) func newRequest(m map[string]string) (*http.Request, error) { url := m["url"] req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") req.Header.Set(m["headerKey"], m["headerValue"]) dump, err := httputil.DumpRequestOut(req, true) fmt.Printf("%s", dump) if err != nil { log.Fatal("Error requesting dump") } return req, err } func getResponse(m map[string]string) (*http.Response, error) { req, err := newRequest(m) res, err := http.DefaultClient.Do(req) if err != nil { return nil, err } else if res.StatusCode != 200 { return nil, fmt.Errorf("http status %d", res.StatusCode) } return res, err }
そんでもってAPIの処理。(実際にはファイル別れてるけど便宜上まとめて書いてます)
import ( "encoding/json" "fmt" "io/ioutil" "log" "time" ) func main() { te := Remo("https://api.nature.global/1/devices") } func Remo(url string) (te float64) { res, err := encodeJson4Remo(url) if err != nil { log.Fatalf("http error: %v", err) } for _, r := range res { jst := time.FixedZone("Asia/Tokyo", 9*60*60) fmt.Println("Name: " + r.Name) fmt.Println("ID: " + r.ID) fmt.Printf("UpdatedAt: %s\n", r.UpdatedAt.In(jst).Format("2006/01/02 15:04:05")) fmt.Printf("TemperatureOffset: %d\n", r.TemperatureOffset) fmt.Println("User ID: " + r.Users[0].ID) fmt.Println("User Nickname: " + r.Users[0].Nickname) fmt.Printf("User Superuser: %t\n", r.Users[0].Superuser) fmt.Printf("Temperature val: %f\n", r.NewestEvents.Te.Val) te = r.NewestEvents.Te.Val fmt.Printf("Temperature CreatedAt: %s\n", r.NewestEvents.Te.CreatedAt.In(jst).Format("2006/01/02 15:04:05")) } return te } func encodeJson4Remo(url string) (RemoDevices, error) { token := "Bearer トークン入れてね" m := map[string]string{ "url": url, "headerKey": "Authorization", "headerValue": token} res, err := getResponse(m) if err != nil { return remoDevices, err } byteArray, err := ioutil.ReadAll(res.Body) if err != nil { return remoDevices, err } defer res.Body.Close() if err := json.Unmarshal(byteArray, &remoDevices); err != nil { log.Fatalf("Error!: %v", err) } return remoDevices, err }
結果。
$ go run natureremo_temperature.go remo.go httpclient.go GET /1/devices HTTP/1.1 Host: api.nature.global User-Agent: Go-http-client/1.1 Authorization: Bearer トークン Content-Type: application/json Accept-Encoding: gzip Name: 3F_Remo ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx UpdatedAt: 2019/09/30 23:12:13 TemperatureOffset: -2 User ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx User Nickname: GOZU Kenichiro User Superuser: true Temperature val: 26.000000 Temperature CreatedAt: 2019/09/30 23:37:19 26.000000 success!
室温データの登録
室温は取れたのでInfluxDBに保存します。
環境構築
brewでinfluxdb入れます。
$ brew install influxdb Updating Homebrew... ==> Auto-updated Homebrew! Updated 1 tap (homebrew/core). ==> New Formulae kepubify tmuxinator ==> Updated Formulae ammonite-repl evince git-secret jenkins-lts libomp squashfs ==> Downloading https://homebrew.bintray.com/bottles/influxdb-1.7.7.mojave.bottle.tar.gz ==> Downloading from https://akamai.bintray.com/aa/aa1cf675fa005f5f5dff6879ca4f5a31886f6d82828037c285656df9786f59c1?__g ######################################################################## 100.0% ==> Pouring influxdb-1.7.7.mojave.bottle.tar.gz ==> Caveats To have launchd start influxdb now and restart at login: brew services start influxdb Or, if you don't want/need a background service you can just run: influxd -config /usr/local/etc/influxdb.conf ==> Summary 🍺 /usr/local/Cellar/influxdb/1.7.7: 9 files, 127.4MB $ influx Failed to connect to http://localhost:8086: Get http://localhost:8086/ping: dial tcp [::1]:8086: connect: connection refused Please check your connection settings and ensure 'influxd' is running.
落ち着け!起動してないとbrewが教えてくれてるじゃないかw
$ influxd -config /usr/local/etc/influxdb.conf 8888888 .d888 888 8888888b. 888888b. 888 d88P" 888 888 "Y88b 888 "88b 888 888 888 888 888 888 .88P 888 88888b. 888888 888 888 888 888 888 888 888 8888888K. 888 888 "88b 888 888 888 888 Y8bd8P' 888 888 888 "Y88b 888 888 888 888 888 888 888 X88K 888 888 888 888 888 888 888 888 888 Y88b 888 .d8""8b. 888 .d88P 888 d88P 8888888 888 888 888 888 "Y88888 888 888 8888888P" 8888888P" 2019-09-30T17:50:06.740730Z info InfluxDB starting {"log_id": "0ICpYNa0000", "version": "v1.7.7", "branch": "master", "commit": "f8fdf652f348fc9980997fe1c972e2b79ddd13b0"} 2019-09-30T17:50:06.740759Z info Go runtime {"log_id": "0ICpYNa0000", "version": "go1.12.6", "maxprocs": 4} 2019-09-30T17:50:06.868211Z info Using data dir {"log_id": "0ICpYNa0000", "service": "store", "path": "/usr/local/var/influxdb/data"} 2019-09-30T17:50:06.868314Z info Compaction settings {"log_id": "0ICpYNa0000", "service": "store", "max_concurrent_compactions": 2, "throughput_bytes_per_second": 50331648, "throughput_bytes_per_second_burst": 50331648} 2019-09-30T17:50:06.868349Z info Open store (start) {"log_id": "0ICpYNa0000", "service": "store", "trace_id": "0ICpYO50000", "op_name": "tsdb_open", "op_event": "start"} 2019-09-30T17:50:06.868582Z info Open store (end) {"log_id": "0ICpYNa0000", "service": "store", "trace_id": "0ICpYO50000", "op_name": "tsdb_open", "op_event": "end", "op_elapsed": "0.236ms"} 2019-09-30T17:50:06.868937Z info Registered diagnostics client {"log_id": "0ICpYNa0000", "service": "monitor", "name": "system"}
DB作ります。
$ influx Connected to http://localhost:8086 version v1.7.7 InfluxDB shell version: v1.7.7 > show databases name: databases name ---- _internal > create database remo > show databases name: databases name ---- _internal remo
アクセス用のユーザ作っておくべきなんですがちょいと手抜きで・・・
データ登録
参考にした記事では、"github.com/influxdata/influxdb/client/v2"
を使えと書いてあったがgo getでエラーになる。URLが変わっているらしい。
import ( "github.com/influxdata/influxdb/client/v2" ) $ go get package github.com/influxdata/influxdb/client/v2: cannot find package "github.com/influxdata/influxdb/client/v2" in any of: /usr/local/Cellar/go/1.13.1/libexec/src/github.com/influxdata/influxdb/client/v2 (from $GOROOT) /Users/gozu/go/src/github.com/influxdata/influxdb/client/v2 (from $GOPATH)
"github.com/influxdata/influxdb1-client/v2"
に修正する。
Client作って tags と fields を登録する流れ。
値は複数登録できるっぽいけどとりあえず一回起動したら一回データを登録するようにしておいた。(cronで登録すればいいし)
package main import ( "fmt" "log" "time" "github.com/influxdata/influxdb1-client/v2" ) func PushData(te float64) { MyDB := "remo" username := "root" password := "root" MyMeasurement := "temperature" c, err := client.NewHTTPClient(client.HTTPConfig{ Addr: "http://localhost:8086", Username: username, Password: password, }) if err != nil { log.Fatalln("Error: ", err) } bp, err := client.NewBatchPoints(client.BatchPointsConfig{ Database: MyDB, Precision: "s", }) if err != nil { log.Fatalln("Error: ", err) } tags := map[string]string{"remo": "3F"} fields := map[string]interface{}{ "temperature": te, } pt, err := client.NewPoint(MyMeasurement, tags, fields, time.Now()) if err != nil { log.Fatalln("Error: ", err) } bp.AddPoint(pt) err = c.Write(bp) if err != nil { log.Fatalf("Unable to write value. %v", err) } fmt.Printf("success!\n") }
実行してみる。
$ go run remo.go remoget.go httpclient.go influxdb.go
確認。
$ influx Connected to http://localhost:8086 version v1.7.7 InfluxDB shell version: v1.7.7 > use remo Using database remo > show series key --- temperature,remo=3F > select * from temperature; name: temperature time remo temperature ---- ---- ----------- 1569869753000000000 3F 26 > precision rfc3339 > select * from temperature; name: temperature time remo temperature ---- ---- ----------- 2019-09-30T18:55:53Z 3F 26 >
入ってるね!
それではとりあえずcronで1分に1回実行するようにしてデータ取ってみる。
$ crontab -e */1 * * * * /Users/gozu/go/src/github.com/gozuk16/natureremo_temperature/natureremo_temperature > /dev/null 2>&1
まあ取れてる。
> select * from temperature; name: temperature time remo temperature ---- ---- ----------- 2019-09-30T18:55:53Z 3F 26 2019-09-30T19:25:02Z 3F 26 2019-09-30T19:26:01Z 3F 26 2019-09-30T19:27:02Z 3F 26 2019-09-30T19:28:01Z 3F 26 2019-09-30T19:29:01Z 3F 26 2019-09-30T19:30:02Z 3F 26 2019-09-30T23:09:01Z 3F 26.59 2019-09-30T23:10:01Z 3F 26.59 2019-09-30T23:16:01Z 3F 26.59 2019-09-30T23:17:01Z 3F 26.59 2019-09-30T23:18:01Z 3F 26.59 2019-09-30T23:45:01Z 3F 26.59 2019-09-30T23:46:01Z 3F 27.2 2019-10-01T00:04:02Z 3F 27.79 2019-10-01T00:05:01Z 3F 27.79 2019-10-01T00:06:01Z 3F 27.79 2019-10-01T00:07:01Z 3F 27.79 2019-10-01T00:11:01Z 3F 27.79 2019-10-01T00:13:47Z 3F 27.79 2019-10-01T00:13:48Z 3F 27.79 2019-10-01T00:14:01Z 3F 27.2 2019-10-01T00:15:01Z 3F 27.2 2019-10-01T00:16:01Z 3F 27.2 2019-10-01T00:17:01Z 3F 27.2 2019-10-01T00:18:01Z 3F 27.2 2019-10-01T00:24:01Z 3F 27.2 2019-10-01T00:25:01Z 3F 27.2 2019-10-01T00:26:01Z 3F 27.2 2019-10-01T00:27:01Z 3F 27.2 2019-10-01T00:28:01Z 3F 27.2 2019-10-01T01:05:01Z 3F 27.79 2019-10-01T01:06:01Z 3F 27.79 2019-10-01T01:07:01Z 3F 27.79 2019-10-01T01:11:01Z 3F 27.79 2019-10-01T01:24:01Z 3F 27.79 2019-10-01T01:25:01Z 3F 27.79 2019-10-01T03:17:02Z 3F 29.59 2019-10-01T03:18:01Z 3F 29.59 2019-10-01T03:19:01Z 3F 29.59 2019-10-01T03:20:01Z 3F 29.59 2019-10-01T03:21:01Z 3F 29.59 2019-10-01T03:22:01Z 3F 29.59
貼り付けたデータは適当に間引いてます。
とりあえずLTするために突貫で書いたので後でまとめなおそう・・・