8 Commits

Author SHA1 Message Date
gballan 6acdc6c7f8 [Issue #7] - Added config.go setting to allow setting the hostname
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2026-09-12 02:01:02 -04:00
gballan a23507b2ca [Issue #6] - added config option to enable/disable icmp packets
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-12 01:58:24 -04:00
gballan 6e2882dc8d Updating repository information to point to Bit Goblin
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-07 01:26:45 -04:00
gballan af084ea33f Updating README with information about the hardware used
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-07 01:21:24 -04:00
gballan 6cc457c958 Adding a version badge via shields.io
ci/woodpecker/push/woodpecker Pipeline is running
2026-09-07 01:10:02 -04:00
gballan d4747ff559 [Issue #2] - adding code to verify a display is connected before attempting to use it
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2026-09-07 01:03:30 -04:00
gballan 20ba285263 [Issue #2] - adding initial support for SSD1306 displays
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-07 00:54:27 -04:00
gballan c60ef1c093 [Issue #1] - Adding UpdateInterval config value to set how long to wait in between each update loop, in seconds
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2026-09-07 00:34:29 -04:00
7 changed files with 98 additions and 9 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2026 gballan.
Copyright (c) 2026 Bit Goblin.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+28 -3
View File
@@ -1,18 +1,43 @@
# Pico Weather
![Woodpecker CI badge](https://builds.metaunix.net/api/badges/92/status.svg)
![shields.io version badge](https://img.shields.io/gitea/v/release/BitGoblin/pico-weather?gitea_url=https://git.metaunix.net)
Raspberry Pi Pico app to read BME280 sensor data and send it to Home Assistant.
Made in Go with [TinyGo](https://tinygo.org/).
## Requirements
## Hardware
The following hardware is what I used for a deployment:
* Raspberry Pi Pico 2 W: https://www.amazon.com/dp/B0DRJXPPWL
* The **W** is VERY important, as without networking it can't reach Home Assistant!
* Also a Raspberry Pi Pico W *should* work too, but I haven't tested it yet
* Waveshare Bosch BME280 sensor: https://www.amazon.com/dp/B07Q47DFXS
* Elegoo SSD1306 LCD: https://www.amazon.com/dp/B0FSRQG23K
Most (or all?) of these components can be swapped for equivalent components. You just need to make sure the code is compatible with whatever board you choose (not likely), is a genuine BME280 sensor, and an SSD1306 display.
Support for other hardware types may come in the future, but if there's a specific sensor or board you're in submit an issue or pull request!
### Prototyping
To help with debugging/testing deployments, I also used these components:
* Elegoo 400-pin Breadboard: https://www.amazon.com/dp/B01EV640I6
* Freenove Raspberry Pi Pico Breakout Board: https://www.amazon.com/dp/B0BFB53Y2N
* Elegoo 20cm Jumper Cables 120 count: https://www.amazon.com/dp/B01EV70C78
## Software Deployment
### Requirements
* [TinyGo](https://tinygo.org/getting-started/install/) - TinyGo installation guide
## Deployment
### Steps
1. Clone this git repository `git clone https://git.metaunix.net/gballan/pico-weather`.
1. Clone this git repository `git clone https://git.metaunix.net/BitGoblin/pico-weather`.
2. Copy `config.go.example` to `config.go` and modify the values to match your environment.
3. Hold down the **BOOTSEL** button on your Raspberry Pi Pico 2W.
4. Plug in your Pico to a USB port on your PC.
+10
View File
@@ -1,9 +1,17 @@
package main
import (
"time"
)
const (
WifiSSID = "My Wifi Name"
WifiPass = "SecretWifiPassword"
// Networking settings
Hostname = "pico2w-bme280"
EnableICMP = true
MQTTHost = "192.168.1.50" // Must be an IP address; DNS resolution is not supported currently
MQTTPort = "1883"
MQTTUser = "" // Leave empty if not required
@@ -12,4 +20,6 @@ const (
NodeName = "Studio Weather"
NodeID = "studio_env"
ClientID = "pico2w_studio_env"
UpdateInterval = 30 * time.Second
)
+4 -1
View File
@@ -1,13 +1,16 @@
module git.metaunix.net/gballan/pico-weather
module git.metaunix.net/BitGoblin/pico-weather
go 1.25.2
require (
github.com/soypat/cyw43439 v0.1.1
github.com/soypat/lneto v0.3.2
tinygo.org/x/drivers v0.36.0
tinygo.org/x/tinyfont v0.7.0
)
require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710 // indirect
github.com/tinygo-org/pio v0.3.0 // indirect
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect
+6
View File
@@ -1,3 +1,5 @@
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/soypat/cyw43439 v0.1.1 h1:vcaTiVzfuz3keK7lJpVxStZ6tV8HCw7Ugzsh1k4mneE=
github.com/soypat/cyw43439 v0.1.1/go.mod h1:R2uSILRwSPmcmmKy5Z0FtK4ypgiPf5YqK+F+IKmXqxc=
github.com/soypat/lneto v0.3.2 h1:iUFeRSq2czT7Db6MMOsAnMCBlKCqvIr941zsNf9dcu0=
@@ -8,3 +10,7 @@ github.com/tinygo-org/pio v0.3.0 h1:opEnOtw58KGB4RJD3/n/Rd0/djYGX3DeJiXLI6y/yDI=
github.com/tinygo-org/pio v0.3.0/go.mod h1:wf6c6lKZp+pQOzKKcpzchmRuhiMc27ABRuo7KVnaMFU=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
tinygo.org/x/drivers v0.36.0 h1:F0x342A6GWqh6abtCa57uAxCyz/b9MbGzvIVvIf+gpE=
tinygo.org/x/drivers v0.36.0/go.mod h1:DQgKyHkB4G6IEOKVTAjApbKnWGwESN91EVJO+nMOE9Y=
tinygo.org/x/tinyfont v0.7.0 h1:Ju901eGRThlHLoti9K7myMYyv4phd3I0DrJwc14NTTU=
tinygo.org/x/tinyfont v0.7.0/go.mod h1:onflMSkpWl7r7j4MIqhPEVV39pn7yL4N3MOePl3G+G8=
+44 -3
View File
@@ -9,6 +9,11 @@ import (
"machine"
"strconv"
"time"
"image/color"
"tinygo.org/x/drivers/ssd1306"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/proggy"
)
var StateTopic = fmt.Sprintf("homeassistant/sensor/%s/state", NodeID)
@@ -292,10 +297,10 @@ func panicErr(msg string, err error) {
func main() {
time.Sleep(2 * time.Second)
// Configure I2C (Pin 16 SDA, Pin 17 SCL -> I2C0)
// Bump frequency to 400kHz for snappy display flushes
i2c := machine.I2C0
err := i2c.Configure(machine.I2CConfig{
Frequency: 100 * machine.KHz,
Frequency: 400 * machine.KHz,
SDA: machine.GPIO16,
SCL: machine.GPIO17,
})
@@ -303,6 +308,31 @@ func main() {
panicErr("I2C configuration", err)
}
// Instantiate nil oled variable
var oled *ssd1306.Device
// White color for display
white := color.RGBA{R: 255, G: 255, B: 255, A: 255}
// Probe address 0x3C with a zero-byte or 1-byte read
var dummy [1]byte
if err := i2c.ReadRegister(0x3C, 0x00, dummy[:]); err == nil {
println("SSD1306 detected at 0x3C")
dev := ssd1306.NewI2C(i2c)
dev.Configure(ssd1306.Config{
Address: 0x3C,
Width: 128,
Height: 64,
})
oled = dev
// Run some initial clearing and add booting text to the screen
oled.ClearBuffer()
tinyfont.WriteLine(oled, &proggy.TinySZ8pt7b, 0, 16, "Booting...", white)
oled.Display()
} else {
println("SSD1306 not found — running headless")
}
bme, err := NewBME280(i2c, BME280Addr)
if err != nil {
panicErr("BME280 init", err)
@@ -338,14 +368,25 @@ func main() {
if err != nil {
println("BME280 read error:", err.Error())
} else {
// Publish MQTT
stateJSON := fmt.Sprintf(`{"temperature":%.2f,"humidity":%.2f,"pressure":%.2f}`, t, h, p)
if err := mqtt.Publish(StateTopic, []byte(stateJSON), false); err != nil {
println("MQTT publish error:", err.Error())
} else {
println(fmt.Sprintf("[%s] Published: %s", NodeName, stateJSON))
}
// Render to OLED
if oled != nil {
oled.ClearBuffer()
tinyfont.WriteLine(oled, &proggy.TinySZ8pt7b, 0, 14, fmt.Sprintf("Node: %s", NodeName), white)
tinyfont.WriteLine(oled, &proggy.TinySZ8pt7b, 0, 30, fmt.Sprintf("Temp: %.1f C", t), white)
tinyfont.WriteLine(oled, &proggy.TinySZ8pt7b, 0, 46, fmt.Sprintf("Hum: %.1f %%", h), white)
tinyfont.WriteLine(oled, &proggy.TinySZ8pt7b, 0, 60, fmt.Sprintf("Pres: %.1f hPa", p), white)
oled.Display()
}
}
time.Sleep(30 * time.Second)
time.Sleep(UpdateInterval)
}
}
+5 -1
View File
@@ -115,16 +115,20 @@ func InitNetwork(ssid, pass string) (*NetworkStack, error) {
stack := &xnet.StackAsync{}
err = stack.Reset(xnet.StackConfig{
Hostname: "pico2w-bme280",
Hostname: Hostname,
RandSeed: time.Now().UnixNano(),
MaxActiveTCPPorts: 2,
MTU: uint16(framelen - ethernet.MaxOverheadSize),
HardwareAddress: hwaddr,
ICMPQueueLimit: 4,
})
if err != nil {
return nil, fmt.Errorf("stack config reset: %w", err)
}
// Enable network pings if configured
_ = stack.EnableICMP(EnableICMP)
// Start background frame pump
ctx := context.Background()
go stackLoop(ctx, stack, adapter)