local _CR = require 'CR' 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 schema = require 'default_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 SECTIONS = 6 local WEATHER_UPDATE_INTERVAL = 900 local WEATHER_PATH = '/tmp/weather.json' local ICON_PATH = CONSTRUCTION_GLOBAL.ABS_PATH .. '/images/weather/' local RECENTLY_UPDATED_PATH = '/tmp/weather_recently_updated' local NA = "N/A" local NA_IMAGE_PATH = ICON_PATH .. 'na.png' --construction params local SPACING = 20 local HEADER_PAD = 20 local ICON_PAD = 20 local ICON_SIDE_LENGTH = 65 local TEMP_SECTION_WIDTH = 140 local SECTION_HEIGHT = HEADER_PAD + ICON_SIDE_LENGTH + 30 local __create_side_section = function(x_offset, y_offset, section_table) for i = 1, SECTIONS do section_table[i] = {} local current_widget = section_table[i] local current_y = y_offset + (i - 1) * SECTION_HEIGHT current_widget.desc = Widget.Text{ x = x_offset, y = current_y, text_color = schema.blue, } current_widget.period = Widget.Text{ x = x_offset + CONSTRUCTION_GLOBAL.SIDE_WIDTH, y = current_y, x_align = 'right', text_color = schema.blue } current_widget.icon = Widget.ScaledImage{ x = x_offset, y = current_y + HEADER_PAD, width = ICON_SIDE_LENGTH, height = ICON_SIDE_LENGTH } current_widget.temp1 = Widget.Text{ x = x_offset + ICON_SIDE_LENGTH + 0.5 * TEMP_SECTION_WIDTH, y = current_y + HEADER_PAD + 20, x_align = 'center', font_size = 28, text_color = schema.blue } current_widget.temp2 = Widget.Text{ x = x_offset + ICON_SIDE_LENGTH + 0.5 * TEMP_SECTION_WIDTH, y = current_y + HEADER_PAD + 50, x_align = 'center', font_size = 11 } current_widget.label_column = Widget.TextColumn{ x = x_offset + ICON_SIDE_LENGTH + TEMP_SECTION_WIDTH, y = current_y + HEADER_PAD + 10, spacing = SPACING, 'H', 'P', 'W' } current_widget.info_column = Widget.TextColumn{ x = x_offset + CONSTRUCTION_GLOBAL.SIDE_WIDTH, y = current_y + HEADER_PAD + 10, spacing = SPACING, x_align = 'right', text_color = schema.blue, num_rows = 3 } if i < SECTIONS then current_widget.divider = Widget.Line{ p1 = { x = x_offset, y = current_y + SECTION_HEIGHT - 18 }, p2 = { x = x_offset + CONSTRUCTION_GLOBAL.SIDE_WIDTH, y = current_y + SECTION_HEIGHT - 18 }, line_pattern = schema.mid_grey } end end end --LEFT local left = { header = Widget.Header{ x = CONSTRUCTION_GLOBAL.LEFT_X, y = CONSTRUCTION_GLOBAL.TOP_Y, width = CONSTRUCTION_GLOBAL.SIDE_WIDTH, header = "HOURLY FORECAST" }, hours = {} } __create_side_section(CONSTRUCTION_GLOBAL.LEFT_X, left.header.bottom_y, left.hours) --CENTER local center = {} center.header = Widget.Header{ x = CONSTRUCTION_GLOBAL.CENTER_X, y = CONSTRUCTION_GLOBAL.TOP_Y, width = CONSTRUCTION_GLOBAL.CENTER_WIDTH, header = "CURRENT CONDITIONS" } center.current_desc = Widget.Text{ x = CONSTRUCTION_GLOBAL.CENTER_X, y = center.header.bottom_y, text_color = schema.blue } local CENTER_SPACING = SPACING + 5 local INFO_Y = center.header.bottom_y + CENTER_SPACING center.icon = Widget.ScaledImage{ x = CONSTRUCTION_GLOBAL.CENTER_X, y = INFO_Y, width = ICON_SIDE_LENGTH, height = ICON_SIDE_LENGTH } local TEXT_1_PAD = 80 local TEXT_1_X = CONSTRUCTION_GLOBAL.CENTER_X + ICON_SIDE_LENGTH + TEXT_1_PAD center.current_temp = Widget.Text{ x = TEXT_1_X, y = INFO_Y + 11, x_align = 'center', font_size = 32, text_color = schema.blue } center.obs_time = Widget.Text{ x = TEXT_1_X, y = INFO_Y + 39, x_align = 'center', font_size = 11, } center.place = Widget.Text{ x = TEXT_1_X, y = INFO_Y + 57, x_align = 'center', font_size = 11, } local COLUMN_WIDTH = 189 local LABEL_COLUMN_1_X = TEXT_1_X + TEXT_1_PAD center.label_column_1 = Widget.TextColumn{ x = LABEL_COLUMN_1_X, y = center.header.bottom_y, spacing = CENTER_SPACING, 'Feels Like', 'Dewpoint', 'Sky Coverage', 'Humidity', 'Precipitation' } center.info_column_1 = Widget.TextColumn{ x = LABEL_COLUMN_1_X + COLUMN_WIDTH, y = center.header.bottom_y, x_align = 'right', text_color = schema.blue, spacing = CENTER_SPACING, num_rows = 5 } local LABEL_COLUMN_2_X = LABEL_COLUMN_1_X + COLUMN_WIDTH + 20 center.label_column_2 = Widget.TextColumn{ x = LABEL_COLUMN_2_X, y = center.header.bottom_y, spacing = CENTER_SPACING, 'WindSpd', 'WindDir', 'Pressure', 'Sunrise', 'Sunset' } center.info_column_2 = Widget.TextColumn{ x = LABEL_COLUMN_2_X + COLUMN_WIDTH, y = center.header.bottom_y, x_align = 'right', text_color = schema.blue, spacing = CENTER_SPACING, num_rows = 5 } --RIGHT local right = { header = Widget.Header{ x = CONSTRUCTION_GLOBAL.RIGHT_X, y = CONSTRUCTION_GLOBAL.TOP_Y, width = CONSTRUCTION_GLOBAL.SIDE_WIDTH, header = "6 DAY FORECAST" }, days = {} } __create_side_section(CONSTRUCTION_GLOBAL.RIGHT_X, right.header.bottom_y, right.days) Widget = nil schema = nil SPACING = nil HEADER_BOTTOM_Y = nil SECTION_HEIGHT = nil INFO_Y = nil TEXT_1_PAD = nil TEXT_1_X = nil COLUMN_WIDTH = nil LABEL_COLUMN_1_X = nil CENTER_SPACING = nil local __populate_section = 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, coverage, humidity, precip, wind, pressure, visibility, sunrise, sunset) 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, coverage or NA) TextColumn.set(info_column_1, cr, 4, humidity or NA) TextColumn.set(info_column_1, cr, 5, precip or NA) local info_column_2 = center_section.info_column_2 TextColumn.set(info_column_2, cr, 1, wind or NA) TextColumn.set(info_column_2, cr, 2, pressure or NA) TextColumn.set(info_column_2, cr, 3, visibility or NA) TextColumn.set(info_column_2, cr, 4, sunrise or NA) TextColumn.set(info_column_2, cr, 5, sunset or NA) end local __update_interface = function(cr) local file = util.read_file(WEATHER_PATH) local data = (file ~= '') and json.decode(file) if data then data = data.response.responses if data[1].success == false then for i = 1, SECTIONS do __populate_section(left.hours[i], cr) end __populate_center(center, cr, nil, nil, nil, nil, 'Invalid Location') for i = 1, SECTIONS do __populate_section(right.days[i], cr) end else --LEFT local hourly = data[2].response[1].periods for i = 1, SECTIONS do local hour_data = hourly[i] __populate_section( 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_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_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.sky and ob.sky..' %', ob.humidity and ob.humidity..' %', ob.precipIN and ob.precipIN..' in', ob.windSpeedMPH and ob.windSpeedMPH..' 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) ) --RIGHT local daily = data[3].response[1].periods for i = 1, SECTIONS do local day_data = daily[i] __populate_section( 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_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, SECTIONS do __populate_section(left.hours[i], cr) end __populate_center(center, cr) for i = 1, SECTIONS do __populate_section(right.days[i], cr) end end end local __draw_sections = function(section_group, cr) for i = 1, SECTIONS do local section = section_group[i] if i < SECTIONS then Line.draw(section.divider, 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 __update_interface(_CR) _CR = nil _OS_EXECUTE('get_weather.sh') local update_cycle = WEATHER_UPDATE_INTERVAL local draw = function(cr, interface, trigger) if update_cycle == 0 then _OS_EXECUTE('get_weather.sh') end local recently_updated = util.read_file(RECENTLY_UPDATED_PATH, nil, '*n') if recently_updated == 1 then update_cycle = WEATHER_UPDATE_INTERVAL util.write_file(RECENTLY_UPDATED_PATH, 0) end if recently_updated == 1 or trigger == 0 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