• Squid has officially released 7.0.2 beta if anyone wants to test

    6
    0 Votes
    6 Posts
    92 Views
    JonathanLeeJ

    @brcuewayne DiagnosticsCommand Prompt
    Shell Output - ls -l /usr/local/sbin/dhcpleases6
    ls: /usr/local/sbin/dhcpleases6: No such file or directory
    Execute Shell Command

  • 0 Votes
    17 Posts
    2k Views
    JonathanLeeJ

    This is a better code for the screen if you have one

    This code is even better it uses moon images and has corrections to the moon functionality

    import os import sys import time import math from datetime import datetime import psutil import subprocess import threading import gpiozero # Import once at the top from PIL import Image, ImageDraw, ImageFont import spidev as SPI import requests from io import BytesIO # <-- Needed for icon image loading # Add parent directory to path to locate 'lib' sys.path.append("..") from lib import LCD_0inch96, LCD_1inch3, Gain_Param # Raspberry Pi pin config RST_0 = 24 DC_0 = 4 BL_0 = 13 BUS_0 = 0 DEVICE_0 = 0 RST_1 = 23 DC_1 = 5 BL_1 = 12 BUS_1 = 0 DEVICE_1 = 1 RST = 27 DC = 22 BL = 19 BUS = 1 DEVICE = 0 state_lock = threading.Lock() debounce_timer = None DEBOUNCE_DELAY = 0.3 # seconds debounce delay to batch rapid presses # Proxy setup (if needed, else remove or set to None) proxies = { "http": "http://192.168.1.1:3128", "https": "http://192.168.1.1:3128", } # OpenWeatherMap API details API_KEY = "API KEY HERE" ZIP_CODE = "ZIP CODE HERE" UNITS = "imperial" # Weather cache to reduce API calls weather_cache = { "data": None, "last_updated": 0, # Unix timestamp "icon_code": None, "icon_image": None } # Doppler radar cache doppler_cache = { "image": None, "last_updated": 0 } doppler_frame_index = 0 # Track which frame to show # Frame caches and timers doppler_frames = [] last_doppler_fetch = 0 DOPPLER_URL = "https://radar.weather.gov/ridge/standard/KMUX_loop.gif" # GOES-18 product definitions GOES_PRODUCTS = [ { "name": "GeoColor", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/GEOCOLOR/GOES18-PSW-GEOCOLOR-600x600.gif" }, { "name": "Air Mass RGB", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/AirMass/GOES18-PSW-AirMass-600x600.gif" }, { "name": "Sandwich RGB", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/Sandwich/GOES18-PSW-Sandwich-600x600.gif" }, { "name": "Day/Night Cloud", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/DayNightCloudMicroCombo/GOES18-PSW-DayNightCloudMicroCombo-600x600.gif" }, { "name": "Fire Temperature", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/FireTemperature/GOES18-PSW-FireTemperature-600x600.gif" }, { "name": "Band 02 - Red Visible", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/02/GOES18-PSW-02-600x600.gif" }, { "name": "Band 07 - Shortwave IR", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/07/GOES18-PSW-07-600x600.gif" }, { "name": "Band 08 - Water Vapor", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/08/GOES18-PSW-08-600x600.gif" }, { "name": "Band 13 - Clean IR", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/13/GOES18-PSW-13-600x600.gif" }, { "name": "Band 14 - IR Window", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/14/GOES18-PSW-14-600x600.gif" }, { "name": "SOHO EIT 171", "url": "https://soho.nascom.nasa.gov/data/LATEST/current_eit_171.gif" }, { "name": "SOHO EIT 304", "url": "https://soho.nascom.nasa.gov/data/LATEST/current_eit_304.gif" } ] # GOES-18 GIF cache containers (one per product) goes_dynamic_cache = [{} for _ in GOES_PRODUCTS] goes_dynamic_frame_index = [0 for _ in GOES_PRODUCTS] FRAME_DELAYS = { "default": 1, "animation": 0.033 } # Get IPv6 address def get_ipv6_address(): try: output = subprocess.check_output("ip -6 addr", shell=True).decode() for line in output.splitlines(): line = line.strip() if line.startswith("inet6") and "scope global" in line: return line.split()[1].split("/")[0] except Exception: pass return "No IPv6" # Fetch detailed weather info (dict with multiple fields) def get_detailed_weather(): now = time.time() if weather_cache["data"] and (now - weather_cache["last_updated"] < 300): return weather_cache["data"] try: response = requests.get( f"http://api.openweathermap.org/data/2.5/weather?zip={ZIP_CODE},us&appid={API_KEY}&units={UNITS}", timeout=5, proxies=proxies ) if response.status_code == 200: data = response.json() weather_cache["data"] = data weather_cache["last_updated"] = now # Download and cache icon if changed icon_code = data["weather"][0]["icon"] if icon_code != weather_cache["icon_code"]: icon_url = f"https://openweathermap.org/img/wn/{icon_code}@2x.png" try: icon_response = requests.get(icon_url, timeout=5, proxies=proxies) if icon_response.status_code == 200: icon_image = Image.open(BytesIO(icon_response.content)).convert("RGBA") weather_cache["icon_image"] = icon_image weather_cache["icon_code"] = icon_code else: weather_cache["icon_image"] = None weather_cache["icon_code"] = None except Exception: weather_cache["icon_image"] = None weather_cache["icon_code"] = None return data else: return None except Exception: return None # Fetch Doppler radar image, cached for 5 minutes def get_doppler_image(): now = time.time() if doppler_cache["image"] and (now - doppler_cache["last_updated"] < 300): return doppler_cache["image"] try: response = requests.get(DOPPLER_URL, timeout=5, proxies=proxies) if response.status_code == 200: gif = Image.open(BytesIO(response.content)) frames = [] try: while True: # Resize here once on load frame = gif.copy().convert("RGBA").resize((disp.width, disp.height), Image.ANTIALIAS) frames.append(frame) gif.seek(gif.tell() + 1) except EOFError: pass # End of frames doppler_cache["image"] = frames doppler_cache["last_updated"] = now return frames except Exception: pass return None # --- Moon phase logic --- moon_image_folder = "/usr/Zero_LCD_HAT_A_Demo/python/example/moon/moon_phases" moon_images_cache = {} def get_moon_image(illumination): # Clamp illumination illumination = max(0, min(illumination, 100)) phase_index = int(illumination / 6.25) if phase_index > 15: phase_index = 15 if phase_index in moon_images_cache: return moon_images_cache[phase_index] filename = f"Fase_{phase_index+1}.jpg" path = os.path.join(moon_image_folder, filename) try: img = Image.open(path).convert("RGBA") except FileNotFoundError: print(f"Warning: {filename} not found.") img = Image.new("RGBA", (100, 100), (0,0,0,0)) # transparent fallback # Cache it moon_images_cache[phase_index] = img # Limit cache size (optional): keep only last 4 images if len(moon_images_cache) > 4: # remove oldest cache item oldest_key = next(iter(moon_images_cache)) del moon_images_cache[oldest_key] return img def get_moon_phase(): # Calculate moon age and illumination as before known_new_moon = datetime(2000, 1, 6, 18, 14) now = datetime.utcnow() diff = now - known_new_moon days_since_new = diff.total_seconds() / 86400.0 lunar_cycle = 29.53058867 age = days_since_new % lunar_cycle phase = age / lunar_cycle illumination = int((1 - abs(phase * 2 - 1)) * 100) # Use illumination % to assign phase name to match images better if illumination <= 1: phase_name = "New Moon" elif illumination < 49: phase_name = "Waxing Crescent" elif 49 <= illumination <= 51: phase_name = "First Quarter" elif illumination < 99: phase_name = "Waxing Gibbous" elif illumination == 100: phase_name = "Full Moon" elif illumination > 51: # Waning phases: just invert ranges or you can use age for waning/waxing detection # For better accuracy, check if moon is waxing or waning by age if age > lunar_cycle / 2: # Waning phases if illumination > 51 and illumination < 99: phase_name = "Waning Gibbous" elif 49 <= illumination <= 51: phase_name = "Last Quarter" elif illumination < 49: phase_name = "Waning Crescent" else: phase_name = "Waxing Gibbous" # fallback else: phase_name = "New Moon" # fallback return phase_name, illumination def draw_moon_icon(base_image, center_x, center_y, radius, illumination): # Get the moon image based on illumination moon_image = get_moon_image(illumination) # Resize the moon image to match the desired diameter moon_image_resized = moon_image.resize((radius * 2, radius * 2), Image.ANTIALIAS) # Calculate position to paste x_offset = center_x - radius y_offset = center_y - radius # Paste moon image onto base image (with transparency if any) base_image.paste(moon_image_resized, (x_offset, y_offset), moon_image_resized.convert("RGBA")) def get_goes_gif(index): now = time.time() product = GOES_PRODUCTS[index] cache = goes_dynamic_cache[index] if "image" in cache and (now - cache.get("last_updated", 0) < 300): return cache["image"] try: response = requests.get(product["url"], timeout=10, proxies=proxies) if response.status_code == 200: gif = Image.open(BytesIO(response.content)) frames = [] try: while True: # Resize frames once on load here frame = gif.copy().convert("RGBA").resize((disp.width, disp.height), Image.ANTIALIAS) frames.append(frame) gif.seek(gif.tell() + 1) except EOFError: pass cache["image"] = frames cache["last_updated"] = now return frames except Exception as e: print(f"Error fetching GOES [{product['name']}] GIF: {e}") return None # Setup displays gain = Gain_Param.Gain_Param() spi0 = SPI.SpiDev() spi0.open(BUS_0, DEVICE_0) spi0.max_speed_hz = 10000000 spi1 = SPI.SpiDev() spi1.open(BUS_1, DEVICE_1) spi1.max_speed_hz = 10000000 spi_main = SPI.SpiDev() spi_main.open(BUS, DEVICE) spi_main.max_speed_hz = 10000000 disp_0 = LCD_0inch96.LCD_0inch96(spi=spi0, spi_freq=10000000, rst=RST_0, dc=DC_0, bl=BL_0, bl_freq=1000) disp_1 = LCD_0inch96.LCD_0inch96(spi=spi1, spi_freq=10000000, rst=RST_1, dc=DC_1, bl=BL_1, bl_freq=1000) disp = LCD_1inch3.LCD_1inch3(spi=spi_main, spi_freq=10000000, rst=RST, dc=DC, bl=BL) disp.Init() disp_0.Init() disp_1.Init() disp.clear() disp_0.clear() disp_1.clear() disp.bl_DutyCycle(100) disp_0.bl_DutyCycle(100) disp_1.bl_DutyCycle(100) # Fonts FontXL = ImageFont.truetype("../Font/Font00.ttf", 28) FontLarge = ImageFont.truetype("../Font/Font00.ttf", 24) FontMid = ImageFont.truetype("../Font/Font00.ttf", 22) FontSmall = ImageFont.truetype("../Font/Font00.ttf", 16) FontTiny = ImageFont.truetype("../Font/Font00.ttf", 12) # Colors (RGB tuples) COLOR_IP = (0, 204, 204) COLOR_IPV6 = (102, 153, 204) COLOR_DATE = (76, 175, 80) COLOR_TIME = (255, 193, 7) COLOR_HEADER = (0, 153, 153) COLOR_WEATHER = (153, 204, 255) COLOR_CPU = (33, 150, 243) COLOR_TEMP = (100, 181, 246) COLOR_RAM = (255, 193, 7) COLOR_DISK = (156, 39, 176) COLOR_UP = (0, 188, 212) COLOR_DN = (77, 182, 172) COLOR_WPAD_HEADER = (0, 153, 153) COLOR_WPAD_USERS = (244, 67, 54) COLOR_WPAD_NONE = (117, 117, 117) # Button GPIO pins (adjust if needed) KEY1_PIN = 25 KEY2_PIN = 26 key1 = gpiozero.Button(KEY1_PIN) key2 = gpiozero.Button(KEY2_PIN) # Initial brightness percentage (10 to 100) brightness = 100 def update_brightness(new_brightness): global brightness brightness = max(10, min(100, new_brightness)) # clamp between 10 and 100 disp.bl_DutyCycle(brightness) disp_0.bl_DutyCycle(brightness) disp_1.bl_DutyCycle(brightness) print(f"Brightness set to {brightness}%") def key2_callback(): new_bright = brightness + 10 if new_bright > 100: new_bright = 10 update_brightness(new_bright) key2.when_pressed = key2_callback # State tracking for pages: 0=original,1=weather,2=moon,3=doppler current_page = 0 NUM_PAGES = 4 + len(GOES_PRODUCTS) # 4 original + new GOES pages key1_hold_timer = None key1_held = False HOLD_TIME_SECONDS = 3 def key1_hold_action(): global current_page, key1_held with state_lock: key1_held = True current_page = 0 print("Button held for 3 seconds: returning to page 0") def key1_pressed(): global key1_hold_timer, key1_held with state_lock: key1_held = False key1_hold_timer = threading.Timer(HOLD_TIME_SECONDS, key1_hold_action) key1_hold_timer.start() def update_display_after_debounce(): global debounce_timer, current_page with state_lock: debounce_timer = None # Usually here you might want to trigger a redraw or notify main loop # You print info for debugging only print(f"Debounced update: Showing page {current_page}") def key1_released(): global key1_hold_timer, key1_held, current_page, debounce_timer with state_lock: if key1_hold_timer: key1_hold_timer.cancel() key1_hold_timer = None if not key1_held: current_page = (current_page + 1) % NUM_PAGES print(f"Button released: page now {current_page}") if debounce_timer: debounce_timer.cancel() debounce_timer = threading.Timer(DEBOUNCE_DELAY, update_display_after_debounce) debounce_timer.start() key1.when_pressed = key1_pressed key1.when_released = key1_released # --- Original display functions --- def draw_top_screen(draw): ip = gain.GET_IP() ipv6 = get_ipv6_address() now = time.localtime() time_t = time.strftime("%H:%M:%S", now) time_D = time.strftime("%Y-%m-%d", now) draw.text((2, 0), 'IP: ' + ip, fill=COLOR_IP, font=FontSmall) draw.text((2, 18), ipv6, fill=COLOR_IPV6, font=FontTiny) draw.text((2, 36), 'DATE: ' + time_D, fill=COLOR_DATE, font=FontSmall) draw.text((2, 54), 'TIME: ' + time_t, fill=COLOR_TIME, font=FontSmall) def draw_middle_screen(draw, last_bytes): y = 0 spacing = 26 header_text = "ROSEVILLE WEATHER" w, _ = draw.textsize(header_text, font=FontSmall) draw.text(((disp.width - w) // 2, y), header_text, fill=COLOR_HEADER, font=FontSmall) y += spacing weather = get_detailed_weather() if weather: temp_f = int(round(weather["main"]["temp"])) humidity = int(weather["main"]["humidity"]) pressure_pa = weather["main"]["pressure"] * 100 pressure_inhg = pressure_pa / 3386.39 summary = f"{temp_f}°{humidity}% {pressure_inhg:.2f} inHg" else: summary = "Weather: Unavailable" w, _ = draw.textsize(summary, font=FontMid) draw.text(((disp.width - w) // 2, y), summary, fill=COLOR_WEATHER, font=FontMid) y += spacing wpad_header = "WPAD HEALTH" w, _ = draw.textsize(wpad_header, font=FontSmall) draw.text(((disp.width - w) // 2, y), wpad_header, fill=COLOR_HEADER, font=FontSmall) y += spacing cpu = int(psutil.cpu_percent(interval=None)) draw.text((2, y), f"CPU: {cpu}%", fill=COLOR_CPU, font=FontLarge) y += spacing temp = int(gain.GET_Temp()) draw.text((2, y), f"Temp: {temp}℃", fill=COLOR_TEMP, font=FontLarge) y += spacing ram = int(psutil.virtual_memory().percent) draw.text((2, y), f"RAM: {ram}%", fill=COLOR_RAM, font=FontLarge) y += spacing disk = int(psutil.disk_usage('/').percent) draw.text((2, y), f"Disk: {disk}%", fill=COLOR_DISK, font=FontLarge) y += spacing # Network speed calculations counters = psutil.net_io_counters() new_sent = counters.bytes_sent new_recv = counters.bytes_recv sent_diff = new_sent - last_bytes[0] recv_diff = new_recv - last_bytes[1] # Time passed between measurements (assumed 1 sec loop; adjust if different) interval = 1.0 # seconds # Avoid negative values and division by zero if sent_diff < 0: sent_diff = 0 if recv_diff < 0: recv_diff = 0 up = (sent_diff * 8) / (1_000_000 * interval) # Mbps dn = (recv_diff * 8) / (1_000_000 * interval) # Mbps draw.text((2, y), f"UP: {up:.2f} Mbps", fill=COLOR_UP, font=FontLarge) y += spacing draw.text((2, y), f"DN: {dn:.2f} Mbps", fill=COLOR_DN, font=FontLarge) return new_sent, new_recv def draw_wpad_status(draw): draw.text((2, 0), "WPAD Users:", fill=COLOR_WPAD_HEADER, font=FontMid) try: users = subprocess.check_output(['who']).decode().splitlines() usernames = sorted(set(line.split()[0] for line in users if line.strip())) display = ', '.join(usernames) if usernames else "None" except Exception as e: display = f"Error: {e}" color = COLOR_WPAD_USERS if display != "None" else COLOR_WPAD_NONE draw.text((2, 20), display, fill=color, font=FontMid) # --- Weather page drawing functions --- def draw_weather_top(draw): now = time.localtime() time_t = time.strftime("%H:%M:%S", now) date_t = time.strftime("%A, %b %d %Y", now) ip = gain.GET_IP() ipv6 = get_ipv6_address() draw.text((2, 0), "Detailed Weather", fill=COLOR_HEADER, font=FontSmall) draw.text((2, 18), f"IP: {ip}", fill=COLOR_IP, font=FontTiny) draw.text((2, 30), f"IPv6: {ipv6}", fill=COLOR_IPV6, font=FontTiny) draw.text((2, 44), date_t, fill=COLOR_DATE, font=FontTiny) draw.text((2, 58), time_t, fill=COLOR_TIME, font=FontTiny) def draw_weather_middle(draw): weather = get_detailed_weather() y = 0 spacing = 20 if weather: main = weather["main"] wind = weather.get("wind", {}) weather_desc = weather["weather"][0]["description"].capitalize() temp = main["temp"] feels_like = main.get("feels_like", temp) humidity = main["humidity"] pressure_pa = main["pressure"] * 100 pressure_inhg = pressure_pa / 3386.39 wind_speed = wind.get("speed", 0) wind_deg = wind.get("deg", 0) draw.text((2, y), f"Temp: {temp:.1f}°F", fill=COLOR_WEATHER, font=FontMid) y += spacing draw.text((2, y), f"Feels Like: {feels_like:.1f}°F", fill=COLOR_WEATHER, font=FontMid) y += spacing draw.text((2, y), f"Humidity: {humidity}%", fill=COLOR_WEATHER, font=FontMid) y += spacing draw.text((2, y), f"Pressure: {pressure_inhg:.2f} inHg", fill=COLOR_WEATHER, font=FontMid) y += spacing draw.text((2, y), f"Wind: {wind_speed} mph @ {wind_deg}°", fill=COLOR_WEATHER, font=FontMid) y += spacing draw.text((2, y), f"Condition: {weather_desc}", fill=COLOR_WEATHER, font=FontMid) else: draw.text((2, y), "Weather data unavailable", fill=COLOR_WPAD_NONE, font=FontMid) def draw_weather_bottom(image): draw = ImageDraw.Draw(image) weather = get_detailed_weather() if weather and "weather" in weather and len(weather["weather"]) > 0: condition = weather["weather"][0]["description"].capitalize() if weather_cache["icon_image"]: try: icon_img = weather_cache["icon_image"].resize((64, 64), Image.ANTIALIAS) x = (image.width - icon_img.width) // 2 y = (image.height - icon_img.height) // 2 - 10 image.paste(icon_img, (x, y), icon_img) font = FontTiny w, h = draw.textsize(condition, font=font) text_x = (image.width - w) // 2 text_y = y + icon_img.height + 2 draw.text((text_x, text_y), condition, fill=COLOR_WEATHER, font=font) except Exception: draw.text((10, 10), condition, fill=COLOR_WPAD_NONE, font=FontMid) else: draw.text((10, 10), condition, fill=COLOR_WPAD_NONE, font=FontMid) else: draw.text((10, 10), "No icon data", fill=COLOR_WPAD_NONE, font=FontMid) # --- Moon phase page --- def draw_moon_top(draw): now = time.localtime() time_t = time.strftime("%H:%M:%S", now) date_t = time.strftime("%A, %b %d %Y", now) ip = gain.GET_IP() ipv6 = get_ipv6_address() draw.text((2, 0), "Moon Phase", fill=COLOR_HEADER, font=FontSmall) draw.text((2, 18), f"IP: {ip}", fill=COLOR_IP, font=FontTiny) draw.text((2, 30), f"IPv6: {ipv6}", fill=COLOR_IPV6, font=FontTiny) draw.text((2, 44), date_t, fill=COLOR_DATE, font=FontTiny) draw.text((2, 58), time_t, fill=COLOR_TIME, font=FontTiny) def draw_moon_middle(image): draw = ImageDraw.Draw(image) phase_name, illumination = get_moon_phase() # Draw phase name at the top with spacing text_y = 10 w, h = draw.textsize(phase_name, font=FontLarge) draw.text(((disp.width - w) // 2, text_y), phase_name, fill=COLOR_WEATHER, font=FontLarge) # Calculate available space for moon below the text top_of_moon_area = text_y + h + 10 # 10px padding below text bottom_of_moon_area = disp.height - 10 # 10px padding from bottom available_height = bottom_of_moon_area - top_of_moon_area radius = min(available_height, disp.width) // 2 # keep it a circle # Calculate center Y within the moon area center_y = top_of_moon_area + available_height // 2 # Center X remains same center_x = disp.width // 2 # Draw the moon draw_moon_icon(image, center_x, center_y, radius, illumination) def draw_moon_bottom(draw): draw.text((2, 0), "Lunar illumination %", fill=COLOR_WPAD_HEADER, font=FontSmall) phase_name, illumination = get_moon_phase() draw.text((2, 18), f"{illumination}%", fill=COLOR_WEATHER, font=FontLarge) # --- Doppler radar page --- def draw_doppler_top(draw): now = time.localtime() date_str = time.strftime("%A, %b %d %Y", now) time_str = time.strftime("%H:%M:%S", now) ip = gain.GET_IP() ipv6 = get_ipv6_address() draw.text((2, 0), "Doppler Radar", fill=COLOR_HEADER, font=FontSmall) draw.text((2, 18), f"IP: {ip}", fill=COLOR_IP, font=FontTiny) draw.text((2, 30), f"IPv6: {ipv6}", fill=COLOR_IPV6, font=FontTiny) draw.text((2, 44), date_str, fill=COLOR_DATE, font=FontTiny) draw.text((2, 58), time_str, fill=COLOR_TIME, font=FontTiny) #---GOES page------- def draw_doppler_middle(image): global doppler_frame_index frames = get_doppler_image() if frames: frame = frames[doppler_frame_index % len(frames)] resized = frame.resize((image.width, image.height), Image.ANTIALIAS) image.paste(resized, (0, 0), resized) doppler_frame_index += 1 else: draw = ImageDraw.Draw(image) msg = "Radar Image\nUnavailable" w, h = draw.textsize(msg, font=FontLarge) draw.text(((image.width - w) // 2, (image.height - h) // 2), msg, fill=COLOR_WPAD_NONE, font=FontLarge) def draw_doppler_bottom(draw): draw.text((2, 2), "Radar updated every 5 min", fill=COLOR_WPAD_NONE, font=FontTiny) def draw_goes_top(draw, label="GOES-18 Product"): now = time.localtime() date_str = time.strftime("%A, %b %d %Y", now) time_str = time.strftime("%H:%M:%S", now) ip = gain.GET_IP() ipv6 = get_ipv6_address() draw.text((2, 0), label, fill=COLOR_HEADER, font=FontSmall) draw.text((2, 18), f"IP: {ip}", fill=COLOR_IP, font=FontTiny) draw.text((2, 30), f"IPv6: {ipv6}", fill=COLOR_IPV6, font=FontTiny) draw.text((2, 44), date_str, fill=COLOR_DATE, font=FontTiny) draw.text((2, 58), time_str, fill=COLOR_TIME, font=FontTiny) def draw_goes_middle(image, index): global goes_dynamic_frame_index frames = get_goes_gif(index) if frames: frame = frames[goes_dynamic_frame_index[index] % len(frames)] resized = frame.resize((image.width, image.height), Image.ANTIALIAS) image.paste(resized, (0, 0), resized) goes_dynamic_frame_index[index] += 1 else: draw = ImageDraw.Draw(image) msg = "GOES GIF\nUnavailable" w, h = draw.textsize(msg, font=FontLarge) draw.text(((image.width - w) // 2, (image.height - h) // 2), msg, fill=COLOR_WPAD_NONE, font=FontLarge) def draw_goes_bottom(draw, product_name=None): # Compose the full text base_text = "Updated every 5 min" if product_name: base_text = f"{product_name} - {base_text}" # Max width available for text on screen max_width = disp_1.width - 4 # some margin (2px left and right) # Font to use font = FontTiny # Simple word wrap helper def wrap_text(text, font, max_width): words = text.split() lines = [] current_line = "" for word in words: test_line = f"{current_line} {word}".strip() w, h = draw.textsize(test_line, font=font) if w <= max_width: current_line = test_line else: if current_line: lines.append(current_line) current_line = word if current_line: lines.append(current_line) return lines lines = wrap_text(base_text, font, max_width) # Draw each line with vertical spacing y = 2 # Start a bit down from top line_spacing = 14 # pixels between lines for line in lines: draw.text((2, y), line, fill=COLOR_WPAD_NONE, font=font) y += line_spacing # --- Main loop --- last_bytes_sent = 0 last_bytes_recv = 0 last_page = None # Track the previous page to detect transitions try: while True: with state_lock: page = current_page # safely snapshot current page under lock # Reset GOES animation frame index if just switched from Doppler (page 3) if (4 <= page < 4 + len(GOES_PRODUCTS)) and (last_page == 3): goes_index = page - 4 goes_dynamic_frame_index[goes_index] = 0 if page == 0: image0 = Image.new("RGB", (disp_0.width, disp_0.height), (0, 0, 0)) draw0 = ImageDraw.Draw(image0) draw_top_screen(draw0) disp_0.ShowImage(image0) image = Image.new("RGB", (disp.width, disp.height), (0, 0, 0)) draw = ImageDraw.Draw(image) last_bytes_sent, last_bytes_recv = draw_middle_screen(draw, (last_bytes_sent, last_bytes_recv)) disp.ShowImage(image) image1 = Image.new("RGB", (disp_1.width, disp_1.height), (0, 0, 0)) draw1 = ImageDraw.Draw(image1) draw_wpad_status(draw1) disp_1.ShowImage(image1) elif page == 1: image0 = Image.new("RGB", (disp_0.width, disp_0.height), (0, 0, 0)) draw0 = ImageDraw.Draw(image0) draw_weather_top(draw0) disp_0.ShowImage(image0) image = Image.new("RGB", (disp.width, disp.height), (0, 0, 0)) draw = ImageDraw.Draw(image) draw_weather_middle(draw) disp.ShowImage(image) image1 = Image.new("RGB", (disp_1.width, disp_1.height), (0, 0, 0)) # Pass image1 (not draw) to draw_weather_bottom if it requires the image object draw_weather_bottom(image1) disp_1.ShowImage(image1) elif page == 2: phase_name, illumination = get_moon_phase() # Create the display image for page 2 image0 = Image.new("RGB", (disp_0.width, disp_0.height), (0, 0, 0)) draw0 = ImageDraw.Draw(image0) draw_moon_top(draw0) # top part uses draw disp_0.ShowImage(image0) # Main display image = Image.new("RGB", (disp.width, disp.height), (0, 0, 0)) # Pass the image (not draw) because your fixed function expects the image draw_moon_middle(image) disp.ShowImage(image) image1 = Image.new("RGB", (disp_1.width, disp_1.height), (0, 0, 0)) draw1 = ImageDraw.Draw(image1) draw_moon_bottom(draw1) # bottom part uses draw disp_1.ShowImage(image1) elif page == 3: if time.time() - last_doppler_fetch > 300 or not doppler_frames: doppler_frames = get_doppler_image() last_doppler_fetch = time.time() doppler_frame_index = 0 image0 = Image.new("RGB", (disp_0.width, disp_0.height), (0, 0, 0)) draw0 = ImageDraw.Draw(image0) draw_doppler_top(draw0) disp_0.ShowImage(image0) image = Image.new("RGBA", (disp.width, disp.height), (0, 0, 0, 0)) if doppler_frames: frame = doppler_frames[doppler_frame_index % len(doppler_frames)] image.paste(frame, (0, 0), frame) doppler_frame_index += 1 disp.ShowImage(image.convert("RGB")) image1 = Image.new("RGB", (disp_1.width, disp_1.height), (0, 0, 0)) draw1 = ImageDraw.Draw(image1) draw_doppler_bottom(draw1) disp_1.ShowImage(image1) elif 4 <= page < 4 + len(GOES_PRODUCTS): goes_index = page - 4 now = time.time() cache = goes_dynamic_cache[goes_index] if "last_updated" not in cache or (now - cache["last_updated"]) > 300: frames = get_goes_gif(goes_index) if frames: cache["image"] = frames cache["last_updated"] = now # We do NOT reset goes_dynamic_frame_index here; it's reset on page transition image0 = Image.new("RGB", (disp_0.width, disp_0.height), (0, 0, 0)) draw0 = ImageDraw.Draw(image0) draw_goes_top(draw0, GOES_PRODUCTS[goes_index]["name"]) disp_0.ShowImage(image0) image = Image.new("RGBA", (disp.width, disp.height), (0, 0, 0, 0)) frames = cache.get("image") if frames: frame = frames[goes_dynamic_frame_index[goes_index] % len(frames)] image.paste(frame, (0, 0), frame) goes_dynamic_frame_index[goes_index] += 1 else: draw = ImageDraw.Draw(image) msg = "GOES GIF\nUnavailable" w, h = draw.textsize(msg, font=FontLarge) draw.text(((image.width - w) // 2, (image.height - h) // 2), msg, fill=COLOR_WPAD_NONE, font=FontLarge) disp.ShowImage(image.convert("RGB")) image1 = Image.new("RGB", (disp_1.width, disp_1.height), (0, 0, 0)) draw1 = ImageDraw.Draw(image1) draw_goes_bottom(draw1, GOES_PRODUCTS[goes_index]["name"]) disp_1.ShowImage(image1) # Sleep delays based on page type if page in [3] + list(range(4, 4 + len(GOES_PRODUCTS))): time.sleep(FRAME_DELAYS["animation"]) else: time.sleep(FRAME_DELAYS["default"]) last_page = page # Update last_page after processing except KeyboardInterrupt: print("Exiting...") disp_0.clear() disp_1.clear() disp.clear() sys.exit()

    use these images
    https://commons.wikimedia.org/wiki/Category:Lunar_phases

    Fase 1-16 need to be used

  • Unable to run squid proxy server after upgrading from 2.7.2 to 2.8.0

    4
    0 Votes
    4 Posts
    100 Views
    patient0P

    @brcuewayne can you provide more details about error(s) you get when you try to start squid?

  • HAProxy with IP Alias

    5
    0 Votes
    5 Posts
    131 Views
    P

    @viragomann

    Damn i completely forgot that i could use the current LAN CARP i have..!! Yeah that works for me !! Thank you very much !!!

  • HAProxy Custom ACL with Firewall Alias now working

    1
    0 Votes
    1 Posts
    62 Views
    No one has replied
  • HAProxy backend port changes are not applied

    7
    3 Votes
    7 Posts
    2k Views
    N

    Hi, just in case someone has the same issue still in 2025. I'm using pfsense 2.8 and haproxy 0.63_10 and I got the same problem: changing the backend port, is not taking effect unless you delete the haproxy_server_state ans reload haproxy (at least this worked for me). It would be good if this process could be automated anytime your reload/restart haproxy. Or itmight be thatI'm missing something.

  • 0 Votes
    17 Posts
    2k Views
    A

    @aGeekhere

    Update, will not be fixed as squid is deprecated from pfSense

  • HA-Proxy on pfSense 2.8 disable proxy buffering for one backend

    1
    0 Votes
    1 Posts
    108 Views
    No one has replied
  • HA-Proxy| 503 Service Unavailable

    2
    0 Votes
    2 Posts
    133 Views
    V

    @pradeep-sl
    Check if the backend is shown up as online on the FS stats page.

  • Unofficial Squid Custom Refresh Patterns

    4
    0 Votes
    4 Posts
    261 Views
    JonathanLeeJ

    @aGeekhere said in Unofficial Squid Custom Refresh Patterns:

    https://github.com/mmd123/squid-cache-dynamic_refresh-list

    I added them thanks.

  • Jitsi Meet behind HAProxy

    1
    0 Votes
    1 Posts
    93 Views
    No one has replied
  • Force traffic through a proxy

    1
    0 Votes
    1 Posts
    106 Views
    No one has replied
  • haproxy not responding

    10
    0 Votes
    10 Posts
    912 Views
    T

    @viragomann

    "Host Matches" in my Case works only when also setting to "use defaults"

  • phpMyAdmin behind HAProxy

    2
    0 Votes
    2 Posts
    272 Views
    C

    I was able to solve the issue by shifting the redirect rules for phpmyadmin to the frontend instead of trying to path it out on the backend. This resolved the issue for me.

    Front End
    e80ffba8-07fd-4520-8b54-abf5e3bdff8e-image.png

    dd4aa560-b111-4f7a-8489-ef46975a5039-image.png

    Since the pathing now happens in the front end, I was able to clean up the backend and it's just a simple passthrough in the case of phpmyadmin.

    Hopefully, this helps someone else out too. There's probably a more elegant way to solve this, but it did the trick for me.

  • ACL with multi Action

    2
    0 Votes
    2 Posts
    186 Views
    V

    @jonny190 said in ACL with multi Action:

    in to one rule, i can get the first line in just not the seccond

    So add a second one.
    The original config has also two rule for what you want.

    BTW: the original rule looks a bit different than yours. It seems, to also replace the last octet of the IP.

  • Sqstat Issue

    Moved
    17
    0 Votes
    17 Posts
    4k Views
    N

    @anemacuore 2.8.0 is work (update)

  • HAProxy Seems to Forward to wrong Backend Port

    8
    0 Votes
    8 Posts
    2k Views
    P

    Hi, a few weeks ago the same problem, HAProxy was working fine about 3 years ago.

    I try pfsense version 2.6, 2.7, 2.8beta and now 2.8. Haproxy and haproxy-devel. no success.

    Its like a cache, when request the first acl rule, the next use the same rule.

  • 1 Votes
    9 Posts
    1k Views
    JonathanLeeJ

    @tinfoilmatt Here you go

    https://forum.netgate.com/topic/195860/mnt-folder-question

    To quote: @stephenw10

    "Jan 6, 2025 at 5:43 AM I would still use a custom location to be sure. I can't find anything off hand but if would conflict with anything that did.

    I'm pretty sure the efi partition is mounted there to test at upgrade for example."

  • Squid error

    1
    0 Votes
    1 Posts
    156 Views
    No one has replied
  • Squid error "FATAL: Unknown http_port option 'NO_TLSv1"

    13
    0 Votes
    13 Posts
    2k Views
    B

    @aniodon

    Apologies for the necro posting.

    How would you apply such a patch ?

    https://github.com/pfsense/FreeBSD-ports/commit/476a7d0e3dca704b236839970f1d215912184f73

    I've created a system patch via pfsense GUI with the commit mentioned in this thread, however, it does not apply to my squid config,

    It seems that it is not recognizing the file to update (?)
    Here's what I'm getting in the debug log (not having changed default patch settings):

    /usr/bin/patch --directory='/' -t --strip '2' -i '/var/patches/682f24bdbc39f.patch' --check --forward --ignore-whitespace Hmm... Looks like a unified diff to me... The text leading up to this was: -------------------------- |From 476a7d0e3dca704b236839970f1d215912184f73 Mon Sep 17 00:00:00 2001 |From: Marcos Mendoza <mmendoza@netgate.com> |Date: Tue, 26 Nov 2024 18:36:53 -0600 |Subject: [PATCH] www/pfSense-pkg-squid: remove duplicate option | |--- | www/pfSense-pkg-squid/files/usr/local/pkg/squid.inc | 1 - | 1 file changed, 1 deletion(-) | |diff --git a/www/pfSense-pkg-squid/files/usr/local/pkg/squid.inc b/www/pfSense-pkg-squid/files/usr/local/pkg/squid.inc |index 719cda2fb3cf..129b8b05335c 100644 |--- a/www/pfSense-pkg-squid/files/usr/local/pkg/squid.inc |+++ b/www/pfSense-pkg-squid/files/usr/local/pkg/squid.inc -------------------------- No file to patch. Skipping... Hunk #1 ignored at 1236. 1 out of 1 hunks ignored while patching pfSense-pkg-squid/files/usr/local/pkg/squid.inc done
Copyright 2025 Rubicon Communications LLC (Netgate). All rights reserved.