tempest

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 14, 2026 License: MIT Imports: 14 Imported by: 0

README

tempest-go

Shared Go library for WeatherFlow Tempest weather stations. Provides REST and WebSocket API clients, observation parsing, unit conversion (metric/imperial), derived weather metrics, rate limiting, connection management, and circuit breaker patterns.

Installation

go get github.com/chadmayfield/tempest-go

Quick Start

REST Client — Fetch Current Conditions
package main

import (
    "context"
    "fmt"
    "log"

    tempest "github.com/chadmayfield/tempest-go"
)

func main() {
    client := tempest.NewClient("YOUR_TOKEN")

    obs, err := client.GetStationObservation(context.Background(), 12345)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Temperature: %.1f°C\n", obs.AirTemperature)
    fmt.Printf("Humidity: %.0f%%\n", obs.RelativeHumidity)
    fmt.Printf("Wind: %.1f m/s\n", obs.WindAvg)
}
WebSocket Client — Live Observations
package main

import (
    "context"
    "fmt"
    "log"

    tempest "github.com/chadmayfield/tempest-go"
)

type handler struct{}

func (h *handler) HandleObservation(obs *tempest.Observation) {
    fmt.Printf("Temp: %.1f°C  Wind: %.1f m/s  Dir: %s\n",
        obs.AirTemperature, obs.WindAvg,
        tempest.WindDirectionToCompass(obs.WindDirection))
}

func (h *handler) HandleLightningStrike(evt *tempest.LightningStrike) {
    fmt.Printf("Lightning! %.1f km away\n", evt.Distance)
}

func (h *handler) HandleRainStart(evt *tempest.RainStart) {
    fmt.Println("Rain started!")
}

func (h *handler) HandleRapidWind(evt *tempest.RapidWind) {
    fmt.Printf("Wind: %.1f m/s from %s\n", evt.WindSpeed,
        tempest.WindDirectionToCompass(evt.WindDirection))
}

func main() {
    ws := tempest.NewWSClient("YOUR_TOKEN")
    ctx := context.Background()

    go func() {
        if err := ws.Connect(ctx, &handler{}); err != nil {
            log.Fatal(err)
        }
    }()

    if err := ws.Subscribe(ctx, 67890); err != nil {
        log.Fatal(err)
    }

    select {} // block forever
}
Unit Conversion
tempC := 22.5
tempF := tempest.CelsiusToFahrenheit(tempC) // 72.5

windMps := 5.0
windMph := tempest.MpsToMph(windMps) // ~11.18

// Convert an entire observation to imperial units
obs := &tempest.Observation{AirTemperature: 22.5, WindAvg: 5.0}
imperial := tempest.ConvertObservation(obs, tempest.Imperial)
Derived Metrics
dp := tempest.DewPoint(25.0, 80.0)           // ~21.3°C
fl := tempest.FeelsLike(35.0, 70.0, 1.0)     // heat index applied
wb := tempest.WetBulb(25.0, 80.0)            // ~22.2°C
dir := tempest.WindDirectionToCompass(225.0)   // "SW"

API Reference

Full documentation is available on pkg.go.dev.

Data Types
Type Description
Observation Single obs_st observation (18 fields, metric units)
StationObservation REST API observation with named fields
Station Station metadata (name, location, devices)
Device Hardware device (ID, serial, type)
Forecast Weather forecast (daily + hourly)
LightningStrike Lightning strike event
RainStart Rain start event
RapidWind Rapid wind observation (3-second interval)
Unit Conversions
Function Description
CelsiusToFahrenheit / FahrenheitToCelsius Temperature
MpsToMph / MpsToKmh / MpsToKnots Wind speed
HpaToInhg / HpaToMmhg Pressure
MmToInches / KmToMiles Distance / precipitation
ConvertObservation Convert all fields in an Observation
Derived Metrics
Function Description
DewPoint(t, rh) Magnus formula
FeelsLike(t, rh, wind) Wind chill / heat index / air temp
WetBulb(t, rh) Stull (2011) formula
WindDirectionToCompass(deg) 16-point compass direction
REST Client
client := tempest.NewClient(token,
    tempest.WithMaxRetries(5),
    // tempest.WithBaseURL(url),
    // tempest.WithHTTPClient(hc),
    // tempest.WithRateLimiter(rl),
    // tempest.WithCircuitBreaker(cb),
)
Method Description
GetStation Station metadata
GetStationObservation Latest observation (named fields)
GetStationObservations Historical observations (time range)
GetDeviceObservations Device observations (time range)
GetForecast Weather forecast
CircuitState Circuit breaker state
WebSocket Client
ws := tempest.NewWSClient(token,
    // tempest.WithWSURL(url),
    // tempest.WithConnectionManager(cm),
)
Method Description
Connect Connect and start read loop (blocking)
Subscribe Subscribe to device observations
Unsubscribe Unsubscribe from device
Close Graceful close with close frame

