rot finished

This commit is contained in:
2026-01-09 11:56:40 +01:00
parent 1ee0afa088
commit ac99f291a7
30 changed files with 5581 additions and 293 deletions

View File

@@ -0,0 +1,96 @@
package solar
import (
"encoding/xml"
"fmt"
"io"
"net/http"
"strings"
"time"
)
// SolarData contains current solar and geomagnetic conditions
type SolarData struct {
SolarFluxIndex int `json:"sfi"` // Solar Flux Index
Sunspots int `json:"sunspots"` // Number of sunspots
AIndex int `json:"a_index"` // A-index (geomagnetic activity)
KIndex int `json:"k_index"` // K-index (geomagnetic activity)
GeomagField string `json:"geomag"` // Geomagnetic field status
UpdatedAt string `json:"updated"` // Last update time
}
// HamQSLResponse matches the XML structure from hamqsl.com
type HamQSLResponse struct {
XMLName xml.Name `xml:"solar"`
SolarData SolarDataXML `xml:"solardata"`
}
type SolarDataXML struct {
Updated string `xml:"updated"`
SolarFlux string `xml:"solarflux"`
AIndex string `xml:"aindex"`
KIndex string `xml:"kindex"`
Sunspots string `xml:"sunspots"`
GeomagField string `xml:"geomagfield"`
}
type Client struct {
httpClient *http.Client
lastUpdate time.Time
cachedData *SolarData
}
func New() *Client {
return &Client{
httpClient: &http.Client{
Timeout: 10 * time.Second,
},
}
}
// GetSolarData fetches current solar data from HamQSL
// Data is cached for 15 minutes to avoid excessive requests
func (c *Client) GetSolarData() (*SolarData, error) {
// Return cached data if less than 15 minutes old
if c.cachedData != nil && time.Since(c.lastUpdate) < 15*time.Minute {
return c.cachedData, nil
}
resp, err := c.httpClient.Get("http://www.hamqsl.com/solarxml.php")
if err != nil {
return nil, fmt.Errorf("failed to fetch solar data: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
var hamqslData HamQSLResponse
if err := xml.Unmarshal(body, &hamqslData); err != nil {
return nil, fmt.Errorf("failed to parse XML: %w", err)
}
// Parse the data
solarData := &SolarData{
GeomagField: hamqslData.SolarData.GeomagField,
UpdatedAt: hamqslData.SolarData.Updated,
}
// Parse numeric values (trim spaces)
fmt.Sscanf(strings.TrimSpace(hamqslData.SolarData.SolarFlux), "%d", &solarData.SolarFluxIndex)
fmt.Sscanf(strings.TrimSpace(hamqslData.SolarData.AIndex), "%d", &solarData.AIndex)
fmt.Sscanf(strings.TrimSpace(hamqslData.SolarData.KIndex), "%d", &solarData.KIndex)
fmt.Sscanf(strings.TrimSpace(hamqslData.SolarData.Sunspots), "%d", &solarData.Sunspots)
// Cache the data
c.cachedData = solarData
c.lastUpdate = time.Now()
return solarData, nil
}

View File

@@ -0,0 +1,126 @@
package weather
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// WeatherData contains current weather conditions
type WeatherData struct {
Temperature float64 `json:"temp"`
FeelsLike float64 `json:"feels_like"`
Humidity int `json:"humidity"`
Pressure int `json:"pressure"`
WindSpeed float64 `json:"wind_speed"`
WindGust float64 `json:"wind_gust"`
WindDeg int `json:"wind_deg"`
Clouds int `json:"clouds"`
Description string `json:"description"`
Icon string `json:"icon"`
UpdatedAt string `json:"updated"`
}
// OpenWeatherMapResponse matches the API response
type OpenWeatherMapResponse struct {
Weather []struct {
Description string `json:"description"`
Icon string `json:"icon"`
} `json:"weather"`
Main struct {
Temp float64 `json:"temp"`
FeelsLike float64 `json:"feels_like"`
Humidity int `json:"humidity"`
Pressure int `json:"pressure"`
} `json:"main"`
Wind struct {
Speed float64 `json:"speed"`
Deg int `json:"deg"`
Gust float64 `json:"gust"`
} `json:"wind"`
Clouds struct {
All int `json:"all"`
} `json:"clouds"`
Dt int64 `json:"dt"`
}
type Client struct {
apiKey string
latitude float64
longitude float64
httpClient *http.Client
lastUpdate time.Time
cachedData *WeatherData
}
func New(apiKey string, latitude, longitude float64) *Client {
return &Client{
apiKey: apiKey,
latitude: latitude,
longitude: longitude,
httpClient: &http.Client{
Timeout: 10 * time.Second,
},
}
}
// GetWeatherData fetches current weather from OpenWeatherMap
// Data is cached for 10 minutes
func (c *Client) GetWeatherData() (*WeatherData, error) {
// Return cached data if less than 10 minutes old
if c.cachedData != nil && time.Since(c.lastUpdate) < 10*time.Minute {
return c.cachedData, nil
}
url := fmt.Sprintf(
"https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s&units=metric",
c.latitude, c.longitude, c.apiKey,
)
resp, err := c.httpClient.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to fetch weather data: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
var owmData OpenWeatherMapResponse
if err := json.Unmarshal(body, &owmData); err != nil {
return nil, fmt.Errorf("failed to parse JSON: %w", err)
}
// Convert to our structure
weatherData := &WeatherData{
Temperature: owmData.Main.Temp,
FeelsLike: owmData.Main.FeelsLike,
Humidity: owmData.Main.Humidity,
Pressure: owmData.Main.Pressure,
WindSpeed: owmData.Wind.Speed,
WindGust: owmData.Wind.Gust,
WindDeg: owmData.Wind.Deg,
Clouds: owmData.Clouds.All,
UpdatedAt: time.Unix(owmData.Dt, 0).Format(time.RFC3339),
}
if len(owmData.Weather) > 0 {
weatherData.Description = owmData.Weather[0].Description
weatherData.Icon = owmData.Weather[0].Icon
}
// Cache the data
c.cachedData = weatherData
c.lastUpdate = time.Now()
return weatherData, nil
}