Alter 13 files

Add `.gitignore`
Update `go.mod`
Add `go.sum`
Add `drawing.go`
Add `JetBrainsMono-Bold.ttf`
Add `JetBrainsMono-Italic.ttf`
Add `JetBrainsMono-Regular.ttf`
Add `weathericons-regular-webfont.ttf`
Add `imagegen.go`
Add `kindle-dashboard.test.png`
Add `rttclient.go`
Add `wxclient.go`
Add `rootpasswordtool.txt`
This commit is contained in:
akp 2023-11-19 19:14:28 +00:00
parent 1178c657e0
commit 2e9b8cdfb0
No known key found for this signature in database
GPG key ID: CF8D58F3DEB20755
13 changed files with 693 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
backup
*_test.go

22
go.mod
View file

@ -1,3 +1,25 @@
module git.tdpain.net/codemicro/kindle-dashboard
go 1.21.4
require (
github.com/carlmjohnson/requests v0.23.5
github.com/disintegration/imaging v1.6.2
github.com/tdewolff/canvas v0.0.0-20231102134958-6de43c767dbf
)
require (
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect
github.com/benoitkugler/textlayout v0.3.0 // indirect
github.com/benoitkugler/textprocessing v0.0.3 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/go-fonts/latin-modern v0.3.1 // indirect
github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/tdewolff/minify/v2 v2.20.5 // indirect
github.com/tdewolff/parse/v2 v2.7.3 // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/text v0.13.0 // indirect
star-tex.org/x/tex v0.4.0 // indirect
)

69
go.sum Normal file
View file

@ -0,0 +1,69 @@
git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8=
git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo=
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw=
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk=
github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w=
github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo=
github.com/benoitkugler/textprocessing v0.0.3 h1:Q2X+Z6vxuW5Bxn1R9RaNt0qcprBfpc2hEUDeTlz90Ng=
github.com/benoitkugler/textprocessing v0.0.3/go.mod h1:/4bLyCf1QYywunMK3Gf89Nhb50YI/9POewqrLxWhxd4=
github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY=
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
github.com/carlmjohnson/requests v0.23.5 h1:NPANcAofwwSuC6SIMwlgmHry2V3pLrSqRiSBKYbNHHA=
github.com/carlmjohnson/requests v0.23.5/go.mod h1:zG9P28thdRnN61aD7iECFhH5iGGKX2jIjKQD9kqYH+o=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/go-fonts/latin-modern v0.3.1 h1:/cT8A7uavYKvglYXvrdDw4oS5ZLkcOU22fa2HJ1/JVM=
github.com/go-fonts/latin-modern v0.3.1/go.mod h1:ysEQXnuT/sCDOAONxC7ImeEDVINbltClhasMAqEtRK0=
github.com/go-fonts/liberation v0.3.1 h1:9RPT2NhUpxQ7ukUvz3jeUckmN42T9D9TpjtQcqK/ceM=
github.com/go-fonts/liberation v0.3.1/go.mod h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY=
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs=
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM=
github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw=
github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=
github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d h1:HrdwTlHVMdi9nOW7ZnYiLmIT1hJHvipIwM0aX3rKn8I=
github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/tdewolff/canvas v0.0.0-20231102134958-6de43c767dbf h1:SiFIPDecTcEpw8GTDi2NsX0Cedp+GizeammteylMvmE=
github.com/tdewolff/canvas v0.0.0-20231102134958-6de43c767dbf/go.mod h1:QT5dPLAqJLD02UXi9Dd2BkCBV4i3zh+dVxITYpd7q2Y=
github.com/tdewolff/minify/v2 v2.20.5 h1:IbJpmpAFESnuJPdsvFBJWsDcXE5qHsmaVQrRqhOI9sI=
github.com/tdewolff/minify/v2 v2.20.5/go.mod h1:N78HtaitkDYAWXFbqhWX/LzgwylwudK0JvybGDVQ+Mw=
github.com/tdewolff/parse/v2 v2.7.3 h1:SHj/ry85FdqniccvzJTG+Gt/mi/HNa1cJcTzYZnvc5U=
github.com/tdewolff/parse/v2 v2.7.3/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8=
github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE=
gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU=
star-tex.org/x/tex v0.4.0 h1:AXUwgpnHLCxZUWW3qrmjv6ezNhH3PjUVBuLLejz2cgU=
star-tex.org/x/tex v0.4.0/go.mod h1:w91ycsU/DkkCr7GWr60GPWqp3gn2U+6VX71T0o8k8qE=

