196 Patterns from Automate Your Home Using Go (2024)

Updated

July 30, 2026

196 Patterns from Automate Your Home Using Go (2024)

This 2024 title emphasizes applied systems engineering: Raspberry Pi deployment, Dockerized services, telemetry, monitoring with Prometheus/Grafana, and practical automation workflows.

What This Adds to Our Book

  • Strong real-world edge/infrastructure use cases beyond traditional web CRUD.
  • Better observability-first mindset for long-running services.
  • Helpful pathway from single-node apps to homelab/multi-service operations.

Edge Automation Topology

sensor/input -> Go collector -> local queue/cache -> automation decision -> action
                    |
                    +-> metrics/logs -> Prometheus/Grafana

Deep Integration Example: Telemetry Collector with Health and Metrics Surface

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"
)

type Reading struct {
    Source string    `json:"source"`
    Value  float64   `json:"value"`
    At     time.Time `json:"at"`
}

type State struct {
    mu       sync.RWMutex
    last     map[string]Reading
    ingested uint64
}

func newState() *State {
    return &State{last: make(map[string]Reading)}
}

func (s *State) ingest(r Reading) {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.last[r.Source] = r
    s.ingested++
}

func (s *State) handleIngest(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
        return
    }
    var x Reading
    if err := json.NewDecoder(r.Body).Decode(&x); err != nil {
        http.Error(w, "bad payload", http.StatusBadRequest)
        return
    }
    x.At = time.Now().UTC()
    s.ingest(x)
    w.WriteHeader(http.StatusAccepted)
}

func (s *State) handleHealth(w http.ResponseWriter, _ *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    _ = json.NewEncoder(w).Encode(map[string]any{"ok": true, "ts": time.Now().UTC()})
}

func (s *State) handleMetrics(w http.ResponseWriter, _ *http.Request) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    _, _ = w.Write([]byte("collector_ingested_total "))
    _, _ = w.Write([]byte(fmt.Sprintf("%d\n", s.ingested)))
}

func main() {
    st := newState()
    mux := http.NewServeMux()
    mux.HandleFunc("POST /ingest", st.handleIngest)
    mux.HandleFunc("GET /healthz", st.handleHealth)
    mux.HandleFunc("GET /metrics", st.handleMetrics)

    srv := &http.Server{
        Addr:              ":8080",
        Handler:           mux,
        ReadHeaderTimeout: 2 * time.Second,
        ReadTimeout:       5 * time.Second,
        WriteTimeout:      10 * time.Second,
        IdleTimeout:       30 * time.Second,
    }
    log.Fatal(srv.ListenAndServe())
}

Why This Matters

Practical automation projects force integration of networking, state handling, deployment, and observability. This makes them high-value capstones for Go learners.

More examples

Device event loop with context cancel

mkdir -p /tmp/go-ha-loop && cd /tmp/go-ha-loop
go mod init example.com/ha-loop

Save as main.go:

package main

import (
    "context"
    "fmt"
    "time"
)

func poll(ctx context.Context, events chan<- string) {
    t := time.NewTicker(10 * time.Millisecond)
    defer t.Stop()
    n := 0
    for {
        select {
        case <-ctx.Done():
            return
        case <-t.C:
            n++
            events <- fmt.Sprintf("tick-%d", n)
            if n == 3 {
                return
            }
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    events := make(chan string, 8)
    go poll(ctx, events)
    for i := 0; i < 3; i++ {
        fmt.Println(<-events)
    }
}
go run .

Expected output:

tick-1
tick-2
tick-3

State machine for a smart switch

mkdir -p /tmp/go-ha-sm && cd /tmp/go-ha-sm
go mod init example.com/ha-sm

Save as main.go:

package main

import "fmt"

type State int

const (
    Off State = iota
    On
)

func (s State) String() string {
    if s == On {
        return "on"
    }
    return "off"
}

func transition(s State, event string) State {
    switch event {
    case "toggle":
        if s == On {
            return Off
        }
        return On
    case "off":
        return Off
    default:
        return s
    }
}

func main() {
    s := Off
    for _, e := range []string{"toggle", "toggle", "off", "toggle"} {
        s = transition(s, e)
        fmt.Println(e, "->", s)
    }
}
go run .

Expected output:

toggle -> on
toggle -> off
off -> off
toggle -> on

Runnable example

Homelab-shaped service: ingest events, healthz, and manual /metrics counter—stdlib HTTP with timeouts (ephemeral listen for a self-check).

mkdir -p /tmp/go-homeauto && cd /tmp/go-homeauto
go mod init example.com/homeauto

Save as main.go:

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net"
    "net/http"
    "strings"
    "sync/atomic"
    "time"
)

type state struct{ events atomic.Int64 }

func main() {
    st := &state{}
    mux := http.NewServeMux()
    mux.HandleFunc("POST /ingest", func(w http.ResponseWriter, r *http.Request) {
        var body struct {
            Device string  `json:"device"`
            Value  float64 `json:"value"`
        }
        if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
            http.Error(w, "bad json", http.StatusBadRequest)
            return
        }
        st.events.Add(1)
        w.WriteHeader(http.StatusAccepted)
        fmt.Fprintf(w, "accepted %s\n", body.Device)
    })
    mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
        w.WriteHeader(http.StatusOK)
        _, _ = w.Write([]byte("ok"))
    })
    mux.HandleFunc("GET /metrics", func(w http.ResponseWriter, _ *http.Request) {
        fmt.Fprintf(w, "# TYPE home_events_total counter\nhome_events_total %d\n", st.events.Load())
    })

    ln, err := net.Listen("tcp", "127.0.0.1:0")
    if err != nil {
        log.Fatal(err)
    }
    srv := &http.Server{Handler: mux, ReadHeaderTimeout: 2 * time.Second}
    go func() { _ = srv.Serve(ln) }()
    base := "http://" + ln.Addr().String()

    resp, err := http.Post(base+"/ingest", "application/json", strings.NewReader(`{"device":"temp-1","value":21.5}`))
    if err != nil {
        log.Fatal(err)
    }
    io.Copy(io.Discard, resp.Body)
    resp.Body.Close()
    fmt.Println("ingest:", resp.StatusCode)

    resp, _ = http.Get(base + "/healthz")
    resp.Body.Close()
    fmt.Println("healthz:", resp.StatusCode)

    resp, _ = http.Get(base + "/metrics")
    b, _ := io.ReadAll(resp.Body)
    resp.Body.Close()
    fmt.Printf("metrics:\n%s", b)
    _ = srv.Close()
}
go run .

Expected output:

ingest: 202
healthz: 200
metrics:
# TYPE home_events_total counter
home_events_total 1

What to notice

  • Homelab services still need health, metrics, and timeouts—not only happy-path handlers.
  • Atomic counters and JSON ingest are enough to start; wire MQTT/GPIO later.
  • Ship the same binary under systemd or Docker next.

Try next

  • Persist last reading to an atomic file write (part 14).
  • Add graceful SIGTERM shutdown for Raspberry Pi deploys.