2019-10-23 18:29:13 -04:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-10-24 21:29:58 -04:00
|
|
|
"encoding/binary"
|
2019-10-23 18:29:13 -04:00
|
|
|
"fmt"
|
2019-12-02 13:29:08 -05:00
|
|
|
"image"
|
|
|
|
"image/color"
|
2019-10-23 18:29:13 -04:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path"
|
2019-12-06 12:16:46 -05:00
|
|
|
"time"
|
2019-10-23 18:29:13 -04:00
|
|
|
|
|
|
|
"git.wow.st/gmp/ble"
|
|
|
|
|
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
|
|
|
|
"gioui.org/app"
|
|
|
|
"gioui.org/io/system"
|
|
|
|
"gioui.org/layout"
|
2019-12-02 13:29:08 -05:00
|
|
|
"gioui.org/f32"
|
|
|
|
"gioui.org/op"
|
|
|
|
"gioui.org/op/clip"
|
|
|
|
"gioui.org/op/paint"
|
2019-10-23 18:29:13 -04:00
|
|
|
"gioui.org/text"
|
2019-10-24 17:00:23 -04:00
|
|
|
"gioui.org/unit"
|
2019-10-23 18:29:13 -04:00
|
|
|
"gioui.org/widget"
|
|
|
|
"gioui.org/widget/material"
|
|
|
|
|
2019-10-25 14:58:07 -04:00
|
|
|
"gioui.org/font/gofont"
|
2019-10-23 18:29:13 -04:00
|
|
|
)
|
|
|
|
|
2020-06-12 16:13:33 -04:00
|
|
|
type (
|
|
|
|
D = layout.Dimensions
|
|
|
|
C = layout.Context
|
|
|
|
)
|
|
|
|
|
2019-10-23 18:29:13 -04:00
|
|
|
type conf struct {
|
2019-10-25 13:29:16 -04:00
|
|
|
Autoconnect string
|
2019-10-23 18:29:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var Config conf
|
2019-10-25 13:29:16 -04:00
|
|
|
var conffile string
|
2019-10-23 18:29:13 -04:00
|
|
|
|
|
|
|
func main() {
|
2019-10-28 12:46:57 -04:00
|
|
|
conffile = path.Join(getConfDir(), "config.yml")
|
2019-10-23 18:29:13 -04:00
|
|
|
if _, err := os.Stat(conffile); os.IsNotExist(err) {
|
|
|
|
fd, err := os.Create(conffile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Cannot create configuration file: ", err)
|
|
|
|
}
|
|
|
|
fd.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
confbytes, err := ioutil.ReadFile(conffile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Cannot read configuration file: ", err)
|
|
|
|
}
|
|
|
|
if err = yaml.UnmarshalStrict(confbytes, &Config); err != nil {
|
|
|
|
log.Fatal("Cannot parse configuration file: ", err)
|
|
|
|
}
|
|
|
|
go eventloop()
|
|
|
|
app.Main()
|
|
|
|
}
|
|
|
|
|
2019-10-25 13:29:16 -04:00
|
|
|
func saveConfig() {
|
|
|
|
confbytes, err := yaml.Marshal(&Config)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Cannot encode configuration: ", err)
|
|
|
|
}
|
|
|
|
err = ioutil.WriteFile(conffile, confbytes, 0700)
|
|
|
|
}
|
|
|
|
|
2019-10-24 21:29:58 -04:00
|
|
|
func hrDecode(x []byte) int {
|
2019-11-27 10:48:13 -05:00
|
|
|
if len(x) < 4 {
|
|
|
|
return 0
|
|
|
|
}
|
2019-10-24 21:29:58 -04:00
|
|
|
flags := x[0]
|
|
|
|
if flags&0x80 != 0 { // uint16 format
|
2019-11-27 10:48:13 -05:00
|
|
|
return int(binary.BigEndian.Uint16(x[1:3]))
|
2019-10-24 21:29:58 -04:00
|
|
|
} else {
|
|
|
|
return int(x[1])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-06 12:16:46 -05:00
|
|
|
type Stopwatch struct {
|
|
|
|
lasttime time.Time
|
|
|
|
elapsed time.Time
|
|
|
|
running bool
|
2020-06-12 16:13:33 -04:00
|
|
|
startstopBtn *widget.Clickable
|
|
|
|
resetBtn *widget.Clickable
|
2019-12-06 12:16:46 -05:00
|
|
|
h, m, s int
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewStopwatch() Stopwatch {
|
|
|
|
return Stopwatch{
|
2020-06-12 16:13:33 -04:00
|
|
|
startstopBtn: &widget.Clickable{},
|
|
|
|
resetBtn: &widget.Clickable{},
|
2019-12-06 12:16:46 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-23 18:29:13 -04:00
|
|
|
func eventloop() {
|
|
|
|
w := app.NewWindow(
|
2019-12-06 12:16:46 -05:00
|
|
|
app.Size(unit.Dp(350), unit.Dp(600)),
|
2019-10-23 18:29:13 -04:00
|
|
|
app.Title("HRM"),
|
|
|
|
)
|
2020-06-12 16:13:33 -04:00
|
|
|
th := material.NewTheme(gofont.Collection())
|
2019-11-22 16:44:26 -05:00
|
|
|
th.TextSize = unit.Sp(fontSize)
|
2020-06-12 16:13:33 -04:00
|
|
|
var ops op.Ops
|
2019-10-23 18:29:13 -04:00
|
|
|
|
|
|
|
sysinset := &layout.Inset{}
|
|
|
|
resetSysinset := func(x system.Insets) {
|
|
|
|
sysinset.Top = x.Top
|
|
|
|
sysinset.Bottom = x.Bottom
|
|
|
|
sysinset.Left = x.Left
|
|
|
|
sysinset.Right = x.Right
|
|
|
|
}
|
|
|
|
margin := layout.UniformInset(unit.Dp(10))
|
|
|
|
|
|
|
|
b := ble.NewBLE()
|
2019-11-27 10:48:13 -05:00
|
|
|
b.Enable(w)
|
2019-10-23 18:29:13 -04:00
|
|
|
|
|
|
|
state := "starting"
|
2019-10-24 21:29:58 -04:00
|
|
|
var hr int
|
2019-10-23 18:29:13 -04:00
|
|
|
var periph ble.Peripheral
|
2019-12-05 21:00:17 -05:00
|
|
|
var wide bool
|
2019-10-24 17:00:23 -04:00
|
|
|
periphs := make([]ble.Peripheral, 0)
|
2020-06-12 16:13:33 -04:00
|
|
|
btns := make([]*widget.Clickable, 0)
|
|
|
|
backBtn := &widget.Clickable{}
|
2019-10-23 18:29:13 -04:00
|
|
|
|
2020-06-12 16:13:33 -04:00
|
|
|
var page, offpage, scanpage, connpage, hrpage func(gtx C) D
|
2019-10-23 18:29:13 -04:00
|
|
|
|
|
|
|
f := &layout.Flex{Axis: layout.Vertical}
|
2020-06-12 16:13:33 -04:00
|
|
|
offpage = func(gtx C) D {
|
|
|
|
return f.Layout(gtx,
|
|
|
|
layout.Rigid(func(gtx C) D {
|
|
|
|
return material.Body1(th, "Heart Rate Monitor").Layout(gtx)
|
2019-12-05 21:00:17 -05:00
|
|
|
}),
|
2020-06-12 16:13:33 -04:00
|
|
|
layout.Rigid(func(gtx C) D {
|
|
|
|
return material.Body1(th, "Bluetooth is Powered Off").Layout(gtx)
|
2019-12-05 21:00:17 -05:00
|
|
|
}),
|
|
|
|
)
|
2019-10-24 17:00:23 -04:00
|
|
|
}
|
2019-12-05 21:00:17 -05:00
|
|
|
|
2020-06-12 16:13:33 -04:00
|
|
|
appname := func(gtx C) D {
|
|
|
|
return layout.Center.Layout(gtx, func(gtx C) D {
|
|
|
|
return material.Body1(th, "Heart Rate Monitor").Layout(gtx)
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
|
|
|
}
|
2020-06-12 16:13:33 -04:00
|
|
|
appstate := func(gtx C) D {
|
|
|
|
return layout.Center.Layout(gtx, func(gtx C) D {
|
|
|
|
return material.Body1(th, state).Layout(gtx)
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
|
|
|
}
|
2020-06-12 16:13:33 -04:00
|
|
|
periphname := func(gtx C) D {
|
|
|
|
return layout.Center.Layout(gtx, func(gtx C) D {
|
|
|
|
return material.Body1(th, periph.Name).Layout(gtx)
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
|
|
|
}
|
2019-12-05 21:00:17 -05:00
|
|
|
|
2020-06-12 16:13:33 -04:00
|
|
|
leftbar := func(gtx C) D {
|
2019-12-05 21:00:17 -05:00
|
|
|
f := &layout.Flex{Axis: layout.Vertical}
|
2020-06-12 16:13:33 -04:00
|
|
|
return f.Layout(gtx,
|
|
|
|
layout.Rigid(appname),
|
|
|
|
layout.Rigid(func(gtx C) D {
|
|
|
|
return material.Body1(th, "").Layout(gtx)
|
|
|
|
}),
|
|
|
|
layout.Rigid(appstate),
|
2019-12-05 21:00:17 -05:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-06-12 16:13:33 -04:00
|
|
|
scanlist := func(gtx C) D {
|
2019-12-05 21:00:17 -05:00
|
|
|
lst := &layout.List{Axis: layout.Vertical}
|
2020-06-12 16:13:33 -04:00
|
|
|
return lst.Layout(gtx, len(periphs), func(gtx C, i int) D {
|
|
|
|
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
|
|
|
ret := layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx C) D {
|
|
|
|
return material.Button(th, btns[i], periphs[i].Name).Layout(gtx)
|
2019-10-23 18:29:13 -04:00
|
|
|
})
|
2020-06-12 16:13:33 -04:00
|
|
|
if btns[i].Clicked() {
|
2019-12-05 21:00:17 -05:00
|
|
|
b.StopScan()
|
|
|
|
periph = periphs[i]
|
|
|
|
b.Connect(periph)
|
|
|
|
state = "connecting"
|
|
|
|
page = connpage
|
|
|
|
w.Invalidate()
|
|
|
|
}
|
2020-06-12 16:13:33 -04:00
|
|
|
return ret
|
2019-10-23 18:29:13 -04:00
|
|
|
})
|
2019-12-05 21:00:17 -05:00
|
|
|
}
|
|
|
|
|
2020-06-12 16:13:33 -04:00
|
|
|
scanpage = func(gtx C) D {
|
2019-12-05 21:00:17 -05:00
|
|
|
if wide {
|
|
|
|
f2 := &layout.Flex{Axis: layout.Horizontal}
|
2020-06-12 16:13:33 -04:00
|
|
|
return f2.Layout(gtx,
|
|
|
|
layout.Rigid(leftbar),
|
|
|
|
layout.Flexed(1, scanlist),
|
2019-12-05 21:00:17 -05:00
|
|
|
)
|
|
|
|
} else {
|
2020-06-12 16:13:33 -04:00
|
|
|
return f.Layout(gtx,
|
|
|
|
layout.Rigid(appname),
|
|
|
|
layout.Rigid(appstate),
|
|
|
|
layout.Rigid(scanlist),
|
2019-12-05 21:00:17 -05:00
|
|
|
)
|
|
|
|
}
|
2019-10-23 18:29:13 -04:00
|
|
|
}
|
|
|
|
|
2020-06-12 16:13:33 -04:00
|
|
|
connpage = func(gtx C) D {
|
|
|
|
return f.Layout(gtx,
|
|
|
|
layout.Rigid(appname),
|
|
|
|
layout.Rigid(appstate),
|
|
|
|
layout.Rigid(periphname),
|
|
|
|
layout.Rigid(func(gtx C) D {
|
|
|
|
ret := material.Button(th, backBtn, "Cancel").Layout(gtx)
|
|
|
|
if backBtn.Clicked() {
|
2019-12-05 21:00:17 -05:00
|
|
|
ble.CancelConnection(periph)
|
|
|
|
periphs = periphs[:0]
|
|
|
|
Config.Autoconnect = ""
|
|
|
|
saveConfig()
|
|
|
|
b.Scan()
|
|
|
|
state = "scanning"
|
|
|
|
page = scanpage
|
|
|
|
}
|
2020-06-12 16:13:33 -04:00
|
|
|
return ret
|
2019-12-05 21:00:17 -05:00
|
|
|
}),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-06-12 16:13:33 -04:00
|
|
|
hrcircle := func(gtx C) (int, D) {
|
2019-12-06 12:16:46 -05:00
|
|
|
var w, h1 float32
|
2020-06-12 16:13:33 -04:00
|
|
|
ret := layout.Center.Layout(gtx, func(gtx C) D {
|
2019-12-06 12:16:46 -05:00
|
|
|
blue := color.RGBA{0x3f, 0x51, 0xb5, 255}
|
|
|
|
white := color.RGBA{255, 255, 255, 255}
|
2020-06-12 16:13:33 -04:00
|
|
|
w, h1 = float32(gtx.Constraints.Max.X), float32(gtx.Constraints.Max.Y)
|
2019-12-06 12:16:46 -05:00
|
|
|
if w < h1 {
|
|
|
|
h1 = w
|
|
|
|
}
|
|
|
|
s2 := h1 * 0.9
|
|
|
|
r1 := f32.Rectangle{f32.Point{0, 0}, f32.Point{h1, h1}}
|
|
|
|
p2a := f32.Point{(h1-s2)/2, (h1-s2)/2}
|
|
|
|
p2b := f32.Point{s2 + (h1-s2)/2, s2 + (h1-s2)/2}
|
|
|
|
r2 := f32.Rectangle{p2a, p2b}
|
|
|
|
clip.Rect{ Rect: r1, NE: h1/2, NW: h1/2, SE: h1/2, SW: h1/2}.Op(gtx.Ops).Add(gtx.Ops)
|
|
|
|
paint.ColorOp{Color: blue}.Add(gtx.Ops)
|
|
|
|
paint.PaintOp{Rect: r1}.Add(gtx.Ops)
|
|
|
|
clip.Rect{ Rect: r2, NE: s2/2, NW: s2/2, SE: s2/2, SW: s2/2}.Op(gtx.Ops).Add(gtx.Ops)
|
|
|
|
paint.ColorOp{Color: white}.Add(gtx.Ops)
|
|
|
|
paint.PaintOp{Rect: r2}.Add(gtx.Ops)
|
|
|
|
op.TransformOp{}.Offset(p2a).Add(gtx.Ops)
|
2020-06-12 16:13:33 -04:00
|
|
|
gtx.Constraints.Max.X = int(s2)
|
|
|
|
gtx.Constraints.Max.Y = int(s2)
|
|
|
|
gtx.Constraints.Min.X = int(s2)
|
|
|
|
gtx.Constraints.Min.Y = int(s2)
|
|
|
|
l := material.H1(th, fmt.Sprintf("%d", hr))
|
2019-12-06 12:16:46 -05:00
|
|
|
l.Alignment = text.Middle
|
2020-06-12 16:13:33 -04:00
|
|
|
layout.Center.Layout(gtx, func(gtx C) D {
|
|
|
|
return l.Layout(gtx)
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
2020-06-12 16:13:33 -04:00
|
|
|
var ret D
|
|
|
|
ret.Size = image.Point{int(h1), int(h1)}
|
|
|
|
return ret
|
2019-10-25 14:04:08 -04:00
|
|
|
})
|
2020-06-12 16:13:33 -04:00
|
|
|
return int(h1), ret
|
2019-10-23 18:29:13 -04:00
|
|
|
}
|
|
|
|
|
2019-12-06 12:16:46 -05:00
|
|
|
sw := NewStopwatch()
|
|
|
|
|
2020-06-12 16:13:33 -04:00
|
|
|
stopwatch := func(gtx C) D {
|
2019-12-06 12:16:46 -05:00
|
|
|
startstoptxt := "start"
|
|
|
|
if sw.running {
|
|
|
|
sw.elapsed = sw.elapsed.Add(time.Since(sw.lasttime))
|
|
|
|
startstoptxt = "stop"
|
|
|
|
}
|
|
|
|
sw.lasttime = time.Now()
|
|
|
|
f := layout.Flex{Axis: layout.Vertical}
|
2020-06-12 16:13:33 -04:00
|
|
|
return f.Layout(gtx,
|
|
|
|
layout.Rigid(func(gtx C) D {
|
|
|
|
return layout.Center.Layout(gtx, func(gtx C) D {
|
|
|
|
gtx.Constraints.Max.X = 1e6
|
|
|
|
return material.H4(th, sw.elapsed.Format("15:04:05.00")).Layout(gtx)
|
2019-12-05 21:00:17 -05:00
|
|
|
})
|
2019-12-06 12:16:46 -05:00
|
|
|
}),
|
2020-06-12 16:13:33 -04:00
|
|
|
layout.Rigid(func(gtx C) D {
|
2019-12-06 12:16:46 -05:00
|
|
|
f2 := layout.Flex{Axis: layout.Horizontal}
|
2020-06-12 16:13:33 -04:00
|
|
|
gtx.Constraints.Min.Y = 100
|
|
|
|
ret := f2.Layout(gtx,
|
|
|
|
layout.Flexed(0.5, func(gtx C) D {
|
|
|
|
return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx C) D {
|
|
|
|
return material.Button(th, sw.startstopBtn, startstoptxt).Layout(gtx)
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
|
|
|
}),
|
2020-06-12 16:13:33 -04:00
|
|
|
layout.Flexed(0.5, func(gtx C) D {
|
|
|
|
return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx C) D {
|
|
|
|
return material.Button(th, sw.resetBtn, "reset").Layout(gtx)
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
|
|
|
}),
|
|
|
|
)
|
2020-06-12 16:13:33 -04:00
|
|
|
if sw.startstopBtn.Clicked() {
|
2019-12-06 12:16:46 -05:00
|
|
|
if sw.running {
|
|
|
|
sw.running = false
|
|
|
|
} else {
|
|
|
|
sw.running = true
|
|
|
|
}
|
|
|
|
}
|
2020-06-12 16:13:33 -04:00
|
|
|
if sw.resetBtn.Clicked() {
|
2019-12-06 12:16:46 -05:00
|
|
|
sw.elapsed = time.Time{}
|
|
|
|
sw.running = false
|
2019-12-05 21:00:17 -05:00
|
|
|
}
|
2020-06-12 16:13:33 -04:00
|
|
|
return ret
|
2019-12-05 21:00:17 -05:00
|
|
|
}),
|
|
|
|
)
|
2019-10-23 18:29:13 -04:00
|
|
|
}
|
|
|
|
|
2019-12-06 12:16:46 -05:00
|
|
|
swidth := new(int)
|
2020-06-12 16:13:33 -04:00
|
|
|
hrpage = func(gtx C) D {
|
|
|
|
var ret, ret2 D
|
|
|
|
stopbtn := func(gtx C) D {
|
|
|
|
gtx.Constraints.Min.Y = 100
|
|
|
|
return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx C) D {
|
|
|
|
return material.Button(th, backBtn, "Disconnect").Layout(gtx)
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
if wide {
|
|
|
|
f2 := &layout.Flex{Axis: layout.Horizontal}
|
2020-06-12 16:13:33 -04:00
|
|
|
ret = f2.Layout(gtx,
|
|
|
|
layout.Flexed(0.4, func(gtx C) D {
|
2019-12-06 12:16:46 -05:00
|
|
|
f3 := &layout.Flex{Axis: layout.Vertical}
|
2020-06-12 16:13:33 -04:00
|
|
|
c1 := layout.Rigid(func(gtx C) D {
|
|
|
|
ret2 = appname(gtx)
|
|
|
|
*swidth = ret2.Size.X
|
|
|
|
return ret2
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
2020-06-12 16:13:33 -04:00
|
|
|
c2 := layout.Rigid(func(gtx C) D {
|
|
|
|
ret2 = periphname(gtx)
|
|
|
|
s2 := ret2.Size.X
|
2019-12-06 12:16:46 -05:00
|
|
|
if s2 > *swidth {
|
|
|
|
*swidth = s2
|
|
|
|
}
|
2020-06-12 16:13:33 -04:00
|
|
|
return ret2
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
2019-12-06 12:58:40 -05:00
|
|
|
var c3 layout.FlexChild
|
2020-06-12 16:13:33 -04:00
|
|
|
if gtx.Constraints.Max.X > 370 {
|
|
|
|
c3 = layout.Rigid(stopwatch)
|
|
|
|
} else {
|
|
|
|
c3 = layout.Rigid(func(gtx C) D {
|
|
|
|
return D{}
|
|
|
|
})
|
2019-12-06 12:58:40 -05:00
|
|
|
}
|
2020-06-12 16:13:33 -04:00
|
|
|
c4 := layout.Rigid(stopbtn)
|
|
|
|
return f3.Layout(gtx,
|
2019-12-06 12:16:46 -05:00
|
|
|
c1,
|
2020-06-12 16:13:33 -04:00
|
|
|
layout.Rigid(appstate),
|
2019-12-06 12:16:46 -05:00
|
|
|
c2,
|
2020-06-12 16:13:33 -04:00
|
|
|
layout.Flexed(1.0, func(gtx C) D {
|
|
|
|
return material.Body1(th, "").Layout(gtx)
|
|
|
|
}),
|
2019-12-06 12:16:46 -05:00
|
|
|
c3,
|
|
|
|
c4,
|
|
|
|
)
|
|
|
|
}),
|
2020-06-12 16:13:33 -04:00
|
|
|
layout.Flexed(0.6, func(gtx C) D {
|
|
|
|
_, ret := hrcircle(gtx)
|
|
|
|
return ret
|
|
|
|
}),
|
2019-12-06 12:16:46 -05:00
|
|
|
)
|
|
|
|
} else { // !wide
|
2020-06-12 16:13:33 -04:00
|
|
|
c1 := layout.Rigid(func(gtx C) D {
|
|
|
|
return layout.Center.Layout(gtx, func(gtx C) D {
|
|
|
|
gtx.Constraints.Min.X = *swidth
|
|
|
|
gtx.Constraints.Max.X = *swidth
|
|
|
|
return stopwatch(gtx)
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
|
|
|
})
|
2020-06-12 16:13:33 -04:00
|
|
|
//swheight := gtx.Dimensions.Size.Y
|
|
|
|
c2 := layout.Rigid(func(gtx C) D {
|
|
|
|
return layout.Center.Layout(gtx, func(gtx C) D {
|
|
|
|
gtx.Constraints.Min.X = *swidth
|
|
|
|
gtx.Constraints.Max.X = *swidth
|
|
|
|
return stopbtn(gtx)
|
2019-12-06 12:58:40 -05:00
|
|
|
})
|
2019-12-06 12:16:46 -05:00
|
|
|
})
|
2020-06-12 16:13:33 -04:00
|
|
|
if gtx.Constraints.Max.Y < 775 {
|
|
|
|
//gtx.Constraints.Max.Y += swheight
|
|
|
|
ret = f.Layout(gtx,
|
|
|
|
layout.Rigid(appname),
|
|
|
|
layout.Rigid(appstate),
|
|
|
|
layout.Rigid(periphname),
|
|
|
|
layout.Flexed(1.0, func(gtx C) D {
|
|
|
|
*swidth, ret2 = hrcircle(gtx)
|
|
|
|
return ret2
|
2019-12-06 12:58:40 -05:00
|
|
|
}),
|
|
|
|
c2,
|
|
|
|
)
|
|
|
|
} else {
|
2020-06-12 16:13:33 -04:00
|
|
|
ret = f.Layout(gtx,
|
|
|
|
layout.Rigid(appname),
|
|
|
|
layout.Rigid(appstate),
|
|
|
|
layout.Rigid(periphname),
|
|
|
|
layout.Flexed(1.0, func(gtx C) D {
|
|
|
|
*swidth, ret2 = hrcircle(gtx)
|
|
|
|
return ret2
|
2019-12-06 12:58:40 -05:00
|
|
|
}),
|
|
|
|
c1,
|
|
|
|
c2,
|
|
|
|
)
|
|
|
|
}
|
2019-12-06 12:16:46 -05:00
|
|
|
}
|
2020-06-12 16:13:33 -04:00
|
|
|
if backBtn.Clicked() {
|
2019-12-06 12:16:46 -05:00
|
|
|
ble.Disconnect(periph)
|
|
|
|
periphs = periphs[:0]
|
|
|
|
Config.Autoconnect = ""
|
|
|
|
saveConfig()
|
|
|
|
b.Scan()
|
|
|
|
state = "scanning"
|
|
|
|
page = scanpage
|
|
|
|
}
|
2020-06-12 16:13:33 -04:00
|
|
|
return ret
|
2019-12-06 12:16:46 -05:00
|
|
|
}
|
|
|
|
|
2019-10-24 17:00:23 -04:00
|
|
|
page = offpage
|
2019-10-23 18:29:13 -04:00
|
|
|
|
2019-12-06 12:16:46 -05:00
|
|
|
t := time.NewTicker(time.Second/30)
|
2019-10-23 18:29:13 -04:00
|
|
|
for {
|
|
|
|
select {
|
2019-12-06 12:16:46 -05:00
|
|
|
case <-t.C:
|
|
|
|
w.Invalidate()
|
2019-11-27 11:50:36 -05:00
|
|
|
case e := <-b.Events():
|
2019-10-23 18:29:13 -04:00
|
|
|
switch e := e.(type) {
|
|
|
|
case ble.UpdateStateEvent:
|
2019-10-25 09:08:14 -04:00
|
|
|
fmt.Printf("UpdateState: %s\n", e.State)
|
2019-10-23 18:29:13 -04:00
|
|
|
state = e.State
|
2019-10-24 17:00:23 -04:00
|
|
|
if state != "powered on" {
|
2019-10-25 14:04:08 -04:00
|
|
|
periphs = periphs[:0]
|
2019-10-24 17:00:23 -04:00
|
|
|
page = offpage
|
|
|
|
} else {
|
|
|
|
|
2019-10-25 13:29:16 -04:00
|
|
|
if Config.Autoconnect != "" && b.Connect(ble.Peripheral{Identifier: Config.Autoconnect}) {
|
2019-10-25 14:04:08 -04:00
|
|
|
state = "connecting"
|
2019-10-24 17:00:23 -04:00
|
|
|
page = connpage
|
|
|
|
} else {
|
2019-10-25 13:29:16 -04:00
|
|
|
periphs = periphs[:0]
|
2019-10-25 14:04:08 -04:00
|
|
|
state = "scanning"
|
2019-10-24 17:00:23 -04:00
|
|
|
page = scanpage
|
|
|
|
b.Scan()
|
|
|
|
}
|
|
|
|
}
|
2019-10-24 21:29:58 -04:00
|
|
|
case ble.DiscoverPeripheralEvent:
|
2019-10-25 09:08:14 -04:00
|
|
|
fmt.Printf("found %s (%s)\n", e.Peripheral.Name, e.Peripheral.Identifier)
|
2019-10-23 18:29:13 -04:00
|
|
|
periphs = append(periphs, e.Peripheral)
|
2020-06-12 16:13:33 -04:00
|
|
|
btns = append(btns, &widget.Clickable{})
|
2019-10-25 13:29:16 -04:00
|
|
|
if e.Peripheral.Identifier == Config.Autoconnect && b.Connect(e.Peripheral) {
|
2019-10-25 14:04:08 -04:00
|
|
|
state = "connecting"
|
2019-10-25 13:29:16 -04:00
|
|
|
page = connpage
|
2019-10-23 18:29:13 -04:00
|
|
|
}
|
|
|
|
case ble.ConnectEvent:
|
2019-10-23 19:29:41 -04:00
|
|
|
fmt.Printf("Connect event\n")
|
2019-10-25 14:04:08 -04:00
|
|
|
b.StopScan()
|
2019-10-23 18:29:13 -04:00
|
|
|
state = "connected"
|
2019-10-24 17:00:23 -04:00
|
|
|
periph = e.Peripheral
|
2019-10-25 13:29:16 -04:00
|
|
|
Config.Autoconnect = periph.Identifier
|
|
|
|
saveConfig()
|
2019-10-24 21:29:58 -04:00
|
|
|
e.Peripheral.DiscoverServices()
|
2019-10-23 18:29:13 -04:00
|
|
|
page = hrpage
|
2019-10-24 17:00:23 -04:00
|
|
|
case ble.ConnectTimeoutEvent:
|
|
|
|
fmt.Printf("Connect timeout\n")
|
|
|
|
state = "timeout"
|
2019-10-25 13:29:16 -04:00
|
|
|
periphs = periphs[:0]
|
2019-10-24 17:00:23 -04:00
|
|
|
page = scanpage
|
2019-10-25 13:29:16 -04:00
|
|
|
Config.Autoconnect = ""
|
|
|
|
saveConfig()
|
2019-10-24 17:00:23 -04:00
|
|
|
b.Scan()
|
2019-10-24 21:29:58 -04:00
|
|
|
case ble.DiscoverServiceEvent:
|
|
|
|
fmt.Printf("DiscoverService %s\n", e.Gatt.UUID)
|
2019-11-22 16:17:17 -05:00
|
|
|
//if e.Gatt.UUID == "180D" {
|
|
|
|
if e.Gatt.IsHRM() {
|
2019-10-24 21:29:58 -04:00
|
|
|
fmt.Printf("Found HRM Service\n")
|
|
|
|
e.Peripheral.DiscoverCharacteristics(e.Service)
|
|
|
|
}
|
|
|
|
case ble.DiscoverCharacteristicEvent:
|
|
|
|
fmt.Printf("DiscoverCharacteristic %s\n", e.Gatt.UUID)
|
2019-11-27 10:48:13 -05:00
|
|
|
//if e.Gatt.UUID == "2A37" {
|
|
|
|
if e.Gatt.IsHRV() {
|
2019-10-24 21:29:58 -04:00
|
|
|
fmt.Printf("Found heart rate value\n")
|
|
|
|
e.Peripheral.SetNotifyValue(e.Characteristic)
|
|
|
|
}
|
|
|
|
case ble.UpdateValueEvent:
|
|
|
|
hr = hrDecode(e.Data)
|
2019-10-23 18:29:13 -04:00
|
|
|
}
|
2019-10-25 09:08:14 -04:00
|
|
|
w.Invalidate() // refresh on any Bluetooth event
|
2019-10-23 18:29:13 -04:00
|
|
|
case e := <-w.Events():
|
|
|
|
switch e := e.(type) {
|
|
|
|
case system.DestroyEvent:
|
|
|
|
return
|
|
|
|
case system.FrameEvent:
|
2020-06-12 16:13:33 -04:00
|
|
|
gtx := layout.NewContext(&ops, e)
|
2019-12-05 21:00:17 -05:00
|
|
|
if e.Size.X > e.Size.Y {
|
|
|
|
wide = true
|
|
|
|
} else {
|
|
|
|
wide = false
|
|
|
|
}
|
2019-10-23 18:29:13 -04:00
|
|
|
resetSysinset(e.Insets)
|
2020-06-12 16:13:33 -04:00
|
|
|
sysinset.Layout(gtx, func(gtx C) D {
|
|
|
|
return margin.Layout(gtx, page)
|
2019-10-23 18:29:13 -04:00
|
|
|
})
|
|
|
|
e.Frame(gtx.Ops)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|