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

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