Skip to content

Commit

Permalink
Add lang parameter to OpenWeathermap input plugin (influxdata#6504)
Browse files Browse the repository at this point in the history
  • Loading branch information
reimda authored Oct 18, 2019
1 parent f241e96 commit 85581fc
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 162 deletions.
26 changes: 20 additions & 6 deletions plugins/inputs/openweathermap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ Collect current weather and forecast data from OpenWeatherMap.

To use this plugin you will need an [api key][] (app_id).

City identifiers can be found in the [city list][]. Alternately you can
[search][] by name; the `city_id` can be found as the last digits of the URL:
https://openweathermap.org/city/2643743
City identifiers can be found in the [city list][]. Alternately you
can [search][] by name; the `city_id` can be found as the last digits
of the URL: https://openweathermap.org/city/2643743. Language
identifiers can be found in the [lang list][]. Documentation for
condition ID, icon, and main is at [weather conditions][].

### Configuration

Expand All @@ -18,6 +20,12 @@ https://openweathermap.org/city/2643743
## City ID's to collect weather data from.
city_id = ["5391959"]

## Language of the description field. Can be one of "ar", "bg",
## "ca", "cz", "de", "el", "en", "fa", "fi", "fr", "gl", "hr", "hu",
## "it", "ja", "kr", "la", "lt", "mk", "nl", "pl", "pt", "ro", "ru",
## "se", "sk", "sl", "es", "tr", "ua", "vi", "zh_cn", "zh_tw"
# lang = "en"

## APIs to fetch; can contain "weather" or "forecast".
fetch = ["weather", "forecast"]

Expand All @@ -42,6 +50,8 @@ https://openweathermap.org/city/2643743
- tags:
- city_id
- forecast
- condition_id
- condition_main
- fields:
- cloudiness (int, percent)
- humidity (int, percent)
Expand All @@ -53,16 +63,20 @@ https://openweathermap.org/city/2643743
- visibility (int, meters, not available on forecast data)
- wind_degrees (float, wind direction in degrees)
- wind_speed (float, wind speed in meters/sec or miles/sec)
- condition_description (string, localized long description)
- condition_icon


### Example Output

```
> weather,city=San\ Francisco,city_id=5391959,country=US,forecast=* cloudiness=40i,humidity=72i,pressure=1013,rain=0,sunrise=1559220629000000000i,sunset=1559273058000000000i,temperature=13.31,visibility=16093i,wind_degrees=280,wind_speed=4.6 1559268695000000000
> weather,city=San\ Francisco,city_id=5391959,country=US,forecast=3h cloudiness=0i,humidity=86i,pressure=1012.03,rain=0,temperature=10.69,wind_degrees=222.855,wind_speed=2.76 1559271600000000000
> weather,city=San\ Francisco,city_id=5391959,country=US,forecast=6h cloudiness=11i,humidity=93i,pressure=1012.79,rain=0,temperature=9.34,wind_degrees=212.685,wind_speed=1.85 1559282400000000000
> weather,city=San\ Francisco,city_id=5391959,condition_id=800,condition_main=Clear,country=US,forecast=* cloudiness=1i,condition_description="clear sky",condition_icon="01d",humidity=35i,pressure=1012,rain=0,sunrise=1570630329000000000i,sunset=1570671689000000000i,temperature=21.52,visibility=16093i,wind_degrees=280,wind_speed=5.7 1570659256000000000
> weather,city=San\ Francisco,city_id=5391959,condition_id=800,condition_main=Clear,country=US,forecast=3h cloudiness=0i,condition_description="clear sky",condition_icon="01n",humidity=41i,pressure=1010,rain=0,temperature=22.34,wind_degrees=249.393,wind_speed=2.085 1570665600000000000
> weather,city=San\ Francisco,city_id=5391959,condition_id=800,condition_main=Clear,country=US,forecast=6h cloudiness=0i,condition_description="clear sky",condition_icon="01n",humidity=50i,pressure=1012,rain=0,temperature=17.09,wind_degrees=310.754,wind_speed=3.009 1570676400000000000
```

[api key]: https://openweathermap.org/appid
[city list]: http://bulk.openweathermap.org/sample/city.list.json.gz
[search]: https://openweathermap.org/find
[lang list]: https://openweathermap.org/current#multi
[weather conditions]: https://openweathermap.org/weather-conditions
194 changes: 116 additions & 78 deletions plugins/inputs/openweathermap/openweathermap.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,23 @@ const (
// The limit of locations is 20.
owmRequestSeveralCityId int = 20

defaultBaseURL = "https://api.openweathermap.org/"
defaultBaseUrl = "https://api.openweathermap.org/"
defaultResponseTimeout time.Duration = time.Second * 5
defaultUnits string = "metric"
defaultLang string = "en"
)

type OpenWeatherMap struct {
AppId string `toml:"app_id"`
CityId []string `toml:"city_id"`
Lang string `toml:"lang"`
Fetch []string `toml:"fetch"`
BaseUrl string `toml:"base_url"`
ResponseTimeout internal.Duration `toml:"response_timeout"`
Units string `toml:"units"`

client *http.Client
client *http.Client
baseUrl *url.URL
}

var sampleConfig = `
Expand All @@ -46,6 +49,12 @@ var sampleConfig = `
## City ID's to collect weather data from.
city_id = ["5391959"]
## Language of the description field. Can be one of "ar", "bg",
## "ca", "cz", "de", "el", "en", "fa", "fi", "fr", "gl", "hr", "hu",
## "it", "ja", "kr", "la", "lt", "mk", "nl", "pl", "pt", "ro", "ru",
## "se", "sk", "sl", "es", "tr", "ua", "vi", "zh_cn", "zh_tw"
# lang = "en"
## APIs to fetch; can contain "weather" or "forecast".
fetch = ["weather", "forecast"]
Expand Down Expand Up @@ -76,41 +85,10 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
var strs []string

base, err := url.Parse(n.BaseUrl)
if err != nil {
return err
}

// Create an HTTP client that is re-used for each
// collection interval
if n.client == nil {
client, err := n.createHttpClient()
if err != nil {
return err
}
n.client = client
}

units := n.Units
switch n.Units {
case "imperial", "standard":
break
default:
units = defaultUnits
}

for _, fetch := range n.Fetch {
if fetch == "forecast" {
var u *url.URL

for _, city := range n.CityId {
u, err = url.Parse(fmt.Sprintf("/data/2.5/forecast?id=%s&APPID=%s&units=%s", city, n.AppId, units))
if err != nil {
acc.AddError(fmt.Errorf("unable to parse address '%s': %s", u, err))
continue
}

addr := base.ResolveReference(u).String()
addr := n.formatURL("/data/2.5/forecast", city)
wg.Add(1)
go func() {
defer wg.Done()
Expand All @@ -126,21 +104,14 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error {
} else if fetch == "weather" {
j := 0
for j < len(n.CityId) {
var u *url.URL
strs = make([]string, 0)
for i := 0; j < len(n.CityId) && i < owmRequestSeveralCityId; i++ {
strs = append(strs, n.CityId[j])
j++
}
cities := strings.Join(strs, ",")

u, err = url.Parse(fmt.Sprintf("/data/2.5/group?id=%s&APPID=%s&units=%s", cities, n.AppId, units))
if err != nil {
acc.AddError(fmt.Errorf("Unable to parse address '%s': %s", u, err))
continue
}

addr := base.ResolveReference(u).String()
addr := n.formatURL("/data/2.5/group", cities)
wg.Add(1)
go func() {
defer wg.Done()
Expand Down Expand Up @@ -226,6 +197,12 @@ type WeatherEntry struct {
Lon float64 `json:"lon"`
} `json:"coord"`
Visibility int64 `json:"visibility"`
Weather []struct {
ID int64 `json:"id"`
Main string `json:"main"`
Description string `json:"description"`
Icon string `json:"icon"`
} `json:"weather"`
}

type Status struct {
Expand Down Expand Up @@ -253,27 +230,34 @@ func gatherWeatherUrl(r io.Reader) (*Status, error) {
func gatherWeather(acc telegraf.Accumulator, status *Status) {
for _, e := range status.List {
tm := time.Unix(e.Dt, 0)
acc.AddFields(
"weather",
map[string]interface{}{
"cloudiness": e.Clouds.All,
"humidity": e.Main.Humidity,
"pressure": e.Main.Pressure,
"rain": e.Rain.Rain3,
"sunrise": time.Unix(e.Sys.Sunrise, 0).UnixNano(),
"sunset": time.Unix(e.Sys.Sunset, 0).UnixNano(),
"temperature": e.Main.Temp,
"visibility": e.Visibility,
"wind_degrees": e.Wind.Deg,
"wind_speed": e.Wind.Speed,
},
map[string]string{
"city": e.Name,
"city_id": strconv.FormatInt(e.Id, 10),
"country": e.Sys.Country,
"forecast": "*",
},
tm)

fields := map[string]interface{}{
"cloudiness": e.Clouds.All,
"humidity": e.Main.Humidity,
"pressure": e.Main.Pressure,
"rain": e.Rain.Rain3,
"sunrise": time.Unix(e.Sys.Sunrise, 0).UnixNano(),
"sunset": time.Unix(e.Sys.Sunset, 0).UnixNano(),
"temperature": e.Main.Temp,
"visibility": e.Visibility,
"wind_degrees": e.Wind.Deg,
"wind_speed": e.Wind.Speed,
}
tags := map[string]string{
"city": e.Name,
"city_id": strconv.FormatInt(e.Id, 10),
"country": e.Sys.Country,
"forecast": "*",
}

if len(e.Weather) > 0 {
fields["condition_description"] = e.Weather[0].Description
fields["condition_icon"] = e.Weather[0].Icon
tags["condition_id"] = strconv.FormatInt(e.Weather[0].ID, 10)
tags["condition_main"] = e.Weather[0].Main
}

acc.AddFields("weather", fields, tags, tm)
}
}

Expand All @@ -286,20 +270,23 @@ func gatherForecast(acc telegraf.Accumulator, status *Status) {
}
for i, e := range status.List {
tm := time.Unix(e.Dt, 0)
fields := map[string]interface{}{
"cloudiness": e.Clouds.All,
"humidity": e.Main.Humidity,
"pressure": e.Main.Pressure,
"rain": e.Rain.Rain3,
"temperature": e.Main.Temp,
"wind_degrees": e.Wind.Deg,
"wind_speed": e.Wind.Speed,
}
if len(e.Weather) > 0 {
fields["condition_description"] = e.Weather[0].Description
fields["condition_icon"] = e.Weather[0].Icon
tags["condition_id"] = strconv.FormatInt(e.Weather[0].ID, 10)
tags["condition_main"] = e.Weather[0].Main
}
tags["forecast"] = fmt.Sprintf("%dh", (i+1)*3)
acc.AddFields(
"weather",
map[string]interface{}{
"cloudiness": e.Clouds.All,
"humidity": e.Main.Humidity,
"pressure": e.Main.Pressure,
"rain": e.Rain.Rain3,
"temperature": e.Main.Temp,
"wind_degrees": e.Wind.Deg,
"wind_speed": e.Wind.Speed,
},
tags,
tm)
acc.AddFields("weather", fields, tags, tm)
}
}

Expand All @@ -310,8 +297,59 @@ func init() {
}
return &OpenWeatherMap{
ResponseTimeout: tmout,
Units: defaultUnits,
BaseUrl: defaultBaseURL,
BaseUrl: defaultBaseUrl,
}
})
}

func (n *OpenWeatherMap) Init() error {
var err error
n.baseUrl, err = url.Parse(n.BaseUrl)
if err != nil {
return err
}

// Create an HTTP client that is re-used for each
// collection interval
n.client, err = n.createHttpClient()
if err != nil {
return err
}

switch n.Units {
case "imperial", "standard", "metric":
case "":
n.Units = defaultUnits
default:
return fmt.Errorf("unknown units: %s", n.Units)
}

switch n.Lang {
case "ar", "bg", "ca", "cz", "de", "el", "en", "fa", "fi", "fr", "gl",
"hr", "hu", "it", "ja", "kr", "la", "lt", "mk", "nl", "pl",
"pt", "ro", "ru", "se", "sk", "sl", "es", "tr", "ua", "vi",
"zh_cn", "zh_tw":
case "":
n.Lang = defaultLang
default:
return fmt.Errorf("unknown language: %s", n.Lang)
}

return nil
}

func (n *OpenWeatherMap) formatURL(path string, city string) string {
v := url.Values{
"id": []string{city},
"APPID": []string{n.AppId},
"lang": []string{n.Lang},
"units": []string{n.Units},
}

relative := &url.URL{
Path: path,
RawQuery: v.Encode(),
}

return n.baseUrl.ResolveReference(relative).String()
}
Loading

0 comments on commit 85581fc

Please sign in to comment.