Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
ae53a1d5fd | |||
cd04beeec6 | |||
3d3492a283 | |||
ce0ef7d291 |
2
go.mod
2
go.mod
@ -3,7 +3,7 @@ module gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
gitea.libretechconsulting.com/rmcguire/go-app v0.4.2
|
||||
gitea.libretechconsulting.com/rmcguire/go-app v0.5.0
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/gorilla/schema v1.4.1
|
||||
github.com/rs/zerolog v1.33.0
|
||||
|
2
go.sum
2
go.sum
@ -4,6 +4,8 @@ gitea.libretechconsulting.com/rmcguire/go-app v0.4.1 h1:gjDg2M/j1AdMCtkXqQnLCo6j
|
||||
gitea.libretechconsulting.com/rmcguire/go-app v0.4.1/go.mod h1:9c71S+sJb2NqvOwt3CFsW5WjE895goiRlMTdLimgwHs=
|
||||
gitea.libretechconsulting.com/rmcguire/go-app v0.4.2 h1:LQxVLXEHruY32GaMsS5K/tMdjS5kvw6reUh25gshn40=
|
||||
gitea.libretechconsulting.com/rmcguire/go-app v0.4.2/go.mod h1:9c71S+sJb2NqvOwt3CFsW5WjE895goiRlMTdLimgwHs=
|
||||
gitea.libretechconsulting.com/rmcguire/go-app v0.5.0 h1:5yYyaXXN5KcxMIPBYLZKztvKGMlYol3+oqzUnkvHBaQ=
|
||||
gitea.libretechconsulting.com/rmcguire/go-app v0.5.0/go.mod h1:9c71S+sJb2NqvOwt3CFsW5WjE895goiRlMTdLimgwHs=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
||||
|
3
main.go
3
main.go
@ -10,6 +10,7 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/ambient"
|
||||
"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/ambient/ambienthttp"
|
||||
"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/ambient/config"
|
||||
)
|
||||
|
||||
@ -46,6 +47,8 @@ func main() {
|
||||
HandlerFunc: aw.GetAWNHandlerFunc(ctx),
|
||||
},
|
||||
},
|
||||
CustomListener: ambienthttp.NewAWNMutatingListener(ctx,
|
||||
awConfig.HTTP.Listen), // Necessary to fix certain bad AWN firmware
|
||||
HealthChecks: []srv.HealthCheckFunc{
|
||||
// TODO: Implement
|
||||
func(ctx context.Context) error {
|
||||
|
112
pkg/ambient/ambienthttp/ambienthttp.go
Normal file
112
pkg/ambient/ambienthttp/ambienthttp.go
Normal file
@ -0,0 +1,112 @@
|
||||
// This package exists purely to override the net.Listener used
|
||||
// by the application's http server. This is necessary for certain versions
|
||||
// of firmware which errantly put an 0x0a (LF) following PASSKEY for
|
||||
// AmbientWeather type http reporting.
|
||||
//
|
||||
// This needs to be fixed upstream by Ambient Weather and is a complete
|
||||
// hack that should never be necessary. Without this, the http server
|
||||
// will silently crank back an HTTP:400
|
||||
package ambienthttp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"regexp"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// Invalid Request Pattern
|
||||
var badReqURI = regexp.MustCompile(`PASSKEY=[^\n&]{16,}$`)
|
||||
|
||||
// Listener encapsulates LFStrippingConn to perform
|
||||
// infuriating strip of newline character present after PASSKEY
|
||||
// sent errantly by specific versions of firmware sending updates
|
||||
// in AmbientWeather protocol
|
||||
type LFStrippingListener struct {
|
||||
ctx context.Context
|
||||
net.Listener
|
||||
}
|
||||
|
||||
type LFStrippingConn struct {
|
||||
ctx context.Context
|
||||
reader io.Reader
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (l *LFStrippingListener) WrapConn(conn net.Conn) net.Conn {
|
||||
buf := new(bytes.Buffer)
|
||||
reader := io.TeeReader(conn, buf)
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
var newData []byte
|
||||
for scanner.Scan() {
|
||||
line := scanner.Bytes()
|
||||
newData = append(newData, line...)
|
||||
|
||||
// Only restore newline if not a bad request
|
||||
if !badReqURI.Match(line) {
|
||||
newData = append(newData, '\n')
|
||||
} else {
|
||||
zerolog.Ctx(l.ctx).Trace().Bytes("line", line).
|
||||
Msg("malformed request found, stripped 0x0a")
|
||||
}
|
||||
|
||||
if len(line) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if scanner.Err() != nil {
|
||||
zerolog.Ctx(l.ctx).Err(scanner.Err()).Send()
|
||||
}
|
||||
|
||||
zerolog.Ctx(l.ctx).Trace().
|
||||
Int("numBytes", len(newData)).
|
||||
Bytes("request", newData).
|
||||
Msg("stripping conn complete")
|
||||
|
||||
// Use a multi-reader to prepend the modified request
|
||||
finalReader := io.MultiReader(bytes.NewReader(newData), conn)
|
||||
|
||||
return &LFStrippingConn{
|
||||
Conn: conn,
|
||||
ctx: l.ctx,
|
||||
reader: finalReader,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAWNMutatingListener(ctx context.Context, listen string) net.Listener {
|
||||
rawListener, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Encapsulate the raw listener with ours
|
||||
return &LFStrippingListener{
|
||||
Listener: rawListener,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LFStrippingListener) Accept() (net.Conn, error) {
|
||||
conn, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l.WrapConn(conn), nil
|
||||
}
|
||||
|
||||
func (l *LFStrippingListener) Close() error {
|
||||
return l.Listener.Close()
|
||||
}
|
||||
|
||||
func (l *LFStrippingListener) Addr() net.Addr {
|
||||
return l.Listener.Addr()
|
||||
}
|
||||
|
||||
func (c *LFStrippingConn) Read(b []byte) (int, error) {
|
||||
return c.reader.Read(b)
|
||||
}
|
@ -15,11 +15,27 @@ func (u *WeatherUpdate) Enrich(weatherStations ...*config.WeatherStation) {
|
||||
return
|
||||
}
|
||||
|
||||
// Clear invalid measurements, would be better if these weren't
|
||||
// sent when sensor was out of range.
|
||||
// TODO: This should probably just be done for all fields where
|
||||
// the value is -9999
|
||||
if u.BaromAbsoluteIn != nil && *u.BaromAbsoluteIn < 20 {
|
||||
u.BaromAbsoluteIn = nil
|
||||
}
|
||||
if u.BaromRelativeIn != nil && *u.BaromRelativeIn < 20 {
|
||||
u.BaromRelativeIn = nil
|
||||
}
|
||||
if u.TempIndoorF != nil && *u.TempIndoorF < -1000 {
|
||||
u.TempIndoorF = nil
|
||||
}
|
||||
|
||||
// Calculate Wind Chill
|
||||
if u.WindChillF == nil && u.TempOutdoorF != nil && u.WindSpeedMPH != nil {
|
||||
wc := CalculateWindChill(*u.TempOutdoorF, *u.WindSpeedMPH)
|
||||
u.WindChillF = &wc
|
||||
}
|
||||
|
||||
// Calculate Dew Point
|
||||
if u.DewPointF == nil && (u.TempOutdoorF != nil && u.HumidityOudoor != nil) {
|
||||
if *u.TempOutdoorF != 0 || *u.HumidityOudoor != 0 {
|
||||
dp := CalculateDewPoint(*u.TempOutdoorF, float64(*u.HumidityOudoor))
|
||||
@ -27,6 +43,7 @@ func (u *WeatherUpdate) Enrich(weatherStations ...*config.WeatherStation) {
|
||||
}
|
||||
}
|
||||
|
||||
// Use relative pressure if absolute isn't provided
|
||||
if u.BaromAbsoluteIn == nil && u.BaromRelativeIn != nil {
|
||||
u.BaromAbsoluteIn = u.BaromRelativeIn
|
||||
}
|
||||
|
Reference in New Issue
Block a user