conky-config/drawing/Weather.lua

483 lines
13 KiB
Lua

local Widget = require 'Widget'
local Text = require 'Text'
local Line = require 'Line'
local TextColumn = require 'TextColumn'
local ScaledImage = require 'ScaledImage'
local Util = require 'Util'
local Json = require 'Json'
local Patterns = require 'Patterns'
local __string_match = string.match
local __string_sub = string.sub
local __string_upper = string.upper
local __os_execute = os.execute
local TIME_FORMAT = '%-I:%M %p'
local DATE_FORMAT = '%A'
local NUM_ROWS = 8
local WEATHER_UPDATE_INTERVAL = 900
local WEATHER_JSON_PATH = '/tmp/weather.json'
local ICON_DIR_PATH = _G_INIT_DATA_.ABS_PATH .. '/images/weather/'
local RECENTLY_UPDATED_PATH = '/tmp/weather_recently_updated'
local NA = 'N/A'
local NA_IMAGE_PATH = ICON_DIR_PATH .. 'na.png'
local _SPACING_ = 20
local _HEADER_PAD_ = 20
local _ICON_SIDE_LENGTH_ = 75
local _TEMP_SECTION_WIDTH_ = 220
local _SECTION_HEIGHT_ = _HEADER_PAD_ + _ICON_SIDE_LENGTH_ + 30
local create_side_rows = function(side_rows_x, side_rows_y, side_rows_tbl)
for i = 1, NUM_ROWS do
side_rows_tbl[i] = {}
local current_row = side_rows_tbl[i]
local current_row_y = side_rows_y + (i - 1) * _SECTION_HEIGHT_
current_row.desc = Widget.Text{
x = side_rows_x,
y = current_row_y,
text_color = Patterns.BLUE,
}
current_row.period = Widget.Text{
x = side_rows_x + _G_INIT_DATA_.SECTION_WIDTH,
y = current_row_y,
x_align = 'right',
text_color = Patterns.BLUE
}
current_row.icon = Widget.ScaledImage{
x = side_rows_x,
y = current_row_y + _HEADER_PAD_,
width = _ICON_SIDE_LENGTH_,
height = _ICON_SIDE_LENGTH_
}
current_row.temp1 = Widget.Text{
x = side_rows_x + _ICON_SIDE_LENGTH_ + _TEMP_SECTION_WIDTH_ / 2,
y = current_row_y + _HEADER_PAD_ + 25,
x_align = 'center',
font_size = 28,
text_color = Patterns.BLUE
}
current_row.temp2 = Widget.Text{
x = side_rows_x + _ICON_SIDE_LENGTH_ + _TEMP_SECTION_WIDTH_ / 2,
y = current_row_y + _HEADER_PAD_ + 55,
x_align = 'center',
font_size = 11
}
current_row.label_column = Widget.TextColumn{
x = side_rows_x + _ICON_SIDE_LENGTH_ + _TEMP_SECTION_WIDTH_,
y = current_row_y + _HEADER_PAD_ + 15,
spacing = _SPACING_,
'H',
'P',
'W'
}
current_row.info_column = Widget.TextColumn{
x = side_rows_x + _G_INIT_DATA_.SECTION_WIDTH,
y = current_row_y + _HEADER_PAD_ + 15,
spacing = _SPACING_,
x_align = 'right',
text_color = Patterns.BLUE,
num_rows = 3
}
if i < NUM_ROWS then
current_row.separator = Widget.Line{
p1 = {
x = side_rows_x,
y = current_row_y + _SECTION_HEIGHT_ - 18
},
p2 = {
x = side_rows_x + _G_INIT_DATA_.SECTION_WIDTH,
y = current_row_y + _SECTION_HEIGHT_ - 18
},
line_pattern = Patterns.MID_GREY
}
end
end
end
-- LEFT
local left = {
header = Widget.Header{
x = _G_INIT_DATA_.LEFT_X,
y = _G_INIT_DATA_.TOP_Y,
width = _G_INIT_DATA_.SECTION_WIDTH,
header = 'HOURLY FORECAST'
},
hours = {}
}
create_side_rows(_G_INIT_DATA_.LEFT_X, left.header.bottom_y, left.hours)
-- CENTER
local center = {}
center.header = Widget.Header{
x = _G_INIT_DATA_.CENTER_LEFT_X,
y = _G_INIT_DATA_.TOP_Y,
width = _G_INIT_DATA_.CENTER_WIDTH,
header = 'CURRENT CONDITIONS'
}
center.current_desc = Widget.Text{
x = _G_INIT_DATA_.CENTER_LEFT_X,
y = center.header.bottom_y + 8,
text_color = Patterns.BLUE,
font_size = 24
}
local _CENTER_X_1_ = _G_INIT_DATA_.CENTER_LEFT_X + _G_INIT_DATA_.SECTION_WIDTH * 0.25
local _CENTER_ICON_WIDTH_ = 120
center.icon = Widget.ScaledImage{
x = _CENTER_X_1_ - _CENTER_ICON_WIDTH_ / 2,
y = center.header.bottom_y + 105 - _CENTER_ICON_WIDTH_ / 2,
width = _CENTER_ICON_WIDTH_,
height = _CENTER_ICON_WIDTH_
}
local _CENTER_X_2_ = _G_INIT_DATA_.CENTER_LEFT_X + _G_INIT_DATA_.SECTION_WIDTH * 0.70
local _INFO_Y_ = center.header.bottom_y + 70
center.current_temp = Widget.Text{
x = _CENTER_X_2_,
y = _INFO_Y_,
x_align = 'center',
font_size = 48,
text_color = Patterns.BLUE
}
center.obs_time = Widget.Text{
x = _CENTER_X_2_,
y = _INFO_Y_ + 42,
x_align = 'center',
font_size = 12,
}
center.place = Widget.Text{
x = _CENTER_X_2_,
y = _INFO_Y_ + 66,
x_align = 'center',
font_size = 12,
}
local _COLUMN_PADDING_ = 15
local _CENTER_SPACING_ = _SPACING_ + 7
center.label_column_1 = Widget.TextColumn{
x = _G_INIT_DATA_.CENTER_RIGHT_X,
y = center.header.bottom_y,
spacing = _CENTER_SPACING_,
font_size = 14,
'Feels Like',
'Dewpoint',
'Humidity',
'Sky Coverage',
'Visibility',
'Ceiling',
'Precipitation'
}
center.info_column_1 = Widget.TextColumn{
x = _G_INIT_DATA_.CENTER_RIGHT_X + (_G_INIT_DATA_.SECTION_WIDTH - _COLUMN_PADDING_) / 2,
y = center.header.bottom_y,
x_align = 'right',
text_color = Patterns.BLUE,
spacing = _CENTER_SPACING_,
font_size = 14,
num_rows = 7
}
center.label_column_2 = Widget.TextColumn{
x = _G_INIT_DATA_.CENTER_RIGHT_X + (_G_INIT_DATA_.SECTION_WIDTH + _COLUMN_PADDING_) / 2,
y = center.header.bottom_y,
spacing = _CENTER_SPACING_,
font_size = 14,
'WindSpd',
'WindGust',
'WindDir',
'Pressure',
'Sunrise',
'Sunset',
'Light Rate'
}
center.info_column_2 = Widget.TextColumn{
x = _G_INIT_DATA_.CENTER_RIGHT_X + _G_INIT_DATA_.SECTION_WIDTH,
y = center.header.bottom_y,
x_align = 'right',
text_color = Patterns.BLUE,
spacing = _CENTER_SPACING_,
font_size = 14,
num_rows = 7
}
-- RIGHT
local right = {
header = Widget.Header{
x = _G_INIT_DATA_.RIGHT_X,
y = _G_INIT_DATA_.TOP_Y,
width = _G_INIT_DATA_.SECTION_WIDTH,
header = '8 DAY FORECAST'
},
days = {}
}
create_side_rows(_G_INIT_DATA_.RIGHT_X, right.header.bottom_y, right.days)
Widget = nil
Patterns = nil
_SPACING_ = nil
_HEADER_PAD_ = nil
_ICON_SIDE_LENGTH_ = nil
_TEMP_SECTION_WIDTH_ = nil
_SECTION_HEIGHT_ = nil
_CENTER_X_1_ = nil
_CENTER_ICON_WIDTH_ = nil
_CENTER_X_2_ = nil
_INFO_Y_ = nil
_COLUMN_PADDING_ = nil
_CENTER_SPACING_ = nil
local populate_row = function(current_section, cr, desc, period, icon_path,
temp1, temp2, humidity, pop, wind)
if desc then
Text.set(current_section.desc, cr, Text.trim_to_length(desc, 20))
else
Text.set(current_section.desc, cr, NA)
end
Text.set(current_section.period, cr, period or NA)
ScaledImage.set(current_section.icon, icon_path or NA_IMAGE_PATH)
Text.set(current_section.temp1, cr, temp1 or NA)
Text.set(current_section.temp2, cr, temp2 or NA)
TextColumn.set(current_section.info_column, cr, 1, humidity or NA)
TextColumn.set(current_section.info_column, cr, 2, pop or NA)
TextColumn.set(current_section.info_column, cr, 3, wind or NA)
end
local populate_center = function(center_section, cr, desc, icon_path, temp,
obs_time, place, feels_like, dewpoint, humidity, coverage, visibility, ceiling,
precip, wind_spd, wind_gust_spd, wind_dir, pressure, sunrise, sunset, light)
if desc then
Text.set(center_section.current_desc, cr, Text.trim_to_length(desc, 20))
else
Text.set(center_section.current_desc, cr, NA)
end
ScaledImage.set(center_section.icon, icon_path or NA_IMAGE_PATH)
Text.set(center_section.current_temp, cr, temp or NA)
Text.set(center_section.obs_time, cr, obs_time or NA)
Text.set(center_section.place, cr, place or NA)
local info_column_1 = center_section.info_column_1
TextColumn.set(info_column_1, cr, 1, feels_like or NA)
TextColumn.set(info_column_1, cr, 2, dewpoint or NA)
TextColumn.set(info_column_1, cr, 3, humidity or NA)
TextColumn.set(info_column_1, cr, 4, coverage or NA)
TextColumn.set(info_column_1, cr, 5, visibility or NA)
TextColumn.set(info_column_1, cr, 6, ceiling or NA)
TextColumn.set(info_column_1, cr, 7, precip or NA)
local info_column_2 = center_section.info_column_2
TextColumn.set(info_column_2, cr, 1, wind_spd or NA)
TextColumn.set(info_column_2, cr, 2, wind_gust_spd or NA)
TextColumn.set(info_column_2, cr, 3, wind_dir or NA)
TextColumn.set(info_column_2, cr, 4, pressure or NA)
TextColumn.set(info_column_2, cr, 5, sunrise or NA)
TextColumn.set(info_column_2, cr, 6, sunset or NA)
TextColumn.set(info_column_2, cr, 7, light or NA)
end
local update_interface = function(cr)
local file = Util.read_file(WEATHER_JSON_PATH)
local data = (file ~= '') and Json.decode(file)
if data then
data = data.response.responses
if data[1].success == false then
for i = 1, NUM_ROWS do populate_row(left.hours[i], cr) end
populate_center(center, cr, nil, nil, nil, nil, 'Invalid Location')
for i = 1, NUM_ROWS do populate_row(right.days[i], cr) end
else
-- LEFT
local hourly = data[2].response[1].periods
for i = 1, NUM_ROWS do
local hour_data = hourly[i]
populate_row(
left.hours[i],
cr,
hour_data.weatherPrimary,
hour_data.timestamp and Util.convert_unix_time(hour_data.timestamp, TIME_FORMAT),
hour_data.icon and ICON_DIR_PATH..hour_data.icon,
hour_data.avgTempF and hour_data.avgTempF..'°F',
hour_data.feelslikeF and 'Feels like '..hour_data.feelslikeF..'°F',
hour_data.humidity and hour_data.humidity..' %',
hour_data.pop and hour_data.pop..' %',
hour_data.windSpeedMPH and hour_data.windSpeedMPH..' mph'
)
end
-- CENTER
local current_data = data[1].response
local ob = current_data.ob
local place
if current_data.place then
place = current_data.place.name
if place then place = Util.capitalize_each_word(__string_match(place, '([%w%s]+)/?')) end
local state = current_data.place.state
if state == '' then state = nil end
if place and state then
place = place..', '..__string_upper(state)
elseif place then
local country = current_data.place.country
if country then place = place..', '..__string_upper(country) end
end
end
populate_center(
center,
cr,
ob.weather,
ob.icon and ICON_DIR_PATH..ob.icon,
ob.tempF and ob.tempF..'°F',
ob.timestamp and Util.convert_unix_time(ob.timestamp, TIME_FORMAT),
place,
ob.feelslikeF and ob.feelslikeF..'°F',
ob.dewpointF and ob.dewpointF..'°F',
ob.humidity and ob.humidity..' %',
ob.sky and ob.sky..' %',
ob.visibilityMI and ob.visibilityMI..' mi',
ob.ceilingFT and ob.ceilingFT..' ft',
ob.precipIN and ob.precipIN..' in',
ob.windSpeedMPH and ob.windSpeedMPH..' mph',
ob.windGustMPH and ob.windGustMPH..' mph',
ob.windDirDEG and ob.windDirDEG..' deg',
ob.pressureMB and ob.pressureMB..' mbar',
ob.sunrise and Util.convert_unix_time(ob.sunrise, TIME_FORMAT),
ob.sunset and Util.convert_unix_time(ob.sunset, TIME_FORMAT),
ob.light and ob.light..' %'
)
-- RIGHT
local daily = data[3].response[1].periods
for i = 1, NUM_ROWS do
local day_data = daily[i]
populate_row(
right.days[i],
cr,
day_data.weatherPrimary,
day_data.timestamp and __string_sub(Util.convert_unix_time(
day_data.timestamp, DATE_FORMAT), 1, 3),
day_data.icon and ICON_DIR_PATH..day_data.icon,
day_data.maxTempF and day_data.maxTempF..'°F',
day_data.minTempF and 'Low of '..day_data.minTempF..'°F',
day_data.humidity and day_data.humidity..' %',
day_data.pop and day_data.pop..' %',
day_data.windSpeedMPH and day_data.windSpeedMPH..' mph'
)
end
end
else
for i = 1, NUM_ROWS do
populate_row(left.hours[i], cr)
populate_row(right.days[i], cr)
end
populate_center(center, cr)
end
end
local draw_sections = function(section_group, cr)
for i = 1, NUM_ROWS do
local section = section_group[i]
if i < NUM_ROWS then Line.draw(section.separator, cr) end
Text.draw(section.desc, cr)
Text.draw(section.period, cr)
ScaledImage.draw(section.icon)
Text.draw(section.temp1, cr)
Text.draw(section.temp2, cr)
TextColumn.draw(section.label_column, cr)
TextColumn.draw(section.info_column, cr)
end
end
__os_execute('get_weather.sh')
local update_cycle = WEATHER_UPDATE_INTERVAL
local draw = function(cr, interface, interface_is_changed)
if update_cycle == 0 then __os_execute('get_weather.sh') end
local json_is_recently_updated = (Util.read_file(RECENTLY_UPDATED_PATH, nil, '*l') == 'true')
if json_is_recently_updated then
update_cycle = WEATHER_UPDATE_INTERVAL
Util.write_file(RECENTLY_UPDATED_PATH, 'false')
end
if json_is_recently_updated or interface_is_changed then update_interface(cr) end
update_cycle = update_cycle - 1
if interface == 1 then
-- LEFT
Text.draw(left.header.text, cr)
Line.draw(left.header.underline, cr)
draw_sections(left.hours, cr)
-- CENTER
Text.draw(center.header.text, cr)
Line.draw(center.header.underline, cr)
Text.draw(center.current_desc, cr)
ScaledImage.draw(center.icon)
Text.draw(center.current_temp, cr)
Text.draw(center.obs_time, cr)
Text.draw(center.place, cr)
TextColumn.draw(center.label_column_1, cr)
TextColumn.draw(center.info_column_1, cr)
TextColumn.draw(center.label_column_2, cr)
TextColumn.draw(center.info_column_2, cr)
-- RIGHT
Text.draw(right.header.text, cr)
Line.draw(right.header.underline, cr)
draw_sections(right.days, cr)
end
end
return draw