I create a script to get past Open-Meteo forcasts with a 15' resolution, works for the last 14 days-
import urllib.request
import json
from datetime import datetime, timedelta
import time
# ----------------------------------------------------------------------
# NO LOG FILE – console & GC.log only
# ----------------------------------------------------------------------
def log(msg):
ts = datetime.now().strftime('%H:%M:%S')
full = f"[{ts}] {msg}"
print(full)
try:
GC.log(msg + "\n")
except Exception:
pass
BASE_URL = "
https://api.open-meteo.com/v1/forecast"
def fetch_with_retry(url, retries=5, timeout=60):
headers = {"User-Agent": "Mozilla/5.0"}
for attempt in range(1, retries + 1):
try:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=timeout) as resp:
return json.loads(resp.read().decode())
except Exception as e:
log(f"Attempt {attempt}/{retries} failed: {e}")
if attempt < retries:
time.sleep(2 ** attempt)
else:
raise
return None
def main():
log("--- Weather Export (timezone=auto) ---")
try:
# Activity metrics
act_date = GC.activityMetrics()["date"]
act_time = GC.activityMetrics()["time"]
act_duration = float(GC.activityMetrics()["Duration"])
log(f"Activity: {act_date} {act_time} Duration: {act_duration:.0f}s")
except Exception as e:
log(f"Error reading activity: {e}")
return
# Delete old tab
try:
GC.deleteXData("WEATHER")
log("Removed existing WEATHER tab.")
except Exception:
pass
# GPS average
lat_raw = GC.series(GC.SERIES_LAT)
lon_raw = GC.series(GC.SERIES_LON)
lat = [float(x) for x in lat_raw if x is not None and float(x) != 0.0]
lon = [float(x) for x in lon_raw if x is not None and float(x) != 0.0]
if not lat or not lon:
log("No valid GPS data.")
return
avg_lat, avg_lon = sum(lat) / len(lat), sum(lon) / len(lon)
log(f"GPS center: {avg_lat:.4f}, {avg_lon:.4f}")
# Build URL
vars_list = [
"temperature_2m", "relative_humidity_2m", "apparent_temperature",
"dewpoint_2m", "precipitation", "surface_pressure",
"wind_speed_10m", "wind_direction_10m", "wind_gusts_10m", "uv_index"
]
url = (f"{BASE_URL}?latitude={avg_lat:.6f}&longitude={avg_lon:.6f}"
f"&minutely_15={','.join(vars_list)}&past_days=90&timezone=auto")
log("Fetching data...")
try:
data = fetch_with_retry(url)
except Exception as e:
log(f"Fetch failed: {e}")
return
if not data:
log("No data returned.")
return
detected_tz = data.get('timezone', 'unknown')
log(f"Detected timezone: {detected_tz}")
minutely = data.get('minutely_15')
if not minutely:
log("Missing minutely_15 data.")
return
times = minutely.get('time')
if not times:
log("No time data.")
return
# Parse start time
try:
h, m, _ = map(int, str(act_time).split(':')[:3])
except:
h = m = 0
start = datetime.combine(act_date, datetime.min.time()) + timedelta(hours=h, minutes=m)
end = start + timedelta(seconds=act_duration)
log(f"Local range: {start} → {end}")
# Collect points (15 min before to 15 min after)
known = []
margin = timedelta(minutes=15)
for i, tstr in enumerate(times):
dt = datetime.fromisoformat(tstr)
if dt.date() != act_date:
continue
if dt < start - margin or dt > end + margin:
continue
elapsed = (dt - start).total_seconds()
known.append((
elapsed,
float(minutely.get('temperature_2m', [0])[i] or 0),
float(minutely.get('relative_humidity_2m', [0])[i] or 0),
float(minutely.get('apparent_temperature', [0])[i] or 0),
float(minutely.get('dewpoint_2m', [0])[i] or 0),
float(minutely.get('precipitation', [0])[i] or 0),
float(minutely.get('surface_pressure', [0])[i] or 0),
float(minutely.get('wind_speed_10m', [0])[i] or 0),
float(minutely.get('wind_direction_10m', [0])[i] or 0),
float(minutely.get('wind_gusts_10m', [0])[i] or 0),
float(minutely.get('uv_index', [0])[i] or 0)
))
if len(known) < 2:
log("Not enough weather points.")
return
known.sort(key=lambda p: p[0])
# Interpolate sec=0
idx = 0
while idx < len(known) and known[idx][0] < 0:
idx += 1
if idx > 0:
prev = known[idx - 1]
log(f"Previous point: sec={prev[0]:.0f}, temp={prev[1]:.1f}")
if idx < len(known) and known[idx][0] == 0:
sec0 = known[idx]
else:
if idx > 0 and idx < len(known):
p0, p1 = known[idx - 1], known[idx]
x0, x1 = p0[0], p1[0]
def interp(j):
return p0[j] + (p1[j] - p0[j]) * (0 - x0) / (x1 - x0) if x1 != x0 else p0[j]
sec0 = (0.0, interp(1), interp(2), interp(3), interp(4),
interp(5), interp(6), interp(7), interp(8), interp(9), interp(10))
log("Interpolated sec=0 from previous & next.")
else:
log("Cannot interpolate sec=0.")
return
# Build final points
points = [sec0]
for p in known:
if p[0] > 0:
capped = min(p[0], act_duration)
points.append((capped,) + p[1:])
points.sort(key=lambda p: p[0])
# Remove duplicates
unique = []
seen = set()
for p in points:
if p[0] not in seen:
seen.add(p[0])
unique.append(p)
points = unique
if points[0][0] != 0:
points.insert(0, sec0)
log(f"Writing {len(points)} points.")
# Create XData tab
tab = "WEATHER"
fields = [
("secs", "s"),
("TEMPERATURE", "C"),
("HUMIDITY", "%"),
("APPARENT_TEMP", "C"),
("DEWPOINT", "C"),
("PRECIPITATION", "mm"),
("SURFACE_PRESSURE", "hPa"),
("WINDSPEED", "km/h"),
("WIND_DIRECTION", "°"),
("WIND_GUSTS", "km/h"),
("UV_INDEX", "index")
]
for name, unit in fields:
GC.createXDataSeries(tab, name, unit)
# Populate with rounding
for pt in points:
sec = int(pt[0])
vals = list(pt[1:])
secs_series = GC.xdataSeries(tab, "secs")
secs_series.append(sec)
idx = len(secs_series) - 1
for j, (name, _) in enumerate(fields[1:]):
val = vals[j]
if name == "HUMIDITY":
val = round(val)
else:
val = round(val, 1)
series = GC.xdataSeries(tab, name)
while len(series) <= idx:
series.append(0.0)
series[idx] = val
# Save
try:
GC.activitySave()
except:
pass
log(f"✅ Wrote {len(points)} points to 'WEATHER' tab.")
log(f"Timezone: {detected_tz}")
if __name__ == "__main__":
main()