152 lines
3.6 KiB
Go
152 lines
3.6 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"time"
|
||
|
|
||
|
"tinygo.org/x/bluetooth"
|
||
|
)
|
||
|
|
||
|
type MonitoredDevice struct {
|
||
|
MacAddress string
|
||
|
RssiHistory []int16 // RSSI readings over time. The latest is always last element: RssiHistory[len(RssiHistory)-1]
|
||
|
LastPing time.Time // Last time device was scanned
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
rssiHistoryLength = 3
|
||
|
pingTimeout = time.Second * 60
|
||
|
// rssiThreshold int16 = -80 // anything lower doesn't count
|
||
|
|
||
|
rssiThresholdLower int16 = -82
|
||
|
rssiThresholdUpper int16 = -72
|
||
|
terminalDashboardURL = "http://localhost:8080/set?screen="
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
adapter = bluetooth.DefaultAdapter
|
||
|
monitoredDevices []MonitoredDevice
|
||
|
)
|
||
|
|
||
|
// Application Startup
|
||
|
func init() {
|
||
|
log.Printf("embedded-bt-rssi-monitor init")
|
||
|
|
||
|
// Load application configuration from environment variables
|
||
|
/*
|
||
|
envVars := make(map[string]string)
|
||
|
envVars["required"] = os.Getenv("required")
|
||
|
// Validate that all required environment variables are set
|
||
|
for key, value := range envVars {
|
||
|
if value == "" {
|
||
|
log.Fatalf("shell environment variable %s is not set", key)
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
monitoredDevices = make([]MonitoredDevice, 0)
|
||
|
|
||
|
for i := 0; true; i++ {
|
||
|
macAddress := os.Getenv(fmt.Sprintf("btmacaddress_%d", i))
|
||
|
if macAddress == "" {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
fmt.Println("Watching MAC Address: ", macAddress)
|
||
|
|
||
|
monitoredDevice := MonitoredDevice{MacAddress: macAddress}
|
||
|
monitoredDevices = append(monitoredDevices, monitoredDevice)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
// Enable BLE interface.
|
||
|
must("enable BLE stack", adapter.Enable())
|
||
|
|
||
|
go evaluator()
|
||
|
|
||
|
// Start scanning.
|
||
|
println("scanning...")
|
||
|
err := adapter.Scan(scanHandler)
|
||
|
must("start scan", err)
|
||
|
}
|
||
|
|
||
|
func must(action string, err error) {
|
||
|
if err != nil {
|
||
|
panic("failed to " + action + ": " + err.Error())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Runs against every device returned in a scan result
|
||
|
func scanHandler(adapter *bluetooth.Adapter, device bluetooth.ScanResult) {
|
||
|
// First filter out devices that we don't care about
|
||
|
for i := range monitoredDevices {
|
||
|
if monitoredDevices[i].MacAddress == device.Address.String() {
|
||
|
log.Println(device.LocalName(), device.Address, device.RSSI)
|
||
|
monitoredDevices[i].LastPing = time.Now()
|
||
|
monitoredDevices[i].RssiHistory = append(monitoredDevices[i].RssiHistory, device.RSSI)
|
||
|
if len(monitoredDevices[i].RssiHistory) > rssiHistoryLength { // keep up to rssiHistoryLength, and then begin popping out the first element
|
||
|
monitoredDevices[i].RssiHistory = monitoredDevices[i].RssiHistory[1:]
|
||
|
}
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Reads the MonitoredDevices and takes an action
|
||
|
// meant to run in a goroutine
|
||
|
func evaluator() {
|
||
|
|
||
|
var screenOff bool
|
||
|
|
||
|
for {
|
||
|
time.Sleep(time.Second * 20)
|
||
|
|
||
|
bestSignal := rssiThresholdLower
|
||
|
|
||
|
for i := range monitoredDevices {
|
||
|
if time.Now().After(monitoredDevices[i].LastPing.Add(pingTimeout)) {
|
||
|
continue // ignore device
|
||
|
}
|
||
|
|
||
|
averageRssi := getAverage(monitoredDevices[i].RssiHistory)
|
||
|
|
||
|
if averageRssi > bestSignal {
|
||
|
bestSignal = averageRssi
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if bestSignal > rssiThresholdUpper {
|
||
|
if screenOff {
|
||
|
screenOff = false
|
||
|
log.Printf("setting screen to: budget")
|
||
|
_, err := http.Get(fmt.Sprintf("%sbudget", terminalDashboardURL))
|
||
|
if err != nil {
|
||
|
log.Printf("failed to set budget screen: %v", err)
|
||
|
}
|
||
|
}
|
||
|
} else if bestSignal <= rssiThresholdLower {
|
||
|
if !screenOff {
|
||
|
screenOff = true
|
||
|
log.Printf("setting screen to: off")
|
||
|
_, err := http.Get(fmt.Sprintf("%soff", terminalDashboardURL))
|
||
|
if err != nil {
|
||
|
log.Printf("failed to set off screen: %v", err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func getAverage(nums []int16) int16 {
|
||
|
var sum int16
|
||
|
for _, num := range nums {
|
||
|
sum += num
|
||
|
}
|
||
|
|
||
|
return sum / int16(len(nums))
|
||
|
}
|