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)) }