From d28db0788f2a4c06a50648167272f34f69c766a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Rasmussen?= Date: Fri, 29 Apr 2022 21:02:33 +0200 Subject: [PATCH] Add Tilt support --- cmd/fermentord/main.go | 4 +- go.mod | 10 ++++ go.sum | 26 ++++++++++ pkg/tilt/ibeacon.go | 71 +++++++++++++++++++++++++ pkg/tilt/ibeacon_test.go | 76 +++++++++++++++++++++++++++ pkg/tilt/main.go | 38 ++++++++++++++ pkg/tilt/metrics.go | 30 +++++++++++ pkg/tilt/scanner.go | 108 +++++++++++++++++++++++++++++++++++++++ pkg/tilt/tilt.go | 99 +++++++++++++++++++++++++++++++++++ pkg/tilt/tilt_test.go | 92 +++++++++++++++++++++++++++++++++ 10 files changed, 553 insertions(+), 1 deletion(-) create mode 100644 pkg/tilt/ibeacon.go create mode 100644 pkg/tilt/ibeacon_test.go create mode 100644 pkg/tilt/main.go create mode 100644 pkg/tilt/metrics.go create mode 100644 pkg/tilt/scanner.go create mode 100644 pkg/tilt/tilt.go create mode 100644 pkg/tilt/tilt_test.go diff --git a/cmd/fermentord/main.go b/cmd/fermentord/main.go index 707548d..0401610 100644 --- a/cmd/fermentord/main.go +++ b/cmd/fermentord/main.go @@ -16,6 +16,7 @@ import ( "git.joco.dk/sng/fermentord/internal/metrics" "git.joco.dk/sng/fermentord/pkg/daemon" "git.joco.dk/sng/fermentord/pkg/temperature" + "git.joco.dk/sng/fermentord/pkg/tilt" "github.com/fsnotify/fsnotify" "github.com/getsentry/sentry-go" "github.com/nats-io/nats.go" @@ -48,10 +49,11 @@ func mainLoop(ctx context.Context, wg *sync.WaitGroup, js nats.JetStream, config } defer gpio.Close() - wg.Add(3) + wg.Add(4) go ctrl.Run(ctx, wg) go ingest.Run(ctx, wg, js, config) go temperature.PollSensors(ctx, wg, 1*time.Second, config.Sensors.Weight) + go tilt.PollSensors(ctx, wg, 1*time.Minute, 20*time.Second) for { select { diff --git a/go.mod b/go.mod index dbc471d..19f3f3f 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,29 @@ module git.joco.dk/sng/fermentord go 1.18 require ( + github.com/JuulLabs-OSS/ble v0.0.0-20200716215611-d4fcc9d598bb github.com/fsnotify/fsnotify v1.5.4 github.com/getsentry/sentry-go v0.13.0 github.com/nats-io/nats.go v1.15.0 + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.2 github.com/spf13/viper v1.11.0 github.com/warthog618/gpiod v0.8.0 ) require ( + github.com/JuulLabs-OSS/cbgo v0.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nats-io/nats-server/v2 v2.7.1 // indirect github.com/nats-io/nkeys v0.3.0 // indirect @@ -27,6 +35,8 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.34.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/raff/goble v0.0.0-20190909174656-72afc67d6a99 // indirect + github.com/sirupsen/logrus v1.6.0 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index 080baf0..3716b7c 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,10 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/JuulLabs-OSS/ble v0.0.0-20200716215611-d4fcc9d598bb h1:kIZ7fr8RxucJXNHifPxm71yiWuzpw0SmjlafMzoOd0U= +github.com/JuulLabs-OSS/ble v0.0.0-20200716215611-d4fcc9d598bb/go.mod h1:6deIuswYSv6W1l3sM/nonw0OKWtIZCn7ZOWvIREoq2A= +github.com/JuulLabs-OSS/cbgo v0.0.1 h1:A5JdglvFot1J9qYR0POZ4qInttpsVPN9lqatjaPp2ro= +github.com/JuulLabs-OSS/cbgo v0.0.1/go.mod h1:L4YtGP+gnyD84w7+jN66ncspFRfOYB5aj9QSXaFHmBA= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -58,6 +62,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -164,6 +169,7 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -174,8 +180,18 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab h1:n8cgpHzJ5+EDyDri2s/GC7a9+qK3/YEGnBsd0uS/8PY= +github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab/go.mod h1:y1pL58r5z2VvAjeG1VLGc8zOQgSOzbKN7kMHPvFXJ+8= github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -232,10 +248,16 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/raff/goble v0.0.0-20190909174656-72afc67d6a99 h1:JtoVdxWJ3tgyqtnPq3r4hJ9aULcIDDnPXBWxZsdmqWU= +github.com/raff/goble v0.0.0-20190909174656-72afc67d6a99/go.mod h1:CxaUhijgLFX0AROtH5mluSY71VqpjQBw9JXE2UKZmc4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= @@ -258,6 +280,7 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/warthog618/gpiod v0.8.0 h1:qxH9XVvWHpTxzWFSndBcujFyNH5zVRzHM63tcmm85o4= github.com/warthog618/gpiod v0.8.0/go.mod h1:a7Csa+IJtDBZ39++zC/6Srjo01qWejt/5velrDWuNkY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -388,6 +411,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -414,6 +438,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/tilt/ibeacon.go b/pkg/tilt/ibeacon.go new file mode 100644 index 0000000..6c5ad0c --- /dev/null +++ b/pkg/tilt/ibeacon.go @@ -0,0 +1,71 @@ +// MIT License +// +// Copyright (c) 2020 Alex Howarth +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package tilt + +import ( + "encoding/binary" + "encoding/hex" + "errors" +) + +//iBeaconType identifies an iBeacon +const iBeaconType uint32 = 0x4c000215 + +// ErrNotBeacon - the BLE device is not an iBeacon +var ErrNotBeacon = errors.New("not an iBeacon") + +// IBeacon data +type IBeacon struct { + UUID string + Major uint16 + Minor uint16 +} + +// IsIBeacon to determine if data is an iBeacon +func IsIBeacon(data []byte) bool { + if len(data) < 25 { + return false + } + + if binary.BigEndian.Uint32(data) != iBeaconType { + return false + } + + return true +} + +// NewIBeacon creates an iBeacon from a valid BLE event +func NewIBeacon(data []byte) (b *IBeacon, err error) { + if !IsIBeacon(data) { + err = ErrNotBeacon + return + } + + b = &IBeacon{ + UUID: hex.EncodeToString(data[4:20]), + Major: binary.BigEndian.Uint16(data[20:22]), + Minor: binary.BigEndian.Uint16(data[22:24]), + } + + return +} diff --git a/pkg/tilt/ibeacon_test.go b/pkg/tilt/ibeacon_test.go new file mode 100644 index 0000000..c757b50 --- /dev/null +++ b/pkg/tilt/ibeacon_test.go @@ -0,0 +1,76 @@ +// MIT License +// +// Copyright (c) 2020 Alex Howarth +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package tilt + +import ( + "errors" + "testing" +) + +func TestNewIBeacon(t *testing.T) { + tt := []struct { + name string + data []byte + err error + uuid string + major uint16 + minor uint16 + }{ + { + name: "Valid iBeacon", + data: []uint8{0x4c, 0x0, 0x2, 0x15, 0xa4, 0x95, 0xbb, 0x30, 0xc5, 0xb1, 0x4b, 0x44, 0xb5, 0x12, 0x13, 0x70, 0xf0, 0x2d, 0x74, 0xde, 0x0, 0x46, 0x3, 0xfc, 0xc5}, + err: nil, + uuid: "a495bb30c5b14b44b5121370f02d74de", + major: 70, + minor: 1020, + }, + { + name: "Invalid iBeacon", + data: []uint8{0x4c, 0x0, 0x2, 0x15}, + err: ErrNotBeacon, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + got, err := NewIBeacon(tc.data) + + if tc.err != nil { + // expecting an error + if !errors.Is(err, tc.err) { + t.Fatalf("Expected '%v' error, got '%v' error", tc.err, err) + } + return + } + if got.UUID != tc.uuid { + t.Errorf("Expected %v, got %v", tc.uuid, got.UUID) + } + if got.Major != tc.major { + t.Errorf("Expected %v, got %v", tc.major, got.Major) + } + if got.Minor != tc.minor { + t.Errorf("Expected %v, got %v", tc.minor, got.Minor) + } + }) + } +} diff --git a/pkg/tilt/main.go b/pkg/tilt/main.go new file mode 100644 index 0000000..2ee909f --- /dev/null +++ b/pkg/tilt/main.go @@ -0,0 +1,38 @@ +package tilt + +import ( + "context" + "log" + "sync" + "time" +) + +func PollSensors(ctx context.Context, wg *sync.WaitGroup, interval time.Duration, scanDuration time.Duration) { + if interval < scanDuration { + log.Fatal("Unable to use interval < scanDuration") + } + + defer wg.Done() + ticker := time.NewTicker(interval) + + for { + select { + case <-ctx.Done(): + return + + case <-ticker.C: + scan(ctx, scanDuration) + } + } +} + +func scan(ctx context.Context, timeout time.Duration) { + scanner := NewScanner() + scanner.Scan(ctx, timeout) + + for _, t := range scanner.Tilts() { + color := string(t.Color()) + metricGravity.WithLabelValues(color).Set(t.Gravity()) + metricTemp.WithLabelValues(color).Set(t.Celsius()) + } +} diff --git a/pkg/tilt/metrics.go b/pkg/tilt/metrics.go new file mode 100644 index 0000000..43fa90b --- /dev/null +++ b/pkg/tilt/metrics.go @@ -0,0 +1,30 @@ +package tilt + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + metricGravity = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "fermentord", + Subsystem: "tilt", + Name: "specific_gravity", + Help: "Wort specific gravity", + }, + []string{ + "color", + }, + ) + + metricTemp = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "fermentord", + Subsystem: "tilt", + Name: "temperature_degrees_celcius", + Help: "Wort temperature in degrees celcius", + }, + []string{ + "color", + }, + ) +) diff --git a/pkg/tilt/scanner.go b/pkg/tilt/scanner.go new file mode 100644 index 0000000..a055ec5 --- /dev/null +++ b/pkg/tilt/scanner.go @@ -0,0 +1,108 @@ +// MIT License +// +// Copyright (c) 2020 Alex Howarth +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package tilt + +import ( + "context" + "log" + "time" + + "github.com/JuulLabs-OSS/ble" + "github.com/JuulLabs-OSS/ble/examples/lib/dev" + "github.com/pkg/errors" +) + +// Scanner for Tilt devices +type Scanner struct { + devices Devices +} + +// Devices stores discovered devices +type Devices map[Color]Tilt + +// NewScanner returns a Scanner +func NewScanner() *Scanner { + return &Scanner{} +} + +// Scan finds Tilt devices and times out after a duration +func (s *Scanner) Scan(ctx context.Context, timeout time.Duration) error { + s.devices = make(map[Color]Tilt) + + d, err := dev.NewDevice("go-tilt") + if err != nil { + return err + } + ble.SetDefaultDevice(d) + + ctx2 := ble.WithSigHandler(context.WithTimeout(ctx, timeout)) + err = ble.Scan(ctx2, false, s.advHandler, advFilter) + if err != nil { + switch errors.Cause(err) { + case nil: + case context.DeadlineExceeded: + // Finished scanning + break + + case context.Canceled: + break + + default: + return err + } + } + + return err +} + +func advFilter(a ble.Advertisement) bool { + return IsTilt(a.ManufacturerData()) +} + +func (s *Scanner) advHandler(a ble.Advertisement) { + // create iBeacon + b, err := NewIBeacon(a.ManufacturerData()) + if err != nil { + log.Println(err) + return + } + + // create Tilt from iBeacon + t, err := NewTilt(b) + if err != nil { + log.Println(err) + return + } + + s.HandleTilt(t) +} + +// HandleTilt adds a discovered Tilt to a map +func (s *Scanner) HandleTilt(t Tilt) { + s.devices[t.col] = t +} + +// Tilts contains the found devices +func (s *Scanner) Tilts() Devices { + return s.devices +} diff --git a/pkg/tilt/tilt.go b/pkg/tilt/tilt.go new file mode 100644 index 0000000..0d5f491 --- /dev/null +++ b/pkg/tilt/tilt.go @@ -0,0 +1,99 @@ +// MIT License +// +// Copyright (c) 2020 Alex Howarth +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package tilt + +import ( + "encoding/hex" + "log" + "math" + + "github.com/pkg/errors" +) + +// tiltIdentifier iBeacon identifier portion (4c000215) as well as Tilt specific uuid preamble (a495) +const tiltIdentifier = "4c000215a495" + +// ErrNotTilt - the BLE device does not match anything in tiltType +var ErrNotTilt = errors.New("Not a Tilt iBeacon") + +// Color of the Tilt +type Color string + +var tiltType = map[string]Color{ + "a495bb10c5b14b44b5121370f02d74de": "Red", + "a495bb20c5b14b44b5121370f02d74de": "Green", + "a495bb30c5b14b44b5121370f02d74de": "Black", + "a495bb40c5b14b44b5121370f02d74de": "Purple", + "a495bb50c5b14b44b5121370f02d74de": "Orange", + "a495bb60c5b14b44b5121370f02d74de": "Blue", + "a495bb70c5b14b44b5121370f02d74de": "Yellow", + "a495bb80c5b14b44b5121370f02d74de": "Pink", +} + +// Tilt struct +type Tilt struct { + col Color + temp uint16 + sg uint16 +} + +// NewTilt returns a Tilt from an iBeacon +func NewTilt(b *IBeacon) (t Tilt, err error) { + if col, ok := tiltType[b.UUID]; ok { + t = Tilt{col: col, temp: b.Major, sg: b.Minor} + return + } + err = ErrNotTilt + return +} + +// IsTilt tests if the data is from a Tilt +func IsTilt(d []byte) bool { + if len(d) >= 25 && hex.EncodeToString(d)[0:12] == tiltIdentifier { + return true + } + return false +} + +func (t *Tilt) Celsius() float64 { + return math.Round(float64(t.temp-32)/1.8*100) / 100 +} + +func (t *Tilt) Fahrenheit() uint16 { + return t.temp +} + +func (t *Tilt) Gravity() float64 { + return float64(t.sg) / 1000 +} + +func (t *Tilt) Color() Color { + return t.col +} + +func (t *Tilt) Print() { + log.Printf("Tilt: %v", t.Color()) + log.Printf("Fahrenheit: %v\n", t.Fahrenheit()) + log.Printf("Specific Gravity: %v\n", t.Gravity()) + log.Printf("Celsius: %v\n", t.Celsius()) +} diff --git a/pkg/tilt/tilt_test.go b/pkg/tilt/tilt_test.go new file mode 100644 index 0000000..2815eeb --- /dev/null +++ b/pkg/tilt/tilt_test.go @@ -0,0 +1,92 @@ +// MIT License +// +// Copyright (c) 2020 Alex Howarth +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package tilt + +import ( + "errors" + "testing" +) + +func TestNewTilt(t *testing.T) { + tt := []struct { + name string + ibeacon *IBeacon + color Color + celsius float64 + fahrenheit uint16 + gravity float64 + err error + }{ + { + name: "Red iBeacon", + ibeacon: &IBeacon{UUID: "a495bb10c5b14b44b5121370f02d74de", Major: 70, Minor: 1035}, + color: Color("Red"), + fahrenheit: 70, + celsius: 21.11, + gravity: 1.035, + err: nil, + }, + { + name: "Black iBeacon", + ibeacon: &IBeacon{UUID: "a495bb30c5b14b44b5121370f02d74de", Major: 69, Minor: 1065}, + color: Color("Black"), + fahrenheit: 69, + celsius: 20.56, + gravity: 1.065, + err: nil, + }, + { + name: "Not an iBeacon", + ibeacon: &IBeacon{UUID: "a495bb99c5b14b44b5121370f02d74de", Major: 1, Minor: 2}, + err: ErrNotTilt, + }, + } + + for _, tc := range tt { + + t.Run(tc.name, func(t *testing.T) { + got, err := NewTilt(tc.ibeacon) + + if tc.err != nil { + // expecting an error + if !errors.Is(err, tc.err) { + t.Fatalf("Expected '%v' error, got '%v' error", tc.err, err) + } + return + } + + if got.Color() != tc.color { + t.Errorf("Expected %v, got %v", tc.color, got.Color()) + } + if got.Celsius() != tc.celsius { + t.Errorf("Expected %v, got %v", tc.celsius, got.Celsius()) + } + if got.Fahrenheit() != tc.fahrenheit { + t.Errorf("Expected %v, got %v", tc.fahrenheit, got.Fahrenheit()) + } + if got.Gravity() != tc.gravity { + t.Errorf("Expected %v, got %v", tc.gravity, got.Gravity()) + } + }) + } +}