Resilience

  • Rate limiting: Token bucket, 80 req/min (conservative vs 100 API limit)
  • Circuit breaker: Opens after 5 failures, 60s cooldown, half-open testing
  • Retry with backoff: Exponential backoff with jitter on 429/5xx (max 3 retries)
  • WebSocket reconnection: Exponential backoff 1s-60s with jitter, resets after 2min stable
  • Connection tracking: LIFO close order, graceful shutdown
  • Response size limit: 10 MB cap on HTTP responses

Security

  • TLS 1.2 minimum on all HTTPS connections
  • Tokens are never logged or included in error messages
  • Response body size capped at 10 MB
  • All network methods accept context.Context for cancellation

License

MIT

Documentation

Overview

Package tempest provides reusable building blocks for WeatherFlow Tempest weather stations, including API clients, data types, unit conversion, derived metrics, and connection management.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CelsiusToFahrenheit

func CelsiusToFahrenheit(c float64) float64

CelsiusToFahrenheit converts a temperature from Celsius to Fahrenheit.

func DewPoint

func DewPoint(t, rh float64) float64

DewPoint computes the dew point temperature in Celsius using the Magnus formula. t is the air temperature in Celsius and rh is the relative humidity in percent.

func FahrenheitToCelsius

func FahrenheitToCelsius(f float64) float64

FahrenheitToCelsius converts a temperature from Fahrenheit to Celsius.

func FeelsLike

func FeelsLike(t, rh, wind float64) float64

FeelsLike computes the "feels like" temperature in Celsius. It applies wind chill when cold and windy, heat index when hot and humid, and returns the air temperature otherwise. t is the air temperature in Celsius, rh is relative humidity in percent, and wind is the wind speed in meters per second.

func HpaToInhg

func HpaToInhg(hpa float64) float64

HpaToInhg converts pressure from hectopascals to inches of mercury.

func HpaToMmhg

func HpaToMmhg(hpa float64) float64

HpaToMmhg converts pressure from hectopascals to millimeters of mercury.

func InchesToMm

func InchesToMm(inches float64) float64

InchesToMm converts length from inches to millimeters.

func InhgToHpa

func InhgToHpa(inhg float64) float64

InhgToHpa converts pressure from inches of mercury to hectopascals.

func KmToMiles

func KmToMiles(km float64) float64

KmToMiles converts distance from kilometers to miles.

func KmhToMps

func KmhToMps(kmh float64) float64

KmhToMps converts wind speed from kilometers per hour to meters per second.

func KnotsToMps

func KnotsToMps(knots float64) float64

KnotsToMps converts wind speed from knots to meters per second.

func MilesToKm

func MilesToKm(mi float64) float64

MilesToKm converts distance from miles to kilometers.

func MmToInches

func MmToInches(mm float64) float64

MmToInches converts length from millimeters to inches.

func MmhgToHpa

func MmhgToHpa(mmhg float64) float64

MmhgToHpa converts pressure from millimeters of mercury to hectopascals.

func MphToMps

func MphToMps(mph float64) float64

MphToMps converts wind speed from miles per hour to meters per second.

func MpsToKmh

func MpsToKmh(mps float64) float64

MpsToKmh converts wind speed from meters per second to kilometers per hour.

func MpsToKnots

func MpsToKnots(mps float64) float64

MpsToKnots converts wind speed from meters per second to knots.

func MpsToMph

func MpsToMph(mps float64) float64

MpsToMph converts wind speed from meters per second to miles per hour.