274
imagegen/drawing.go Normal file
View file

@ -0,0 +1,274 @@
package imagegen
import (
_ "embed"
"errors"
"fmt"
"github.com/tdewolff/canvas"
"time"
)
//go:embed fonts/JetBrainsMono-Regular.ttf
var srcfontJBMonoRegular []byte
//go:embed fonts/JetBrainsMono-Bold.ttf
var srcfontJBMonoBold []byte
//go:embed fonts/JetBrainsMono-Italic.ttf
var srcfontJBMonoItalic []byte
//go:embed fonts/weathericons-regular-webfont.ttf
var srcFontWeatherIcons []byte
var (
fontJetBrainsMono *canvas.FontFamily
fontWeatherIcons *canvas.Font
fontMonoTitle *canvas.FontFace
fontMonoSubtitleInverted *canvas.FontFace
fontMonoLittleSubtitle *canvas.FontFace
fontMonoMedText *canvas.FontFace
fontMonoSmallText *canvas.FontFace
fontWeatherIconsSmall *canvas.FontFace
fontWeatherIconsLarge *canvas.FontFace
)
func init() {
fontJetBrainsMono = canvas.NewFontFamily("jetbrainsmono")
if err := fontJetBrainsMono.LoadFont(srcfontJBMonoRegular, 0, canvas.FontRegular); err != nil {
panic(err)
}
if err := fontJetBrainsMono.LoadFont(srcfontJBMonoBold, 0, canvas.FontBold); err != nil {
panic(err)
}
if err := fontJetBrainsMono.LoadFont(srcfontJBMonoItalic, 0, canvas.FontItalic); err != nil {
panic(err)
}
fontMonoTitle = fontJetBrainsMono.Face(220)
fontMonoSubtitleInverted = fontJetBrainsMono.Face(95, canvas.White, canvas.FontBold)
fontMonoLittleSubtitle = fontJetBrainsMono.Face(80, canvas.FontBold)
fontMonoMedText = fontJetBrainsMono.Face(150, canvas.FontRegular)
fontMonoSmallText = fontJetBrainsMono.Face(75, canvas.FontRegular)
if i, err := canvas.LoadFont(srcFontWeatherIcons, 0, canvas.FontRegular); err != nil {
panic(err)
} else {
fontWeatherIcons = i
}
fontWeatherIconsSmall = fontWeatherIcons.Face(150, canvas.Black)
fontWeatherIconsLarge = fontWeatherIcons.Face(300, canvas.Black)
}
func drawTitle(ctx *canvas.Context, startFrom float64) float64 {
now := time.Now().UTC()
var tod string
if hr := now.Hour(); 0 <= hr && hr < 12 {
tod = "morning"
} else if 12 <= hr && hr < 17 {
tod = "afternoon"
} else if 17 <= hr && hr < 21 {
tod = "evening"
} else {
tod = "night"
}
titleText := "good " + tod
var (
titleBounds canvas.Rect
)
{
// draw title
tl := canvas.NewTextLine(fontMonoTitle, titleText, canvas.Left)
titleBounds = tl.OutlineBounds()
ctx.DrawText(padding, startFrom+titleBounds.H, tl)
}
{
// draw date
rt := canvas.NewTextLine(fontMonoLittleSubtitle, now.Format("02 Jan"), canvas.Right)
ctx.DrawText(imageWidth-padding, startFrom+titleBounds.H, rt)
}
return startFrom + titleBounds.H
}
func drawWeather(ctx *canvas.Context, startFrom float64, wxLoc string, wx []*weatherEntry) (float64, error) {
if len(wx) < 1 {
return 0, errors.New("at least 1 weather entry required")
}
cursorX := float64(0)
cursorY := startFrom + 15
startFrom += 15
tl := canvas.NewTextLine(fontMonoSubtitleInverted, "Weather", canvas.Left)
bounds := tl.OutlineBounds()
{
cursorX += padding
cursorY += padding
//posX := padding
//posY := startFrom + padding
titlePad := float64(5)
ctx.SetFill(canvas.Black)
ctx.DrawPath(cursorX, cursorY, canvas.Rectangle(bounds.W+titlePad*2, bounds.H+titlePad*2))
cursorX += titlePad
cursorY += titlePad + bounds.H
ctx.DrawText(cursorX, cursorY, tl)
cursorX += bounds.W
ctx.DrawText(cursorX, cursorY, canvas.NewTextLine(fontMonoLittleSubtitle, fmt.Sprintf(" in %s at %02d00z", wxLoc, wx[0].Time), canvas.Left))
}
cursorY += bounds.H + 5
cursorX = padding
titleBaseline := startFrom + (bounds.H * 2) + 5
//ctx.SetFill(canvas.Red)
//ctx.DrawPath(1, titleBaseline, canvas.Rectangle(900, 1))
currTempStr := fmt.Sprintf("%02d", wx[0].Temperature)
currWindSpeedStr := fmt.Sprintf("w%dmph", wx[0].WindSpeed)
currPrecipProbStr := fmt.Sprintf("r%d%%", wx[0].PrecipitationChance)
tl = canvas.NewTextLine(fontWeatherIconsLarge, weatherIcons[wx[0].Type], canvas.Left)
bounds = tl.OutlineBounds()
wxIconsBaseline := titleBaseline + fontWeatherIconsLarge.Size
ctx.DrawText(padding*2, wxIconsBaseline, tl)
minitextblockxpos := (padding * 2) + 15 + bounds.W
rt := canvas.NewRichText(fontMonoMedText)
rt.WriteString(currTempStr)
rt.SetFace(fontWeatherIconsSmall)
rt.WriteString("\uf03c")
rtt := rt.ToText(fontMonoMedText.TextWidth(currTempStr)+fontWeatherIconsSmall.TextWidth("\uf03c"), fontMonoMedText.LineHeight()+10, canvas.Left, canvas.Left, 0.0, 0.0)
ctx.DrawText(minitextblockxpos, titleBaseline, rtt)
tl = canvas.NewTextLine(fontMonoSmallText, currWindSpeedStr, canvas.Left)
ctx.DrawText(minitextblockxpos, titleBaseline+90, tl)
tl = canvas.NewTextLine(fontMonoSmallText, currPrecipProbStr, canvas.Left)
ctx.DrawText(minitextblockxpos, titleBaseline+120, tl)
// -----
minitextblockxpos += rtt.Width + 15
dividerLineHeight := wxIconsBaseline - titleBaseline
for _, weather := range wx[1:] {
ctx.SetFill(canvas.Black)
ctx.DrawPath(minitextblockxpos, titleBaseline+20, canvas.Rectangle(3, dividerLineHeight))
minitextblockxpos += 15
wtl := canvas.NewTextLine(fontWeatherIconsSmall, weatherIcons[weather.Type], canvas.Left)
bounds := wtl.OutlineBounds()
tl = canvas.NewTextLine(fontMonoSmallText, fmt.Sprintf("%02dz %02dc", weather.Time, weather.Temperature), canvas.Left)
nbounds := tl.OutlineBounds()
ctx.DrawText(minitextblockxpos, titleBaseline+bounds.H+nbounds.H+50, tl)
ctx.DrawText(minitextblockxpos+((8+nbounds.W-bounds.W)/2), titleBaseline+bounds.H+30, wtl)
minitextblockxpos += 15 + nbounds.W
}
return titleBaseline + dividerLineHeight, nil
}
// https://erikflowers.github.io/weather-icons/
var weatherIcons = map[int]string{
0: "\uf02e", // clear night
1: "\uf00d", // sunny day
2: "\uf086", // partly cloudy (night)
3: "\uf002", // partly cloudy (day)
// 4 not used
5: "\uf014", // mist
6: "\uf014", // fog
7: "\uf013", // cloudy
8: "\uf041", // overcast
9: "\uf029", // light rain shower (night)
10: "\uf009", // light rain shower (day)
11: "\uf01c", // drizzle
12: "\uf01a", // light rain
13: "\uf008", // Heavy rain shower (night)
14: "\uf028", // Heavy rain shower (day)
15: "\uf019", // Heavy rain
16: "\uf0b4", // Sleet shower (night)
17: "\uf0b2", // Sleet shower (day)
18: "\uf0b5", // Sleet
19: "\uf024", // Hail shower (night)
20: "\uf004", // Hail shower (day)
21: "\uf015", // Hail
22: "\uf067", // Light snow shower (night)
23: "\uf065", // Light snow shower (day)
24: "\uf064", // Light snow
25: "\uf076", // Heavy snow shower (night)
26: "\uf076", // Heavy snow shower (day)
27: "\uf076", // Heavy snow
28: "\uf02d", // Thunder shower (night)
29: "\uf010", // Thunder shower (day)
30: "\uf01e", // Thunder
}
func drawTrains(ctx *canvas.Context, startFrom float64, services []string) (float64, error) {
cursorX := float64(0)
cursorY := startFrom + 15
startFrom += 15
tl := canvas.NewTextLine(fontMonoSubtitleInverted, "Trains", canvas.Left)
bounds := tl.OutlineBounds()
{
cursorX += padding
cursorY += padding
//posX := padding
//posY := startFrom + padding
titlePad := float64(5)
ctx.SetFill(canvas.Black)
ctx.DrawPath(cursorX, cursorY, canvas.Rectangle(bounds.W+titlePad*2, bounds.H+titlePad*2))
cursorX += titlePad
cursorY += titlePad + bounds.H
ctx.DrawText(cursorX, cursorY, tl)
cursorX += bounds.W
ctx.DrawText(cursorX, cursorY, canvas.NewTextLine(fontMonoLittleSubtitle, " at SLY", canvas.Left))
}
cursorY += bounds.H + 10
cursorX = padding
titleBaseline := startFrom + (bounds.H * 2) + 5
if len(services) == 0 {
services = []string{"*** No services in the next 30 mins ***"}
}
var lh float64
for _, str := range services {
tl := canvas.NewTextLine(fontMonoSmallText, str, canvas.Left)
if lh == 0 {
lh = tl.OutlineBounds().H
}
ctx.DrawText(cursorX, cursorY, tl)
cursorY += lh + 5
}
return titleBaseline, nil
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

78
imagegen/imagegen.go Normal file
View file

@ -0,0 +1,78 @@
package imagegen
import (
"bytes"
"fmt"
"github.com/disintegration/imaging"
"github.com/tdewolff/canvas"
"github.com/tdewolff/canvas/renderers/rasterizer"
"image"
"image/png"
"strings"
)
type Config struct {
WxLocation string
MetOfficeDatapointAPIKey string
TrainsLocation string
RTTUsername string
RTTPassword string
}
const (
padding float64 = 20
imageWidth float64 = 800
imageHeight float64 = 600
)
func Generate(conf *Config) ([]byte, error) {
// TODO: hardcoded secrets ick
locStr, wx, err := getWeatherInLocation(conf.MetOfficeDatapointAPIKey, conf.WxLocation)
if err != nil {
return nil, err
}
trains, err := getNextTrains(conf.RTTUsername, conf.RTTPassword, conf.TrainsLocation)
if err != nil {
return nil, err
}
fmt.Println(strings.Join(trains, "\n"))
for _, w := range wx {
fmt.Printf("%#v\n", w)
}
c := canvas.New(imageWidth, imageHeight)
ctx := canvas.NewContext(c)
ctx.SetCoordSystem(canvas.CartesianIV)
ctx.SetFill(canvas.White)
ctx.DrawPath(0, 0, canvas.Rectangle(imageWidth, imageHeight))
bottom := drawTitle(ctx, padding)
bottom, err = drawWeather(ctx, bottom, locStr, wx)
if err != nil {
return nil, err
}
bottom, err = drawTrains(ctx, bottom, trains)
if err != nil {
return nil, err
}
//bottom += padding
//
//ctx.SetFill(canvas.Red)
//ctx.DrawPath(1, bottom, canvas.Rectangle(900, 3))
rasterizer.New(imageWidth, imageHeight, canvas.DPMM(1), canvas.DefaultColorSpace)
var img image.Image
img = rasterizer.Draw(c, canvas.DPMM(1), canvas.DefaultColorSpace)
img = imaging.Rotate270(img)
buf := new(bytes.Buffer)
if err := png.Encode(buf, img); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

110
imagegen/rttclient.go Normal file
View file

@ -0,0 +1,110 @@
package imagegen
import (
"context"
"fmt"
"github.com/carlmjohnson/requests"
"time"
)
type rawTrainsResp struct {
Location struct {
Name string `json:"name"`
Crs string `json:"crs"`
Tiploc string `json:"tiploc"`
Country string `json:"country"`
System string `json:"system"`
} `json:"location"`
Filter interface{} `json:"filter"`
Services []struct {
LocationDetail struct {
RealtimeActivated bool `json:"realtimeActivated"`
Tiploc string `json:"tiploc"`
Crs string `json:"crs"`
Description string `json:"description"`
GbttBookedArrival string `json:"gbttBookedArrival"`
GbttBookedDeparture string `json:"gbttBookedDeparture"`
Origin []struct {
Tiploc string `json:"tiploc"`
Description string `json:"description"`
WorkingTime string `json:"workingTime"`
PublicTime string `json:"publicTime"`
} `json:"origin"`
Destination []struct {
Tiploc string `json:"tiploc"`
Description string `json:"description"`
WorkingTime string `json:"workingTime"`
PublicTime string `json:"publicTime"`
} `json:"destination"`
IsCall bool `json:"isCall"`
IsPublicCall bool `json:"isPublicCall"`
RealtimeArrival string `json:"realtimeArrival"`
RealtimeArrivalActual bool `json:"realtimeArrivalActual"`
RealtimeDeparture string `json:"realtimeDeparture"`
RealtimeDepartureActual bool `json:"realtimeDepartureActual"`
DisplayAs string `json:"displayAs"`
} `json:"locationDetail"`
ServiceUid string `json:"serviceUid"`
RunDate string `json:"runDate"`
TrainIdentity string `json:"trainIdentity"`
RunningIdentity string `json:"runningIdentity"`
AtocCode string `json:"atocCode"`
AtocName string `json:"atocName"`
ServiceType string `json:"serviceType"`
IsPassenger bool `json:"isPassenger"`
} `json:"services"`
}
func getNextTrains(username, password, location string) ([]string, error) {
resp := new(rawTrainsResp)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
err := requests.
URL("https://api.rtt.io").
Pathf("/api/v1/json/search/%s", location).
BasicAuth(username, password).
ToJSON(&resp).
Fetch(ctx)
cancel()
if err != nil {
return nil, fmt.Errorf("get trains: %w", err)
}
var res []string
for _, service := range resp.Services {
depTime, err := time.ParseInLocation("1504 2006-01-02", service.LocationDetail.GbttBookedDeparture+" "+service.RunDate, time.UTC)
if err != nil {
return nil, fmt.Errorf("parse trains: %w", err)
}
now := time.Now().UTC()
if depTime.Sub(now) < time.Minute*60 {
str := fmt.Sprintf("%s % -25s", service.LocationDetail.GbttBookedDeparture, service.LocationDetail.Destination[0].Description)
var time string
if service.LocationDetail.DisplayAs == "CANCELLED_CALL" {
time = "Cancelled"
} else if service.LocationDetail.RealtimeDeparture != service.LocationDetail.GbttBookedDeparture {
time = "Expt " + service.LocationDetail.RealtimeDeparture
} else if service.LocationDetail.RealtimeArrivalActual {
time = "Arrived"
} else {
time = "On time"
}
str += " " + time
res = append(res, str)
}
}
for i, str := range res {
suffix := "th"
if i+1 == 1 {
suffix = "st"
} else if i+1 == 2 {
suffix = "nd"
} else if i+1 == 3 {
suffix = "rd"
}
res[i] = fmt.Sprintf("%d%s %s", i+1, suffix, str)
}
return res, nil
}

137
imagegen/wxclient.go Normal file
View file

@ -0,0 +1,137 @@
package imagegen
import (
"context"
"fmt"
"github.com/carlmjohnson/requests"
"strconv"
"time"
"unicode"
)
type rawWxResp struct {
SiteRep struct {
DV struct {
DataDate time.Time `json:"dataDate"`
Type string `json:"type"`
Location struct {
ID string `json:"i"`
Lat string `json:"lat"`
Lon string `json:"lon"`
Name string `json:"name"`
Country string `json:"country"`
Continent string `json:"continent"`
Elevation string `json:"elevation"`
Period []struct {
Type string `json:"type"`
Value string `json:"value"`
Rep []struct {
WindDirection string `json:"D"`
FeelsLikeTemperature string `json:"F"`
WindGust string `json:"G"`
RelativeHumidityPercentage string `json:"H"`
PrecipitationProbability string `json:"Pp"`
WindSpeed string `json:"S"`
Temperature string `json:"T"`
Visibility string `json:"V"`
WeatherType string `json:"W"`
MaxUVIndex string `json:"U"`
MinutesPastMidnight string `json:"$"`
} `json:"Rep"`
} `json:"Period"`
} `json:"Location"`
} `json:"DV"`
} `json:"SiteRep"`
}
type weatherEntry struct {
Time int
Type int
Temperature int
PrecipitationChance int
WindSpeed int
}
func getWeatherInLocation(apiKey string, location string) (string, []*weatherEntry, error) {
resp := new(rawWxResp)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
err := requests.
URL("http://datapoint.metoffice.gov.uk").
Pathf("/public/data/val/wxfcs/all/json/%s", location).
Params(map[string][]string{
"key": {apiKey},
"res": {"3hourly"},
}).
ToJSON(&resp).
Fetch(ctx)
cancel()
if err != nil {
return "", nil, fmt.Errorf("get weather: %w", err)
}
// get 4
var res []*weatherEntry
dayLoop:
for _, dayWx := range resp.SiteRep.DV.Location.Period {
day, err := time.Parse("2006-01-02Z", dayWx.Value)
if err != nil {
return "", nil, fmt.Errorf("parse date: %w", err)
}
for _, wx := range dayWx.Rep {
if len(res) >= 4 {
break dayLoop
}
minsPastMidnight, err := strconv.Atoi(wx.MinutesPastMidnight)
if err != nil {
return "", nil, fmt.Errorf("parse weather: %w", err)
}
thisTime := day.Add(time.Minute * time.Duration(minsPastMidnight))
if resp.SiteRep.DV.DataDate.Equal(thisTime) || resp.SiteRep.DV.DataDate.Before(thisTime.Add(time.Hour*3)) {
// if this is applicable
wxE := new(weatherEntry)
wxE.Time = minsPastMidnight / 60
wxE.Type, err = strconv.Atoi(wx.WeatherType)
if err != nil {
return "", nil, fmt.Errorf("parse weather: %w", err)
}
{
temp, err := strconv.ParseFloat(wx.Temperature, 32)
if err != nil {
return "", nil, fmt.Errorf("parse weather: %w", err)
}
wxE.Temperature = int(temp)
}
{
precProb, err := strconv.ParseFloat(wx.PrecipitationProbability, 32)
if err != nil {
return "", nil, fmt.Errorf("parse weather: %w", err)
}
wxE.PrecipitationChance = int(precProb)
}
{
windSpeed, err := strconv.ParseFloat(wx.WindSpeed, 32)
if err != nil {
return "", nil, fmt.Errorf("parse weather: %w", err)
}
wxE.WindSpeed = int(windSpeed)
}
res = append(res, wxE)
}
}
}
loc := []rune(resp.SiteRep.DV.Location.Name)
for i, x := range loc {
if i == 0 || loc[i-1] == ' ' {
loc[i] = unicode.ToUpper(x)
} else {
loc[i] = unicode.ToLower(x)
}
}
return string(loc), res, nil
}

1
rootpasswordtool.txt Normal file
View file

@ -0,0 +1 @@
https://www.sven.de/kindle/#