trminimal

Very basic trmnl server scripts
Log | Files | Refs | README

commit 477313169a965776d7fca62ff4df8aa75a0cd3bb
Author: Martin Ashby <martin@ashbysoft.com>
Date:   Sun, 27 Sep 2026 22:38:35 +0100

initial

Diffstat:
A.gitignore | 2++
AREADME.md | 3+++
Afonts/ARIAL.TTF | 0
Ago.mod | 11+++++++++++
Ago.sum | 10++++++++++
Ahello.go | 43+++++++++++++++++++++++++++++++++++++++++++
Arotate_display.go | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweather.go | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 229 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +images/ +api/ diff --git a/README.md b/README.md @@ -0,0 +1,3 @@ +# trminimal + +Martin's very minimal trmnl server. diff --git a/fonts/ARIAL.TTF b/fonts/ARIAL.TTF Binary files differ. diff --git a/go.mod b/go.mod @@ -0,0 +1,11 @@ +module mfashby.net/trminimal/v2 + +go 1.25.0 + +require ( + github.com/gogpu/gg v0.52.5 // indirect + github.com/gogpu/gpucontext v0.28.0 // indirect + github.com/gogpu/gputypes v0.5.2 // indirect + golang.org/x/image v0.44.0 // indirect + golang.org/x/text v0.40.0 // indirect +) diff --git a/go.sum b/go.sum @@ -0,0 +1,10 @@ +github.com/gogpu/gg v0.52.5 h1:aDAxjwcUYWTaEcKiAVSX52FPtYKtMhuFYCQYK2SwJwE= +github.com/gogpu/gg v0.52.5/go.mod h1:mKiEGiR8lT7BCECfSoxRYBQ96HrDc0DHv/CNDxYQG5M= +github.com/gogpu/gpucontext v0.28.0 h1:W27+0PRnHLVRscDJ2L6r4B1JlaKjlV/EmL0Wxx8vGIc= +github.com/gogpu/gpucontext v0.28.0/go.mod h1:5rpKj+DpixgAFm8ArBXFXOZQRbeJSFncVMsqyyA9Rhc= +github.com/gogpu/gputypes v0.5.2 h1:3sbKI0+XW36Ekd3d4QhkbpdY7gbgMVgp8Q4+ZNdGhtE= +github.com/gogpu/gputypes v0.5.2/go.mod h1:cnXrDMwTpWTvJLW1Vreop3PcT6a2YP/i3s91rPaOavw= +golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I= +golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= diff --git a/hello.go b/hello.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "log" + "time" + + "github.com/gogpu/gg" + "github.com/gogpu/gg/text" +) + +const ( + outfile = "images/hello.png" +) + +func main() { + err := do(); + if err != nil { + log.Fatal(err) + } +} + +func do() error { + dc := gg.NewContext(600, 800) + defer dc.Close() + + dc.ClearWithColor(gg.White) + dc.SetColor(gg.Black) + + source, err := text.NewFontSourceFromFile("fonts/ARIAL.TTF") + if err != nil { + return err + } + defer source.Close() + dc.SetFont(source.Face(32)) + nowstr := time.Now().Local().Format("Mon, 02 Jan 2006") + dc.DrawString(fmt.Sprintf("Hello, it is %s", nowstr), 20, 32) + err = dc.SavePNG(outfile) + if err != nil { + return err + } + return nil +} diff --git a/rotate_display.go b/rotate_display.go @@ -0,0 +1,81 @@ +package main + +import ( + "encoding/json" + "log" + "os" + "path" + "slices" +) + +const ( + apifile = "api/display" + imagesbase = "https://trmnl.mfashby.net/images/" +) +var ( + rotation = []string{"weather.png", "hello.png"} +) + + +func main() { + err := do() + if err != nil { + log.Fatal(err) + } +} + +func do() error { + dispraw, err := os.ReadFile(apifile) + var current string + var d Display + if os.IsNotExist(err) { + current = rotation[0] + } else if err != nil { + return err + } else { + err = json.Unmarshal(dispraw, &d) + if err != nil { + return err + } + current = d.Filename + } + ix := slices.IndexFunc(rotation, func (x string) bool { + return x == current + }) + ix = (ix + 1) % len(rotation) + d = NewDisplay(imagesbase + rotation[ix]) + dispraw, err = json.MarshalIndent(d, "", " ") + if err != nil { + return err + } + err = os.WriteFile(apifile, dispraw, 0644) + if err != nil { + return err + } + return nil +} + +func NewDisplay(imageUrl string) Display { + filename := path.Base(imageUrl) + return Display{ + ImageUrl: imageUrl, + Filename: filename, + RefreshRate: 300, + ResetFirmware: false, + UpdateFirmware: false, + FirmwareUrl: "https://trmnl.com/firmware/1.0.0.bin", + SpecialFunction: "identify", + Action: "identify", + } +} + +type Display struct { + ImageUrl string `json:"image_url"` + Filename string `json:"filename"` + RefreshRate int `json:"refresh_rate"` + ResetFirmware bool `json:"reset_firmware"` + UpdateFirmware bool `json:"update_firmware"` + FirmwareUrl string `json:"firmware_url"` + SpecialFunction string `json:"special_function"` + Action string `json:"action"` +} +\ No newline at end of file diff --git a/weather.go b/weather.go @@ -0,0 +1,77 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/gogpu/gg" + "github.com/gogpu/gg/text" +) + +const ( + // Weather for Calverley + weatherApiUrl = "https://api.open-meteo.com/v1/forecast?latitude=58.829906&longitude=-1.687577&daily=temperature_2m_max,temperature_2m_min,precipitation_hours&models=ukmo_seamless&forecast_days=3" + outfile = "images/weather.png" +) + +func main() { + err := domain(); + if err != nil { + log.Fatal(err) + } +} + +func domain() error { + res, err := http.DefaultClient.Get(weatherApiUrl) + if err != nil { + return err + } + if res.StatusCode < 200 || res.StatusCode >= 400 { + return fmt.Errorf("Error status code from %s: %d %s", weatherApiUrl, res.StatusCode, res.Status) + } + var weather WeatherApiRes + err = json.NewDecoder(res.Body).Decode(&weather) + if err != nil { + return err + } + + dc := gg.NewContext(600, 800) + defer dc.Close() + + dc.ClearWithColor(gg.White) + dc.SetColor(gg.Black) + + // Divider lines + dc.SetLineWidth(5) + dc.DrawLine(0, 233, 600, 233) + dc.DrawLine(0, 466, 600, 466) + + source, err := text.NewFontSourceFromFile("fonts/ARIAL.TTF") + if err != nil { + return err + } + defer source.Close() + dc.SetFont(source.Face(32)) + for ix, time := range weather.Daily.Time { + dc.DrawString(time, 20, float64((ix*233) + 32)) + dc.DrawString(fmt.Sprintf("Max %.1f°C", weather.Daily.Temp2MMax[ix]), 20, float64((ix*233) + 64)) + dc.DrawString(fmt.Sprintf("Min %.1f°C", weather.Daily.Temp2MMin[ix]), 20, float64((ix*233) + 96)) + } + + err = dc.SavePNG(outfile) + if err != nil { + return err + } + return nil +} + +type WeatherApiRes struct { + Daily struct { + Time []string `json:"time"` + Temp2MMax []float64 `json:"temperature_2m_max"` + Temp2MMin []float64 `json:"temperature_2m_min"` + PrecipitationHours []float64 `json:"precipitation_hours"` + } `json:"daily"` +} +\ No newline at end of file