Diode_exe

marquee issue

Oct 9th, 2025
32
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 52.36 KB | None | 0 0
  1. # Retro Winnipeg Weather Channel
  2. # By probnot, fixed by Diode-exe - Fixed version with improved error handling
  3.  
  4. from tkinter import *
  5. import time
  6. import datetime
  7. import asyncio # for env_canada
  8. import textwrap # used to format forecast text
  9. from env_canada import ECWeather
  10. import feedparser # for RSS feed
  11. import pygame # for background music
  12. import random # for background music
  13. import os # for background music
  14. import re # for word shortener
  15. import signal
  16. import sys
  17.  
  18. prog = "wpg-weather"
  19. ver = "2.2"
  20.  
  21. # Global variables for weather data
  22. real_forecast_time = ""
  23. real_forecast_date = ""
  24. text_forecast = []
  25.  
  26. # DEF clock Updater
  27. def clock():
  28.     try:
  29.         # Get current time in 12-hour format with leading zeros
  30.         current = time.strftime("%I:%M:%S")  
  31.        
  32.         # Update the label
  33.         timeText.config(text=current)
  34.        
  35.         # Schedule next update in 1 second
  36.         root.after(1000, clock)
  37.     except Exception as e:
  38.         debug_msg(f"CLOCK-error: {str(e)}", 1)
  39.         root.after(1000, clock)
  40.    
  41. # DEF main weather pages
  42. def weather_page(PageColour, PageNum):
  43.     try:
  44.         # pull in current seconds and minutes -- to be used to cycle the middle section every 30sec  
  45.         linebreak = ['\n']
  46.  
  47.         PageTotal = 11
  48.  
  49.         if (PageNum == 1):
  50.             # ===================== Screen 1 =====================
  51.             debug_msg(("WEATHER_PAGE-display page " + str(PageNum)),2)            
  52.            
  53.             # get local timezone to show on screen
  54.             local_tz = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo
  55.            
  56.             # weather data with safe access
  57.             try:
  58.                 temp_cur = str(safe_get_weather_value(ec_en_wpg.conditions, "temperature", "value", default="--"))
  59.                 temp_high = str(safe_get_weather_value(ec_en_wpg.conditions, "high_temp", "value", default="--"))
  60.                 temp_low = str(safe_get_weather_value(ec_en_wpg.conditions, "low_temp", "value", default="--"))
  61.                 humidity = str(safe_get_weather_value(ec_en_wpg.conditions, "humidity", "value", default="--"))
  62.                 condition = safe_get_weather_value(ec_en_wpg.conditions, "condition", "value", default="NO DATA")
  63.                 pressure = str(safe_get_weather_value(ec_en_wpg.conditions, "pressure", "value", default="--"))  
  64.                 tendency = safe_get_weather_value(ec_en_wpg.conditions, "tendency", "value", default="STEADY")
  65.                 dewpoint = str(safe_get_weather_value(ec_en_wpg.conditions, "dewpoint", "value", default="--"))
  66.                 uv_index_val = safe_get_weather_value(ec_en_wpg.conditions, "uv_index", "value", default="--")
  67.                 uv_index = str(uv_index_val) if uv_index_val is not None else "--"
  68.                 pop_val = safe_get_weather_value(ec_en_wpg.conditions, "pop", "value", default="--")
  69.                 pop = str(pop_val) if pop_val is not None else "0"
  70.  
  71.                 try:
  72.                     uv_index_val = float(uv_index_val)
  73.                 except (ValueError, TypeError):
  74.                     uv_index_val = None
  75.  
  76.                
  77.                 # check severity of uv index
  78.                 if uv_index_val is not None:
  79.                     if uv_index_val <= 2:
  80.                         uv_cat = "LOW"
  81.                     elif uv_index_val <= 5:
  82.                         uv_cat = "MODERT"
  83.                     elif uv_index_val <= 7:
  84.                         uv_cat = "HIGH"
  85.                     elif uv_index_val <= 10:
  86.                         uv_cat = "V.HIGH"
  87.                     else:
  88.                         uv_cat = "EXTRM"
  89.                 else:
  90.                     uv_cat = ""
  91.                
  92.                 # check if windchill or humidex is present
  93.                 windchill_val = safe_get_weather_value(ec_en_wpg.conditions, "wind_chill", "value", default="--")
  94.                 humidex_val = safe_get_weather_value(ec_en_wpg.conditions, "humidex", "value", default="--")
  95.                
  96.                 if windchill_val not in [None, "--"]:
  97.                     windchildex = "WIND CHILL " + str(windchill_val) + " C"
  98.                 elif humidex_val not in [None, "--"]:
  99.                     windchildex = "HUMIDEX " + str(humidex_val) + " C       "
  100.                 else:
  101.                     windchildex = ""
  102.                
  103.                 # check if there is wind
  104.                 wind_dir_val = safe_get_weather_value(ec_en_wpg.conditions, "wind_dir", "value", default="--")
  105.                 wind_spd_val = safe_get_weather_value(ec_en_wpg.conditions, "wind_speed", "value", default="--")
  106.                
  107.                 if wind_dir_val is not None and wind_spd_val is not None:
  108.                     windstr = "WIND " + str(wind_dir_val) + " " + str(wind_spd_val) + " KMH"
  109.                 else:
  110.                     windstr = "NO WIND"
  111.                        
  112.                 # check visibility
  113.                 visibility_val = safe_get_weather_value(ec_en_wpg.conditions, "visibility", "value", default="--")
  114.                 if visibility_val is not None:
  115.                     visibstr = "VISBY " + str(visibility_val).rjust(5," ") + " KM         "
  116.                 else:
  117.                     visibstr = "VISBY    -- KM         "
  118.            
  119.             except Exception as e:
  120.                 debug_msg(f"WEATHER_PAGE-error getting weather data: {str(e)}", 1)
  121.                 # Set default values
  122.                 temp_cur = temp_high = temp_low = humidity = dewpoint = pressure = uv_index = pop = "--"
  123.                 condition = "NO DATA AVAILABLE"
  124.                 tendency = "STEADY"
  125.                 windchildex = windstr = visibstr = ""
  126.                 uv_cat = ""
  127.          
  128.             # create 8 lines of text    
  129.             s1 = ("WINNIPEG " + real_forecast_time + " " + str(local_tz) + "  " + real_forecast_date.upper()).center(35," ")
  130.             s2 = "TEMP  " + temp_cur.rjust(5," ") + " C                "
  131.             s2 = s2[0:24] + " HIGH " + temp_high.rjust(3," ") + " C"
  132.             s3 = word_short(condition,24) + "                         "
  133.             s3 = s3[0:24] + "  LOW " + temp_low.rjust(3," ") + " C"
  134.             s4 = ("CHANCE OF PRECIP. " + pop + " %").center(35," ")
  135.             s5 = "HUMID  " + humidity.rjust(5," ") + " %         "
  136.             s5 = s5[0:18] + windstr.rjust(17," ")
  137.             s6 = visibstr[0:18] + windchildex.rjust(17," ")
  138.             s7 = "DEW   " + dewpoint.rjust(5," ") + " C         "
  139.             s7 = s7[0:18] + ("UV INDEX " + uv_index + " " + uv_cat).rjust(17," ")
  140.             s8 = ("PRESSURE " + pressure + " KPA AND " + tendency.upper()).center(35," ")
  141.  
  142.         elif (PageNum == 2):
  143.             # ===================== Screen 2 =====================
  144.             debug_msg(("WEATHER_PAGE-display page " + str(PageNum)),2)  
  145.  
  146.             try:
  147.                 # pull text forecasts from env_canada with safe access
  148.                 current_summary = safe_get_weather_value(ec_en_wpg.conditions, "text_summary", "value", "NO FORECAST AVAILABLE")
  149.                 wsum_day1 = textwrap.wrap(current_summary.upper(), 35)
  150.                
  151.                 wsum_day2 = wsum_day3 = wsum_day4 = wsum_day5 = wsum_day6 = []
  152.                
  153.                 try:
  154.                     if hasattr(ec_en_wpg, 'daily_forecasts') and len(ec_en_wpg.daily_forecasts) > 1:
  155.                         for i, day_offset in enumerate([1,2,3,4,5], 1):
  156.                             if len(ec_en_wpg.daily_forecasts) > day_offset:
  157.                                 period = ec_en_wpg.daily_forecasts[day_offset].get("period", "DAY " + str(i+1))
  158.                                 summary = ec_en_wpg.daily_forecasts[day_offset].get("text_summary", "NO DATA")
  159.                                 forecast_text = textwrap.wrap((period + ".." + summary).upper(), 35)
  160.                                 if i == 1: wsum_day2 = forecast_text
  161.                                 elif i == 2: wsum_day3 = forecast_text
  162.                                 elif i == 3: wsum_day4 = forecast_text
  163.                                 elif i == 4: wsum_day5 = forecast_text
  164.                                 elif i == 5: wsum_day6 = forecast_text
  165.                 except Exception as e:
  166.                     debug_msg(f"WEATHER_PAGE-error getting daily forecasts: {str(e)}", 1)
  167.                
  168.                 # build text_forecast string
  169.                 global text_forecast
  170.                 text_forecast = wsum_day1 + linebreak + wsum_day2 + linebreak + wsum_day3 + linebreak + wsum_day4 + linebreak + wsum_day5 + linebreak + wsum_day6
  171.            
  172.             except Exception as e:
  173.                 debug_msg(f"WEATHER_PAGE-error building forecasts: {str(e)}", 1)
  174.                 text_forecast = ["NO FORECAST DATA AVAILABLE"]
  175.        
  176.             # create 8 lines of text
  177.             s1 = "WINNIPEG CITY FORECAST".center(35," ")
  178.             s2 = (text_forecast[0]).center(35," ") if len(text_forecast) >= 1 else " "
  179.             s3 = (text_forecast[1]).center(35," ") if len(text_forecast) >= 2 else " "
  180.             s4 = (text_forecast[2]).center(35," ") if len(text_forecast) >= 3 else " "
  181.             s5 = (text_forecast[3]).center(35," ") if len(text_forecast) >= 4 else " "
  182.             s6 = (text_forecast[4]).center(35," ") if len(text_forecast) >= 5 else " "
  183.             s7 = (text_forecast[5]).center(35," ") if len(text_forecast) >= 6 else " "
  184.             s8 = (text_forecast[6]).center(35," ") if len(text_forecast) >= 7 else " "
  185.  
  186.         elif (PageNum == 3):
  187.             # ===================== Screen 3 =====================
  188.             debug_msg(("WEATHER_PAGE-display page " + str(PageNum)),2)
  189.            
  190.             # create 8 lines of text
  191.             s1 = "WINNIPEG CITY FORECAST CONT'D".center(35," ")
  192.             s2 = (text_forecast[7]).center(35," ") if len(text_forecast) >= 8 else " "
  193.             s3 = (text_forecast[8]).center(35," ") if len(text_forecast) >= 9 else " "
  194.             s4 = (text_forecast[9]).center(35," ") if len(text_forecast) >= 10 else " "
  195.             s5 = (text_forecast[10]).center(35," ") if len(text_forecast) >= 11 else " "
  196.             s6 = (text_forecast[11]).center(35," ") if len(text_forecast) >= 12 else " "
  197.             s7 = (text_forecast[12]).center(35," ") if len(text_forecast) >= 13 else " "
  198.             s8 = (text_forecast[13]).center(35," ") if len(text_forecast) >= 14 else " "
  199.  
  200.         elif (PageNum == 4):
  201.             # ===================== Screen 4 =====================
  202.             # check if this page is needed
  203.             if len(text_forecast) <= 14:
  204.                 debug_msg(("WEATHER_PAGE-display page " + str(PageNum) + " skipped!"),2)
  205.                 PageNum = PageNum + 1 #skip this page
  206.                 if (PageColour == "#00006D"): # blue
  207.                     PageColour = "#6D0000" # red
  208.                 else:
  209.                     PageColour = "#00006D" # blue
  210.             else:
  211.                 debug_msg(("WEATHER_PAGE-display page " + str(PageNum)),2)  
  212.            
  213.                 # create 8 lines of text      
  214.                 s1 = "WINNIPEG CITY FORECAST CONT'D".center(35," ")
  215.                 s2 = (text_forecast[14]).center(35," ") if len(text_forecast) >= 15 else " "      
  216.                 s3 = (text_forecast[15]).center(35," ") if len(text_forecast) >= 16 else " "        
  217.                 s4 = (text_forecast[16]).center(35," ") if len(text_forecast) >= 17 else " "
  218.                 s5 = (text_forecast[17]).center(35," ") if len(text_forecast) >= 18 else " "
  219.                 s6 = (text_forecast[18]).center(35," ") if len(text_forecast) >= 19 else " "
  220.                 s7 = (text_forecast[19]).center(35," ") if len(text_forecast) >= 20 else " "
  221.                 s8 = (text_forecast[20]).center(35," ") if len(text_forecast) >= 21 else " "                  
  222.    
  223.         elif (PageNum == 5):
  224.             # ===================== Screen 5 =====================
  225.             debug_msg(("WEATHER_PAGE-display page " + str(PageNum)),2)        
  226.      
  227.             # weather data with safe access
  228.             # current temperature
  229.             temp_cur = str(safe_get_weather_value(ec_en_wpg.conditions, "temperature", "value", default="--"))
  230.  
  231.             # forecast highs/lows for today
  232.             # get today’s forecast from daily_forecasts
  233.             if hasattr(ec_en_wpg, "daily_forecasts") and len(ec_en_wpg.daily_forecasts) > 0:
  234.                 today_forecast = ec_en_wpg.daily_forecasts[0]
  235.                 temp_high = str(safe_get_weather_value(today_forecast, "high_temp", "value", default="--"))
  236.                 temp_low  = str(safe_get_weather_value(today_forecast, "low_temp", "value", default="--"))
  237.             else:
  238.                 temp_high = temp_low = "--"
  239.  
  240.  
  241.             # yesterday’s temps
  242.             temp_yest_high_val = safe_get_weather_value(ec_en_wpg.observations.get("yesterday", {}), "high_temp", "value", default="--")
  243.             temp_yest_high = safe_round(temp_yest_high_val)
  244.  
  245.             temp_yest_low_val = safe_get_weather_value(ec_en_wpg.observations.get("yesterday", {}), "low_temp", "value", default="--")
  246.             temp_yest_low = safe_round(temp_yest_low_val)
  247.  
  248.             # normal temps
  249.             temp_norm_high = str(safe_get_weather_value(ec_en_wpg.observations.get("normals", {}), "high_temp", "value", default="--"))
  250.             temp_norm_low  = str(safe_get_weather_value(ec_en_wpg.observations.get("normals", {}), "low_temp", "value", default="--"))
  251.  
  252.  
  253.             # create 8 lines of text  
  254.             s1 = ("TEMPERATURE STATISTICS FOR WINNIPEG").center(35," ")
  255.             s2 = "       CURRENT " + temp_cur.rjust(5," ") + " C  "
  256.             s3 = ""
  257.             s4 = "                 LOW    HIGH"
  258.             s5 = "        TODAY   " + temp_low.rjust(3," ") + " C  " + temp_high.rjust(3," ") + " C"
  259.             s6 = "    YESTERDAY   " + temp_yest_low.rjust(3," ") + " C  " + temp_yest_high.rjust(3," ") + " C"
  260.             s7 = "       NORMAL   " + temp_norm_low.rjust(3," ") + " C  " + temp_norm_high.rjust(3," ") + " C"
  261.             s8 = ""
  262.    
  263.         elif (PageNum == 6):    
  264.             # ===================== Screen 6 =====================
  265.             debug_msg(("WEATHER_PAGE-display page " + str(PageNum)),2)
  266.  
  267.             # Regional temperatures with safe access
  268.             temp_brn = str(safe_get_weather_value(ec_en_brn.conditions, "temperature", "value", default="--"))
  269.             temp_thm = str(safe_get_weather_value(ec_en_thm.conditions, "temperature", "value", default="--"))
  270.             temp_tps = str(safe_get_weather_value(ec_en_tps.conditions, "temperature", "value", default="--"))    
  271.             temp_fln = str(safe_get_weather_value(ec_en_fln.conditions, "temperature", "value", default="--"))  
  272.             temp_chu = str(safe_get_weather_value(ec_en_chu.conditions, "temperature", "value", default="--"))
  273.             temp_ken = str(safe_get_weather_value(ec_en_ken.conditions, "temperature", "value", default="--"))  
  274.             temp_tby = str(safe_get_weather_value(ec_en_tby.conditions, "temperature", "value", default="--"))  
  275.  
  276.             cond_brn = safe_get_weather_value(ec_en_brn.conditions, "condition", "value", default="NO DATA")
  277.             cond_thm = safe_get_weather_value(ec_en_thm.conditions, "condition", "value", default="NO DATA")
  278.             cond_tps = safe_get_weather_value(ec_en_tps.conditions, "condition", "value", default="NO DATA")
  279.             cond_fln = safe_get_weather_value(ec_en_fln.conditions, "condition", "value", default="NO DATA")
  280.             cond_chu = safe_get_weather_value(ec_en_chu.conditions, "condition", "value", default="NO DATA")
  281.             cond_ken = safe_get_weather_value(ec_en_ken.conditions, "condition", "value", default="NO DATA")
  282.             cond_tby = safe_get_weather_value(ec_en_tby.conditions, "condition", "value", default="NO DATA")
  283.            
  284.             # create 8 lines of text  
  285.             s1=(real_forecast_date.upper()).center(35," ")
  286.             s2="BRANDON     " + temp_brn.rjust(5," ") + " C    "
  287.             s2= s2[0:20] + word_short(cond_brn,13)[0:13]
  288.             s3="THE PAS     " + temp_tps.rjust(5," ") + " C     "
  289.             s3= s3[0:20] + word_short(cond_tps,13)[0:13]
  290.             s4="FLIN FLON   " + temp_fln.rjust(5," ") + " C     "
  291.             s4= s4[0:20] + word_short(cond_fln,13)[0:13]
  292.             s5="THOMPSON    " + temp_thm.rjust(5," ") + " C     "
  293.             s5= s5[0:20] + word_short(cond_thm,13)[0:13]
  294.             s6="CHURCHILL   " + temp_chu.rjust(5," ") + " C     "
  295.             s6= s6[0:20] + word_short(cond_chu,13)[0:13]
  296.             s7="KENORA      " + temp_ken.rjust(5," ") + " C     "
  297.             s7= s7[0:20] + word_short(cond_ken,13)[0:13]
  298.             s8="THUNDER BAY " + temp_tby.rjust(5," ") + " C     "
  299.             s8= s8[0:20] + word_short(cond_tby,13)[0:13]
  300.  
  301.         elif (PageNum == 7):
  302.             # ===================== Screen 7 =====================
  303.             debug_msg(("WEATHER_PAGE-display page " + str(PageNum)),2)
  304.            
  305.             # Western Canada temperatures with safe access
  306.             temp_vic = str(safe_get_weather_value(ec_en_vic.conditions, "temperature", "value", default="--"))
  307.             temp_van = str(safe_get_weather_value(ec_en_van.conditions, "temperature", "value", default="--"))
  308.             temp_edm = str(safe_get_weather_value(ec_en_edm.conditions, "temperature", "value", default="--"))    
  309.             temp_cal = str(safe_get_weather_value(ec_en_cal.conditions, "temperature", "value", default="--"))  
  310.             temp_ssk = str(safe_get_weather_value(ec_en_ssk.conditions, "temperature", "value", default="--"))  
  311.             temp_reg = str(safe_get_weather_value(ec_en_reg.conditions, "temperature", "value", default="--"))  
  312.             temp_wht = str(safe_get_weather_value(ec_en_wht.conditions, "temperature", "value", default="--"))
  313.  
  314.             cond_vic = safe_get_weather_value(ec_en_vic.conditions, "condition", "value", default="NO DATA")
  315.             cond_van = safe_get_weather_value(ec_en_van.conditions, "condition", "value", default="NO DATA")
  316.             cond_edm = safe_get_weather_value(ec_en_edm.conditions, "condition", "value", default="NO DATA")
  317.             cond_cal = safe_get_weather_value(ec_en_cal.conditions, "condition", "value", default="NO DATA")
  318.             cond_ssk = safe_get_weather_value(ec_en_ssk.conditions, "condition", "value", default="NO DATA")
  319.             cond_reg = safe_get_weather_value(ec_en_reg.conditions, "condition", "value", default="NO DATA")
  320.             cond_wht = safe_get_weather_value(ec_en_wht.conditions, "condition", "value", default="NO DATA")
  321.            
  322.             # create 8 lines of text    
  323.             s1=(real_forecast_date.upper()).center(35," ")
  324.             s2="VICTORIA    " + temp_vic.rjust(5," ") + " C     "
  325.             s2= s2[0:20] + word_short(cond_vic,13)[0:13]
  326.             s3="VANCOUVER   " + temp_van.rjust(5," ") + " C     "
  327.             s3= s3[0:20] + word_short(cond_van,13)[0:13]
  328.             s4="EDMONTON    " + temp_edm.rjust(5," ") + " C     "
  329.             s4= s4[0:20] + word_short(cond_edm,13)[0:13]
  330.             s5="CALGARY     " + temp_cal.rjust(5," ") + " C     "
  331.             s5= s5[0:20] + word_short(cond_cal,13)[0:13]
  332.             s6="SASKATOON   " + temp_ssk.rjust(5," ") + " C     "
  333.             s6= s6[0:20] + word_short(cond_ssk,13)[0:13]
  334.             s7="REGINA      " + temp_reg.rjust(5," ") + " C     "
  335.             s7= s7[0:20] + word_short(cond_reg,13)[0:13]
  336.             s8="WHITEHORSE  " + temp_wht.rjust(5," ") + " C     "
  337.             s8= s8[0:20] + word_short(cond_wht,13)[0:13]
  338.                  
  339.         elif (PageNum == 8):  
  340.             # ===================== Screen 8 =====================
  341.             debug_msg(("WEATHER_PAGE-display page " + str(PageNum)),2)
  342.            
  343.             # Eastern Canada temperatures with safe access
  344.             temp_tor = str(safe_get_weather_value(ec_en_tor.conditions, "temperature", "value", default="--"))
  345.             temp_otw = str(safe_get_weather_value(ec_en_otw.conditions, "temperature", "value", default="--"))
  346.             temp_qbc = str(safe_get_weather_value(ec_en_qbc.conditions, "temperature", "value", default="--"))    
  347.             temp_mtl = str(safe_get_weather_value(ec_en_mtl.conditions, "temperature", "value", default="--"))  
  348.             temp_frd = str(safe_get_weather_value(ec_en_frd.conditions, "temperature", "value", default="--"))  
  349.             temp_hal = str(safe_get_weather_value(ec_en_hal.conditions, "temperature", "value", default="--"))  
  350.             temp_stj = str(safe_get_weather_value(ec_en_stj.conditions, "temperature", "value", default="--"))
  351.  
  352.             cond_tor = safe_get_weather_value(ec_en_tor.conditions, "condition", "value", default="NO DATA")
  353.             cond_otw = safe_get_weather_value(ec_en_otw.conditions, "condition", "value", default="NO DATA")
  354.             cond_qbc = safe_get_weather_value(ec_en_qbc.conditions, "condition", "value", default="NO DATA")
  355.             cond_mtl = safe_get_weather_value(ec_en_mtl.conditions, "condition", "value", default="NO DATA")
  356.             cond_frd = safe_get_weather_value(ec_en_frd.conditions, "condition", "value", default="NO DATA")
  357.             cond_hal = safe_get_weather_value(ec_en_hal.conditions, "condition", "value", default="NO DATA")
  358.             cond_stj = safe_get_weather_value(ec_en_stj.conditions, "condition", "value", default="NO DATA")
  359.            
  360.             # create 8 lines of text    
  361.             s1=(real_forecast_date.upper()).center(35," ")
  362.             s2="TORONTO     " + temp_tor.rjust(5," ") + " C    "
  363.             s2= s2[0:20] + word_short(cond_tor,13)[0:13]
  364.             s3="OTTAWA      " + temp_otw.rjust(5," ") + " C     "
  365.             s3= s3[0:20] + word_short(cond_otw,13)[0:13]
  366.             s4="QUEBEC CITY " + temp_qbc.rjust(5," ") + " C     "
  367.             s4= s4[0:20] + word_short(cond_qbc,13)[0:13]
  368.             s5="MONTREAL    " + temp_mtl.rjust(5," ") + " C     "
  369.             s5= s5[0:20] + word_short(cond_mtl,13)[0:13]
  370.             s6="FREDERICTON " + temp_frd.rjust(5," ") + " C     "
  371.             s6= s6[0:20] + word_short(cond_frd,13)[0:13]
  372.             s7="HALIFAX     " + temp_hal.rjust(5," ") + " C     "
  373.             s7= s7[0:20] + word_short(cond_hal,13)[0:13]
  374.             s8="ST.JOHN'S   " + temp_stj.rjust(5," ") + " C     "
  375.             s8= s8[0:20] + word_short(cond_stj,13)[0:13]
  376.    
  377.         elif (PageNum == 9):
  378.             # ===================== Screen 9 =====================
  379.             debug_msg(("WEATHER_PAGE-display page " + str(PageNum)), 2)
  380.  
  381.             # get local timezone to show on screen
  382.             local_tz = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo
  383.  
  384.             try:
  385.                 # convert hourly forecast data with safe access
  386.                 if hasattr(ec_en_wpg, 'hourly_forecasts') and len(ec_en_wpg.hourly_forecasts) >= 13:
  387.                     hrly_period = []
  388.                     hrly_temp = []
  389.                     hrly_cond = []
  390.  
  391.                     for i in [0, 2, 4, 6, 8, 10, 12]:
  392.                         if i < len(ec_en_wpg.hourly_forecasts):
  393.                             forecast = ec_en_wpg.hourly_forecasts[i]
  394.  
  395.                             # Safely get period
  396.                             period = forecast.get("period", datetime.datetime.now())
  397.                             if not isinstance(period, datetime.datetime):
  398.                                 period = datetime.datetime.now()
  399.                             hrly_period.append(period)
  400.  
  401.                             # Safely get temperature and normalize to string
  402.                             temp = forecast.get("temperature", None)
  403.                             if temp is None or temp == "--":
  404.                                 temp_str = "--"
  405.                             else:
  406.                                 try:
  407.                                     temp_str = str(round(float(temp)))
  408.                                 except:
  409.                                     temp_str = "--"
  410.                             hrly_temp.append(temp_str)
  411.  
  412.                             # Safely get condition
  413.                             condition = forecast.get("condition", "NO DATA")
  414.                             hrly_cond.append(str(condition))
  415.                         else:
  416.                             # Fill missing data
  417.                             hrly_period.append(datetime.datetime.now())
  418.                             hrly_temp.append("--")
  419.                             hrly_cond.append("NO DATA")
  420.  
  421.                 else:
  422.                     # Fallback data
  423.                     hrly_period = [datetime.datetime.now()] * 7
  424.                     hrly_temp = ["--"] * 7
  425.                     hrly_cond = ["NO DATA"] * 7
  426.  
  427.                 # convert period to local time
  428.                 hrly_period_local = []
  429.                 for period in hrly_period:
  430.                     try:
  431.                         hrly_period_local.append(period.astimezone())
  432.                     except:
  433.                         hrly_period_local.append(datetime.datetime.now())
  434.  
  435.                 # Shorten condition text
  436.                 hrly_cond = [word_short(cond, 13) for cond in hrly_cond]
  437.  
  438.             except Exception as e:
  439.                 debug_msg(f"WEATHER_PAGE-error getting hourly forecast: {str(e)}", 1)
  440.                 # Fallback data
  441.                 hrly_period_local = [datetime.datetime.now()] * 7
  442.                 hrly_temp = ["--"] * 7
  443.                 hrly_cond = ["NO DATA"] * 7
  444.  
  445.             # create 8 lines of text
  446.             s1 = "WINNIPEG HOURLY FORECAST".center(35, " ")
  447.             s2 = f"{hrly_period_local[0].strftime('%I:%M %p').lstrip('0').rjust(8)} {str(local_tz)}  {hrly_temp[0].rjust(3)} C  {hrly_cond[0][0:13]}"
  448.             s3 = f"{hrly_period_local[1].strftime('%I:%M %p').lstrip('0').rjust(8)} {str(local_tz)}  {hrly_temp[1].rjust(3)} C  {hrly_cond[1][0:13]}"
  449.             s4 = f"{hrly_period_local[2].strftime('%I:%M %p').lstrip('0').rjust(8)} {str(local_tz)}  {hrly_temp[2].rjust(3)} C  {hrly_cond[2][0:13]}"
  450.             s5 = f"{hrly_period_local[3].strftime('%I:%M %p').lstrip('0').rjust(8)} {str(local_tz)}  {hrly_temp[3].rjust(3)} C  {hrly_cond[3][0:13]}"
  451.             s6 = f"{hrly_period_local[4].strftime('%I:%M %p').lstrip('0').rjust(8)} {str(local_tz)}  {hrly_temp[4].rjust(3)} C  {hrly_cond[4][0:13]}"
  452.             s7 = f"{hrly_period_local[5].strftime('%I:%M %p').lstrip('0').rjust(8)} {str(local_tz)}  {hrly_temp[5].rjust(3)} C  {hrly_cond[5][0:13]}"
  453.             s8 = f"{hrly_period_local[6].strftime('%I:%M %p').lstrip('0').rjust(8)} {str(local_tz)}  {hrly_temp[6].rjust(3)} C  {hrly_cond[6][0:13]}"
  454.  
  455.         elif (PageNum == 10):
  456.   # ===================== Screen 10 =====================
  457.             debug_msg(f"WEATHER_PAGE-display page {PageNum}", 2)
  458.  
  459.             def get_daily_pop(ec_obj):
  460.                 """
  461.                Safely get the probability of precipitation (POP) from the first daily forecast.
  462.                Returns a string with % or '-- %' if data missing.
  463.                """
  464.                 try:
  465.                     if hasattr(ec_obj, "daily_forecasts") and len(ec_obj.daily_forecasts) > 0:
  466.                         pop_val = safe_get_weather_value(ec_obj.daily_forecasts[0], "pop", "value", default="--")
  467.                         return f"{pop_val} %" if pop_val is not None else "-- %"
  468.                 except Exception as e:
  469.                     debug_msg(f"get_daily_pop error for {ec_obj}: {e}", 2)
  470.                 return "-- %"
  471.  
  472.             # Get today’s POP for all stations
  473.             wpg_precip = get_daily_pop(ec_en_wpg)
  474.             brn_precip = get_daily_pop(ec_en_brn)
  475.             tps_precip = get_daily_pop(ec_en_tps)
  476.             fln_precip = get_daily_pop(ec_en_fln)
  477.             thm_precip = get_daily_pop(ec_en_thm)
  478.             chu_precip = get_daily_pop(ec_en_chu)
  479.  
  480.             # Yesterday's precipitation — fallback, since current ECWeather version may not provide
  481.             yest_precip_val = safe_get_weather_value(ec_en_wpg.conditions, "precip_yesterday", "value", default=None)
  482.             yest_precip = f"{yest_precip_val} MM" if yest_precip_val is not None else "-- MM"
  483.  
  484.             # Create 8 lines of text
  485.             s1 = "MANITOBA PRECIPITATION FORECAST".center(35, " ")
  486.             s2 = f"    TODAY WINNIPEG  {wpg_precip.rjust(5,' ')}"
  487.             s3 = f"          BRANDON   {brn_precip.rjust(5,' ')}"
  488.             s4 = f"          THE PAS   {tps_precip.rjust(5,' ')}"
  489.             s5 = f"          FLIN FLON {fln_precip.rjust(5,' ')}"
  490.             s6 = f"          THOMPSON  {thm_precip.rjust(5,' ')}"
  491.             s7 = f"          CHURCHILL {chu_precip.rjust(5,' ')}"
  492.             s8 = f" PREV DAY WINNIPEG  {yest_precip.rjust(7,' ')}"
  493.  
  494.  
  495.         elif (PageNum == 11):    
  496.             # ===================== Screen 11 =====================
  497.             debug_msg(("WEATHER_PAGE-display page " + str(PageNum)),2)        
  498.          
  499.             # create 8 lines of text
  500.             s1 = "==========CHANNEL LISTING=========="
  501.             s2 = "  2 SIMPSNS  13.1 CITY    50 SECUR"    
  502.             s3 = "3.1 CBC FR.    14 90sTV   54 COMEDY"
  503.             s4 = "  6 60s/70s    16 TOONS   61 MUSIC"        
  504.             s5 = "6.1 CBC        22 GLOBAL  64 WEATHR"
  505.             s6 = "7.1 CTV        24 80sTV"
  506.             s7 = "9.1 GLOBAL   35.1 FAITH"
  507.             s8 = " 10 CBC        45 CHROMECAST"
  508.  
  509.         else:
  510.             # Default fallback page
  511.             s1 = s2 = s3 = s4 = s5 = s6 = s7 = s8 = ""
  512.  
  513.         # create the canvas for middle page text
  514.         try:
  515.             weather = Canvas(root, height=310, width=720, bg=PageColour)
  516.             weather.place(x=0, y=85)
  517.             weather.config(highlightbackground=PageColour)
  518.            
  519.             # place the 8 lines of text
  520.             weather.create_text(80, 17, anchor='nw', text=s1, font=('VCR OSD Mono', 21, "bold"), fill="white")
  521.             weather.create_text(80, 60, anchor='nw', text=s2, font=('VCR OSD Mono', 21,), fill="white")
  522.             weather.create_text(80, 95, anchor='nw', text=s3, font=('VCR OSD Mono', 21,), fill="white")
  523.             weather.create_text(80, 130, anchor='nw', text=s4, font=('VCR OSD Mono', 21,), fill="white")
  524.             weather.create_text(80, 165, anchor='nw', text=s5, font=('VCR OSD Mono', 21,), fill="white")
  525.             weather.create_text(80, 200, anchor='nw', text=s6, font=('VCR OSD Mono', 21,), fill="white")
  526.             weather.create_text(80, 235, anchor='nw', text=s7, font=('VCR OSD Mono', 21,), fill="white")
  527.             weather.create_text(80, 270, anchor='nw', text=s8, font=('VCR OSD Mono', 21,), fill="white")
  528.         except Exception as e:
  529.             debug_msg(f"WEATHER_PAGE-error creating canvas: {str(e)}", 1)
  530.        
  531.         # Toggle Page Colour between Red & Blue
  532.         if (PageColour == "#00006D"): # blue
  533.             PageColour = "#6D0000" # red
  534.         else:
  535.             PageColour = "#00006D" # blue
  536.            
  537.         # Increment Page Number or Reset
  538.         if (PageNum < PageTotal):
  539.             PageNum = PageNum + 1
  540.         elif (PageNum >= PageTotal):
  541.             PageNum = 1
  542.        
  543.         root.after(20000, weather_page, PageColour, PageNum) # re-run every 20sec from program launch
  544.        
  545.     except Exception as e:
  546.         debug_msg(f"WEATHER_PAGE-critical error: {str(e)}", 1)
  547.         # Continue with next page anyway
  548.         if (PageColour == "#00006D"):
  549.             PageColour = "#6D0000"
  550.         else:
  551.             PageColour = "#00006D"
  552.         if (PageNum < 11):
  553.             PageNum = PageNum + 1
  554.         else:
  555.             PageNum = 1
  556.         root.after(20000, weather_page, PageColour, PageNum)
  557.  
  558. # DEF safe weather data access
  559. def safe_get_weather_value(weather_obj, *keys, default="NO DATA"):
  560.     try:
  561.         data = weather_obj
  562.         for key in keys:
  563.             if isinstance(data, dict) and key in data:
  564.                 data = data[key]
  565.             else:
  566.                 return default
  567.         return data if data is not None else default
  568.     except Exception as e:
  569.         debug_msg(f"SAFE_GET_WEATHER_VALUE-error accessing {keys}: {str(e)}", 2)
  570.         return default
  571.    
  572. #DEF Safe round so that TypeErrors don't occur in the assignments
  573. def safe_round(value):
  574.     try:
  575.         return str(round(float(value)))
  576.     except (TypeError, ValueError):
  577.         return "--"
  578.  
  579. # DEF update weather for all cities with improved error handling
  580. async def weather_update_async(group):
  581.     """Async weather update with proper error handling and timeouts"""
  582.     global real_forecast_time
  583.     global real_forecast_date
  584.  
  585.     # used to calculate update time
  586.     t1 = datetime.datetime.now().timestamp()
  587.     timechk = t1 - updt_tstp[group] if group > 0 else 1801  # Force update for group 0
  588.    
  589.     if (timechk > 1800) or (group == 0):
  590.         debug_msg(f"WEATHER_UPDATE_ASYNC-starting update for group {group}", 1)
  591.        
  592.         async def update_single_station(station, name, timeout=15):
  593.             """Update a single weather station with timeout"""
  594.             try:
  595.                 debug_msg(f"WEATHER_UPDATE_ASYNC-updating {name}", 2)
  596.                 await asyncio.wait_for(station.update(), timeout=timeout)
  597.                 debug_msg(f"WEATHER_UPDATE_ASYNC-{name} updated successfully", 2)
  598.                 return True
  599.             except asyncio.TimeoutError:
  600.                 debug_msg(f"WEATHER_UPDATE_ASYNC-{name} timed out after {timeout}s", 1)
  601.                 return False
  602.             except Exception as e:
  603.                 debug_msg(f"WEATHER_UPDATE_ASYNC-{name} error: {str(e)}", 1)
  604.                 return False
  605.  
  606.         try:
  607.             if (group == 0 or group == 1):
  608.                 debug_msg("WEATHER_UPDATE_ASYNC-updating Manitoba/Regional stations", 1)
  609.                 stations = [
  610.                     (ec_en_wpg, "Winnipeg"),
  611.                     (ec_en_brn, "Brandon"),
  612.                     (ec_en_thm, "Thompson"),
  613.                     (ec_en_tps, "The Pas"),
  614.                     (ec_en_fln, "Flin Flon"),
  615.                     (ec_en_chu, "Churchill"),
  616.                     (ec_en_ken, "Kenora"),
  617.                     (ec_en_tby, "Thunder Bay")
  618.                 ]
  619.                
  620.                 for station, name in stations:
  621.                     await update_single_station(station, name)
  622.                     await asyncio.sleep(0.5)  # Small delay between requests
  623.                
  624.                 # Update time strings
  625.                 real_forecast_time = time.strftime("%I %p").lstrip("0")
  626.                 if real_forecast_time == "12 PM":
  627.                     real_forecast_time = "NOON"
  628.                 real_forecast_date = datetime.datetime.now().strftime("%a %b %d/%Y")
  629.                
  630.                 if group == 0:
  631.                     for i in range(1, 4):
  632.                         updt_tstp[i] = datetime.datetime.now().timestamp()
  633.                 else:
  634.                     updt_tstp[group] = datetime.datetime.now().timestamp()
  635.            
  636.             if (group == 0 or group == 2):
  637.                 debug_msg("WEATHER_UPDATE_ASYNC-updating Western Canada stations", 1)
  638.                 stations = [
  639.                     (ec_en_vic, "Victoria"),
  640.                     (ec_en_van, "Vancouver"),
  641.                     (ec_en_edm, "Edmonton"),
  642.                     (ec_en_cal, "Calgary"),
  643.                     (ec_en_ssk, "Saskatoon"),
  644.                     (ec_en_reg, "Regina"),
  645.                     (ec_en_wht, "Whitehorse")
  646.                 ]
  647.                
  648.                 for station, name in stations:
  649.                     await update_single_station(station, name)
  650.                     await asyncio.sleep(0.5)
  651.                
  652.                 real_forecast_date = datetime.datetime.now().strftime("%a %b %d/%Y")
  653.                 if group != 0:
  654.                     updt_tstp[group] = datetime.datetime.now().timestamp()
  655.        
  656.             if (group == 0 or group == 3):
  657.                 debug_msg("WEATHER_UPDATE_ASYNC-updating Eastern Canada stations", 1)
  658.                 stations = [
  659.                     (ec_en_tor, "Toronto"),
  660.                     (ec_en_otw, "Ottawa"),
  661.                     (ec_en_qbc, "Quebec City"),
  662.                     (ec_en_mtl, "Montreal"),
  663.                     (ec_en_frd, "Fredericton"),
  664.                     (ec_en_hal, "Halifax"),
  665.                     (ec_en_stj, "St. John's")
  666.                 ]
  667.                
  668.                 for station, name in stations:
  669.                     await update_single_station(station, name)
  670.                     await asyncio.sleep(0.5)
  671.                
  672.                 real_forecast_date = datetime.datetime.now().strftime("%a %b %d/%Y")
  673.                 if group != 0:
  674.                     updt_tstp[group] = datetime.datetime.now().timestamp()
  675.  
  676.             # calculate time it took to update
  677.             t = datetime.datetime.now().timestamp() - t1
  678.             debug_msg(f"WEATHER_UPDATE_ASYNC-group {group} completed in {round(t,2)} seconds", 1)
  679.            
  680.         except Exception as e:
  681.             debug_msg(f"WEATHER_UPDATE_ASYNC-critical error in group {group}: {str(e)}", 1)
  682.             # Set fallback values
  683.             if not real_forecast_time:
  684.                 real_forecast_time = time.strftime("%I %p").lstrip("0")
  685.  
  686.             if not real_forecast_date:
  687.                 real_forecast_date = datetime.datetime.now().strftime("%a %b %d/%Y")
  688.                
  689.     else:
  690.         debug_msg(f"WEATHER_UPDATE_ASYNC-group {group} skipped, only {round(timechk//60)} min elapsed", 1)
  691.  
  692. def weather_update(group):
  693.     """Synchronous wrapper for async weather update"""
  694.     try:
  695.         # Run the async function
  696.         asyncio.run(weather_update_async(group))
  697.     except Exception as e:
  698.         debug_msg(f"WEATHER_UPDATE-wrapper error: {str(e)}", 1)
  699.         # Set fallback values
  700.         global real_forecast_time, real_forecast_date
  701.         if not real_forecast_time:
  702.             real_forecast_time = time.strftime("%I %p").lstrip("0")
  703.         if not real_forecast_date:
  704.             real_forecast_date = datetime.datetime.now().strftime("%a %b %d/%Y")
  705.  
  706. # DEF bottom marquee scrolling text with improved error handling
  707. def bottom_marquee(grouptotal, marquee):
  708.     try:
  709.         width = 35
  710.         pad = " " * width
  711.  
  712.         def get_rss_feed():
  713.             try:
  714.                 url = "https://www.cbc.ca/webfeed/rss/rss-canada-manitoba"
  715.                 wpg = feedparser.parse(url)
  716.                 if not wpg.entries:
  717.                     return "NO NEWS DATA AVAILABLE AT THIS TIME"
  718.                 return wpg
  719.             except Exception:
  720.                 return "RSS FEED TEMPORARILY UNAVAILABLE"
  721.  
  722.         wpg = get_rss_feed()
  723.         if isinstance(wpg, str):
  724.             mrq_msg = wpg.upper()
  725.         else:
  726.             try:
  727.                 # Ensure first entry has a valid description
  728.                 first_desc = wpg.entries[0].get("description") or ""
  729.                 wpg_desc = pad + str(first_desc)
  730.  
  731.                 for n in range(1, len(wpg.entries)):
  732.                     new_entry = wpg.entries[n].get("description") or ""
  733.                     new_entry = str(new_entry)
  734.                     if len(wpg_desc + pad + new_entry) * 24 < 31000:
  735.                         wpg_desc = wpg_desc + pad + new_entry
  736.                     else:
  737.                         break
  738.  
  739.                 mrq_msg = wpg_desc.upper()
  740.             except Exception as e:
  741.                 debug_msg(f"RSS parsing failed: {e}", 1)
  742.                 mrq_msg = "RSS PROCESSING ERROR"
  743.  
  744.         marquee_length = len(mrq_msg)
  745.         pixels = marquee_length * 24
  746.         marquee.delete("all")
  747.         text = marquee.create_text(
  748.             1, 2, anchor='nw', text=pad + mrq_msg + pad,
  749.             font=('VCR OSD Mono', 25,), fill="white"
  750.         )
  751.  
  752.         def animate_marquee(pos=0):
  753.             if pos < pixels + 730:
  754.                 marquee.move(text, -1, 0)
  755.                 marquee.update()
  756.                 root.after(2, animate_marquee, pos + 1)
  757.             else:
  758.                 marquee.move(text, pixels + 729, 0)
  759.                 root.after(1000, lambda: bottom_marquee(grouptotal, marquee))
  760.  
  761.         animate_marquee()
  762.  
  763.     except Exception as e:
  764.         debug_msg(f"BOTTOM_MARQUEE-critical error: {str(e)}", 1)
  765.         try:
  766.             root.after(30000, lambda: bottom_marquee(grouptotal, marquee))
  767.         except:
  768.             pass
  769.  
  770.  
  771. # DEF generate playlist from folder with improved error handling
  772. def playlist_generator(musicpath):
  773.     """Generate music playlist with error handling"""
  774.     try:
  775.         debug_msg("PLAYLIST_GENERATOR-searching for music files...", 1)
  776.        
  777.         if not os.path.exists(musicpath):
  778.             debug_msg(f"PLAYLIST_GENERATOR-creating music directory: {musicpath}", 1)
  779.             os.makedirs(musicpath)
  780.             return []
  781.        
  782.         filelist = os.listdir(musicpath)
  783.         allFiles = list()
  784.        
  785.         for entry in filelist:
  786.             try:
  787.                 fullPath = os.path.join(musicpath, entry)
  788.                 if os.path.isdir(fullPath):
  789.                     allFiles = allFiles + playlist_generator(fullPath)
  790.                 elif entry.lower().endswith(('.mp3', '.wav', '.ogg')):  # Only audio files
  791.                     allFiles.append(fullPath)
  792.             except Exception as e:
  793.                 debug_msg(f"PLAYLIST_GENERATOR-error processing {entry}: {str(e)}", 2)
  794.                 continue
  795.        
  796.         debug_msg(f"PLAYLIST_GENERATOR-found {len(allFiles)} music files", 1)
  797.         return allFiles
  798.        
  799.     except Exception as e:
  800.         debug_msg(f"PLAYLIST_GENERATOR-error: {str(e)}", 1)
  801.         return []
  802.  
  803. # DEF play background music with improved error handling
  804. def music_player(songNumber, playlist, musicpath):
  805.     """Play background music with error handling"""
  806.     try:
  807.         if not playlist:
  808.             debug_msg("MUSIC_PLAYER-no music files found, skipping", 1)
  809.             root.after(10000, music_player, 0, playlist, musicpath)
  810.             return
  811.        
  812.         if not pygame.mixer.get_init():
  813.             debug_msg("MUSIC_PLAYER-pygame mixer not initialized", 1)
  814.             root.after(10000, music_player, songNumber, playlist, musicpath)
  815.             return
  816.        
  817.         if ((not pygame.mixer.music.get_busy()) and (songNumber < len(playlist))):
  818.             try:
  819.                 debug_msg(f"MUSIC_PLAYER-playing song {os.path.basename(playlist[songNumber])}", 1)
  820.                 pygame.mixer.music.load(playlist[songNumber])
  821.                 pygame.mixer.music.play(loops=0)
  822.                 songNumber = songNumber + 1
  823.             except Exception as e:
  824.                 debug_msg(f"MUSIC_PLAYER-error playing {playlist[songNumber]}: {str(e)}", 1)
  825.                 songNumber = songNumber + 1  # Skip problematic file
  826.                
  827.         elif ((not pygame.mixer.music.get_busy()) and (songNumber >= len(playlist))):
  828.             debug_msg("MUSIC_PLAYER-playlist complete, re-shuffling...", 1)
  829.             songNumber = 0
  830.             random.shuffle(playlist)  
  831.  
  832.         root.after(2000, music_player, songNumber, playlist, musicpath)
  833.        
  834.     except Exception as e:
  835.         debug_msg(f"MUSIC_PLAYER-error: {str(e)}", 1)
  836.         root.after(10000, music_player, songNumber, playlist, musicpath)
  837.  
  838. # DEF Word Shortener 5000 with improved error handling
  839. def word_short(phrase, length):
  840.     """Shorten phrases with error handling"""
  841.     try:
  842.         if not phrase:
  843.             return "NO DATA"
  844.        
  845.         # dictionary of shortened words
  846.         dict_short = {                    
  847.             "BECOMING" : "BCMG",
  848.             "SCATTERED" : "SCTD",
  849.             "PARTLY" : "PTLY",
  850.             "SHOWER" : "SHWR",
  851.             "CLOUDY" : "CLDY",
  852.             "DRIZZLE" : "DRZLE",
  853.             "FREEZING" : "FRZG",
  854.             "THUNDERSHOWER" : "THNDSHR",
  855.             "THUNDERSTORM" : "THNDSTM",
  856.             "PRECIPITATION" : "PRECIP",
  857.             "CHANCE" : "CHNCE",
  858.             "DEVELOPING" : "DVLPNG",
  859.             "WITH" : "W",
  860.             "LIGHT" : "LT",
  861.             "HEAVY" : "HVY",
  862.             "BLOWING" : "BLWNG"
  863.         }
  864.        
  865.         phrase = str(phrase).upper()
  866.        
  867.         if len(phrase) > length:
  868.             if phrase == "A MIX OF SUN AND CLOUD":
  869.                 phrase = "SUN CLOUD MIX"
  870.            
  871.             for key, value in dict_short.items():
  872.                 phrase = re.sub(key, value, phrase)
  873.            
  874.             debug_msg(f"WORD_SHORT-phrase shortened to {phrase}", 2)
  875.        
  876.         return phrase[:length] if len(phrase) > length else phrase
  877.        
  878.     except Exception as e:
  879.         debug_msg(f"WORD_SHORT-error: {str(e)}", 2)
  880.         return str(phrase)[:length] if phrase else "ERROR"
  881.  
  882. # DEF debug messenger
  883. def debug_msg(message, priority):
  884.     """Debug message handler"""
  885.     try:
  886.         debugmode = 2  # 0=disabled, 1=normal, 2=verbose
  887.         timestamp = 2  # 0=none, 1=time, 2=date+time
  888.        
  889.         if timestamp == 1:
  890.             timestr = time.strftime("%H:%M.")
  891.         elif timestamp == 2:
  892.             timestr = time.strftime("%Y%m%d-%H:%M.")
  893.         else:
  894.             timestr = ""
  895.            
  896.         if ((debugmode > 0) and (priority <= debugmode)):
  897.             print(f"{timestr}{prog}.{ver}.{message}")
  898.            
  899.     except Exception as e:
  900.         print(f"DEBUG_MSG-error: {str(e)}")
  901.  
  902. # DEF signal handler for graceful shutdown
  903. def signal_handler(sig, frame):
  904.     """Handle shutdown signals gracefully"""
  905.     debug_msg("SIGNAL_HANDLER-received shutdown signal", 1)
  906.     try:
  907.         pygame.mixer.quit()
  908.         root.quit()
  909.         root.destroy()
  910.     except:
  911.         pass
  912.     sys.exit(0)
  913.  
  914. # ROOT main setup
  915. def main():
  916.     """Main application setup with error handling"""
  917.     global root, timeText, updt_tstp
  918.     global ec_en_wpg, ec_en_brn, ec_en_thm, ec_en_tps, ec_en_chu, ec_en_fln, ec_en_ken, ec_en_tby
  919.     global ec_en_vic, ec_en_van, ec_en_edm, ec_en_cal, ec_en_ssk, ec_en_reg, ec_en_wht
  920.     global ec_en_tor, ec_en_otw, ec_en_qbc, ec_en_mtl, ec_en_frd, ec_en_hal, ec_en_stj
  921.    
  922.     try:
  923.         # Setup signal handlers
  924.         signal.signal(signal.SIGINT, signal_handler)
  925.         signal.signal(signal.SIGTERM, signal_handler)
  926.        
  927.         # setup root
  928.         root = Tk()
  929.         root.attributes('-fullscreen', False)
  930.         root.geometry("720x480")
  931.         root.config(cursor="none", bg="green")
  932.         root.wm_title("wpg-weatherchan")
  933.  
  934.         # Clock - Top RIGHT
  935.         debug_msg("ROOT-placing clock", 1)
  936.         timeText = Label(root, text="", font=("7-Segment Normal", 22), fg="white", bg="green")
  937.         timeText.place(x=403, y=40)
  938.         timeColon1 = Label(root, text=":", font=("VCR OSD Mono", 32), fg="white", bg="green")
  939.         timeColon1.place(x=465, y=36)
  940.         timeColon2 = Label(root, text=":", font=("VCR OSD Mono", 32), fg="white", bg="green")
  941.         timeColon2.place(x=560, y=36)
  942.         debug_msg("ROOT-launching clock updater", 1)
  943.         clock()
  944.  
  945.         # Title - Top LEFT
  946.         debug_msg("ROOT-placing Title Text", 1)
  947.         Title = Label(root, text="ENVIRONMENT CANADA", font=("VCR OSD Mono", 22, "bold"), fg="white", bg="green")
  948.         Title.place(x=80, y=40)
  949.  
  950.         # Initialize weather station objects
  951.         debug_msg("ROOT-initializing weather stations", 1)
  952.         try:
  953.             # group 1 - Manitoba/Regional
  954.             ec_en_wpg = ECWeather(station_id='MB/s0000193', language='english')
  955.             ec_en_brn = ECWeather(station_id='MB/s0000492', language='english')
  956.             ec_en_thm = ECWeather(station_id='MB/s0000695', language='english')
  957.             ec_en_tps = ECWeather(station_id='MB/s0000644', language='english')
  958.             ec_en_chu = ECWeather(station_id='MB/s0000779', language='english')
  959.             ec_en_fln = ECWeather(station_id='MB/s0000015', language='english')
  960.             ec_en_ken = ECWeather(station_id='ON/s0000651', language='english')
  961.             ec_en_tby = ECWeather(station_id='ON/s0000411', language='english')
  962.  
  963.             # group 2 - Western Canada
  964.             ec_en_vic = ECWeather(station_id='BC/s0000775', language='english')
  965.             ec_en_van = ECWeather(station_id='BC/s0000141', language='english')
  966.             ec_en_edm = ECWeather(station_id='AB/s0000045', language='english')
  967.             ec_en_cal = ECWeather(station_id='AB/s0000047', language='english')
  968.             ec_en_ssk = ECWeather(station_id='SK/s0000797', language='english')
  969.             ec_en_reg = ECWeather(station_id='SK/s0000788', language='english')
  970.             ec_en_wht = ECWeather(station_id='YT/s0000825', language='english')
  971.  
  972.             # group 3 - Eastern Canada
  973.             ec_en_tor = ECWeather(station_id='ON/s0000458', language='english')
  974.             ec_en_otw = ECWeather(station_id='ON/s0000430', language='english')
  975.             ec_en_mtl = ECWeather(station_id='QC/s0000635', language='english')
  976.             ec_en_qbc = ECWeather(station_id='QC/s0000620', language='english')
  977.             ec_en_frd = ECWeather(station_id='NB/s0000250', language='english')
  978.             ec_en_hal = ECWeather(station_id='NS/s0000318', language='english')
  979.             ec_en_stj = ECWeather(station_id='NL/s0000280', language='english')
  980.            
  981.             debug_msg("ROOT-weather stations initialized successfully", 1)
  982.            
  983.         except Exception as e:
  984.             debug_msg(f"ROOT-error initializing weather stations: {str(e)}", 1)
  985.             # Continue anyway - the safe_get_weather_value function will handle missing data
  986.  
  987.         # Initialize update timestamps
  988.         updt_tstp = [0, 0, 0, 0]
  989.         grouptotal = 3
  990.  
  991.         # Set initial fallback values
  992.         global real_forecast_time, real_forecast_date
  993.         real_forecast_time = time.strftime("%I %p").lstrip("0")
  994.         if real_forecast_time == "12 PM":
  995.             real_forecast_time = "NOON"
  996.         real_forecast_date = datetime.datetime.now().strftime("%a %b %d/%Y")
  997.  
  998.         # Update Weather Information
  999.         debug_msg("ROOT-launching initial weather update", 1)
  1000.         try:
  1001.             weather_update(0)  # update all cities
  1002.             debug_msg("ROOT-initial weather update completed", 1)
  1003.         except Exception as e:
  1004.             debug_msg(f"ROOT-initial weather update failed: {str(e)}", 1)
  1005.             debug_msg("ROOT-continuing with fallback data", 1)
  1006.  
  1007.         # Middle Section (Cycling weather pages)
  1008.         debug_msg("ROOT-launching weather_page", 1)
  1009.         PageColour = "#00006D"  # blue
  1010.         PageNum = 1
  1011.         try:
  1012.             weather_page(PageColour, PageNum)
  1013.         except Exception as e:
  1014.             debug_msg(f"ROOT-error starting weather pages: {str(e)}", 1)
  1015.  
  1016.         # Generate background music playlist
  1017.         debug_msg("ROOT-launching playlist generator", 1)
  1018.         musicpath = os.path.expanduser("~/WeatherPi/music")
  1019.         try:
  1020.             playlist = playlist_generator(musicpath)
  1021.             random.shuffle(playlist) if playlist else None
  1022.             debug_msg(f"ROOT-playlist generated with {len(playlist)} songs", 1)
  1023.         except Exception as e:
  1024.             debug_msg(f"ROOT-playlist generation error: {str(e)}", 1)
  1025.             playlist = []
  1026.  
  1027.         # Play background music
  1028.         debug_msg("ROOT-launching background music", 1)
  1029.         songNumber = 0
  1030.         try:
  1031.             pygame.mixer.init()
  1032.             music_player(songNumber, playlist, musicpath)
  1033.             debug_msg("ROOT-background music system started", 1)
  1034.         except Exception as e:
  1035.             debug_msg(f"ROOT-background music error: {str(e)}", 1)
  1036.  
  1037.         # Bottom Scrolling Text (RSS Feed)
  1038.         debug_msg("ROOT-launching bottom_marquee", 1)
  1039.         try:
  1040.             # Use root.after to start the marquee after a short delay
  1041.             marquee = None
  1042.             root.after(2000, lambda: bottom_marquee(grouptotal, marquee))
  1043.         except Exception as e:
  1044.             debug_msg(f"ROOT-bottom marquee error: {str(e)}", 1)
  1045.  
  1046.         # Start the main loop
  1047.         debug_msg("ROOT-starting main application loop", 1)
  1048.         try:
  1049.             root.mainloop()
  1050.         except KeyboardInterrupt:
  1051.             debug_msg("ROOT-keyboard interrupt received", 1)
  1052.             signal_handler(None, None)
  1053.         except Exception as e:
  1054.             debug_msg(f"ROOT-main loop error: {str(e)}", 1)
  1055.         debug_msg("ROOT-launching bottom_marquee", 1)
  1056.         try:
  1057.             marquee = Canvas(root, height=120, width=580, bg="green")
  1058.             marquee.config(highlightbackground="green")
  1059.             marquee.place(x=80, y=400)
  1060.             root.after(2000, lambda: bottom_marquee(grouptotal, marquee))
  1061.         except Exception as e:
  1062.             debug_msg(f"ROOT-bottom marquee error: {str(e)}", 1)
  1063.            
  1064.     except Exception as e:
  1065.         debug_msg(f"ROOT-critical startup error: {str(e)}", 1)
  1066.         sys.exit(1)
  1067.     finally:
  1068.         # Cleanup
  1069.         try:
  1070.             if 'pygame' in globals():
  1071.                 pygame.mixer.quit()
  1072.         except:
  1073.             pass
  1074.  
  1075. if __name__ == "__main__":
  1076.     main()
Advertisement
Add Comment
Please, Sign In to add comment