package main import ( "log" "os" "os/exec" "sync" "syscall" "time" ) // ScannerType identifies scanner type type ScannerType string const ( ScannerBLE ScannerType = "ble" ScannerWiFi ScannerType = "wifi" ) // ScannerManager manages BLE and WiFi scanner processes type ScannerManager struct { binDir string debug bool bleCmd *exec.Cmd wifiCmd *exec.Cmd bleRunning bool wifiRunning bool mu sync.Mutex stopChan chan struct{} } // NewScannerManager creates a new scanner manager func NewScannerManager(binDir string, debug bool) *ScannerManager { return &ScannerManager{ binDir: binDir, debug: debug, stopChan: make(chan struct{}), } } // StartBLE starts the BLE scanner process func (m *ScannerManager) StartBLE(zmqAddr string) error { m.mu.Lock() defer m.mu.Unlock() if m.bleRunning { return nil } binPath := m.binDir + "/ble-scanner" args := []string{"-zmq", zmqAddr} if m.debug { args = append(args, "-debug") } cmd := exec.Command(binPath, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr // Set process group for clean termination cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} if err := cmd.Start(); err != nil { return err } m.bleCmd = cmd m.bleRunning = true log.Printf("[scanner] BLE scanner started (pid=%d)", cmd.Process.Pid) // Monitor process go m.monitorProcess(ScannerBLE, cmd) return nil } // StopBLE stops the BLE scanner process func (m *ScannerManager) StopBLE() { m.mu.Lock() defer m.mu.Unlock() if !m.bleRunning || m.bleCmd == nil { return } log.Printf("[scanner] Stopping BLE scanner...") // Send SIGTERM to process group if m.bleCmd.Process != nil { syscall.Kill(-m.bleCmd.Process.Pid, syscall.SIGTERM) } // Wait with timeout done := make(chan error, 1) go func() { done <- m.bleCmd.Wait() }() select { case <-done: case <-time.After(3 * time.Second): // Force kill if m.bleCmd.Process != nil { syscall.Kill(-m.bleCmd.Process.Pid, syscall.SIGKILL) } } m.bleRunning = false m.bleCmd = nil log.Printf("[scanner] BLE scanner stopped") } // StartWiFi starts the WiFi scanner process func (m *ScannerManager) StartWiFi(zmqAddr, iface string) error { m.mu.Lock() defer m.mu.Unlock() if m.wifiRunning { return nil } binPath := m.binDir + "/wifi-scanner" args := []string{"-zmq", zmqAddr, "-iface", iface} if m.debug { args = append(args, "-debug") } cmd := exec.Command(binPath, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} if err := cmd.Start(); err != nil { return err } m.wifiCmd = cmd m.wifiRunning = true log.Printf("[scanner] WiFi scanner started (pid=%d)", cmd.Process.Pid) // Monitor process go m.monitorProcess(ScannerWiFi, cmd) return nil } // StopWiFi stops the WiFi scanner process func (m *ScannerManager) StopWiFi() { m.mu.Lock() defer m.mu.Unlock() if !m.wifiRunning || m.wifiCmd == nil { return } log.Printf("[scanner] Stopping WiFi scanner...") if m.wifiCmd.Process != nil { syscall.Kill(-m.wifiCmd.Process.Pid, syscall.SIGTERM) } done := make(chan error, 1) go func() { done <- m.wifiCmd.Wait() }() select { case <-done: case <-time.After(3 * time.Second): if m.wifiCmd.Process != nil { syscall.Kill(-m.wifiCmd.Process.Pid, syscall.SIGKILL) } } m.wifiRunning = false m.wifiCmd = nil log.Printf("[scanner] WiFi scanner stopped") } // IsBLERunning returns true if BLE scanner is running func (m *ScannerManager) IsBLERunning() bool { m.mu.Lock() defer m.mu.Unlock() return m.bleRunning } // IsWiFiRunning returns true if WiFi scanner is running func (m *ScannerManager) IsWiFiRunning() bool { m.mu.Lock() defer m.mu.Unlock() return m.wifiRunning } // StopAll stops all scanners func (m *ScannerManager) StopAll() { close(m.stopChan) m.StopBLE() m.StopWiFi() } // monitorProcess monitors a scanner process and marks it as stopped when it exits func (m *ScannerManager) monitorProcess(typ ScannerType, cmd *exec.Cmd) { err := cmd.Wait() m.mu.Lock() defer m.mu.Unlock() switch typ { case ScannerBLE: m.bleRunning = false m.bleCmd = nil if err != nil { log.Printf("[scanner] BLE scanner exited: %v", err) } else { log.Printf("[scanner] BLE scanner exited normally") } case ScannerWiFi: m.wifiRunning = false m.wifiCmd = nil if err != nil { log.Printf("[scanner] WiFi scanner exited: %v", err) } else { log.Printf("[scanner] WiFi scanner exited normally") } } }