embedded-bt-rssi-monitor/main.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))
}