Proxy support for AWN/Wunderground
This commit is contained in:
		
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
								
							| @@ -4,6 +4,7 @@ go 1.23.4 | |||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	gitea.libretechconsulting.com/rmcguire/go-app v0.3.1 | 	gitea.libretechconsulting.com/rmcguire/go-app v0.3.1 | ||||||
|  | 	github.com/go-resty/resty/v2 v2.16.3 | ||||||
| 	github.com/gorilla/schema v1.4.1 | 	github.com/gorilla/schema v1.4.1 | ||||||
| 	github.com/rs/zerolog v1.33.0 | 	github.com/rs/zerolog v1.33.0 | ||||||
| 	go.opentelemetry.io/otel v1.33.0 | 	go.opentelemetry.io/otel v1.33.0 | ||||||
| @@ -22,7 +23,7 @@ require ( | |||||||
| 	github.com/google/uuid v1.6.0 // indirect | 	github.com/google/uuid v1.6.0 // indirect | ||||||
| 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect | 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect | ||||||
| 	github.com/klauspost/compress v1.17.11 // indirect | 	github.com/klauspost/compress v1.17.11 // indirect | ||||||
| 	github.com/mattn/go-colorable v0.1.13 // indirect | 	github.com/mattn/go-colorable v0.1.14 // indirect | ||||||
| 	github.com/mattn/go-isatty v0.0.20 // indirect | 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||||
| 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||||||
| 	github.com/prometheus/client_golang v1.20.5 // indirect | 	github.com/prometheus/client_golang v1.20.5 // indirect | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1,5 +1,3 @@ | |||||||
| gitea.libretechconsulting.com/rmcguire/go-app v0.3.0 h1:TSR6oEDBX+83975gmgGgU/cTFgfG999+9N/1h4RAXq0= |  | ||||||
| gitea.libretechconsulting.com/rmcguire/go-app v0.3.0/go.mod h1:wHOWh4O4AMDATQ3WEUYjq5a5bnICPBpu5G6BsNxqN38= |  | ||||||
| gitea.libretechconsulting.com/rmcguire/go-app v0.3.1 h1:jydcJ+Vv8sk+Le7nTI2+b6E7FfV+ShBJo7YdxmdaCYc= | gitea.libretechconsulting.com/rmcguire/go-app v0.3.1 h1:jydcJ+Vv8sk+Le7nTI2+b6E7FfV+ShBJo7YdxmdaCYc= | ||||||
| gitea.libretechconsulting.com/rmcguire/go-app v0.3.1/go.mod h1:wHOWh4O4AMDATQ3WEUYjq5a5bnICPBpu5G6BsNxqN38= | gitea.libretechconsulting.com/rmcguire/go-app v0.3.1/go.mod h1:wHOWh4O4AMDATQ3WEUYjq5a5bnICPBpu5G6BsNxqN38= | ||||||
| github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= | ||||||
| @@ -20,6 +18,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= | |||||||
| github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | ||||||
| github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= | ||||||
| github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= | ||||||
|  | github.com/go-resty/resty/v2 v2.16.3 h1:zacNT7lt4b8M/io2Ahj6yPypL7bqx9n1iprfQuodV+E= | ||||||
|  | github.com/go-resty/resty/v2 v2.16.3/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= | ||||||
| github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= | ||||||
| github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= | ||||||
| github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= | ||||||
| @@ -39,8 +39,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | |||||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
| github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= | ||||||
| github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= | ||||||
| github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= |  | ||||||
| github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | ||||||
|  | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= | ||||||
|  | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= | ||||||
| github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||||||
| github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||||
| github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||||||
| @@ -91,14 +92,10 @@ go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCt | |||||||
| go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= | go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= | ||||||
| go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= | go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= | ||||||
| go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= | go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= | ||||||
| go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= |  | ||||||
| go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= |  | ||||||
| go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= | go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= | ||||||
| go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= | go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= | ||||||
| go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||||||
| go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||||||
| golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= |  | ||||||
| golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= |  | ||||||
| golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= | ||||||
| golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= | ||||||
| golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| @@ -108,18 +105,14 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= | |||||||
| golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
| golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= | ||||||
| golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= | ||||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d h1:H8tOf8XM88HvKqLTxe755haY6r1fqqzLbEnfrmLXlSA= | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= | ||||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d/go.mod h1:2v7Z7gP2ZUOGsaFyxATQSRoBnKygqVq2Cwnvom7QiqY= | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= | ||||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= | google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= | ||||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= | google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= | ||||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:xJJRGY7TJcvIlpSrN3K6LAWgNFUILlO+OMAqtg9aqnw= |  | ||||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= |  | ||||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= | google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= | ||||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= | google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= | ||||||
| google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= | google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= | ||||||
| google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= | google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= | ||||||
| google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= |  | ||||||
| google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= |  | ||||||
| google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= | google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= | ||||||
| google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= | google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|   | |||||||
| @@ -114,6 +114,26 @@ func (aw *AmbientWeather) handleProviderRequest( | |||||||
| 		Any("update", update). | 		Any("update", update). | ||||||
| 		Msg("successfully handled update") | 		Msg("successfully handled update") | ||||||
| 	w.Write([]byte("ok")) | 	w.Write([]byte("ok")) | ||||||
|  |  | ||||||
|  | 	// Proxy update to one or both services if configured to do so | ||||||
|  | 	// Uses a weather update to allow awn to publish to wunderground and | ||||||
|  | 	// visa versa. | ||||||
|  | 	if station := aw.config.GetStation(update.GetStationName()); station != nil { | ||||||
|  | 		if station.ProxyToAWN { | ||||||
|  | 			err := aw.awnProvider.ProxyReq(ctx, update) | ||||||
|  | 			if err != nil { | ||||||
|  | 				zerolog.Ctx(aw.appCtx).Err(err).Msg("failed to proxy to ambient weather") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if station.ProxyToWunderground { | ||||||
|  | 			err := aw.awnProvider.ProxyReq(ctx, update) | ||||||
|  | 			if err != nil { | ||||||
|  | 				zerolog.Ctx(aw.appCtx).Err(err).Msg("failed to proxy to ambient weather") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (aw *AmbientWeather) InitMetrics() { | func (aw *AmbientWeather) InitMetrics() { | ||||||
|   | |||||||
| @@ -35,3 +35,12 @@ type WeatherStation struct { | |||||||
| 	KeepMetrics []weather.WeatherUpdateField `yaml:"keepMetrics"` | 	KeepMetrics []weather.WeatherUpdateField `yaml:"keepMetrics"` | ||||||
| 	DropMetrics []weather.WeatherUpdateField `yaml:"dropMetrics"` | 	DropMetrics []weather.WeatherUpdateField `yaml:"dropMetrics"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (wc *AmbientLocalExporterConfig) GetStation(name string) *WeatherStation { | ||||||
|  | 	for _, station := range wc.WeatherStations { | ||||||
|  | 		if station.Name == name { | ||||||
|  | 			return &station | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -2,18 +2,27 @@ package awn | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"errors" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-resty/resty/v2" | ||||||
| 	"github.com/gorilla/schema" | 	"github.com/gorilla/schema" | ||||||
|  | 	"go.opentelemetry.io/otel/attribute" | ||||||
|  | 	"go.opentelemetry.io/otel/codes" | ||||||
|  |  | ||||||
|  | 	"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" | ||||||
|  |  | ||||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather" | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AWNProvider struct{} | type AWNProvider struct{} | ||||||
|  |  | ||||||
| const providerName = "awn" | const ( | ||||||
|  | 	providerName = "awn" | ||||||
|  | 	awnURL       = "http://ambientweather.net/data/report" | ||||||
|  | ) | ||||||
|  |  | ||||||
| func (awn *AWNProvider) Name() string { | func (awn *AWNProvider) Name() string { | ||||||
| 	return providerName | 	return providerName | ||||||
| @@ -32,6 +41,74 @@ func (awn *AWNProvider) ReqToWeather(_ context.Context, r *http.Request) ( | |||||||
| 	return MapAwnUpdate(awnUpdate), nil | 	return MapAwnUpdate(awnUpdate), nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Attempts to proxy the weather station update to awn | ||||||
|  | // SAMPLE: | ||||||
|  | // {"PASSKEY":["ABF7E052BC7325A32300ACC89112AA91"],"baromabsin":["28.895"], | ||||||
|  | // "baromrelin":["29.876"],"battin":["1"],"battout":["1"],"battrain":["1"], | ||||||
|  | // "dailyrainin":["0.000"],"dateutc":["2025-01-11 22:07:57"],"eventrainin":["0.000"], | ||||||
|  | // "hourlyrainin":["0.000"],"humidity":["76"],"humidityin":["31"],"maxdailygust":["7.83"], | ||||||
|  | // "monthlyrainin":["0.000"],"solarradiation":["14.21"],"stationtype":["WeatherHub_V1.0.1"], | ||||||
|  | // "tempf":["29.48"],"tempinf":["66.20"],"totalrainin":["0.000"],"uv":["0"], | ||||||
|  | // "weeklyrainin":["0.000"],"winddir":["66"],"winddir_avg10m":["268"],"windgustmph":["2.68"], | ||||||
|  | // "windspeedmph":["0.00"],"yearlyrainin":["0.000"]} | ||||||
|  | func (awn *AWNProvider) ProxyReq(ctx context.Context, update *weather.WeatherUpdate) error { | ||||||
|  | 	tracer := otel.GetTracer(ctx, "awnProvider", "proxyReq") | ||||||
|  | 	ctx, span := tracer.Start(ctx, "proxyToAWN") | ||||||
|  | 	defer span.End() | ||||||
|  |  | ||||||
|  | 	if update.WeatherServiceCredentials["PASSKEY"] == "" { | ||||||
|  | 		err := errors.New("no PASSKEY set in update") | ||||||
|  | 		span.RecordError(err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	params := updateToAWNParams(update) | ||||||
|  |  | ||||||
|  | 	resp, err := resty.New().R(). | ||||||
|  | 		SetContext(ctx). | ||||||
|  | 		SetQueryParamsFromValues(*params). | ||||||
|  | 		Get(awnURL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		span.RecordError(err) | ||||||
|  | 		span.SetStatus(codes.Error, err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	span.SetAttributes( | ||||||
|  | 		attribute.Int("statusCode", resp.StatusCode()), | ||||||
|  | 		attribute.String("body", string(resp.Body())), | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func updateToAWNParams(update *weather.WeatherUpdate) *url.Values { | ||||||
|  | 	params := &url.Values{} | ||||||
|  | 	params.Set("PASSKEY", update.WeatherServiceCredentials["PASSKEY"]) | ||||||
|  | 	params.Set("dateutc", time.Now().Format(time.DateTime)) | ||||||
|  | 	weather.SetURLVal(params, "baromabsin", update.BaromAbsoluteIn) | ||||||
|  | 	weather.SetURLVal(params, "baromrelin", update.BaromRelativeIn) | ||||||
|  | 	weather.SetURLVal(params, "dailyrainin", update.DailyRainIn) | ||||||
|  | 	weather.SetURLVal(params, "weeklyrainin", update.WeeklyRainIn) | ||||||
|  | 	weather.SetURLVal(params, "eventrainin", update.EventRainIn) | ||||||
|  | 	weather.SetURLVal(params, "hourlyrainin", update.HourlyRainIn) | ||||||
|  | 	weather.SetURLVal(params, "monthlyrainin", update.MonthlyRainIn) | ||||||
|  | 	weather.SetURLVal(params, "yearlyrainin", update.YearlyRainIn) | ||||||
|  | 	weather.SetURLVal(params, "totalrainin", update.TotalRainIn) | ||||||
|  | 	weather.SetURLVal(params, "humidity", update.HumidityOudoor) | ||||||
|  | 	weather.SetURLVal(params, "humidityin", update.HumidityIndoor) | ||||||
|  | 	weather.SetURLVal(params, "solarradiation", update.SolarRadiation) | ||||||
|  | 	weather.SetURLVal(params, "uv", update.UV) | ||||||
|  | 	weather.SetURLVal(params, "stationtype", update.StationType) | ||||||
|  | 	weather.SetURLVal(params, "tempf", update.TempOutdoorF) | ||||||
|  | 	weather.SetURLVal(params, "tempinf", update.TempIndoorF) | ||||||
|  | 	weather.SetURLVal(params, "winddir", update.WindDir) | ||||||
|  | 	weather.SetURLVal(params, "winddir_avg10m", update.WindDirAvg10m) | ||||||
|  | 	weather.SetURLVal(params, "windgustmph", update.WindGustMPH) | ||||||
|  | 	weather.SetURLVal(params, "windspeedmph", update.WindSpeedMPH) | ||||||
|  | 	weather.SetURLVal(params, "maxdailygust", update.MaxDailyGust) | ||||||
|  | 	return params | ||||||
|  | } | ||||||
|  |  | ||||||
| func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { | func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { | ||||||
| 	updateTime := time.Now() | 	updateTime := time.Now() | ||||||
| 	if awnUpdate.DateUTC != nil { | 	if awnUpdate.DateUTC != nil { | ||||||
| @@ -41,6 +118,11 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	credentials := make(map[string]string) | ||||||
|  | 	if awnUpdate.PassKey != nil { | ||||||
|  | 		credentials["PASSKEY"] = *awnUpdate.PassKey | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return &weather.WeatherUpdate{ | 	return &weather.WeatherUpdate{ | ||||||
| 		DateUTC:        &updateTime, | 		DateUTC:        &updateTime, | ||||||
| 		StationID:      awnUpdate.PassKey, | 		StationID:      awnUpdate.PassKey, | ||||||
| @@ -79,10 +161,11 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { | |||||||
| 				Status:    awnUpdate.BattCO2, | 				Status:    awnUpdate.BattCO2, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		TempIndoorF:     awnUpdate.TempInF, | 		TempIndoorF:               awnUpdate.TempInF, | ||||||
| 		HumidityIndoor:  awnUpdate.HumidityIn, | 		HumidityIndoor:            awnUpdate.HumidityIn, | ||||||
| 		BaromRelativeIn: awnUpdate.BaromRelIn, | 		BaromRelativeIn:           awnUpdate.BaromRelIn, | ||||||
| 		BaromAbsoluteIn: awnUpdate.BaromAbsIn, | 		BaromAbsoluteIn:           awnUpdate.BaromAbsIn, | ||||||
|  | 		WeatherServiceCredentials: credentials, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,5 +11,6 @@ import ( | |||||||
| // Ambient Weather Network HTTP requests to a stable struct | // Ambient Weather Network HTTP requests to a stable struct | ||||||
| type AmbientProvider interface { | type AmbientProvider interface { | ||||||
| 	ReqToWeather(context.Context, *http.Request) (*weather.WeatherUpdate, error) | 	ReqToWeather(context.Context, *http.Request) (*weather.WeatherUpdate, error) | ||||||
|  | 	ProxyReq(context.Context, *weather.WeatherUpdate) error | ||||||
| 	Name() string | 	Name() string | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,7 +13,10 @@ import ( | |||||||
|  |  | ||||||
| type WUProvider struct{} | type WUProvider struct{} | ||||||
|  |  | ||||||
| const providerName = "weatherunderground" | const ( | ||||||
|  | 	providerName = "weatherunderground" | ||||||
|  | 	wuURL        = "http://rtupdate.wunderground.com/weatherstation/updateweatherstation.php" | ||||||
|  | ) | ||||||
|  |  | ||||||
| func (wu *WUProvider) Name() string { | func (wu *WUProvider) Name() string { | ||||||
| 	return providerName | 	return providerName | ||||||
| @@ -32,6 +35,29 @@ func (wu *WUProvider) ReqToWeather(_ context.Context, r *http.Request) ( | |||||||
| 	return MapWUUpdate(wuUpdate), nil | 	return MapWUUpdate(wuUpdate), nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (wu *WUProvider) ProxyReq(ctx context.Context, update *weather.WeatherUpdate) error { | ||||||
|  | 	// tracer := otel.GetTracer(ctx, "wuProvider", "proxyReq") | ||||||
|  | 	// ctx, span := tracer.Start(ctx, "proxyToWunderground") | ||||||
|  | 	// defer span.End() | ||||||
|  | 	// | ||||||
|  | 	// resp, err := resty.New().R(). | ||||||
|  | 	// 	SetContext(ctx). | ||||||
|  | 	// 	SetQueryParamsFromValues(r.URL.Query()). | ||||||
|  | 	// 	Get(wuURL) | ||||||
|  | 	// if err != nil { | ||||||
|  | 	// 	span.SetStatus(codes.Error, err.Error()) | ||||||
|  | 	// 	span.RecordError(err) | ||||||
|  | 	// } | ||||||
|  | 	// | ||||||
|  | 	// span.SetAttributes( | ||||||
|  | 	// 	attribute.Int("statusCode", resp.StatusCode()), | ||||||
|  | 	// 	attribute.String("body", string(resp.Body())), | ||||||
|  | 	// ) | ||||||
|  | 	// | ||||||
|  | 	// return err | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func MapWUUpdate(wuUpdate *WundergroundUpdate) *weather.WeatherUpdate { | func MapWUUpdate(wuUpdate *WundergroundUpdate) *weather.WeatherUpdate { | ||||||
| 	updateTime := time.Now() | 	updateTime := time.Now() | ||||||
|  |  | ||||||
| @@ -42,25 +68,32 @@ func MapWUUpdate(wuUpdate *WundergroundUpdate) *weather.WeatherUpdate { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	credentials := make(map[string]string) | ||||||
|  | 	if wuUpdate.ID != nil && wuUpdate.Password != nil { | ||||||
|  | 		credentials["ID"] = *wuUpdate.ID | ||||||
|  | 		credentials["Password"] = *wuUpdate.Password | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return &weather.WeatherUpdate{ | 	return &weather.WeatherUpdate{ | ||||||
| 		DateUTC:         &updateTime, | 		DateUTC:                   &updateTime, | ||||||
| 		StationID:       wuUpdate.ID, | 		StationID:                 wuUpdate.ID, | ||||||
| 		StationType:     wuUpdate.SoftwareType, | 		StationType:               wuUpdate.SoftwareType, | ||||||
| 		TempOutdoorF:    wuUpdate.Tempf, | 		TempOutdoorF:              wuUpdate.Tempf, | ||||||
| 		HumidityOudoor:  wuUpdate.Humidity, | 		HumidityOudoor:            wuUpdate.Humidity, | ||||||
| 		WindSpeedMPH:    wuUpdate.WindGustMPH, | 		WindSpeedMPH:              wuUpdate.WindGustMPH, | ||||||
| 		WindGustMPH:     wuUpdate.WindGustMPH, | 		WindGustMPH:               wuUpdate.WindGustMPH, | ||||||
| 		WindDir:         wuUpdate.WindDir, | 		WindDir:                   wuUpdate.WindDir, | ||||||
| 		UV:              wuUpdate.UV, | 		UV:                        wuUpdate.UV, | ||||||
| 		SolarRadiation:  wuUpdate.SolarRadiation, | 		SolarRadiation:            wuUpdate.SolarRadiation, | ||||||
| 		HourlyRainIn:    wuUpdate.RainIn, | 		HourlyRainIn:              wuUpdate.RainIn, | ||||||
| 		DailyRainIn:     wuUpdate.DailyRainIn, | 		DailyRainIn:               wuUpdate.DailyRainIn, | ||||||
| 		WeeklyRainIn:    wuUpdate.WeeklyRainIn, | 		WeeklyRainIn:              wuUpdate.WeeklyRainIn, | ||||||
| 		MonthlyRainIn:   wuUpdate.MonthlyRainIn, | 		MonthlyRainIn:             wuUpdate.MonthlyRainIn, | ||||||
| 		YearlyRainIn:    wuUpdate.YearlyRainIn, | 		YearlyRainIn:              wuUpdate.YearlyRainIn, | ||||||
| 		TempIndoorF:     wuUpdate.IndoorTempF, | 		TempIndoorF:               wuUpdate.IndoorTempF, | ||||||
| 		HumidityIndoor:  wuUpdate.IndoorHumidity, | 		HumidityIndoor:            wuUpdate.IndoorHumidity, | ||||||
| 		BaromRelativeIn: wuUpdate.BaromIn, | 		BaromRelativeIn:           wuUpdate.BaromIn, | ||||||
|  | 		WeatherServiceCredentials: credentials, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,10 @@ | |||||||
| package weather | package weather | ||||||
|  |  | ||||||
| import "math" | import ( | ||||||
|  | 	"math" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  |  | ||||||
| // Attempts to complete missing fields that may not | // Attempts to complete missing fields that may not | ||||||
| // be set by a specific provider, such as DewPoint and WindChill | // be set by a specific provider, such as DewPoint and WindChill | ||||||
| @@ -53,3 +57,31 @@ func CalculateWindChill(tempF float64, windSpeedMPH float64) float64 { | |||||||
| 		35.75*math.Pow(windSpeedMPH, 0.16) + | 		35.75*math.Pow(windSpeedMPH, 0.16) + | ||||||
| 		0.4275*tempF*math.Pow(windSpeedMPH, 0.16) | 		0.4275*tempF*math.Pow(windSpeedMPH, 0.16) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Helper function to set values from fields | ||||||
|  | // typically from a WeatherUpdate | ||||||
|  | func SetURLVal(vals *url.Values, key string, value any) { | ||||||
|  | 	if value == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch v := value.(type) { | ||||||
|  | 	case *float64: | ||||||
|  | 		if v == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		str := strconv.FormatFloat(*v, 'f', 4, 64) | ||||||
|  | 		vals.Set(key, str) | ||||||
|  | 	case *int: | ||||||
|  | 		if v == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		str := strconv.FormatInt(int64(*v), 10) | ||||||
|  | 		vals.Set(key, str) | ||||||
|  | 	case *string: | ||||||
|  | 		if v == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		vals.Set(key, *v) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -36,6 +36,9 @@ type WeatherUpdate struct { | |||||||
| 	// if not otherwise set | 	// if not otherwise set | ||||||
| 	DewPointF  *float64 | 	DewPointF  *float64 | ||||||
| 	WindChillF *float64 | 	WindChillF *float64 | ||||||
|  | 	// First URL parameters given to AWN/Wunderground | ||||||
|  | 	// if proxying is enabled | ||||||
|  | 	WeatherServiceCredentials map[string]string | ||||||
| } | } | ||||||
|  |  | ||||||
| type StationInfo struct { | type StationInfo struct { | ||||||
| @@ -82,3 +85,17 @@ const ( | |||||||
| 	FieldDewPointF       WeatherUpdateField = "DewPointF" | 	FieldDewPointF       WeatherUpdateField = "DewPointF" | ||||||
| 	FieldWindChillF      WeatherUpdateField = "WindChillF" | 	FieldWindChillF      WeatherUpdateField = "WindChillF" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | func (u *WeatherUpdate) GetStationName() string { | ||||||
|  | 	if u.StationInfo != nil { | ||||||
|  | 		return u.StationInfo.GetName() | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (si *StationInfo) GetName() string { | ||||||
|  | 	if si.Name != nil { | ||||||
|  | 		return *si.Name | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user