Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CONTROL/preinst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ else
echo "⚠ Could not install ${PY}-requests, but continuing installation"
fi

echo "Installing ${PY}-pillow..."
if opkg install --force-reinstall "${PY}-pillow" > /dev/null 2>&1; then
echo "✓ ${PY}-pillow installed successfully"
else
echo "⚠ Could not install ${PY}-pillow, but continuing installation"
fi

# Additional dependency checks
echo "Verifying system compatibility..."
if [ -e "/usr/bin/enigma2" ]; then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# fallback

import requests
from urllib.parse import quote_plus
from os.path import exists, join
from enigma import eListboxPythonMultiContent, gFont, RT_VALIGN_CENTER, eTimer, eListbox

Expand Down Expand Up @@ -296,7 +297,8 @@ def search_online(self, search_term):
"""Cerca tramite API Foreca. Ritorna True se ha trovato risultati, False altrimenti."""
current_lang = _get_system_language()
try:
url = "%s/locations/search/%s.json" % (BASE_URL, search_term)
url = "%s/locations/search/%s.json" % (
BASE_URL, quote_plus(search_term))
params = {
"limit": 20,
"lang": current_lang
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ def load_config(self):
config_data = default_config

# Assign values
self.user = config_data.get("API_USER", "ekekaz")
self.password = config_data.get("API_PASSWORD", "im5issEYcMUG")
self.user = config_data.get("API_USER", "your_username_here")
self.password = config_data.get("API_PASSWORD", "your_password_here")
self.token_expire_hours = int(
config_data.get(
"TOKEN_EXPIRE_HOURS", 720))
Expand Down Expand Up @@ -150,10 +150,10 @@ def create_example_config(self):
# Rename this file to api_config.txt and fill with your credentials

# Your Foreca API username
API_USER=ekekaz
API_USER=your_username_here

# Your Foreca API password
API_PASSWORD=im5issEYcMUG
API_PASSWORD=your_password_here

# Token expiration in hours (max 720 = 30 days)
TOKEN_EXPIRE_HOURS=720
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,13 @@ def load_layers(self):
self.layers = self.api.get_capabilities()
if DEBUG:
print(f"[DEBUG] Layers ricevuti ({len(self.layers)}):")
for layer in self.layers:
layer_id = layer['id']
title = layer.get('title', 'N/A')
layer_type = layer.get('type', 'N/A')
colorschemes = layer.get('colorschemes', [])
if DEBUG:
if DEBUG:
for layer in self.layers:
title = layer.get('title', 'N/A')
layer_type = layer.get('type', 'N/A')
colorschemes = layer.get('colorschemes', [])
print(
f" ID: {layer_id}, Title: {title}, Type: {layer_type}, Schemes: {colorschemes}")
f" ID: {layer['id']}, Title: {title}, Type: {layer_type}, Schemes: {colorschemes}")

if not self.layers:
self["info"].setText(_("Error loading maps. Check connection."))
Expand All @@ -81,7 +80,7 @@ def load_layers(self):
title = layer.get('title', f"Layer {layer['id']}")
if 'wind symbol' in title.lower():
continue
if layer_id == 3:
if layer['id'] == 3:
continue
items.append((trans(title), layer))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,40 @@
apply_global_theme,
TEMP_DIR
)
from .foreca_map_viewer import REGION_CENTERS, get_background_for_layer

SVG_MAPS_DIR = join(TEMP_DIR, "svgmapviewer")
if not exists(SVG_MAPS_DIR):
makedirs(SVG_MAPS_DIR)

TILE_SIZE = 256

# Fallback center coordinates per region, used when a layer has no usable extent.
REGION_CENTERS = {
'eu': (50.0, 10.0),
'europe': (50.0, 10.0),
'us': (39.0, -98.0),
'usa': (39.0, -98.0),
'africa': (1.0, 20.0),
'asia': (34.0, 100.0),
'oceania': (-25.0, 135.0),
'world': (20.0, 0.0),
}

# Maps a region to a background PNG that actually exists under thumb/.
_REGION_BACKGROUNDS = {
'eu': 'europa.png',
'europe': 'europa.png',
'us': 'nordamerika.png',
'usa': 'nordamerika.png',
'africa': 'africa.png',
'asia': 'asia_se.png',
'oceania': 'australia.png',
}


def get_background_for_layer(layer_title, region):
"""Pick a background PNG for the given region (layer_title currently unused)."""
return _REGION_BACKGROUNDS.get((region or '').lower(), 'world.png')


class ForecaSVGMapViewer(Screen, HelpableScreen):
def __init__(self, session, api, layer, unit_system='metric', region='eu'):
Expand Down
14 changes: 12 additions & 2 deletions usr/lib/enigma2/python/Plugins/Extensions/Foreca1/meteogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
from json import loads, JSONDecodeError
from os.path import exists, join
from os import makedirs, listdir, remove
from threading import Thread
import requests
from twisted.internet import reactor

from enigma import getDesktop, ePoint

Expand Down Expand Up @@ -224,11 +226,15 @@ def cleanup_temp_files(self):
print(f"[Meteogram] Error cleaning temp files: {e}")

def fetch_data(self):
"""Download the detailed forecast page and extract JSON data."""
"""Kick off the (blocking) forecast download in a background thread."""
Thread(target=self._fetch_data_worker).start()

def _fetch_data_worker(self):
"""Download the detailed forecast page and extract JSON data. Runs off the UI thread."""
lang = _get_system_language()
place = self.api.get_location_by_id(self.loc_id)
if not place:
self.close()
reactor.callFromThread(self.close)
return

