196 Patterns from Automate Your Home Using Go (2024)
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.
Curriculum Upgrades Recommended
- Add an edge/homelab tutorial track under specialized chapters.
- Add observability hooks by default in systems examples.
- Add deployment story from local process -> Docker -> lightweight orchestrated environment.
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-loopSave 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-smSave 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/homeautoSave 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
SIGTERMshutdown for Raspberry Pi deploys.