]> 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:

Merge branch 'master' of github.com:lcpz/lain
[etc/awesome.git] / widget / weather.lua
1 --[[
2
3      Licensed under GNU General Public License v2
4       * (c) 2015, Luca CPZ
5
6 --]]
7
8 local helpers  = require("lain.helpers")
9 local json     = require("lain.util").dkjson
10 local focused  = require("awful.screen").focused
11 local naughty  = require("naughty")
12 local wibox    = require("wibox")
13 local math     = math
14 local os       = os
15 local string   = string
16 local tonumber = tonumber
17
18 -- OpenWeatherMap
19 -- current weather and X-days forecast
20 -- lain.widget.weather
21
22 local function factory(args)
23     local weather               = { widget = wibox.widget.textbox() }
24     local args                  = args or {}
25     local APPID                 = args.APPID or "3e321f9414eaedbfab34983bda77a66e" -- lain's default
26     local timeout               = args.timeout or 60 * 15 -- 15 min
27     local timeout_forecast      = args.timeout or 60 * 60 * 24 -- 24 hrs
28     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'"
29     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'"
30     local city_id               = args.city_id or 0 -- placeholder
31     local units                 = args.units or "metric"
32     local lang                  = args.lang or "en"
33     local cnt                   = args.cnt or 5
34     local date_cmd              = args.date_cmd or "date -u -d @%d +'%%a %%d'"
35     local icons_path            = args.icons_path or helpers.icons_dir .. "openweathermap/"
36     local notification_preset   = args.notification_preset or {}
37     local notification_text_fun = args.notification_text_fun or
38                                   function (wn)
39                                       local day = os.date("%a %d", wn["dt"])
40                                       local tmin = math.floor(wn["temp"]["min"])
41                                       local tmax = math.floor(wn["temp"]["max"])
42                                       local desc = wn["weather"][1]["description"]
43                                       return string.format("<b>%s</b>: %s, %d - %d ", day, desc, tmin, tmax)
44                                   end
45     local weather_na_markup     = args.weather_na_markup or " N/A "
46     local followtag             = args.followtag or false
47     local showpopup             = args.showpopup or "on"
48     local settings              = args.settings or function() end
49
50     weather.widget:set_markup(weather_na_markup)
51     weather.icon_path = icons_path .. "na.png"
52     weather.icon = wibox.widget.imagebox(weather.icon_path)
53
54     function weather.show(t_out)
55         weather.hide()
56
57         if followtag then
58             notification_preset.screen = focused()
59         end
60
61         if not weather.notification_text then
62             weather.update()
63             weather.forecast_update()
64         end
65
66         weather.notification = naughty.notify({
67             text    = weather.notification_text,
68             icon    = weather.icon_path,
69             timeout = t_out,
70             preset  = notification_preset
71         })
72     end
73
74     function weather.hide()
75         if weather.notification then
76             naughty.destroy(weather.notification)
77             weather.notification = nil
78         end
79     end
80
81     function weather.attach(obj)
82         obj:connect_signal("mouse::enter", function()
83             weather.show(0)
84         end)
85         obj:connect_signal("mouse::leave", function()
86             weather.hide()
87         end)
88     end
89
90     function weather.forecast_update()
91         local cmd = string.format(forecast_call, city_id, units, lang, cnt, APPID)
92         helpers.async(cmd, function(f)
93             local pos, err
94             weather_now, pos, err = json.decode(f, 1, nil)
95
96             if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then
97                 weather.notification_text = ""
98                 for i = 1, weather_now["cnt"] do
99                     weather.notification_text = weather.notification_text ..
100                                                 notification_text_fun(weather_now["list"][i])
101                     if i < weather_now["cnt"] then
102                         weather.notification_text = weather.notification_text .. "\n"
103                     end
104                 end
105             end
106         end)
107     end
108
109     function weather.update()
110         local cmd = string.format(current_call, city_id, units, lang, APPID)
111         helpers.async(cmd, function(f)
112             local pos, err, icon
113             weather_now, pos, err = json.decode(f, 1, nil)
114
115             if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then
116                 local sunrise = tonumber(weather_now["sys"]["sunrise"])
117                 local sunset  = tonumber(weather_now["sys"]["sunset"])
118                 local icon    = weather_now["weather"][1]["icon"]
119                 local loc_now = os.time() -- local time
120                 local loc_m   = os.time { year = os.date("%Y"), month = os.date("%m"), day = os.date("%d"), hour = 0 } -- local time from midnight
121                 local loc_d   = os.date("*t",  loc_now) -- table YMDHMS for current local time (for TZ calculation)
122                 local utc_d   = os.date("!*t", loc_now) -- table YMDHMS for current UTC time
123                 local utc_now = os.time(utc_d) -- UTC time now
124                 local offdt   = (loc_d.isdst and 1 or 0) * 3600 + 100 * (loc_d.min  - utc_d.min) / 60 -- DST offset
125                 local offset  = os.difftime(loc_now, utc_now) + (loc_d.isdst and 1 or 0) * 3600 + 100 * (loc_d.min  - utc_d.min) / 60 -- TZ offset (including DST)
126                 local offday  = (offset < 0 and -86400) or 86400 -- 24 hour correction value (+86400 or -86400)
127
128                 -- if current UTC time is earlier then local midnight -> positive offset (negative otherwise)
129                 if offset * (loc_m - utc_now + offdt) > 0 then
130                     sunrise = sunrise + offday -- Shift sunset and sunrise times by 24 hours
131                     sunset  = sunset  + offday
132                 end
133
134                 if sunrise <= loc_now and loc_now <= sunset then
135                     icon = string.gsub(icon, "n", "d")
136                 else
137                     icon = string.gsub(icon, "d", "n")
138                 end
139
140                 weather.icon_path = icons_path .. icon .. ".png"
141                 widget = weather.widget
142                 settings()
143             else
144                 weather.icon_path = icons_path .. "na.png"
145                 weather.widget:set_markup(weather_na_markup)
146             end
147
148             weather.icon:set_image(weather.icon_path)
149         end)
150     end
151
152     if showpopup == "on" then weather.attach(weather.widget) end
153
154     weather.timer = helpers.newtimer("weather-" .. city_id, timeout, weather.update, false, true)
155     weather.timer_forecast = helpers.newtimer("weather_forecast-" .. city_id, timeout, weather.forecast_update, false, true)
156
157     return weather
158 end
159
160 return factory