weather_agent.rb 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. require 'date'
  2. require 'cgi'
  3. module Agents
  4. class WeatherAgent < Agent
  5. cannot_receive_events!
  6. gem_dependency_check { defined?(Wunderground) && defined?(ForecastIO) }
  7. description <<-MD
  8. The Weather Agent creates an event for the day's weather at a given `location`.
  9. #{'## Include `forecast_io` and `wunderground` in your Gemfile to use this Agent!' if dependencies_missing?}
  10. You also must select when you would like to get the weather forecast for using the `which_day` option, where the number 1 represents today, 2 represents tomorrow and so on. Weather forecast inforation is only returned for at most one week at a time.
  11. The weather forecast information can be provided by either Wunderground or Dark Sky. To choose which `service` to use, enter either `darksky` or `wunderground`.
  12. The `location` should be:
  13. * For Wunderground: A US zipcode, or any location that Wunderground supports. To find one, search [wunderground.com](https://wunderground.com) and copy the location part of the URL. For example, a result for San Francisco gives `https://www.wunderground.com/US/CA/San_Francisco.html` and London, England gives `https://www.wunderground.com/q/zmw:00000.1.03772`. The locations in each are `US/CA/San_Francisco` and `zmw:00000.1.03772`, respectively.
  14. * For Dark Sky: `location` must be a comma-separated string of map co-ordinates (longitude, latitude). For example, San Francisco would be `37.7771,-122.4196`.
  15. You must set up an [API key for Wunderground](https://www.wunderground.com/weather/api/) in order to use this Agent with Wunderground.
  16. You must set up an [API key for Dark Sky](https://darksky.net/dev/) in order to use this Agent with Dark Sky.
  17. Set `expected_update_period_in_days` to the maximum amount of time that you'd expect to pass between Events being created by this Agent.
  18. If you want to see the returned texts in your language, set the `language` parameter in ISO 639-1 format.
  19. MD
  20. event_description <<-MD
  21. Events look like this:
  22. {
  23. "location": "12345",
  24. "date": {
  25. "epoch": "1357959600",
  26. "pretty": "10:00 PM EST on January 11, 2013"
  27. },
  28. "high": {
  29. "fahrenheit": "64",
  30. "celsius": "18"
  31. },
  32. "low": {
  33. "fahrenheit": "52",
  34. "celsius": "11"
  35. },
  36. "conditions": "Rain Showers",
  37. "icon": "rain",
  38. "icon_url": "https://icons-ak.wxug.com/i/c/k/rain.gif",
  39. "skyicon": "mostlycloudy",
  40. ...
  41. }
  42. MD
  43. default_schedule "8pm"
  44. def working?
  45. event_created_within?((interpolated['expected_update_period_in_days'].presence || 2).to_i) && !recent_error_logs? && key_setup?
  46. end
  47. def key_setup?
  48. interpolated['api_key'].present? && interpolated['api_key'] != "your-key" && interpolated['api_key'] != "put-your-key-here"
  49. end
  50. def default_options
  51. {
  52. 'service' => 'wunderground',
  53. 'api_key' => 'your-key',
  54. 'location' => '94103',
  55. 'which_day' => '1',
  56. 'language' => 'EN',
  57. 'expected_update_period_in_days' => '2'
  58. }
  59. end
  60. def check
  61. if key_setup?
  62. create_event :payload => model(weather_provider, which_day).merge('location' => location)
  63. end
  64. end
  65. private
  66. def weather_provider
  67. interpolated["service"].presence || "wunderground"
  68. end
  69. def which_day
  70. (interpolated["which_day"].presence || 1).to_i
  71. end
  72. def location
  73. interpolated["location"].presence || interpolated["zipcode"]
  74. end
  75. def language
  76. interpolated['language'].presence || 'EN'
  77. end
  78. def validate_options
  79. errors.add(:base, "service must be set to 'darksky' or 'wunderground'") unless %w[darksky forecastio wunderground].include?(weather_provider)
  80. errors.add(:base, "location is required") unless location.present?
  81. errors.add(:base, "api_key is required") unless interpolated['api_key'].present?
  82. errors.add(:base, "which_day selection is required") unless which_day.present?
  83. end
  84. def wunderground
  85. if key_setup?
  86. forecast = Wunderground.new(interpolated['api_key'], language: language.upcase).forecast_for(location)
  87. merged = {}
  88. forecast['forecast']['simpleforecast']['forecastday'].each { |daily| merged[daily['period']] = daily }
  89. forecast['forecast']['txt_forecast']['forecastday'].each { |daily| (merged[daily['period']] || {}).merge!(daily) }
  90. merged
  91. end
  92. end
  93. def dark_sky
  94. if key_setup?
  95. ForecastIO.api_key = interpolated['api_key']
  96. lat, lng = location.split(',')
  97. ForecastIO.forecast(lat, lng, params: {lang: language.downcase})['daily']['data']
  98. end
  99. end
  100. def model(weather_provider,which_day)
  101. if weather_provider == "wunderground"
  102. wunderground[which_day]
  103. elsif weather_provider == "darksky" || weather_provider == "forecastio"
  104. dark_sky.each do |value|
  105. timestamp = Time.at(value.time)
  106. if (timestamp.to_date - Time.now.to_date).to_i == which_day
  107. day = {
  108. 'date' => {
  109. 'epoch' => value.time.to_s,
  110. 'pretty' => timestamp.strftime("%l:%M %p %Z on %B %d, %Y"),
  111. 'day' => timestamp.day,
  112. 'month' => timestamp.month,
  113. 'year' => timestamp.year,
  114. 'yday' => timestamp.yday,
  115. 'hour' => timestamp.hour,
  116. 'min' => timestamp.strftime("%M"),
  117. 'sec' => timestamp.sec,
  118. 'isdst' => timestamp.isdst ? 1 : 0 ,
  119. 'monthname' => timestamp.strftime("%B"),
  120. 'monthname_short' => timestamp.strftime("%b"),
  121. 'weekday_short' => timestamp.strftime("%a"),
  122. 'weekday' => timestamp.strftime("%A"),
  123. 'ampm' => timestamp.strftime("%p"),
  124. 'tz_short' => timestamp.zone
  125. },
  126. 'period' => which_day.to_i,
  127. 'high' => {
  128. 'fahrenheit' => value.temperatureMax.round().to_s,
  129. 'epoch' => value.temperatureMaxTime.to_s,
  130. 'fahrenheit_apparent' => value.apparentTemperatureMax.round().to_s,
  131. 'epoch_apparent' => value.apparentTemperatureMaxTime.to_s,
  132. 'celsius' => ((5*(Float(value.temperatureMax) - 32))/9).round().to_s
  133. },
  134. 'low' => {
  135. 'fahrenheit' => value.temperatureMin.round().to_s,
  136. 'epoch' => value.temperatureMinTime.to_s,
  137. 'fahrenheit_apparent' => value.apparentTemperatureMin.round().to_s,
  138. 'epoch_apparent' => value.apparentTemperatureMinTime.to_s,
  139. 'celsius' => ((5*(Float(value.temperatureMin) - 32))/9).round().to_s
  140. },
  141. 'conditions' => value.summary,
  142. 'icon' => value.icon,
  143. 'avehumidity' => (value.humidity * 100).to_i,
  144. 'sunriseTime' => value.sunriseTime.to_s,
  145. 'sunsetTime' => value.sunsetTime.to_s,
  146. 'moonPhase' => value.moonPhase.to_s,
  147. 'precip' => {
  148. 'intensity' => value.precipIntensity.to_s,
  149. 'intensity_max' => value.precipIntensityMax.to_s,
  150. 'intensity_max_epoch' => value.precipIntensityMaxTime.to_s,
  151. 'probability' => value.precipProbability.to_s,
  152. 'type' => value.precipType
  153. },
  154. 'dewPoint' => value.dewPoint.to_s,
  155. 'avewind' => {
  156. 'mph' => value.windSpeed.round().to_s,
  157. 'kph' => (Float(value.windSpeed) * 1.609344).round().to_s,
  158. 'degrees' => value.windBearing.to_s
  159. },
  160. 'visibility' => value.visibility.to_s,
  161. 'cloudCover' => value.cloudCover.to_s,
  162. 'pressure' => value.pressure.to_s,
  163. 'ozone' => value.ozone.to_s
  164. }
  165. return day
  166. end
  167. end
  168. end
  169. end
  170. end
  171. end