Compare commits
	
		
			9 Commits
		
	
	
		
			v0.2.7
			...
			fa0a9f4ddc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fa0a9f4ddc | |||
| 689790fe86 | |||
| 3af1cc40a5 | |||
| 4ff684abe3 | |||
| 87f0cbac01 | |||
| 4c93303f27 | |||
| e8654e76bc | |||
| 54f725c822 | |||
| c002331fd4 | 
							
								
								
									
										11
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								TODO.md
									
									
									
									
									
								
							@@ -1,11 +1,18 @@
 | 
			
		||||
# TODO
 | 
			
		||||
- [ ] Fix shutdown
 | 
			
		||||
- [ ] Configuration for app
 | 
			
		||||
- [ ] Configurable metric prefix
 | 
			
		||||
- [ ] Helm Chart
 | 
			
		||||
- [ ] Update README
 | 
			
		||||
- [ ] Add new fields from WS-2192
 | 
			
		||||
- [ ] Add Grafana dashboard
 | 
			
		||||
- [ ] Add device name field with ID/Key mappings
 | 
			
		||||
- [ ] Add device type field with ID/Key mappings
 | 
			
		||||
- [ ] Move EVERYTHING to pointers to support nil
 | 
			
		||||
- [ ] Add proxy to upstream support
 | 
			
		||||
 | 
			
		||||
## Done
 | 
			
		||||
- [x] Consolidate battery status into one metric with device label
 | 
			
		||||
- [x] Fix shutdown
 | 
			
		||||
- [x] Add new fields from WS-2192
 | 
			
		||||
- [x] Gitea CI
 | 
			
		||||
- [x] Version flag
 | 
			
		||||
- [x] Makefile
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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.2.0
 | 
			
		||||
	gitea.libretechconsulting.com/rmcguire/go-app v0.3.0
 | 
			
		||||
	github.com/gorilla/schema v1.4.1
 | 
			
		||||
	github.com/rs/zerolog v1.33.0
 | 
			
		||||
	go.opentelemetry.io/otel v1.33.0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							@@ -2,6 +2,8 @@ gitea.libretechconsulting.com/rmcguire/go-app v0.1.3 h1:EwmEJLpN+rQjJ5stGEkZsqED
 | 
			
		||||
gitea.libretechconsulting.com/rmcguire/go-app v0.1.3/go.mod h1:wHOWh4O4AMDATQ3WEUYjq5a5bnICPBpu5G6BsNxqN38=
 | 
			
		||||
gitea.libretechconsulting.com/rmcguire/go-app v0.2.0 h1:pOm/PysC0IWPuEbmEjNSHHa8Qc5OhuoksYExcuJMFE4=
 | 
			
		||||