func WetBulb

func WetBulb(t, rh float64) float64

WetBulb computes the wet bulb temperature in Celsius using the Stull (2011) formula. t is the air temperature in Celsius and rh is the relative humidity in percent.

func WindDirectionToCompass

func WindDirectionToCompass(degrees float64) string

WindDirectionToCompass converts a wind direction in degrees (0-360) to a 16-point compass direction string.

Types

type CircuitBreaker

type CircuitBreaker struct {
	// contains filtered or unexported fields
}

CircuitBreaker implements a circuit breaker pattern with closed, open, and half-open states.

func NewCircuitBreaker

func NewCircuitBreaker(maxFailures int, cooldown time.Duration) *CircuitBreaker

NewCircuitBreaker creates a new circuit breaker that opens after maxFailures consecutive failures and waits cooldown before transitioning to half-open.

func (*CircuitBreaker) Allow

func (cb *CircuitBreaker) Allow() bool

Allow returns true if a request is allowed through the circuit breaker. In the closed state, all requests are allowed. In the open state, requests are rejected until the cooldown has elapsed, at which point the circuit transitions to half-open and allows one request. In the half-open state, one request is allowed through for testing.

func (*CircuitBreaker) RecordFailure

func (cb *CircuitBreaker) RecordFailure()

RecordFailure records a failed request. In the closed state, consecutive failures are counted; when maxFailures is reached, the circuit opens. In the half-open state, any failure immediately re-opens the circuit.

func (*CircuitBreaker) RecordSuccess

func (cb *CircuitBreaker) RecordSuccess()

RecordSuccess records a successful request. In the half-open state, this closes the circuit. In the closed state, it resets the failure counter.

func (*CircuitBreaker) State

func (cb *CircuitBreaker) State() string

State returns the current state of the circuit breaker as a string: "closed", "open", or "half-open".

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a REST API client for the WeatherFlow Tempest API.

func NewClient

func NewClient(token string, opts ...ClientOption) (*Client, error)

NewClient creates a new REST API client for the WeatherFlow Tempest API. The token is the personal access token for authentication. Returns an error if the token is empty.

func (*Client) CircuitState

func (c *Client) CircuitState() string

CircuitState returns the current state of the client's circuit breaker.

func (*Client) GetDeviceObservations

func (c *Client) GetDeviceObservations(ctx context.Context, deviceID int, start, end time.Time) ([]Observation, error)

GetDeviceObservations retrieves observations for a device within a time range.

func (*Client) GetForecast

func (c *Client) GetForecast(ctx context.Context, stationID int) (*Forecast, error)

GetForecast retrieves the weather forecast for a station's location.

func (*Client) GetStation

func (c *Client) GetStation(ctx context.Context, stationID int) (*Station, error)

GetStation retrieves station metadata.

func (*Client) GetStationObservation

func (c *Client) GetStationObservation(ctx context.Context, stationID int) (*StationObservation, error)

GetStationObservation retrieves the latest observation for a station.

func (*Client) GetStationObservations

func (c *Client) GetStationObservations(ctx context.Context, stationID int, start, end time.Time) ([]Observation, error)

GetStationObservations retrieves observations for a station within a time range.

type ClientOption

type ClientOption func(*Client)

ClientOption configures a Client.

func WithBaseURL

func WithBaseURL(url string) ClientOption

WithBaseURL sets a custom base URL (useful for testing).

func WithCircuitBreaker

func WithCircuitBreaker(cb *CircuitBreaker) ClientOption

WithCircuitBreaker sets a custom circuit breaker.

func WithHTTPClient

func WithHTTPClient(c *http.Client) ClientOption

WithHTTPClient sets a custom HTTP client.

func WithMaxRetries

func WithMaxRetries(n int) ClientOption

WithMaxRetries sets the maximum number of retries for failed requests.

func WithRateLimiter

func WithRateLimiter(rl *RateLimiter) ClientOption

WithRateLimiter sets a custom rate limiter.

type ConnectionManager

type ConnectionManager struct {
	// contains filtered or unexported fields
}

ConnectionManager tracks open connections and provides orderly shutdown.

func NewConnectionManager

func NewConnectionManager() *ConnectionManager

