Initial commit
This commit is contained in:
16
.env.example
Normal file
16
.env.example
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Home Assistant Configuration
|
||||||
|
HA_TOKEN=your_home_assistant_long_lived_access_token
|
||||||
|
HASS_URL=https://your-home-assistant-url.com
|
||||||
|
|
||||||
|
# Entity IDs
|
||||||
|
BRIGHTNESS_ENTITY_ID=sensor.your_brightness_sensor
|
||||||
|
WEATHER_ENTITY_ID=weather.forecast_home
|
||||||
|
INTERIOR_TEMP_ENTITY_ID=sensor.your_temperature_sensor
|
||||||
|
INTERIOR_HUMIDITY_ENTITY_ID=sensor.your_humidity_sensor
|
||||||
|
|
||||||
|
# Netdata Configuration
|
||||||
|
NETDATA_URL=http://your-netdata-host:19999
|
||||||
|
|
||||||
|
# LED Matrix Configuration
|
||||||
|
LED_ROWS=64
|
||||||
|
LED_COLS=64
|
||||||
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Environment variables (contains secrets)
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
26
config.py
Normal file
26
config.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Configuration module for Matrix64 LED display.
|
||||||
|
Loads environment variables from .env file.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Load environment variables from .env file
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Home Assistant Configuration
|
||||||
|
HA_TOKEN = os.getenv('HA_TOKEN')
|
||||||
|
HASS_URL = os.getenv('HASS_URL')
|
||||||
|
|
||||||
|
# Entity IDs
|
||||||
|
BRIGHTNESS_ENTITY_ID = os.getenv('BRIGHTNESS_ENTITY_ID')
|
||||||
|
WEATHER_ENTITY_ID = os.getenv('WEATHER_ENTITY_ID')
|
||||||
|
INTERIOR_TEMP_ENTITY_ID = os.getenv('INTERIOR_TEMP_ENTITY_ID')
|
||||||
|
INTERIOR_HUMIDITY_ENTITY_ID = os.getenv('INTERIOR_HUMIDITY_ENTITY_ID')
|
||||||
|
|
||||||
|
# Netdata Configuration
|
||||||
|
NETDATA_URL = os.getenv('NETDATA_URL')
|
||||||
|
|
||||||
|
# LED Matrix Configuration
|
||||||
|
LED_ROWS = int(os.getenv('LED_ROWS', '64'))
|
||||||
|
LED_COLS = int(os.getenv('LED_COLS', '64'))
|
||||||
63
home_assistant.py
Normal file
63
home_assistant.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
"""
|
||||||
|
Home Assistant API client for Matrix64 LED display.
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
from config import (
|
||||||
|
HA_TOKEN, HASS_URL,
|
||||||
|
BRIGHTNESS_ENTITY_ID, WEATHER_ENTITY_ID,
|
||||||
|
INTERIOR_TEMP_ENTITY_ID, INTERIOR_HUMIDITY_ENTITY_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_entity_value(entity_id):
|
||||||
|
"""Fetch entity state from Home Assistant."""
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {HA_TOKEN}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
url = f"{HASS_URL}/api/states/{entity_id}"
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Failed to retrieve entity {entity_id}: {response.text}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error fetching {entity_id}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_weather():
|
||||||
|
"""Get weather attributes from Home Assistant."""
|
||||||
|
weather = get_entity_value(WEATHER_ENTITY_ID)
|
||||||
|
if weather:
|
||||||
|
return weather.get("attributes", {})
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_weather_description():
|
||||||
|
"""Get current weather state/condition."""
|
||||||
|
weather = get_entity_value(WEATHER_ENTITY_ID)
|
||||||
|
if weather:
|
||||||
|
return weather.get("state", "unknown")
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def get_interior_weather():
|
||||||
|
"""Get interior temperature and humidity from BTH01-3132 sensor."""
|
||||||
|
temp_data = get_entity_value(INTERIOR_TEMP_ENTITY_ID)
|
||||||
|
humidity_data = get_entity_value(INTERIOR_HUMIDITY_ENTITY_ID)
|
||||||
|
|
||||||
|
temperature = temp_data.get("state") if temp_data else None
|
||||||
|
humidity = humidity_data.get("state") if humidity_data else None
|
||||||
|
|
||||||
|
return {"humidity": humidity, "temperature": temperature}
|
||||||
|
|
||||||
|
|
||||||
|
def get_brightness():
|
||||||
|
"""Get screen brightness value."""
|
||||||
|
brightness = get_entity_value(BRIGHTNESS_ENTITY_ID)
|
||||||
|
if brightness:
|
||||||
|
return brightness.get("state")
|
||||||
|
return None
|
||||||
56
install.sh
Normal file
56
install.sh
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Installation script for Matrix64 LED Display
|
||||||
|
# Run this once on your Raspberry Pi
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
INSTALL_DIR="/opt/matrix64"
|
||||||
|
REPO_URL="https://gitlab.kingstudio.es/jocaru/matrix64"
|
||||||
|
|
||||||
|
echo "=== Matrix64 LED Display Installation ==="
|
||||||
|
|
||||||
|
# Clone or update repository
|
||||||
|
if [ -d "$INSTALL_DIR" ]; then
|
||||||
|
echo "Directory exists, updating..."
|
||||||
|
cd "$INSTALL_DIR"
|
||||||
|
git pull origin master
|
||||||
|
else
|
||||||
|
echo "Cloning repository..."
|
||||||
|
git clone "$REPO_URL" "$INSTALL_DIR"
|
||||||
|
cd "$INSTALL_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
echo "Installing Python dependencies..."
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
|
||||||
|
# Create .env from example if not exists
|
||||||
|
if [ ! -f "$INSTALL_DIR/.env" ]; then
|
||||||
|
echo "Creating .env from template..."
|
||||||
|
cp "$INSTALL_DIR/.env.example" "$INSTALL_DIR/.env"
|
||||||
|
echo "IMPORTANT: Edit $INSTALL_DIR/.env with your actual values!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install systemd service
|
||||||
|
echo "Installing systemd service..."
|
||||||
|
cp "$INSTALL_DIR/matrix64.service" /etc/systemd/system/
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable matrix64
|
||||||
|
|
||||||
|
# Make update script executable
|
||||||
|
chmod +x "$INSTALL_DIR/update.sh"
|
||||||
|
|
||||||
|
# Set up cron job for auto-updates (every 5 minutes)
|
||||||
|
CRON_JOB="*/5 * * * * $INSTALL_DIR/update.sh"
|
||||||
|
(crontab -l 2>/dev/null | grep -v "matrix64/update.sh"; echo "$CRON_JOB") | crontab -
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Installation Complete ==="
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Edit /opt/matrix64/.env with your Home Assistant token and URLs"
|
||||||
|
echo "2. Start the service: sudo systemctl start matrix64"
|
||||||
|
echo "3. Check status: sudo systemctl status matrix64"
|
||||||
|
echo "4. View logs: sudo journalctl -u matrix64 -f"
|
||||||
|
echo ""
|
||||||
|
echo "Auto-updates are enabled (every 5 minutes via cron)"
|
||||||
153
matrix.py
Normal file
153
matrix.py
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Matrix64 LED Display - Main Entry Point
|
||||||
|
|
||||||
|
Displays Home Assistant and Netdata data on a 64x64 LED matrix.
|
||||||
|
"""
|
||||||
|
from samplebase import SampleBase
|
||||||
|
from rgbmatrix import graphics
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from config import LED_ROWS, LED_COLS
|
||||||
|
from weather_icons import draw_weather_icon
|
||||||
|
from home_assistant import (
|
||||||
|
get_weather, get_weather_description,
|
||||||
|
get_interior_weather, get_brightness
|
||||||
|
)
|
||||||
|
from netdata import get_hdd_temps
|
||||||
|
|
||||||
|
|
||||||
|
def get_temperature_color(temp):
|
||||||
|
"""Determine color based on temperature value."""
|
||||||
|
try:
|
||||||
|
temp_value = float(str(temp).replace('°C', '').strip())
|
||||||
|
if temp_value < 0:
|
||||||
|
return graphics.Color(0, 100, 255)
|
||||||
|
elif temp_value < 10:
|
||||||
|
return graphics.Color(100, 180, 255)
|
||||||
|
elif temp_value < 20:
|
||||||
|
return graphics.Color(0, 255, 100)
|
||||||
|
elif temp_value < 30:
|
||||||
|
return graphics.Color(255, 200, 0)
|
||||||
|
else:
|
||||||
|
return graphics.Color(255, 50, 50)
|
||||||
|
except ValueError:
|
||||||
|
return graphics.Color(255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
|
class Matrix64Display(SampleBase):
|
||||||
|
"""Main display class for the LED matrix."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Matrix64Display, self).__init__(*args, **kwargs)
|
||||||
|
self.parser.add_argument(
|
||||||
|
"-t", "--text",
|
||||||
|
help="The text to scroll on the RGB LED panel",
|
||||||
|
default="Hello world!"
|
||||||
|
)
|
||||||
|
self.temperature = None
|
||||||
|
self.humidity = None
|
||||||
|
self.interior_temperature = None
|
||||||
|
self.interior_humidity = None
|
||||||
|
self.last_update = time.time()
|
||||||
|
self.weather_desc = "unknown"
|
||||||
|
self.hdd_temps = None
|
||||||
|
|
||||||
|
def update_data(self):
|
||||||
|
"""Fetch all data from APIs."""
|
||||||
|
try:
|
||||||
|
# Weather data
|
||||||
|
weather = get_weather()
|
||||||
|
if weather.get("temperature") is not None:
|
||||||
|
self.temperature = f'{weather.get("temperature")}°C'
|
||||||
|
self.humidity = weather.get("humidity")
|
||||||
|
self.weather_desc = get_weather_description()
|
||||||
|
|
||||||
|
# Interior data
|
||||||
|
interior = get_interior_weather()
|
||||||
|
if interior.get("temperature") is not None:
|
||||||
|
self.interior_temperature = f'{interior.get("temperature")}°C'
|
||||||
|
self.interior_humidity = interior.get("humidity")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error updating data: {e}")
|
||||||
|
|
||||||
|
def update_brightness(self):
|
||||||
|
"""Update LED matrix brightness."""
|
||||||
|
try:
|
||||||
|
brightness = get_brightness()
|
||||||
|
if brightness:
|
||||||
|
self.matrix.brightness = int(float(brightness)) / 10
|
||||||
|
except Exception as e:
|
||||||
|
self.matrix.brightness = 5
|
||||||
|
print(f"Error updating brightness: {e}")
|
||||||
|
|
||||||
|
def update_hdd_temps(self):
|
||||||
|
"""Update HDD temperatures."""
|
||||||
|
self.hdd_temps = get_hdd_temps()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Main display loop."""
|
||||||
|
canvas = self.matrix.CreateFrameCanvas()
|
||||||
|
|
||||||
|
# Load fonts
|
||||||
|
font = graphics.Font()
|
||||||
|
font.LoadFont("../../../fonts/7x13.bdf")
|
||||||
|
temp_font = graphics.Font()
|
||||||
|
temp_font.LoadFont("../../../fonts/5x8.bdf")
|
||||||
|
|
||||||
|
text_color = graphics.Color(20, 75, 200)
|
||||||
|
|
||||||
|
# Initial data fetch
|
||||||
|
self.update_brightness()
|
||||||
|
self.update_data()
|
||||||
|
self.update_hdd_temps()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
time_str = f"{now.hour:02d}:{now.minute:02d}:{now.second:02d}"
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# Update every 60 seconds
|
||||||
|
if current_time - self.last_update >= 60:
|
||||||
|
self.update_data()
|
||||||
|
self.update_brightness()
|
||||||
|
self.update_hdd_temps()
|
||||||
|
self.last_update = current_time
|
||||||
|
|
||||||
|
canvas.Clear()
|
||||||
|
|
||||||
|
# Time display
|
||||||
|
graphics.DrawText(canvas, font, 4, 42, text_color, time_str)
|
||||||
|
|
||||||
|
# Weather icon
|
||||||
|
draw_weather_icon(canvas, 0, 0, self.weather_desc)
|
||||||
|
|
||||||
|
# Outdoor temperature (right-aligned)
|
||||||
|
if self.temperature:
|
||||||
|
color = get_temperature_color(self.temperature)
|
||||||
|
length = graphics.DrawText(canvas, temp_font, 0, 0, color, self.temperature)
|
||||||
|
graphics.DrawText(canvas, temp_font, 64 - length, 8, color, self.temperature)
|
||||||
|
|
||||||
|
# Interior temperature (right-aligned)
|
||||||
|
if self.interior_temperature:
|
||||||
|
color = get_temperature_color(self.interior_temperature)
|
||||||
|
length = graphics.DrawText(canvas, temp_font, 0, 0, color, self.interior_temperature)
|
||||||
|
graphics.DrawText(canvas, temp_font, 64 - length, 16, color, self.interior_temperature)
|
||||||
|
|
||||||
|
# HDD temperatures
|
||||||
|
if self.hdd_temps and len(self.hdd_temps) > 4:
|
||||||
|
row1 = " ".join(str(t[0])[:2] for t in self.hdd_temps[1:4])
|
||||||
|
row2 = " ".join(str(t[0])[:2] for t in self.hdd_temps[4:])
|
||||||
|
graphics.DrawText(canvas, temp_font, 12, 52, text_color, row1)
|
||||||
|
graphics.DrawText(canvas, temp_font, 12, 60, text_color, row2)
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
canvas = self.matrix.SwapOnVSync(canvas)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
display = Matrix64Display()
|
||||||
|
if not display.process():
|
||||||
|
display.print_help()
|
||||||
17
matrix64.service
Normal file
17
matrix64.service
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Matrix64 LED Display
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/opt/matrix64
|
||||||
|
ExecStart=/usr/bin/python3 /opt/matrix64/matrix.py --led-rows=64 --led-cols=64
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
17
netdata.py
Normal file
17
netdata.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Netdata API client for Matrix64 LED display.
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
from config import NETDATA_URL
|
||||||
|
|
||||||
|
|
||||||
|
def get_hdd_temps():
|
||||||
|
"""Get HDD temperatures from Netdata."""
|
||||||
|
try:
|
||||||
|
url = f"{NETDATA_URL}/api/v2/data?contexts=hddtemp.disk_temperature&points=1&group_by=instance"
|
||||||
|
response = requests.get(url, timeout=10)
|
||||||
|
hdd_temps = response.json()['result']['data'][0]
|
||||||
|
return hdd_temps
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error fetching HDD temps: {e}")
|
||||||
|
return None
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
python-dotenv>=1.0.0
|
||||||
|
requests>=2.28.0
|
||||||
29
update.sh
Normal file
29
update.sh
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Update script for Matrix64 LED Display
|
||||||
|
# Pulls latest changes from git and restarts service if updated
|
||||||
|
|
||||||
|
INSTALL_DIR="/opt/matrix64"
|
||||||
|
LOGFILE="/var/log/matrix64-update.log"
|
||||||
|
|
||||||
|
cd "$INSTALL_DIR" || exit 1
|
||||||
|
|
||||||
|
# Fetch latest changes
|
||||||
|
git fetch origin
|
||||||
|
|
||||||
|
# Check if there are updates
|
||||||
|
LOCAL=$(git rev-parse HEAD)
|
||||||
|
REMOTE=$(git rev-parse origin/master)
|
||||||
|
|
||||||
|
if [ "$LOCAL" != "$REMOTE" ]; then
|
||||||
|
echo "$(date): Updates found, pulling changes..." >> "$LOGFILE"
|
||||||
|
git pull origin master >> "$LOGFILE" 2>&1
|
||||||
|
|
||||||
|
# Install any new dependencies
|
||||||
|
pip3 install -r requirements.txt >> "$LOGFILE" 2>&1
|
||||||
|
|
||||||
|
# Restart the service
|
||||||
|
systemctl restart matrix64
|
||||||
|
echo "$(date): Service restarted" >> "$LOGFILE"
|
||||||
|
else
|
||||||
|
echo "$(date): No updates" >> "$LOGFILE"
|
||||||
|
fi
|
||||||
236
weather_icons.py
Normal file
236
weather_icons.py
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
"""
|
||||||
|
Weather icon drawing functions for 64x64 LED matrix.
|
||||||
|
All icons are designed for a 24x24 pixel area.
|
||||||
|
"""
|
||||||
|
from rgbmatrix import graphics
|
||||||
|
|
||||||
|
|
||||||
|
def draw_sunny(canvas, x, y):
|
||||||
|
"""Draw a sun with rays - clear/sunny weather."""
|
||||||
|
sun_color = graphics.Color(255, 200, 0)
|
||||||
|
ray_color = graphics.Color(255, 140, 0)
|
||||||
|
|
||||||
|
# Draw sun rays (8 directions)
|
||||||
|
graphics.DrawLine(canvas, x + 12, y + 2, x + 12, y + 5, ray_color)
|
||||||
|
graphics.DrawLine(canvas, x + 12, y + 19, x + 12, y + 22, ray_color)
|
||||||
|
graphics.DrawLine(canvas, x + 2, y + 12, x + 5, y + 12, ray_color)
|
||||||
|
graphics.DrawLine(canvas, x + 19, y + 12, x + 22, y + 12, ray_color)
|
||||||
|
graphics.DrawLine(canvas, x + 5, y + 5, x + 7, y + 7, ray_color)
|
||||||
|
graphics.DrawLine(canvas, x + 17, y + 17, x + 19, y + 19, ray_color)
|
||||||
|
graphics.DrawLine(canvas, x + 5, y + 19, x + 7, y + 17, ray_color)
|
||||||
|
graphics.DrawLine(canvas, x + 17, y + 7, x + 19, y + 5, ray_color)
|
||||||
|
|
||||||
|
# Draw filled sun
|
||||||
|
for r in range(6, 0, -1):
|
||||||
|
graphics.DrawCircle(canvas, x + 12, y + 12, r, sun_color)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_clear_night(canvas, x, y):
|
||||||
|
"""Draw a crescent moon with stars."""
|
||||||
|
moon_color = graphics.Color(255, 255, 200)
|
||||||
|
|
||||||
|
# Draw moon
|
||||||
|
for r in range(7, 0, -1):
|
||||||
|
graphics.DrawCircle(canvas, x + 10, y + 12, r, moon_color)
|
||||||
|
|
||||||
|
# Draw stars
|
||||||
|
canvas.SetPixel(x + 20, y + 6, 255, 255, 255)
|
||||||
|
canvas.SetPixel(x + 18, y + 10, 255, 255, 255)
|
||||||
|
canvas.SetPixel(x + 22, y + 14, 255, 255, 255)
|
||||||
|
canvas.SetPixel(x + 19, y + 18, 255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_cloud_shape(canvas, x, y, color):
|
||||||
|
"""Helper to draw a cloud shape."""
|
||||||
|
for r in range(4, 0, -1):
|
||||||
|
graphics.DrawCircle(canvas, x + 4, y + 4, r, color)
|
||||||
|
for r in range(5, 0, -1):
|
||||||
|
graphics.DrawCircle(canvas, x + 10, y + 3, r, color)
|
||||||
|
for r in range(4, 0, -1):
|
||||||
|
graphics.DrawCircle(canvas, x + 15, y + 5, r, color)
|
||||||
|
for r in range(3, 0, -1):
|
||||||
|
graphics.DrawCircle(canvas, x + 6, y + 7, r, color)
|
||||||
|
for r in range(3, 0, -1):
|
||||||
|
graphics.DrawCircle(canvas, x + 12, y + 7, r, color)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_partly_cloudy(canvas, x, y):
|
||||||
|
"""Draw sun partially covered by cloud."""
|
||||||
|
sun_color = graphics.Color(255, 200, 0)
|
||||||
|
ray_color = graphics.Color(255, 140, 0)
|
||||||
|
cloud_color = graphics.Color(220, 220, 220)
|
||||||
|
|
||||||
|
# Draw partial sun
|
||||||
|
graphics.DrawLine(canvas, x + 8, y + 2, x + 8, y + 4, ray_color)
|
||||||
|
graphics.DrawLine(canvas, x + 2, y + 8, x + 4, y + 8, ray_color)
|
||||||
|
graphics.DrawLine(canvas, x + 3, y + 3, x + 5, y + 5, ray_color)
|
||||||
|
for r in range(4, 0, -1):
|
||||||
|
graphics.DrawCircle(canvas, x + 8, y + 8, r, sun_color)
|
||||||
|
|
||||||
|
# Draw cloud
|
||||||
|
_draw_cloud_shape(canvas, x + 6, y + 10, cloud_color)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_partly_cloudy_night(canvas, x, y):
|
||||||
|
"""Draw moon partially covered by cloud."""
|
||||||
|
moon_color = graphics.Color(255, 255, 200)
|
||||||
|
cloud_color = graphics.Color(180, 180, 200)
|
||||||
|
|
||||||
|
for r in range(4, 0, -1):
|
||||||
|
graphics.DrawCircle(canvas, x + 8, y + 8, r, moon_color)
|
||||||
|
_draw_cloud_shape(canvas, x + 6, y + 10, cloud_color)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_cloudy(canvas, x, y):
|
||||||
|
"""Draw overcast clouds."""
|
||||||
|
cloud_light = graphics.Color(200, 200, 200)
|
||||||
|
cloud_dark = graphics.Color(150, 150, 150)
|
||||||
|
|
||||||
|
_draw_cloud_shape(canvas, x + 2, y + 6, cloud_dark)
|
||||||
|
_draw_cloud_shape(canvas, x + 6, y + 10, cloud_light)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_rainy(canvas, x, y):
|
||||||
|
"""Draw cloud with rain drops."""
|
||||||
|
cloud_color = graphics.Color(150, 150, 170)
|
||||||
|
rain_color = graphics.Color(100, 150, 255)
|
||||||
|
|
||||||
|
_draw_cloud_shape(canvas, x + 4, y + 4, cloud_color)
|
||||||
|
|
||||||
|
for i in range(4):
|
||||||
|
x_start = x + 6 + i * 4
|
||||||
|
graphics.DrawLine(canvas, x_start, y + 16, x_start - 2, y + 20, rain_color)
|
||||||
|
graphics.DrawLine(canvas, x_start + 1, y + 18, x_start - 1, y + 22, rain_color)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_pouring(canvas, x, y):
|
||||||
|
"""Draw cloud with heavy rain."""
|
||||||
|
cloud_color = graphics.Color(100, 100, 120)
|
||||||
|
rain_color = graphics.Color(80, 130, 255)
|
||||||
|
|
||||||
|
_draw_cloud_shape(canvas, x + 4, y + 2, cloud_color)
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
x_start = x + 4 + i * 4
|
||||||
|
graphics.DrawLine(canvas, x_start, y + 14, x_start - 3, y + 22, rain_color)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_snowy(canvas, x, y):
|
||||||
|
"""Draw cloud with snowflakes."""
|
||||||
|
cloud_color = graphics.Color(180, 180, 200)
|
||||||
|
|
||||||
|
_draw_cloud_shape(canvas, x + 4, y + 4, cloud_color)
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
sx = x + 8 + i * 5
|
||||||
|
sy = y + 18 + (i % 2) * 3
|
||||||
|
canvas.SetPixel(sx, sy, 255, 255, 255)
|
||||||
|
canvas.SetPixel(sx - 1, sy, 255, 255, 255)
|
||||||
|
canvas.SetPixel(sx + 1, sy, 255, 255, 255)
|
||||||
|
canvas.SetPixel(sx, sy - 1, 255, 255, 255)
|
||||||
|
canvas.SetPixel(sx, sy + 1, 255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_thunderstorm(canvas, x, y):
|
||||||
|
"""Draw cloud with lightning bolt."""
|
||||||
|
cloud_color = graphics.Color(80, 80, 100)
|
||||||
|
lightning_color = graphics.Color(255, 255, 0)
|
||||||
|
|
||||||
|
_draw_cloud_shape(canvas, x + 4, y + 2, cloud_color)
|
||||||
|
|
||||||
|
graphics.DrawLine(canvas, x + 12, y + 12, x + 10, y + 16, lightning_color)
|
||||||
|
graphics.DrawLine(canvas, x + 10, y + 16, x + 14, y + 16, lightning_color)
|
||||||
|
graphics.DrawLine(canvas, x + 14, y + 16, x + 10, y + 22, lightning_color)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_foggy(canvas, x, y):
|
||||||
|
"""Draw horizontal fog lines."""
|
||||||
|
fog_color = graphics.Color(180, 180, 180)
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
y_pos = y + 6 + i * 3
|
||||||
|
graphics.DrawLine(canvas, x + 2, y_pos, x + 20, y_pos, fog_color)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_windy(canvas, x, y):
|
||||||
|
"""Draw wind lines."""
|
||||||
|
wind_color = graphics.Color(150, 200, 255)
|
||||||
|
|
||||||
|
graphics.DrawLine(canvas, x + 2, y + 8, x + 18, y + 8, wind_color)
|
||||||
|
graphics.DrawLine(canvas, x + 18, y + 8, x + 20, y + 6, wind_color)
|
||||||
|
graphics.DrawLine(canvas, x + 4, y + 12, x + 22, y + 12, wind_color)
|
||||||
|
graphics.DrawLine(canvas, x + 22, y + 12, x + 23, y + 10, wind_color)
|
||||||
|
graphics.DrawLine(canvas, x + 2, y + 16, x + 16, y + 16, wind_color)
|
||||||
|
graphics.DrawLine(canvas, x + 16, y + 16, x + 18, y + 14, wind_color)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_hail(canvas, x, y):
|
||||||
|
"""Draw cloud with hail."""
|
||||||
|
cloud_color = graphics.Color(140, 140, 160)
|
||||||
|
hail_color = graphics.Color(200, 220, 255)
|
||||||
|
|
||||||
|
_draw_cloud_shape(canvas, x + 4, y + 4, cloud_color)
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
hx = x + 8 + i * 5
|
||||||
|
hy = y + 18 + (i % 2) * 2
|
||||||
|
graphics.DrawCircle(canvas, hx, hy, 2, hail_color)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_unknown(canvas, x, y):
|
||||||
|
"""Draw question mark for unknown weather."""
|
||||||
|
color = graphics.Color(255, 255, 255)
|
||||||
|
|
||||||
|
graphics.DrawCircle(canvas, x + 12, y + 8, 4, color)
|
||||||
|
canvas.SetPixel(x + 12, y + 14, 255, 255, 255)
|
||||||
|
canvas.SetPixel(x + 12, y + 17, 255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
|
# Weather state to icon function mapping
|
||||||
|
WEATHER_ICONS = {
|
||||||
|
'sunny': draw_sunny,
|
||||||
|
'clear-day': draw_sunny,
|
||||||
|
'clear-night': draw_clear_night,
|
||||||
|
'partlycloudy': draw_partly_cloudy,
|
||||||
|
'partly-cloudy-day': draw_partly_cloudy,
|
||||||
|
'partly-cloudy-night': draw_partly_cloudy_night,
|
||||||
|
'cloudy': draw_cloudy,
|
||||||
|
'overcast': draw_cloudy,
|
||||||
|
'rainy': draw_rainy,
|
||||||
|
'rain': draw_rainy,
|
||||||
|
'showers': draw_rainy,
|
||||||
|
'pouring': draw_pouring,
|
||||||
|
'snowy': draw_snowy,
|
||||||
|
'snow': draw_snowy,
|
||||||
|
'snowy-rainy': draw_snowy,
|
||||||
|
'sleet': draw_snowy,
|
||||||
|
'hail': draw_hail,
|
||||||
|
'lightning': draw_thunderstorm,
|
||||||
|
'lightning-rainy': draw_thunderstorm,
|
||||||
|
'thunderstorm': draw_thunderstorm,
|
||||||
|
'fog': draw_foggy,
|
||||||
|
'foggy': draw_foggy,
|
||||||
|
'mist': draw_foggy,
|
||||||
|
'hazy': draw_foggy,
|
||||||
|
'windy': draw_windy,
|
||||||
|
'windy-variant': draw_windy,
|
||||||
|
'exceptional': draw_unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def draw_weather_icon(canvas, x, y, weather_state):
|
||||||
|
"""Draw weather icon based on state string."""
|
||||||
|
weather_lower = weather_state.lower() if weather_state else ''
|
||||||
|
|
||||||
|
# Try exact match
|
||||||
|
if weather_lower in WEATHER_ICONS:
|
||||||
|
WEATHER_ICONS[weather_lower](canvas, x, y)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Try partial match
|
||||||
|
for key, draw_func in WEATHER_ICONS.items():
|
||||||
|
if key in weather_lower or weather_lower in key:
|
||||||
|
draw_func(canvas, x, y)
|
||||||
|
return
|
||||||
|
|
||||||
|
draw_unknown(canvas, x, y)
|
||||||
Reference in New Issue
Block a user