gitea.libretechconsulting.com/rmcguire/go-app v0.2.0/go.mod h1:wHOWh4O4AMDATQ3WEUYjq5a5bnICPBpu5G6BsNxqN38=
 | 
			
		||||
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=
 | 
			
		||||
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=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								main.go
									
									
									
									
									
								
							@@ -16,10 +16,10 @@ func main() {
 | 
			
		||||
	ctx, cncl := signal.NotifyContext(context.Background(), os.Kill, unix.SIGTERM)
 | 
			
		||||
	defer cncl()
 | 
			
		||||
 | 
			
		||||
	// Read config and environment, set up logging, load up
 | 
			
		||||
	// an appCtx, and prepare ambient weather local exporter
 | 
			
		||||
	ctx = app.MustSetupConfigAndLogging(ctx)
 | 
			
		||||
 | 
			
		||||
	aw := ambient.New(ctx)
 | 
			
		||||
	aw.MustInit()
 | 
			
		||||
	aw := ambient.New(ctx).Init()
 | 
			
		||||
 | 
			
		||||
	awApp := app.App{
 | 
			
		||||
		AppContext: ctx,
 | 
			
		||||
 
 | 
			
		||||
@@ -37,10 +37,12 @@ func New(appCtx context.Context) *AmbientWeather {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (aw *AmbientWeather) MustInit() {
 | 
			
		||||
// Initialize with defaults, set logger from context
 | 
			
		||||
func (aw *AmbientWeather) Init() *AmbientWeather {
 | 
			
		||||
	aw.awnProvider = &awn.AWNProvider{}
 | 
			
		||||
	aw.wuProvider = &wunderground.WUProvider{}
 | 
			
		||||
	aw.l = zerolog.Ctx(aw.appCtx)
 | 
			
		||||
	return aw
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (aw *AmbientWeather) GetAWNHandlerFunc(appCtx context.Context) func(http.ResponseWriter, *http.Request) {
 | 
			
		||||
@@ -55,7 +57,7 @@ func (aw *AmbientWeather) GetWundergroundHandlerFunc(appCtx context.Context) fun
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Takes an HTTP requests and convers it to a
 | 
			
		||||
// Takes an HTTP requests and converts it to a
 | 
			
		||||
// stable type. Enrich is called on the type to complete
 | 
			
		||||
// any missing fields as the two providers supported by Ambient
 | 
			
		||||
// devices (awn/wunderground) produce different fields
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate {
 | 
			
		||||
		DateUTC:        &updateTime,
 | 
			
		||||
		TempOutdoorF:   awnUpdate.TempF,
 | 
			
		||||
		HumidityOudoor: awnUpdate.Humidity,
 | 
			
		||||
		WindSpeedMPH:      awnUpdate.WindGustMPH,
 | 
			
		||||
		WindSpeedMPH:   awnUpdate.WindSpeedMPH,
 | 
			
		||||
		WindGustMPH:    awnUpdate.WindGustMPH,
 | 
			
		||||
		MaxDailyGust:   awnUpdate.MaxDailyGust,
 | 
			
		||||
		WindDir:        awnUpdate.WindDir,
 | 
			
		||||
@@ -57,10 +57,24 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate {
 | 
			
		||||
		MonthlyRainIn:  awnUpdate.MonthlyRainIn,
 | 
			
		||||
		YearlyRainIn:   awnUpdate.YearlyRainIn,
 | 
			
		||||
		TotalRainIn:    awnUpdate.TotalRainIn,
 | 
			
		||||
		BattOutdoorSensor: awnUpdate.BattOut,
 | 
			
		||||
		BattIndoorSensor:  awnUpdate.BattIn,
 | 
			
		||||
		BattRainSensor:    awnUpdate.BattRain,
 | 
			
		||||
		BattCO2Sensor:     awnUpdate.BattCO2,
 | 
			
		||||
		Batteries: []weather.BatteryStatus{
 | 
			
		||||
			{
 | 
			
		||||
				Component: "OutdoorSensor",
 | 
			
		||||
				Status:    awnUpdate.BattOut,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Component: "IndoorSensor",
 | 
			
		||||
				Status:    awnUpdate.BattIn,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Component: "RainSensor",
 | 
			
		||||
				Status:    awnUpdate.BattRain,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Component: "CO2Sensor",
 | 
			
		||||
				Status:    awnUpdate.BattCO2,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		TempIndoorF:     awnUpdate.TempInF,
 | 
			
		||||
		HumidityIndoor:  awnUpdate.HumidityIn,
 | 
			
		||||
		BaromRelativeIn: awnUpdate.BaromRelIn,
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ func (u *WeatherUpdate) Enrich() {
 | 
			
		||||
		u.WindChillF = CalculateWindChill(u.TempOutdoorF, u.WindSpeedMPH)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if u.DewPointF == 0 {
 | 
			
		||||
	if u.DewPointF == 0 && (u.TempOutdoorF != 0 && u.HumidityOudoor != 0) {
 | 
			
		||||
		u.DewPointF = CalculateDewPoint(u.TempOutdoorF, float64(u.HumidityOudoor))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,9 +30,7 @@ type WeatherMetrics struct {
 | 
			
		||||
	MonthlyRainIn   metric.Float64Gauge
 | 
			
		||||
	YearlyRainIn    metric.Float64Gauge
 | 
			
		||||
	TotalRainIn     metric.Float64Gauge
 | 
			
		||||
	BattOutdoorSensor metric.Int64Gauge
 | 
			
		||||
	BattIndoorSensor  metric.Int64Gauge
 | 
			
		||||
	BattRainSensor    metric.Int64Gauge
 | 
			
		||||
	BatteryStatus   metric.Int64Gauge
 | 
			
		||||
	BaromRelativeIn metric.Float64Gauge
 | 
			
		||||
	BaromAbsoluteIn metric.Float64Gauge
 | 
			
		||||
	DewPointF       metric.Float64Gauge
 | 
			
		||||
@@ -45,6 +43,8 @@ type WeatherMetrics struct {
 | 
			
		||||
	meter           metric.Meter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var MetricPrefix = "weather"
 | 
			
		||||
 | 
			
		||||
func MustInitMetrics(appCtx context.Context) *WeatherMetrics {
 | 
			
		||||
	wm := &WeatherMetrics{
 | 
			
		||||
		appCtx: appCtx,
 | 
			
		||||
@@ -54,59 +54,55 @@ func MustInitMetrics(appCtx context.Context) *WeatherMetrics {
 | 
			
		||||
	wm.meter = otel.GetMeter(appCtx, "weather", "metrics")
 | 
			
		||||
 | 
			
		||||
	// Weather Metrics
 | 
			
		||||
	wm.TempOutdoorF, _ = wm.meter.Float64Gauge("weather_temp_outdoor_f",
 | 
			
		||||
	wm.TempOutdoorF, _ = wm.meter.Float64Gauge(MetricPrefix+"_temp_outdoor_f",
 | 
			
		||||
		metric.WithDescription("Outdoor Temperature in Faherenheit"))
 | 
			
		||||
	wm.TempIndoorF, _ = wm.meter.Float64Gauge("weather_temp_indoor_f",
 | 
			
		||||
	wm.TempIndoorF, _ = wm.meter.Float64Gauge(MetricPrefix+"_temp_indoor_f",
 | 
			
		||||
		metric.WithDescription("Indoor Temperature in Faherenheit"))
 | 
			
		||||
	wm.HumidityOudoor, _ = wm.meter.Int64Gauge("weather_humidity_oudoor",
 | 
			
		||||
	wm.HumidityOudoor, _ = wm.meter.Int64Gauge(MetricPrefix+"_humidity_outdoor",
 | 
			
		||||
		metric.WithDescription("Outdoor Humidity %"))
 | 
			
		||||
	wm.HumidityIndoor, _ = wm.meter.Int64Gauge("weather_humidity_indoor",
 | 
			
		||||
	wm.HumidityIndoor, _ = wm.meter.Int64Gauge(MetricPrefix+"_humidity_indoor",
 | 
			
		||||
		metric.WithDescription("Indoor Humidity %"))
 | 
			
		||||
	wm.WindSpeedMPH, _ = wm.meter.Float64Gauge("weather_wind_speed_mph",
 | 
			
		||||
	wm.WindSpeedMPH, _ = wm.meter.Float64Gauge(MetricPrefix+"_wind_speed_mph",
 | 
			
		||||
		metric.WithDescription("Wind Speed in MPH"))
 | 
			
		||||
	wm.WindGustMPH, _ = wm.meter.Float64Gauge("weather_wind_gust_mph",
 | 
			
		||||
	wm.WindGustMPH, _ = wm.meter.Float64Gauge(MetricPrefix+"_wind_gust_mph",
 | 
			
		||||
		metric.WithDescription("Wind Gust in MPH"))
 | 
			
		||||
	wm.MaxDailyGust, _ = wm.meter.Float64Gauge("weather_max_daily_gust",
 | 
			
		||||
	wm.MaxDailyGust, _ = wm.meter.Float64Gauge(MetricPrefix+"_max_daily_gust",
 | 
			
		||||
		metric.WithDescription("Max Daily Wind Gust"))
 | 
			
		||||
	wm.WindDir, _ = wm.meter.Int64Gauge("weather_wind_dir",
 | 
			
		||||
	wm.WindDir, _ = wm.meter.Int64Gauge(MetricPrefix+"_wind_dir",
 | 
			
		||||
		metric.WithDescription("Wind Direction in Degrees"))
 | 
			
		||||
	wm.WindDirAvg10m, _ = wm.meter.Int64Gauge("weather_wind_dir_avg_10m",
 | 
			
		||||
	wm.WindDirAvg10m, _ = wm.meter.Int64Gauge(MetricPrefix+"_wind_dir_avg_10m",
 | 
			
		||||
		metric.WithDescription("Wind Direction 10m Average"))
 | 
			
		||||
	wm.UV, _ = wm.meter.Int64Gauge("weather_uv",
 | 
			
		||||
	wm.UV, _ = wm.meter.Int64Gauge(MetricPrefix+"_uv",
 | 
			
		||||
		metric.WithDescription("UV Index"))
 | 
			
		||||
	wm.SolarRadiation, _ = wm.meter.Float64Gauge("weather_solar_radiation",
 | 
			
		||||
	wm.SolarRadiation, _ = wm.meter.Float64Gauge(MetricPrefix+"_solar_radiation",
 | 
			
		||||
		metric.WithDescription("Solar Radiation in W/㎡"))
 | 
			
		||||
	wm.HourlyRainIn, _ = wm.meter.Float64Gauge("weather_hourly_rain_in",
 | 
			
		||||
	wm.HourlyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_hourly_rain_in",
 | 
			
		||||
		metric.WithDescription("Hourly Rain in Inches"))
 | 
			
		||||
	wm.EventRainIn, _ = wm.meter.Float64Gauge("weather_event_rain_in",
 | 
			
		||||
	wm.EventRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_event_rain_in",
 | 
			
		||||
		metric.WithDescription("Event Rain in Inches"))
 | 
			
		||||
	wm.DailyRainIn, _ = wm.meter.Float64Gauge("weather_daily_rain_in",
 | 
			
		||||
	wm.DailyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_daily_rain_in",
 | 
			
		||||
		metric.WithDescription("Daily Rain in Inches"))
 | 
			
		||||
	wm.WeeklyRainIn, _ = wm.meter.Float64Gauge("weather_weekly_rain_in",
 | 
			
		||||
	wm.WeeklyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_weekly_rain_in",
 | 
			
		||||
		metric.WithDescription("Weekly Rain in Inches"))
 | 
			
		||||
	wm.MonthlyRainIn, _ = wm.meter.Float64Gauge("weather_monthly_rain_in",
 | 
			
		||||
	wm.MonthlyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_monthly_rain_in",
 | 
			
		||||
		metric.WithDescription("Monthly Rain in Inches"))
 | 
			
		||||
	wm.YearlyRainIn, _ = wm.meter.Float64Gauge("weather_yearly_rain_in",
 | 
			
		||||
	wm.YearlyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_yearly_rain_in",
 | 
			
		||||
		metric.WithDescription("Yearly Rain in Inches"))
 | 
			
		||||
	wm.TotalRainIn, _ = wm.meter.Float64Gauge("weather_total_rain_in",
 | 
			
		||||
	wm.TotalRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_total_rain_in",
 | 
			
		||||
		metric.WithDescription("Total Rain in Inches"))
 | 
			
		||||
	wm.BattOutdoorSensor, _ = wm.meter.Int64Gauge("weather_batt_outdoor_sensor",
 | 
			
		||||
		metric.WithDescription("Outdoor Equipment Battery"))
 | 
			
		||||
	wm.BattIndoorSensor, _ = wm.meter.Int64Gauge("weather_batt_indoor_sensor",
 | 
			
		||||
		metric.WithDescription("Indoor Equipmenet Battery"))
 | 
			
		||||
	wm.BattRainSensor, _ = wm.meter.Int64Gauge("weather_batt_rain_sensor",
 | 
			
		||||
		metric.WithDescription("Rain Sensor Battery"))
 | 
			
		||||
	wm.BaromRelativeIn, _ = wm.meter.Float64Gauge("weather_barometric_pressure_relative_in",
 | 
			
		||||
	wm.BatteryStatus, _ = wm.meter.Int64Gauge(MetricPrefix+"_battery_status",
 | 
			
		||||
		metric.WithDescription("Per-component battery status"))
 | 
			
		||||
	wm.BaromRelativeIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_barometric_pressure_relative_in",
 | 
			
		||||
		metric.WithDescription("Relative Pressure in Inches of Mercury"))
 | 
			
		||||
	wm.BaromAbsoluteIn, _ = wm.meter.Float64Gauge("weather_barometric_pressure_absolute_in",
 | 
			
		||||
	wm.BaromAbsoluteIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_barometric_pressure_absolute_in",
 | 
			
		||||
		metric.WithDescription("Absolute Pressure in Inches of Mercury"))
 | 
			
		||||
	wm.DewPointF, _ = wm.meter.Float64Gauge("weather_dew_point_f",
 | 
			
		||||
	wm.DewPointF, _ = wm.meter.Float64Gauge(MetricPrefix+"_dew_point_f",
 | 
			
		||||
		metric.WithDescription("Dew Point in Faherenheit"))
 | 
			
		||||
	wm.WindChillF, _ = wm.meter.Float64Gauge("weather_wind_chill_f",
 | 
			
		||||
	wm.WindChillF, _ = wm.meter.Float64Gauge(MetricPrefix+"_wind_chill_f",
 | 
			
		||||
		metric.WithDescription("Wind Chill in Faherenheit"))
 | 
			
		||||
 | 
			
		||||
	// Internal Telemetry
 | 
			
		||||
	wm.UpdatesReceived, _ = wm.meter.Int64Counter("weather_updates_received",
 | 
			
		||||
	wm.UpdatesReceived, _ = wm.meter.Int64Counter(MetricPrefix+"_updates_received",
 | 
			
		||||
		metric.WithDescription("Metric Updates Processed by Exporter"))
 | 
			
		||||
 | 
			
		||||
	return wm
 | 
			
		||||
@@ -136,13 +132,18 @@ func (wm *WeatherMetrics) Update(u *WeatherUpdate) {
 | 
			
		||||
	wm.MonthlyRainIn.Record(wm.appCtx, u.MonthlyRainIn, metric.WithAttributeSet(attributes))
 | 
			
		||||
	wm.YearlyRainIn.Record(wm.appCtx, u.YearlyRainIn, metric.WithAttributeSet(attributes))
 | 
			
		||||
	wm.TotalRainIn.Record(wm.appCtx, u.TotalRainIn, metric.WithAttributeSet(attributes))
 | 
			
		||||
	wm.BattOutdoorSensor.Record(wm.appCtx, int64(u.BattOutdoorSensor), metric.WithAttributeSet(attributes))
 | 
			
		||||
	wm.BattIndoorSensor.Record(wm.appCtx, int64(u.BattIndoorSensor), metric.WithAttributeSet(attributes))
 | 
			
		||||
	wm.BattRainSensor.Record(wm.appCtx, int64(u.BattRainSensor), metric.WithAttributeSet(attributes))
 | 
			
		||||
	wm.BaromRelativeIn.Record(wm.appCtx, u.BaromRelativeIn, metric.WithAttributeSet(attributes))
 | 
			
		||||
	wm.BaromAbsoluteIn.Record(wm.appCtx, u.BaromAbsoluteIn, metric.WithAttributeSet(attributes))
 | 
			
		||||
	wm.DewPointF.Record(wm.appCtx, u.DewPointF, metric.WithAttributeSet(attributes))
 | 
			
		||||
	wm.WindChillF.Record(wm.appCtx, u.WindChillF, metric.WithAttributeSet(attributes))
 | 
			
		||||
 | 
			
		||||
	// Batteries
 | 
			
		||||
	for _, battery := range u.Batteries {
 | 
			
		||||
		wm.BatteryStatus.Record(wm.appCtx, int64(battery.Status),
 | 
			
		||||
			metric.WithAttributeSet(attributes),
 | 
			
		||||
			metric.WithAttributes(attribute.String("component", battery.Component)),
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wm.UpdatesReceived.Add(wm.appCtx, 1)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,10 +27,11 @@ type WeatherUpdate struct {
 | 
			
		||||
	MonthlyRainIn  float64
 | 
			
		||||
	YearlyRainIn   float64
 | 
			
		||||
	TotalRainIn    float64
 | 
			
		||||
	BattOutdoorSensor int
 | 
			
		||||
	BattIndoorSensor  int
 | 
			
		||||
	BattRainSensor    int
 | 
			
		||||
	BattCO2Sensor     int
 | 
			
		||||
	Batteries      []BatteryStatus
 | 
			
		||||
	// BattOutdoorSensor int
 | 
			
		||||
	// BattIndoorSensor  int
 | 
			
		||||
	// BattRainSensor    int
 | 
			
		||||
	// BattCO2Sensor     int
 | 
			
		||||
	BaromRelativeIn float64
 | 
			
		||||
	BaromAbsoluteIn float64
 | 
			
		||||
	// These fields may be calculated
 | 
			
		||||
@@ -38,3 +39,8 @@ type WeatherUpdate struct {
 | 
			
		||||
	DewPointF  float64
 | 
			
		||||
	WindChillF float64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BatteryStatus struct {
 | 
			
		||||
	Component string
 | 
			
		||||
	Status    int
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user