NewConnectionManager creates a new connection manager.

func (*ConnectionManager) CloseAll

func (cm *ConnectionManager) CloseAll() error

CloseAll closes all tracked connections in LIFO order (newest first). Errors are logged but do not stop closing of remaining connections. Connections are protected against double-close.

func (*ConnectionManager) Count

func (cm *ConnectionManager) Count() int

Count returns the number of currently tracked connections.

func (*ConnectionManager) Deregister

func (cm *ConnectionManager) Deregister(id string)

Deregister removes a connection from the manager without closing it.

func (*ConnectionManager) Register

func (cm *ConnectionManager) Register(conn io.Closer, label string) string

Register adds a connection to the manager and returns a unique ID.

func (*ConnectionManager) Shutdown

func (cm *ConnectionManager) Shutdown(ctx context.Context) error

Shutdown gracefully closes all connections, respecting the context deadline.

type DailyForecast

type DailyForecast struct {
	Date         time.Time
	HighTemp     float64 // celsius
	LowTemp      float64 // celsius
	Conditions   string
	Icon         string
	PrecipChance int // percent
	PrecipType   string
	Sunrise      time.Time
	Sunset       time.Time
}

DailyForecast represents a single day's forecast.

type Device

type Device struct {
	DeviceID   int
	SerialNum  string
	DeviceType string
}

Device represents a hardware device attached to a station.

type Forecast

type Forecast struct {
	StationID int
	Latitude  float64
	Longitude float64
	Daily     []DailyForecast
	Hourly    []HourlyForecast
}

Forecast represents a weather forecast for a station's location.

type HourlyForecast

type HourlyForecast struct {
	Time          time.Time
	Temperature   float64 // celsius
	FeelsLike     float64 // celsius
	Humidity      float64 // percent
	WindAvg       float64 // m/s
	WindDirection float64 // degrees
	WindGust      float64 // m/s
	Conditions    string
	Icon          string
	PrecipChance  int // percent
	PrecipType    string
	UV            float64
}

HourlyForecast represents a single hour's forecast.

type LightningStrike

type LightningStrike struct {
	Timestamp time.Time
	Distance  float64 // km
	Energy    float64
}

LightningStrike represents a detected lightning strike event.

func ParseLightningStrike

func ParseLightningStrike(msg []byte) (*LightningStrike, error)

ParseLightningStrike parses an evt_strike JSON message.

type MessageHandler

type MessageHandler interface {
	HandleObservation(obs *Observation)
	HandleLightningStrike(evt *LightningStrike)
	HandleRainStart(evt *RainStart)
	HandleRapidWind(evt *RapidWind)
}

MessageHandler handles incoming WebSocket messages from a Tempest station.

type Observation

type Observation struct {
	Timestamp          time.Time
	StationID          int
	DeviceID           int
	WindLull           float64 // m/s
	WindAvg            float64 // m/s
	WindGust           float64 // m/s
	WindDirection      float64 // degrees (0-360)
	WindSampleInterval int     // seconds
	StationPressure    float64 // MB (hPa)
	AirTemperature     float64 // celsius
	RelativeHumidity   float64 // %
	Illuminance        float64 // lux
	UVIndex            float64
	SolarRadiation     float64 // W/m²
	RainAccumulation   float64 // mm
	PrecipitationType  int     // 0=none, 1=rain, 2=hail, 3=rain+hail
	LightningAvgDist   float64 // km
	LightningCount     int
	Battery            float64 // volts
	ReportInterval     int     // minutes

	// Derived fields (computed by caller or via REST).
	FeelsLike float64 // celsius
	DewPoint  float64 // celsius
	WetBulb   float64 // celsius
}

Observation represents a single observation from a Tempest station (obs_st). All values are stored in metric units.

func ConvertObservation

func ConvertObservation(obs *Observation, units UnitSystem) *Observation

ConvertObservation converts an Observation to the specified unit system. When units is Imperial, temperature fields are converted to Fahrenheit, wind fields to mph, pressure to inHg, precipitation to inches, and distance to miles. When units is Metric, a copy is returned unchanged.

func ParseObsArray

func ParseObsArray(obs []any) (*Observation, error)

