Conversation
There was a problem hiding this comment.
Pull request overview
This PR configures GitHub Actions to build cross-platform binaries for multiple operating systems and architectures, while also making several code changes including dependency updates, message topic reorganization, and device initialization refactoring.
- Adds a comprehensive GitHub Actions workflow matrix to build binaries for Linux (amd64, ARM variants), macOS (Intel and Apple Silicon), and Windows
- Updates dependencies (devices v0.0.1→v0.0.3, otto v0.0.9→v0.0.11) and removes local replace directives to use published versions
- Refactors device initialization to store devices as struct fields and changes MQTT topic naming scheme with "d/" and "c/" prefixes
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/go.yml | Adds multi-architecture build matrix for Linux (ARM variants), macOS, and Windows with artifact uploads |
| go.mod | Updates dependency versions and comments out local replace directives for remote packages |
| go.sum | Adds checksums for updated dependency versions |
| main.go | Renames log file and removes goroutine wrapper from gardener.Start() call |
| gardener.go | Refactors device initialization to use struct fields, updates MQTT topics with prefixes, adds centralized message handler |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| cache: true | ||
|
|
||
| - name: Prepare dist directory | ||
| run: mkdir -p dist |
There was a problem hiding this comment.
The mkdir command syntax is Unix-specific and will fail on Windows runners. While 'mkdir dist' might work on Windows PowerShell, the '-p' flag is bash-specific. For cross-platform compatibility with all matrix builds, specify 'shell: bash' or use a platform-independent approach. Since the workflow already needs bash for the build step, add 'shell: bash' here as well.
| run: mkdir -p dist | |
| run: mkdir -p dist | |
| shell: bash |
| fmt.Println("Got soil: ") | ||
|
|
||
| case "env": | ||
| fmt.Println("Got env: ") | ||
|
|
||
| case "on", "off": | ||
| fmt.Println("Got a button") | ||
|
|
||
| default: | ||
| slog.Error("unknown msg type", "topic", msg.Topic, "msg", msg) | ||
| } | ||
| fmt.Printf("msg: %#v\n", msg) |
There was a problem hiding this comment.
The MsgHandler function uses fmt.Println for debugging output (lines 189, 192, 195, 200) instead of the structured logging (slog) used throughout the rest of the codebase. For consistency and proper log level control, these should use slog.Debug or slog.Info. Additionally, the messages are incomplete (e.g., "Got soil: " with no value), which suggests these are debugging statements that may have been left in accidentally.
| fmt.Println("Got soil: ") | |
| case "env": | |
| fmt.Println("Got env: ") | |
| case "on", "off": | |
| fmt.Println("Got a button") | |
| default: | |
| slog.Error("unknown msg type", "topic", msg.Topic, "msg", msg) | |
| } | |
| fmt.Printf("msg: %#v\n", msg) | |
| slog.Debug("received soil message", "topic", msg.Topic, "value", msg.Data) | |
| case "env": | |
| slog.Debug("received env message", "topic", msg.Topic, "value", msg.Data) | |
| case "on", "off": | |
| slog.Debug("received button message", "topic", msg.Topic, "value", msg.Data) | |
| default: | |
| slog.Error("unknown msg type", "topic", msg.Topic, "msg", msg) | |
| } | |
| slog.Debug("raw MQTT message", "msg", msg) |
| gardener := &Gardener{} | ||
| gardener.Init() | ||
| go gardener.Start() | ||
| gardener.Start() |
There was a problem hiding this comment.
Removing the goroutine from gardener.Start() changes the application's behavior significantly. The Start() method now blocks the main thread, which means signal handling won't work until Start() returns. This breaks graceful shutdown because the signal handler goroutine at line 72-76 won't be able to process signals while Start() is blocking.
Looking at the Start() method in gardener.go (lines 165-182), it doesn't appear to have a blocking loop, so this might work differently than expected. However, if Start() is intended to be blocking (e.g., if it starts servers or waits for connections), the signal handler needs to be set up before calling Start(), which it already is. But if Start() returns immediately, the program will proceed to wait on gardener.Done at line 78 without anything blocking.
|
|
||
| func (g *Gardener) initDisplay() { | ||
| display, err := oled.New("lcd", 0x27, 1) | ||
| display, err := oled.New("c/lcd", 0x27, 1) |
There was a problem hiding this comment.
The display variable is assigned but never stored in the struct field g.display (line 32). This means the display device cannot be accessed later by other methods of the Gardener struct. The pump device is similarly created but assigned to g.pump, while display should be assigned to g.display for consistency with other device initialization methods.
| topics := []string{"soil", "env", "on", "off", "pump", "display"} | ||
| for _, topic := range topics { | ||
| g.Subscribe(topic, g.MsgHandler) | ||
| } |
There was a problem hiding this comment.
The message topic subscriptions use base topic names ("soil", "env", "on", "off", "pump", "display"), but the publishing code uses prefixed topics like "d/soil", "d/env", "d/on", "d/off" (device topics) and "c/pump", "c/lcd" (control topics). This mismatch means published messages won't match the subscribed topics, breaking the message flow. The subscriptions should use the same topic prefixes as the publishers, or the publishers should use unprefixed topics.
| g.pump, err = relay.New("pump", pinmap["pump"]) | ||
| if err != nil { | ||
| panic(err) | ||
| } |
There was a problem hiding this comment.
The pump device is initialized but never added to the DeviceManager, unlike all other devices (soil, env, on, off buttons, display). This inconsistency means the pump won't be tracked or managed like other devices. Add g.DeviceManager.Add(g.pump) after line 150, similar to how other devices are registered.
| } | |
| } | |
| g.DeviceManager.Add(g.pump) |
| if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi | ||
| OUT="dist/${BIN_NAME}-${{ matrix.build.name }}${EXT}" | ||
| echo "Building $OUT (GOOS=$GOOS GOARCH=$GOARCH GOARM=$GOARM)" | ||
| go build -trimpath -ldflags "-s -w" -o "$OUT" ./... |
There was a problem hiding this comment.
The build command uses 'go build ... ./...' which builds all packages in the module and its subdirectories. However, the intent appears to be building a single binary from the main package. This should be 'go build -trimpath -ldflags "-s -w" -o "$OUT"' (without ./...) or 'go build -trimpath -ldflags "-s -w" -o "$OUT" .' to build only the main package. Building with ./... may produce unexpected results or fail if there are multiple main packages.
| go build -trimpath -ldflags "-s -w" -o "$OUT" ./... | |
| go build -trimpath -ldflags "-s -w" -o "$OUT" . |
| - name: Build binary | ||
| env: | ||
| GOOS: ${{ matrix.build.goos }} | ||
| GOARCH: ${{ matrix.build.goarch }} | ||
| GOARM: ${{ matrix.build.goarm }} | ||
| run: | | ||
| set -e | ||
| BIN_NAME=garden-station | ||
| EXT="" | ||
| if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi | ||
| OUT="dist/${BIN_NAME}-${{ matrix.build.name }}${EXT}" | ||
| echo "Building $OUT (GOOS=$GOOS GOARCH=$GOARCH GOARM=$GOARM)" | ||
| go build -trimpath -ldflags "-s -w" -o "$OUT" ./... | ||
| ls -l "$OUT" |
There was a problem hiding this comment.
The shell script uses bash-specific syntax ('set -e', conditional with 'if [ ... ]; then'), but the default shell in GitHub Actions varies by runner OS. On Windows runners, this will fail. Either specify 'shell: bash' in the step configuration, or use cross-platform commands. For consistency across all matrix builds (including Windows), add 'shell: bash' to the step.
| - name: darwin-amd64 # macOS Intel | ||
| os: macos-latest | ||
| goos: darwin | ||
| goarch: amd64 | ||
| - name: darwin-arm64 # macOS Apple Silicon | ||
| os: macos-latest |
There was a problem hiding this comment.
Both darwin-amd64 and darwin-arm64 builds are configured to use 'macos-latest' as the runner. However, GitHub Actions currently provides separate runner types: macos-latest (Apple Silicon/ARM64) and macos-13 (Intel/AMD64). Building darwin-amd64 on an ARM64 runner works through cross-compilation, but for optimal build performance and to follow best practices, consider using 'macos-13' for darwin-amd64 builds and 'macos-latest' for darwin-arm64 builds. This is optional since cross-compilation works, but it would be more consistent with how the Windows builds are structured.
Have actions pipeline generate binaries for different OS/Architecture combos.