tui simulation

This commit is contained in:
2026-02-07 02:24:26 +01:00
parent 97427bb3c0
commit 7eb052f20e
10 changed files with 1004 additions and 64 deletions

View File

@@ -4,76 +4,118 @@ import (
"fmt"
"log"
"math/rand"
"os"
"time"
"ble_simulator/internal/ble"
"ble_simulator/internal/device"
"ble_simulator/internal/tui"
tea "github.com/charmbracelet/bubbletea"
"tinygo.org/x/bluetooth"
)
func main() {
log.Println("[INFO] BLE Simulator v1.0")
// Create shared state and log buffer
state := device.NewDeviceState()
logBuffer := tui.NewLogBuffer(500)
logBuffer.Info("BLE Simulator v1.0")
adapter := bluetooth.DefaultAdapter
must("enable BLE", adapter.Enable())
if err := adapter.Enable(); err != nil {
log.Fatalf("Failed to enable BLE: %v", err)
}
// Channel for alarm notifications from TUI
notifyCh := make(chan string, 10)
// Connection handler
adapter.SetConnectHandler(func(dev bluetooth.Device, connected bool) {
if connected {
log.Printf("[CONN] Client Connected: %s", dev.Address.String())
state.IncrConnectedClients()
logBuffer.Conn("Client Connected: %s", dev.Address.String())
} else {
log.Println("[CONN] Client Disconnected")
state.DecrConnectedClients()
logBuffer.Conn("Client Disconnected")
}
})
// Setup device state and service
state := device.NewDeviceState()
notifyChar, err := ble.SetupService(adapter, state)
must("add service", err)
// Setup BLE service with logger
notifyChar, err := ble.SetupService(adapter, state, logBuffer)
if err != nil {
log.Fatalf("Failed to add service: %v", err)
}
// Configure advertising
adv := adapter.DefaultAdvertisement()
must("configure adv", adv.Configure(bluetooth.AdvertisementOptions{
if err := adv.Configure(bluetooth.AdvertisementOptions{
LocalName: "GO_SIMULATOR",
ServiceUUIDs: []bluetooth.UUID{ble.ServiceUUID},
}))
}); err != nil {
log.Fatalf("Failed to configure advertising: %v", err)
}
must("start advertising", adv.Start())
if err := adv.Start(); err != nil {
log.Fatalf("Failed to start advertising: %v", err)
}
log.Println("[INFO] Advertising as GO_SIMULATOR...")
log.Println("[INFO] Using Nordic UART Service (NUS) UUIDs")
log.Println("[INFO] Service UUID: 6e400001-b5a3-f393-e0a9-e50e24dcca9e")
log.Println("[INFO] Command Char: 6e400002-b5a3-f393-e0a9-e50e24dcca9e (Write/RX)")
log.Println("[INFO] Notify Char: 6e400003-b5a3-f393-e0a9-e50e24dcca9e (Notify/TX)")
log.Println("[INFO] Waiting for connections...")
logBuffer.Info("Advertising as GO_SIMULATOR...")
logBuffer.Info("Using Nordic UART Service (NUS) UUIDs")
logBuffer.Info("Service UUID: 6e400001-b5a3-f393-e0a9-e50e24dcca9e")
logBuffer.Info("Command Char: 6e400002-b5a3-f393-e0a9-e50e24dcca9e (Write/RX)")
logBuffer.Info("Notify Char: 6e400003-b5a3-f393-e0a9-e50e24dcca9e (Notify/TX)")
logBuffer.Info("Waiting for connections...")
// Start notification loop (500ms heartbeat with sensor data)
go notificationLoop(notifyChar, state)
go notificationLoop(notifyChar, state, logBuffer, notifyCh)
// Block forever
select {}
// Create and run TUI
model := tui.NewModel(state, logBuffer, notifyCh)
p := tea.NewProgram(model, tea.WithAltScreen())
if _, err := p.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error running TUI: %v\n", err)
os.Exit(1)
}
}
func notificationLoop(char *bluetooth.Characteristic, state *device.DeviceState) {
func notificationLoop(char *bluetooth.Characteristic, state *device.DeviceState, logger *tui.LogBuffer, notifyCh chan string) {
ticker := time.NewTicker(500 * time.Millisecond)
for range ticker.C {
value := state.GetSensorValue()
// Add small jitter for realism (-10 to +10)
jitter := rand.Intn(21) - 10
msg := fmt.Sprintf("SENSOR:%d", value+jitter)
for {
select {
case <-ticker.C:
// Check for alarm state and send alarm message if active
if alarmActive, alarmType := state.GetAlarmState(); alarmActive {
msg := fmt.Sprintf("ALARM: %s", alarmType)
_, err := char.Write([]byte(msg + "\n"))
if err == nil {
logger.TX("Sending: %s", msg)
}
}
_, err := char.Write([]byte(msg + "\n"))
if err != nil {
// Silently ignore write errors (no subscribers)
continue
// Send regular sensor data
value := state.GetSensorValue()
jitter := state.GetJitterRange()
jitterVal := 0
if jitter > 0 {
jitterVal = rand.Intn(jitter*2+1) - jitter
}
msg := fmt.Sprintf("SENSOR:%d", value+jitterVal)
_, err := char.Write([]byte(msg + "\n"))
if err != nil {
// Silently ignore write errors (no subscribers)
continue
}
logger.TX("Sending: %s", msg)
case alarmMsg := <-notifyCh:
// Handle alarm notifications from TUI
_, err := char.Write([]byte(alarmMsg + "\n"))
if err == nil {
logger.TX("Sending: %s", alarmMsg)
}
}
log.Printf("[TX] Sending: %s", msg)
}
}
func must(action string, err error) {
if err != nil {
log.Fatalf("Failed to %s: %v", action, err)
}
}