Files
gballan d09369c394
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
[Issue #1] - Added configurable NTPServer option in config.go; also added DNS name resolution so you don't have to use IPs
2026-09-11 00:39:54 -04:00

174 lines
4.5 KiB
Go

package main
import (
"fmt"
"machine"
"net/netip"
"time"
"image/color"
"tinygo.org/x/drivers/ssd1306"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freesans"
"tinygo.org/x/tinyfont/proggy"
)
var (
defaultFont = &freesans.Regular9pt7b
tinyFont = &proggy.TinySZ8pt7b
)
// -------------------------------------------------------------
// Panic Handler & Entry Point
// -------------------------------------------------------------
func panicErr(msg string, err error) {
if err != nil {
println("FATAL:", msg, "-", err.Error())
}
for {
time.Sleep(500 * time.Millisecond)
}
}
// syncNTP triggers an NTP request and polls until completed or timed out.
func syncNTP(stack *NetworkStack, server netip.Addr, timeout time.Duration) (time.Duration, error) {
if err := stack.stack.StartNTP(server); err != nil {
return 0, fmt.Errorf("start NTP: %w", err)
}
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
offset, done := stack.stack.ResultNTPOffset()
if done {
return offset, nil
}
time.Sleep(50 * time.Millisecond)
}
return 0, fmt.Errorf("NTP request timed out")
}
func main() {
time.Sleep(2 * time.Second)
println("Starting app...")
// Bump frequency to 400kHz for snappy display flushes
i2c := machine.I2C0
err := i2c.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
SDA: machine.GPIO4,
SCL: machine.GPIO5,
})
if err != nil {
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
println("Initializing display...")
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, defaultFont, 0, 32, "Booting...", white)
oled.Display()
time.Sleep(1 * time.Second)
} else {
panicErr("Failed to init display.", err)
}
println("Starting Wi-Fi...")
oled.ClearBuffer()
tinyfont.WriteLine(oled, defaultFont, 0, 32, "Starting Wi-Fi...", white)
oled.Display()
netStack, err := InitNetwork(WifiSSID, WifiPass)
if err != nil {
panicErr("network init", err)
}
println("Wi-Fi Connected!")
oled.ClearBuffer()
tinyfont.WriteLine(oled, defaultFont, 0, 32, "Wi-Fi Connected!", white)
oled.Display()
time.Sleep(1 * time.Second)
// Extract IP string from the returned NetworkStack
ipStr := netStack.localIP.String()
println("Resolving NTP server:", NTPServer)
oled.ClearBuffer()
tinyfont.WriteLine(oled, defaultFont, 0, 32, "Resolving NTP...", white)
oled.Display()
ntpServer, err := resolveHostToIP(netStack, NTPServer, 5*time.Second)
if err != nil {
panicErr("NTP DNS lookup failed", err)
}
println("NTP server IP: ", ntpServer.String())
time.Sleep(1 * time.Second)
println("Synchronizing clock via NTP...")
oled.ClearBuffer()
tinyfont.WriteLine(oled, defaultFont, 0, 32, "Syncing NTP...", white)
oled.Display()
timeOffset, err := syncNTP(netStack, ntpServer, 5*time.Second)
if err != nil {
panicErr("NTP sync failed", err)
}
println("NTP synced successfully!")
oled.ClearBuffer()
tinyfont.WriteLine(oled, defaultFont, 0, 32, "NTP synced!", white)
oled.Display()
time.Sleep(1 * time.Second)
lastSecond := -1
// Remove static call before the loop, then in the loop:
for {
currentTime := time.Now().Add(timeOffset).UTC()
currentSecond := currentTime.Second()
if currentSecond != lastSecond {
lastSecond = currentSecond
timeSeperator := ":"
if currentSecond%2 == 1 {
timeSeperator = " "
}
// Dynamically determine DST vs Standard time for this second
loc, zoneName := ResolveTimezone(ActiveTimezone, currentTime)
localTime := currentTime.In(loc)
timeStr := fmt.Sprintf("%02d%s%02d%s%02d %s",
localTime.Hour(), timeSeperator,
localTime.Minute(), timeSeperator,
localTime.Second(),
zoneName)
oled.ClearBuffer()
tinyfont.WriteLine(oled, defaultFont, 8, 32, timeStr, white)
tinyfont.WriteLine(oled, tinyFont, 0, 60, fmt.Sprintf("IP: %s", ipStr), white)
oled.Display()
}
time.Sleep(10 * time.Millisecond)
}
}