]> git.madduck.net Git - etc/awesome.git/blob - widgets/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:

1e398cc065f8acaaeb51545a429bd2a9c301056e
[etc/awesome.git] / widgets / 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.widgets.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 we are 1 day after the GMT, return 1 day back, and viceversa
136                 if offset > 0 and loc_m >= utc_m then
137                     now = now - 86400
138                 elseif offset < 0 and loc_m <= utc_m then
139                     now = now + 86400
140                 end
141
142                 if sunrise <= now and now <= sunset then
143                     icon = string.gsub(icon, "n", "d")
144                 else
145                     icon = string.gsub(icon, "d", "n")
146                 end
147
148                 weather.icon_path = icons_path .. icon .. ".png"
149                 widget = weather.widget
150                 settings()
151             else
152                 weather.icon_path = icons_path .. "na.png"
153                 weather.widget:set_markup(weather_na_markup)
154             end
155
156             weather.icon:set_image(weather.icon_path)
157         end)
158     end
159
160     weather.attach(weather.widget)
161
162     weather.timer = helpers.newtimer("weather-" .. city_id, timeout, weather.update, false, true)
163     weather.timer_forecast = helpers.newtimer("weather_forecast-" .. city_id, timeout, weather.forecast_update, false, true)
164
165     return weather
166 end
167
168 return setmetatable({}, { __call = function(_, ...) return worker(...) end })