url = f"https://www.foreca.com/{lang}/{self.loc_id}/{place.address}/detailed-forecast"
Expand Down Expand Up @@ -272,6 +278,10 @@ def fetch_data(self):
write_meteogram_debug(
f"First element keys: {list(forecast[0].keys())}")

reactor.callFromThread(self._apply_fetched_data, forecast, ranges)

def _apply_fetched_data(self, forecast, ranges):
"""Populate widgets from downloaded forecast data. Runs on the UI thread."""
# Update time (first element's 'updated' field)
if forecast and len(forecast) > 1:
updated_utc = forecast[1].get('updated', '').replace('Z', '+00:00')
Expand Down
10 changes: 10 additions & 0 deletions usr/lib/enigma2/python/Plugins/Extensions/Foreca1/moon_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from datetime import datetime, timedelta
from os.path import exists, join
from collections import defaultdict
from threading import Thread
from twisted.internet import reactor
from Screens.Screen import Screen
from Screens.HelpMenu import HelpableScreen
from Screens.MessageBox import MessageBox
Expand Down Expand Up @@ -156,6 +158,10 @@ def _jd_to_datetime(self, jd):
def load_calendar(self):
"""Generate the list of lunar phases and special events for the next 12 months."""
self["info"].setText(_("Calculating..."))
Thread(target=self._load_calendar_worker).start()

def _load_calendar_worker(self):
"""Heavy lunar-phase computation. Runs off the UI thread."""
self.phases = []
today = datetime.now()
# Start from the first day of next month
Expand Down Expand Up @@ -253,6 +259,10 @@ def load_calendar(self):
# Update current moon info
info = self.moon.get_phase_info()

reactor.callFromThread(self._apply_calendar_data, info)

def _apply_calendar_data(self, info):
"""Populate widgets with the computed calendar data. Runs on the UI thread."""
if info["icon_path"] and exists(info["icon_path"]):
self["current_phase_icon"].instance.setPixmapFromFile(
info["icon_path"])
Expand Down
26 changes: 17 additions & 9 deletions usr/lib/enigma2/python/Plugins/Extensions/Foreca1/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ def save(self):
f"TOKEN_EXPIRE_HOURS={token_expire_hours_int}\n")
config_file.write(f"MAP_SERVER={map_server}\n")
config_file.write(f"AUTH_SERVER={auth_server}\n")
chmod(CONFIG_FILE, 0o600)
except Exception as error:
self.session.open(
MessageBox,
Expand Down Expand Up @@ -1201,6 +1202,7 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None):
print(
f"[DEBUG] sunrise={daily_all[self.tag].sunrise}, sunset={daily_all[self.tag].sunset}")

day_selected = None
if daily_all and len(daily_all) > self.tag:
day_selected = daily_all[self.tag]

Expand Down Expand Up @@ -1269,6 +1271,7 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None):

# Hourly forecast (try free, fallback to auth)
hourly = None
target_date = None
# First try free API
try:
hourly = self.weather_api.get_hourly_forecast(
Expand Down Expand Up @@ -1395,10 +1398,10 @@ def _save_favorite(self, index, city_id):
try:
with open(filename, "w") as f:
f.write(city_id)
chmod(filename, 0o655)
chmod(filename, 0o644)
if DEBUG:
print(
f"[Foreca1] Saved {names[index]} = {city_id} (perms 655)")
f"[Foreca1] Saved {names[index]} = {city_id} (perms 644)")
except Exception as e:
print(f"[Foreca1] Error saving {names[index]}: {e}")

Expand All @@ -1407,7 +1410,7 @@ def _save_color(self):
try:
with open(path, "w") as f:
f.write(f"{self.rgbmyr} {self.rgbmyg} {self.rgbmyb}")
chmod(path, 0o655)
chmod(path, 0o644)
if DEBUG:
print(
f"[Foreca1] Color saved: {self.rgbmyr} {self.rgbmyg} {self.rgbmyb}")
Expand All @@ -1419,7 +1422,7 @@ def _save_alpha(self):
try:
with open(path, "w") as f:
f.write(self.alpha)
chmod(path, 0o655)
chmod(path, 0o644)
except Exception as e:
print("[Foreca1] Error saving alpha:", e)

Expand Down Expand Up @@ -2212,11 +2215,16 @@ def truncate(text, max_len=25):
# Truncate text if too long
station_text = truncate(station_text)

# Safely update the widget only if it exists
if "station_name" in self:
self["station_name"].setText(station_text)
if source:
print(f"[Foreca1] Station source: {source}")
from twisted.internet import reactor

def update_ui():
# Safely update the widget only if it exists
if "station_name" in self:
self["station_name"].setText(station_text)
if source:
print(f"[Foreca1] Station source: {source}")

reactor.callFromThread(update_ui)

def _update_moon(self, target_date=None):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,14 @@ def _fetch_frames(self):
_("API error")))
return
data = resp.json()
self.host = data['host']
returned_host = data['host']
if returned_host.startswith('https://') and returned_host[8:].split(
'/')[0].endswith('.rainviewer.com'):
self.host = returned_host
else:
print(
f"[RainViewer] Unexpected host in API response, ignoring: {returned_host}")
self.host = 'https://tilecache.rainviewer.com'
self.frames = [frame['path'] for frame in data['radar']['past']]
self.frames.reverse() # oldest to newest
self.current_frame = len(self.frames) - 1 # last frame
Expand Down
Loading