ble/ble.go

285 lines
5.3 KiB
Go

package ble
import (
"fmt"
"log"
"sync"
"time"
"git.wow.st/gmp/ble/gatt"
)
type BLE struct {
state bleState
events chan interface{}
peripherals Peripherals
handle bleHandle
ready, wantScan bool
sync.Mutex
connections Connections
}
type PeripheralListItem struct {
p Peripheral
seen time.Time
}
type Peripherals struct {
items []PeripheralListItem
sync.Mutex
}
type ConnectionListItem struct {
p Peripheral
state chan string
}
type Connections struct {
items []ConnectionListItem
close chan struct{}
sync.Mutex
}
func (c *Connections) UpdateState(p Peripheral, s string) {
var ch chan string
c.Lock()
for _, item := range c.items {
if p.Identifier == item.p.Identifier {
ch = item.state
}
}
c.Unlock()
ch <- s
}
func (ps *Peripherals) Add(x Peripheral) bool {
ps.Lock()
defer ps.Unlock()
found := false
for n, item := range ps.items {
if item.p.Identifier == x.Identifier {
item.p = x
item.seen = time.Now()
ps.items[n] = item
found = true
break
}
}
if found {
return false
}
//take ownership of this object
x.Retain()
item := PeripheralListItem{p: x, seen: time.Now()}
ps.items = append(ps.items, item)
return true
}
func (cs *Connections) Add(x ConnectionListItem) bool {
cs.Lock()
defer cs.Unlock()
found := false
for _, item := range cs.items {
if item.p.Identifier == x.p.Identifier {
// are we allowed to have multiple connections to the same peripheral?
return false
}
}
if found {
return false
}
cs.items = append(cs.items, x)
return true
}
type UpdateStateEvent struct {
State string
}
type DiscoverPeripheralEvent struct {
Peripheral Peripheral
}
//FIXME: create a Service type that is platform dependent to make the
//event platform independent
type DiscoverServiceEvent struct {
Peripheral Peripheral
Gatt gatt.Service
Service Service
}
//FIXME: create a Characteristic type that is platform dependent to make the
//event platform independent
type DiscoverCharacteristicEvent struct {
Peripheral Peripheral
Gatt gatt.Characteristic
Service Service
Characteristic Characteristic
}
//FIXME: create a Characteristic type that is platform dependent to make the
//event platform independent
type UpdateValueEvent struct {
Peripheral Peripheral
Characteristic Characteristic
Data []byte
}
type ConnectEvent struct {
Peripheral Peripheral
}
type DisconnectEvent struct {
Peripheral Peripheral
}
type ConnectTimeoutEvent struct {
Peripheral Peripheral
}
type UpdageValueEvent struct {
Peripheral Peripheral
Characteristic gatt.Characteristic
}
func (b *BLE) Events() chan interface{} {
return b.events
}
func (b *BLE) State() string {
b.Lock()
defer b.Unlock()
return b.stringState()
}
func (b *BLE) Scan() {
b.Lock()
defer b.Unlock()
if b.readyToScan() {
log.Printf("ready to scan")
b.peripherals.Lock()
b.peripherals.items = b.peripherals.items[:0]
b.peripherals.Unlock()
fmt.Printf("Go: Scanning\n")
b.scan()
} else {
b.wantScan = true
}
}
func (b *BLE) StopScan() {
b.Lock()
if !b.ready {
b.Unlock()
return
}
b.Unlock()
fmt.Printf("Go: stopping scan\n")
b.stopScan()
}
func CancelConnection(p Peripheral) {
b := peripheralLookup(p)
if b == nil {
return
}
b.connections.Lock()
var ch chan string;
for _, item := range b.connections.items {
if item.p.Identifier == p.Identifier {
ch = item.state
break
}
}
b.connections.Unlock()
if ch != nil {
ch <- "cancel"
}
}
func connectTracker(b *BLE, x ConnectionListItem) bool {
fmt.Printf("connectTracker(): %s\n", x.p.Name)
b.connections.Lock()
for _, item := range b.connections.items {
if item.p.Identifier == x.p.Identifier {
fmt.Printf("connectTracker(): already connecting to %s\n", x.p.Name)
b.connections.Unlock()
return true
}
}
b.connections.items = append(b.connections.items, x)
b.connections.Unlock()
b.connectPeripheral(x.p)
cancel := func() {
b.cancelConnection(x.p)
b.connections.Lock()
for n, item := range b.connections.items {
if item.p.Identifier == x.p.Identifier {
b.connections.items = append(b.connections.items[:n], b.connections.items[n+1:]...)
}
}
b.connections.Unlock()
}
go func() {
tick := time.NewTicker(time.Second * 60)
select {
case <-b.connections.close:
fmt.Printf("connectTracker(): Closing connection to %s\n", x.p.Name)
b.cancelConnection(x.p)
case <-tick.C:
fmt.Printf("connectTracker(): Connection to %s timed out\n", x.p.Name)
cancel()
b.events <- ConnectTimeoutEvent{x.p}
case state := <-x.state:
if state == "cancel" {
cancel()
}
fmt.Printf("connectTracker(): state %s\n", state)
}
}()
return true
}
func (b *BLE) Connect(p Peripheral) bool {
b.Lock()
if !b.ready {
b.Unlock()
fmt.Printf("--BLE not ready\n")
return false
}
b.Unlock()
if p.IsIncomplete() {
var ok bool
p, ok = b.knownPeripheral(p)
if !ok {
return false
}
}
item := ConnectionListItem{p, make(chan string)}
fmt.Printf("BLE.Connect() calling connectTracker\n")
return connectTracker(b, item)
}
func Disconnect(p Peripheral) {
b := peripheralLookup(p)
found := false
b.connections.Lock()
for i, item := range b.connections.items {
if item.p.Identifier == p.Identifier {
found = true
b.connections.items = append(b.connections.items[:i], b.connections.items[i+1:]...)
break
}
}
b.connections.Unlock()
if !found {
return
}
b.cancelConnection(p)
}