]> git.madduck.net Git - etc/awesome.git/blob - widget/weather.lua

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

calendar, #289: attempt to assert notification timeout when t_out is 0
[etc/awesome.git] / widget / weather.lua
1
2 --[[
3                                                   
4      Licensed under GNU General Public License v2 
5       * (c) 2015, Luke Bonham                     
6                                                   
7 --]]
8
9 local helpers      = require("lain.helpers")
10 local json         = require("lain.util").dkjson
11 local focused      = require("awful.screen").focused
12 local naughty      = require("naughty")
13 local wibox        = require("wibox")
14 local math         = { floor    = math.floor }
15 local os           = { time     = os.time,
16                        date     = os.date,
17                        difftime = os.difftime }
18 local string       = { format   = string.format,
19                        gsub     = string.gsub }
20 local tonumber     = tonumber
21 local setmetatable = setmetatable
22
23 -- OpenWeatherMap
24 -- current weather and X-days forecast
25 -- lain.widget.weather
26
27 local function worker(args)
28     local weather               = { widget = wibox.widget.textbox() }
29     local args                  = args or {}
30     local APPID                 = args.APPID or "3e321f9414eaedbfab34983bda77a66e" -- lain default
31     local timeout               = args.timeout or 900   -- 15 min
32     local timeout_forecast      = args.timeout or 86400 -- 24 hrs
33     local current_call          = args.current_call  or "curl -s 'http://api.openweathermap.org/data/2.5/weather?id=%s&units=%s&lang=%s&APPID=%s'"
34     local forecast_call         = args.forecast_call or "curl -s 'http://api.openweathermap.org/data/2.5/forecast/daily?id=%s&units=%s&lang=%s&cnt=%s&APPID=%s'"
35     local city_id               = args.city_id or 0 -- placeholder
36     local utc_offset            = args.utc_offset or
37                                   function ()
38                                       local now = os.time()
39                                       return os.difftime(now, os.time(os.date("!*t", now))) + ((os.date("*t").isdst and 1 or 0) * 3600)
40                                   end
41     local units                 = args.units or "metric"
42     local lang                  = args.lang or "en"
43     local cnt                   = args.cnt or 5
44     local date_cmd              = args.date_cmd or "date -u -d @%d +'%%a %%d'"
45     local icons_path            = args.icons_path or helpers.icons_dir .. "openweathermap/"
46     local notification_preset   = args.notification_preset or {}
47     local notification_text_fun = args.notification_text_fun or
48                                   function (wn)
49                                       local day = os.date("%a %d", wn["dt"])
50                                       local tmin = math.floor(wn["temp"]["min"])
51                                       local tmax = math.floor(wn["temp"]["max"])
52                                       local desc = wn["weather"][1]["description"]
53                                       return string.format("<b>%s</b>: %s, %d - %d ", day, desc, tmin, tmax)
54                                   end
55     local weather_na_markup     = args.weather_na_markup or " N/A "
56     local followtag             = args.followtag or false
57     local settings              = args.settings or function() end
58
59     weather.widget:set_markup(weather_na_markup)
60     weather.icon_path = icons_path .. "na.png"
61     weather.icon = wibox.widget.imagebox(weather.icon_path)
62
63     function weather.show(t_out)
64         weather.hide()
65
66         if followtag then
67             notification_preset.screen = focused()
68         end
69
70         if not weather.notification_text then
71             weather.update()
72             weather.forecast_update()
73         end
74
75         weather.notification = naughty.notify({
76             text    = weather.notification_text,
77             icon    = weather.icon_path,
78             timeout = t_out,
79             preset  = notification_preset
80         })
81     end
82
83     function weather.hide()
84         if weather.notification then
85             naughty.destroy(weather.notification)
86             weather.notification = nil
87         end
88     end
89
90     function weather.attach(obj)
91         obj:connect_signal("mouse::enter", function()
92             weather.show(0)
93         end)
94         obj:connect_signal("mouse::leave", function()
95             weather.hide()
96         end)
97     end
98
99     function weather.forecast_update()
100         local cmd = string.format(forecast_call, city_id, units, lang, cnt, APPID)
101         helpers.async(cmd, function(f)
102             local pos, err
103             weather_now, pos, err = json.decode(f, 1, nil)
104
105             if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then
106                 weather.notification_text = ''
107                 for i = 1, weather_now["cnt"] do
108                     weather.notification_text = weather.notification_text ..
109                                                 notification_text_fun(weather_now["list"][i])
110
111                     if i < weather_now["cnt"] then
112                         weather.notification_text = weather.notification_text .. "\n"
113                     end
114                 end
115             end
116         end)
117     end
118
119     function weather.update()
120         local cmd = string.format(current_call, city_id, units, lang, APPID)
121         helpers.async(cmd, function(f)
122             local pos, err, icon
123             weather_now, pos, err = json.decode(f, 1, nil)
124
125             if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then
126                 -- weather icon based on localtime
127                 local now     = os.time()
128                 local sunrise = tonumber(weather_now["sys"]["sunrise"])
129                 local sunset  = tonumber(weather_now["sys"]["sunset"])
130                 local icon    = weather_now["weather"][1]["icon"]
131                 local loc_m   = os.time { year = os.date("%Y"), month = os.date("%m"), day = os.date("%d"), hour = 0 }
132                 local offset  = utc_offset()
133                 local utc_m   = loc_m - offset
134
135                 if offset > 0 and (now - utc_m)>=86400 then
136                     utc_m = utc_m + 86400
137                 elseif offset < 0 and (utc_m - now)>=86400 then
138                     utc_m = utc_m - 86400
139                 end
140
141                 -- if we are 1 day after the GMT, return 1 day back, and viceversa
142                 if offset > 0 and loc_m >= utc_m then
143                     now = now - 86400
144                 elseif offset < 0 and loc_m <= utc_m then
145                     now = now + 86400
146                 end
147
148                 if sunrise <= now and now <= sunset then
149                     icon = string.gsub(icon, "n", "d")
150                 else
151                     icon = string.gsub(icon, "d", "n")
152                 end
153
154                 weather.icon_path = icons_path .. icon .. ".png"
155                 widget = weather.widget
156                 settings()
157             else
158                 weather.icon_path = icons_path .. "na.png"
159                 weather.widget:set_markup(weather_na_markup)
160             end
161
162             weather.icon:set_image(weather.icon_path)
163         end)
164     end
165
166     weather.attach(weather.widget)
167
168     weather.timer = helpers.newtimer("weather-" .. city_id, timeout, weather.update, false, true)
169     weather.timer_forecast = helpers.newtimer("weather_forecast-" .. city_id, timeout, weather.forecast_update, false, true)
170
171     return weather
172 end
173
174 return setmetatable({}, { __call = function(_, ...) return worker(...) end })