ParseObsArray parses a single obs_st array into an Observation. Missing fields (short arrays or null values) are set to zero values.

func ParseObsStMessage

func ParseObsStMessage(msg []byte) ([]Observation, error)

ParseObsStMessage parses a raw obs_st JSON message into a slice of Observations.

type RainStart

type RainStart struct {
	Timestamp time.Time
}

RainStart represents a rain-start event.

func ParseRainStart

func ParseRainStart(msg []byte) (*RainStart, error)

ParseRainStart parses an evt_precip JSON message.

type RapidWind

type RapidWind struct {
	Timestamp     time.Time
	WindSpeed     float64 // m/s
	WindDirection float64 // degrees
}

RapidWind represents a rapid wind observation (every 3 seconds).

func ParseRapidWind

func ParseRapidWind(msg []byte) (*RapidWind, error)

ParseRapidWind parses a rapid_wind JSON message.

type RateLimiter

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter implements a token bucket rate limiter.

func NewRateLimiter

func NewRateLimiter(maxPerMinute int) *RateLimiter

NewRateLimiter creates a new token bucket rate limiter that allows maxPerMinute requests per minute.

func (*RateLimiter) Allow

func (rl *RateLimiter) Allow() bool

Allow checks if a request is allowed without blocking. Returns true and consumes a token if available, false otherwise.

func (*RateLimiter) Wait

func (rl *RateLimiter) Wait(ctx context.Context) error

Wait blocks until a token is available or the context is cancelled.

type Station

type Station struct {
	StationID int
	Name      string
	Latitude  float64
	Longitude float64
	Elevation float64 // meters
	Devices   []Device
	CreatedAt time.Time
}

Station represents a Tempest station and its devices.

type StationObservation

type StationObservation struct {
	StationID                   int
	Timestamp                   time.Time
	AirTemperature              float64
	RelativeHumidity            float64
	WindAvg                     float64
	WindGust                    float64
	WindLull                    float64
	WindDirection               float64
	BarometricPressure          float64
	SeaLevelPressure            float64
	SolarRadiation              float64
	UV                          float64
	Brightness                  float64
	FeelsLike                   float64
	DewPoint                    float64
	WetBulbTemperature          float64
	PrecipAccumDay              float64
	LightningCount3hr           int
	LightningStrikeLastDistance float64   // km
	LightningStrikeLastEpoch    time.Time // time of last strike
	PressureTrend               string    // "rising", "falling", "steady"
}

StationObservation represents a station-level observation returned by the REST API with named fields (as opposed to positional obs_st arrays).

type UnitSystem

type UnitSystem int

UnitSystem represents a measurement unit system.

const (
	// Metric represents the metric unit system (SI).
	Metric UnitSystem = iota
	// Imperial represents the imperial unit system (US customary).
	Imperial
)

type WSClient

type WSClient struct {
	// contains filtered or unexported fields
}

WSClient is a WebSocket client for the WeatherFlow Tempest API.

func NewWSClient

func NewWSClient(token string, opts ...WSClientOption) (*WSClient, error)

NewWSClient creates a new WebSocket client for the WeatherFlow Tempest API. Returns an error if the token is empty.

func (*WSClient) Close

func (ws *WSClient) Close() error

Close gracefully closes the WebSocket connection.

func (*WSClient) Connect

func (ws *WSClient) Connect(ctx context.Context, handler MessageHandler) error

Connect establishes a WebSocket connection and starts the read loop. It blocks until the context is cancelled, reconnecting on failure with exponential backoff.

func (*WSClient) Subscribe

func (ws *WSClient) Subscribe(ctx context.Context, deviceID int) error

Subscribe sends a listen_start message for a device.

func (*WSClient) Unsubscribe

func (ws *WSClient) Unsubscribe(ctx context.Context, deviceID int) error

Unsubscribe sends a listen_stop message for a device.

type WSClientOption

type WSClientOption func(*WSClient)

WSClientOption configures a WSClient.

func WithConnectionManager

func WithConnectionManager(cm *ConnectionManager) WSClientOption

WithConnectionManager sets a custom connection manager.

func WithWSURL

func WithWSURL(url string) WSClientOption

WithWSURL sets a custom WebSocket URL (useful for testing).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL