Merge branch 'main' of https://github.com/HamsterSpaceNerd3000/RocketLaunchCountdown
This commit is contained in:
96
main.py
96
main.py
@@ -1,12 +1,16 @@
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
import requests
|
import requests
|
||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
try:
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
except Exception:
|
||||||
|
ZoneInfo = None
|
||||||
|
|
||||||
|
|
||||||
# Get the user's Documents folder (cross-platform)
|
# Get the user's Documents folder (cross-platform)
|
||||||
@@ -19,9 +23,9 @@ os.makedirs(app_folder, exist_ok=True)
|
|||||||
# Define file paths
|
# Define file paths
|
||||||
COUNTDOWN_HTML = os.path.join(app_folder, "countdown.html")
|
COUNTDOWN_HTML = os.path.join(app_folder, "countdown.html")
|
||||||
GONOGO_HTML = os.path.join(app_folder, "gonogo.html")
|
GONOGO_HTML = os.path.join(app_folder, "gonogo.html")
|
||||||
SHEET_LINK = "https://docs.google.com/spreadsheets/d/1UPJTW8vH2mgEzispjg_Y_zSqYTFaLoxuoZnqleVlSZ0/export?format=csv&gid=855477916"
|
SHEET_LINK = ""
|
||||||
session = requests.Session()
|
session = requests.Session()
|
||||||
appVersion = "0.3.0"
|
appVersion = "0.4.0"
|
||||||
SETTINGS_FILE = os.path.join(app_folder, "settings.json")
|
SETTINGS_FILE = os.path.join(app_folder, "settings.json")
|
||||||
|
|
||||||
# Default settings
|
# Default settings
|
||||||
@@ -34,6 +38,14 @@ DEFAULT_SETTINGS = {
|
|||||||
"vehicle_row": 4,
|
"vehicle_row": 4,
|
||||||
"column": 12 # 1-based column (default column 12 -> index 11)
|
"column": 12 # 1-based column (default column 12 -> index 11)
|
||||||
}
|
}
|
||||||
|
# default timezone: 'local' uses system local tz, otherwise an IANA name or 'UTC'
|
||||||
|
DEFAULT_SETTINGS.setdefault('timezone', 'local')
|
||||||
|
|
||||||
|
# A small list of common timezone choices.
|
||||||
|
TIMEZONE_CHOICES = [
|
||||||
|
'local', 'UTC', 'US/Eastern', 'US/Central', 'US/Mountain', 'US/Pacific',
|
||||||
|
'Europe/London', 'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def load_settings():
|
def load_settings():
|
||||||
@@ -276,13 +288,21 @@ class CountdownApp:
|
|||||||
self.seconds_entry.insert(0, "0")
|
self.seconds_entry.insert(0, "0")
|
||||||
self.seconds_entry.pack(side="left", padx=2)
|
self.seconds_entry.pack(side="left", padx=2)
|
||||||
|
|
||||||
# Clock time input
|
# Clock time input (separate HH, MM, SS boxes)
|
||||||
frame_clock = tk.Frame(root, bg="black")
|
frame_clock = tk.Frame(root, bg="black")
|
||||||
frame_clock.pack(pady=5)
|
frame_clock.pack(pady=5)
|
||||||
tk.Label(frame_clock, text="HH:MM", fg="white", bg="black").pack(side="left")
|
tk.Label(frame_clock, text="Clock (HH:MM:SS)", fg="white", bg="black").pack(side="left")
|
||||||
self.clock_entry = tk.Entry(frame_clock, width=7, font=("Arial", 18))
|
self.clock_hours_entry = tk.Entry(frame_clock, width=3, font=("Arial", 18), fg='white', bg='#111', insertbackground='white')
|
||||||
self.clock_entry.insert(0, "14:00")
|
self.clock_hours_entry.insert(0, "14")
|
||||||
self.clock_entry.pack(side="left", padx=5)
|
self.clock_hours_entry.pack(side="left", padx=2)
|
||||||
|
tk.Label(frame_clock, text=":", fg="white", bg="black").pack(side="left")
|
||||||
|
self.clock_minutes_entry = tk.Entry(frame_clock, width=3, font=("Arial", 18), fg='white', bg='#111', insertbackground='white')
|
||||||
|
self.clock_minutes_entry.insert(0, "00")
|
||||||
|
self.clock_minutes_entry.pack(side="left", padx=2)
|
||||||
|
tk.Label(frame_clock, text=":", fg="white", bg="black").pack(side="left")
|
||||||
|
self.clock_seconds_entry = tk.Entry(frame_clock, width=3, font=("Arial", 18), fg='white', bg='#111', insertbackground='white')
|
||||||
|
self.clock_seconds_entry.insert(0, "00")
|
||||||
|
self.clock_seconds_entry.pack(side="left", padx=2)
|
||||||
|
|
||||||
# Control buttons
|
# Control buttons
|
||||||
frame_buttons = tk.Frame(root, bg="black")
|
frame_buttons = tk.Frame(root, bg="black")
|
||||||
@@ -363,7 +383,7 @@ class CountdownApp:
|
|||||||
win = tk.Toplevel(self.root)
|
win = tk.Toplevel(self.root)
|
||||||
win.config(bg="black")
|
win.config(bg="black")
|
||||||
win.title("Settings")
|
win.title("Settings")
|
||||||
win.geometry("560x200")
|
win.geometry("560x250")
|
||||||
win.transient(self.root)
|
win.transient(self.root)
|
||||||
|
|
||||||
# Mode selection
|
# Mode selection
|
||||||
@@ -453,6 +473,16 @@ class CountdownApp:
|
|||||||
frame_buttons_cfg.config(bg='black')
|
frame_buttons_cfg.config(bg='black')
|
||||||
frame_buttons_cfg.pack(fill='x', padx=8, pady=6)
|
frame_buttons_cfg.pack(fill='x', padx=8, pady=6)
|
||||||
|
|
||||||
|
# Timezone selector
|
||||||
|
tz_frame = tk.Frame(frame_sheet, bg='black')
|
||||||
|
tz_frame.pack(fill='x', padx=6, pady=4)
|
||||||
|
tk.Label(tz_frame, text='Timezone:', fg='white', bg='black').pack(side='left')
|
||||||
|
tz_var = tk.StringVar(value=settings.get('timezone', DEFAULT_SETTINGS.get('timezone', 'local')))
|
||||||
|
# OptionMenu with a few choices, but user may edit the text to any IANA name
|
||||||
|
tz_menu = tk.OptionMenu(tz_frame, tz_var, *TIMEZONE_CHOICES)
|
||||||
|
tz_menu.config(fg='white', bg='#222', activebackground='#333')
|
||||||
|
tz_menu.pack(side='left', padx=6)
|
||||||
|
|
||||||
def set_manual(val_type, val):
|
def set_manual(val_type, val):
|
||||||
# store on fetch_gonogo func for now
|
# store on fetch_gonogo func for now
|
||||||
if val_type == 'range':
|
if val_type == 'range':
|
||||||
@@ -523,7 +553,8 @@ class CountdownApp:
|
|||||||
# persist manual values if present
|
# persist manual values if present
|
||||||
'manual_range': getattr(fetch_gonogo, 'manual_range', None),
|
'manual_range': getattr(fetch_gonogo, 'manual_range', None),
|
||||||
'manual_weather': getattr(fetch_gonogo, 'manual_weather', None),
|
'manual_weather': getattr(fetch_gonogo, 'manual_weather', None),
|
||||||
'manual_vehicle': getattr(fetch_gonogo, 'manual_vehicle', None)
|
'manual_vehicle': getattr(fetch_gonogo, 'manual_vehicle', None),
|
||||||
|
'timezone': tz_var.get()
|
||||||
}
|
}
|
||||||
save_settings(new_settings)
|
save_settings(new_settings)
|
||||||
# update immediately
|
# update immediately
|
||||||
@@ -551,12 +582,16 @@ class CountdownApp:
|
|||||||
self.hours_entry.config(state="normal")
|
self.hours_entry.config(state="normal")
|
||||||
self.minutes_entry.config(state="normal")
|
self.minutes_entry.config(state="normal")
|
||||||
self.seconds_entry.config(state="normal")
|
self.seconds_entry.config(state="normal")
|
||||||
self.clock_entry.config(state="disabled")
|
self.clock_hours_entry.config(state="disabled")
|
||||||
|
self.clock_minutes_entry.config(state="disabled")
|
||||||
|
self.clock_seconds_entry.config(state="disabled")
|
||||||
else:
|
else:
|
||||||
self.hours_entry.config(state="disabled")
|
self.hours_entry.config(state="disabled")
|
||||||
self.minutes_entry.config(state="disabled")
|
self.minutes_entry.config(state="disabled")
|
||||||
self.seconds_entry.config(state="disabled")
|
self.seconds_entry.config(state="disabled")
|
||||||
self.clock_entry.config(state="normal")
|
self.clock_hours_entry.config(state="normal")
|
||||||
|
self.clock_minutes_entry.config(state="normal")
|
||||||
|
self.clock_seconds_entry.config(state="normal")
|
||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Manual controls & helpers
|
# Manual controls & helpers
|
||||||
@@ -632,13 +667,36 @@ class CountdownApp:
|
|||||||
total_seconds = h * 3600 + m * 60 + s
|
total_seconds = h * 3600 + m * 60 + s
|
||||||
else:
|
else:
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
parts = [int(p) for p in self.clock_entry.get().split(":")]
|
# read separate HH, MM, SS boxes
|
||||||
h, m = parts[0], parts[1]
|
h = int(self.clock_hours_entry.get() or 0)
|
||||||
s = parts[2] if len(parts) == 3 else 0
|
m = int(self.clock_minutes_entry.get() or 0)
|
||||||
target_today = now.replace(hour=h, minute=m, second=s, microsecond=0)
|
s = int(self.clock_seconds_entry.get() or 0)
|
||||||
if target_today < now:
|
# determine timezone from settings
|
||||||
target_today = target_today.replace(day=now.day + 1)
|
ssettings = load_settings()
|
||||||
total_seconds = (target_today - now).total_seconds()
|
tzname = ssettings.get('timezone', DEFAULT_SETTINGS.get('timezone', 'local'))
|
||||||
|
if ZoneInfo is None or tzname in (None, '', 'local'):
|
||||||
|
# naive local time handling (existing behavior) — use timedelta to roll day
|
||||||
|
target_today = now.replace(hour=h, minute=m, second=s, microsecond=0)
|
||||||
|
if target_today <= now:
|
||||||
|
target_today = target_today + timedelta(days=1)
|
||||||
|
total_seconds = (target_today - now).total_seconds()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
tz = ZoneInfo(tzname)
|
||||||
|
# construct aware "now" in that timezone and create the target time
|
||||||
|
now_tz = datetime.now(tz)
|
||||||
|
target = now_tz.replace(hour=h, minute=m, second=s, microsecond=0)
|
||||||
|
# if target already passed in that tz, roll to next day
|
||||||
|
if target <= now_tz:
|
||||||
|
target = target + timedelta(days=1)
|
||||||
|
# compute total seconds using aware-datetime subtraction to avoid epoch mixing
|
||||||
|
total_seconds = (target - now_tz).total_seconds()
|
||||||
|
except Exception:
|
||||||
|
# fallback to naive local behavior
|
||||||
|
target_today = now.replace(hour=h, minute=m, second=s, microsecond=0)
|
||||||
|
if target_today <= now:
|
||||||
|
target_today = target_today + timedelta(days=1)
|
||||||
|
total_seconds = (target_today - now).total_seconds()
|
||||||
except Exception:
|
except Exception:
|
||||||
self.text.config(text="Invalid time")
|
self.text.config(text="Invalid time")
|
||||||
write_countdown_html(self.mission_name, "Invalid time")
|
write_countdown_html(self.mission_name, "Invalid time")
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
RocketLaunchCountdown is a python and HTML system to operate a launch countdown and go/nogo indicator. The go/nogo indicator is operated either by a spreadsheet, using a sharelink inserted in the settings windows. Or there are buttons in the app that can manage it as well. Which mode is used is dictated by a simple radio button in the settings window.
|
RocketLaunchCountdown is a python and HTML system to operate a launch countdown and go/nogo indicator. The go/nogo indicator is operated either by a spreadsheet, using a sharelink inserted in the settings windows. Or there are buttons in the app that can manage it as well. Which mode is used is dictated by a simple radio button in the settings window.
|
||||||
|
|
||||||
Latest release: [RocketLaunchCountdown 0.3.0](https://github.com/HamsterSpaceNerd3000/RocketLaunchCountdown/releases/tag/Prealpha_030)
|
Latest release: [RocketLaunchCountdown 0.4.0 THE CLOCKS UPDATE](https://github.com/HamsterSpaceNerd3000/RocketLaunchCountdown/releases/tag/Prealpha_040)
|
||||||
|
Features Added:
|
||||||
|
Full rework of the clock system (countdown method based on clock time, not how long until).
|
||||||
|
Minor background fixes
|
||||||
|
|
||||||
|
|
||||||
INSTALL INSTRUCTIONS
|
INSTALL INSTRUCTIONS
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user