Created
November 25, 2023 02:34
-
-
Save idkrn123/fdebf29e8d98918b0824e4c321ce49f2 to your computer and use it in GitHub Desktop.
golang program to watch for and display nearby BLE devices, their manufacturers, and signal strength. may become a repo in the future if logic gets complex enough
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"net/http" | |
"os" | |
"sync" | |
"time" | |
"github.com/go-ble/ble" | |
"github.com/go-ble/ble/linux" | |
"github.com/gdamore/tcell/v2" | |
"github.com/rivo/tview" | |
) | |
const ( | |
apiKeyFilename = "macvendors_api_key.txt" | |
deviceProcessInterval = time.Second * 5 | |
manufacturerLookupURL = "https://api.macvendors.com/v1/lookup/" | |
) | |
var ( | |
seenDevices = make(map[string]struct{}) | |
mutex = &sync.Mutex{} | |
) | |
func main() { | |
device, err := linux.NewDevice() | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "Failed to create BLE device: %v\n", err) | |
return | |
} | |
ble.SetDefaultDevice(device) | |
apiKey, err := ioutil.ReadFile(apiKeyFilename) | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "Failed to read API key: %v\n", err) | |
return | |
} | |
app := tview.NewApplication() | |
table := tview.NewTable().SetBorders(true) | |
setupTable(table) | |
exitButton := tview.NewButton(`E["underline"]x[""]it`).SetSelectedFunc(func() { app.Stop() }) | |
flex := setupLayout(table, exitButton) | |
setupExitHandler(app, exitButton) | |
go scanBLEDevices(app, table, string(apiKey)) | |
app.SetRoot(flex, true).SetFocus(table) | |
if err := app.Run(); err != nil { | |
fmt.Fprintf(os.Stderr, "Failed to run application: %v\n", err) | |
} | |
} | |
func setupTable(table *tview.Table) { | |
headers := []string{"MAC Address", "RSSI", "Manufacturer"} | |
for i, header := range headers { | |
table.SetCell(0, i, tview.NewTableCell(header).SetAlign(tview.AlignCenter)) | |
} | |
} | |
func setupLayout(table *tview.Table, exitButton *tview.Button) *tview.Flex { | |
return tview.NewFlex(). | |
SetDirection(tview.FlexRow). | |
AddItem(table, 0, 1, true). | |
AddItem(exitButton, 1, 0, false) | |
} | |
func setupExitHandler(app *tview.Application, exitButton *tview.Button) { | |
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { | |
if event.Key() == tcell.KeyESC || event.Rune() == 'x' || event.Rune() == 'X' { | |
app.Stop() | |
return nil | |
} | |
return event | |
}) | |
exitButton.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { | |
if event.Key() == tcell.KeyEnter { | |
app.Stop() | |
return nil | |
} | |
return event | |
}) | |
} | |
func scanBLEDevices(app *tview.Application, table *tview.Table, apiKey string) { | |
ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), 10*time.Second)) | |
ble.Scan(ctx, false, func(a ble.Advertisement) { | |
mac := a.Addr().String() | |
mutex.Lock() | |
if _, seen := seenDevices[mac]; seen { | |
mutex.Unlock() | |
return | |
} | |
seenDevices[mac] = struct{}{} | |
mutex.Unlock() | |
row := table.GetRowCount() | |
app.QueueUpdateDraw(func() { | |
table.SetCell(row, 0, tview.NewTableCell(mac)) | |
table.SetCell(row, 1, tview.NewTableCell(fmt.Sprintf("%d", a.RSSI()))) | |
}) | |
go fetchManufacturer(mac, apiKey, app, table, row) | |
}, nil) | |
} | |
func fetchManufacturer(mac, apiKey string, app *tview.Application, table *tview.Table, row int) { | |
resp, err := http.Get(manufacturerLookupURL + mac) | |
if err != nil { | |
updateTableWithManufacturer(app, table, row, "Error fetching manufacturer") | |
return | |
} | |
defer resp.Body.Close() | |
var result map[string]interface{} | |
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { | |
updateTableWithManufacturer(app, table, row, "Error parsing JSON") | |
return | |
} | |
manufacturer := "Unknown Manufacturer" | |
if name, ok := result["companyName"].(string); ok { | |
manufacturer = name | |
} | |
updateTableWithManufacturer(app, table, row, manufacturer) | |
} | |
func updateTableWithManufacturer(app *tview.Application, table *tview.Table, row int, manufacturer string) { | |
app.QueueUpdateDraw(func() { | |
table.SetCell(row, 2, tview.NewTableCell(manufacturer)) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment