// cmd/server/main.go package main import ( "context" "encoding/json" "fmt" "log" "net/http" "gpio-monitor/internal/adapter" ) // API обработчики HTTP type API struct { buf *adapter.RingBuffer } func (a *API) HandleLatest(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } latest, ok := a.buf.GetLatest() if !ok { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "error": "no data yet", }) return } // Явное преобразование в []int для читаемого JSON lastN := a.buf.GetLast(10) historyInts := make([]int, len(lastN)) for i, v := range lastN { historyInts[i] = int(v) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "latest_byte": latest, "latest_hex": fmt.Sprintf("0x%02x", latest), "latest_char": string(rune(latest)), "history": historyInts, // теперь будет [1,2,3,...] "stats": a.buf.Stats(), }) } func (a *API) HandleNibbles(w http.ResponseWriter, r *http.Request) { // Для 4-битной шины — показываем последние 16 полубайт data := a.buf.GetLast(16) nibbles := make([]map[string]interface{}, len(data)) for i, b := range data { nibbles[i] = map[string]interface{}{ "index": i, "value": int(b), "hex": fmt.Sprintf("0x%02x", b), "bin": fmt.Sprintf("%04b", b), // 4 бита "bin_full": fmt.Sprintf("%08b", b), // полный байт } } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "nibbles": nibbles, "count": len(nibbles), }) } func (a *API) HandleHistory(w http.ResponseWriter, r *http.Request) { // Можно добавить параметр ?n=100 n := 100 data := a.buf.GetLast(n) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "bytes": data, "count": len(data), }) } // CORS middleware для удобства разработки func corsMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } next(w, r) } } func main() { // Инициализация updates := make(chan byte, 256) buf := adapter.NewRingBuffer(1024 * 10) // 10KB буфер // Запускаем чтение stdin в фоне ctx, cancel := context.WithCancel(context.Background()) defer cancel() reader := adapter.NewStdinReader(updates) if err := reader.Start(ctx, buf); err != nil { log.Fatal("Ошибка запуска читателя stdin:", err) } // Инициализация API api := &API{buf: buf} // HTTP маршруты (каждый только ОДИН раз!) http.HandleFunc("/api/latest", corsMiddleware(api.HandleLatest)) http.HandleFunc("/api/history", corsMiddleware(api.HandleHistory)) http.HandleFunc("/api/nibbles", corsMiddleware(api.HandleNibbles)) // Статические файлы для веб-интерфейса fs := http.FileServer(http.Dir("web")) http.Handle("/", fs) // Запуск сервера log.Println("=== GPIO Monitor Server ===") log.Println("Сервер запущен на http://localhost:8080") log.Println("API endpoints:") log.Println(" GET /api/latest - последний байт + история") log.Println(" GET /api/history - последние 100 байт") log.Println(" GET /api/nibbles - 4-битное представление") log.Println("Ожидание данных из stdin...") log.Println("") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal("Ошибка сервера:", err) } }