Skip to content
9 changes: 7 additions & 2 deletions python/PiFinder/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ def main(
if (
not location.source == "WEB"
and not location.source.startswith("CONFIG:")
and not location.source == "MANUAL"
and (
location.error_in_m == 0
or float(gps_content["error_in_m"])
Expand Down Expand Up @@ -610,18 +611,22 @@ def main(
location.lon,
location.altitude,
)
if gps_msg == "time":
if gps_msg in ("time", "time_force"):
if isinstance(gps_content, datetime.datetime):
gps_dt = gps_content
else:
gps_dt = gps_content["time"]
shared_state.set_datetime(gps_dt)
shared_state.set_datetime(
gps_dt, force=(gps_msg == "time_force")
)
if log_time:
logger.info("GPS Time (logged only once): %s", gps_dt)
log_time = False
if gps_msg == "reset":
location.reset()
shared_state.set_location(location)
if gps_msg == "reset_datetime":
shared_state.reset_datetime()
if gps_msg == "satellites":
# logger.debug("Main: GPS nr sats seen: %s", gps_content)
shared_state.set_sats(gps_content)
Expand Down
14 changes: 2 additions & 12 deletions python/PiFinder/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pydeepskylog as pds
from PIL import Image
from PiFinder import utils, calc_utils, config
from PiFinder.state import Location
from PiFinder.db.observations_db import (
ObservationsDatabase,
)
Expand Down Expand Up @@ -938,18 +939,7 @@ def serve_pil_image():

@auth_required
def gps_lock(lat: float = 50, lon: float = 3, altitude: float = 10):
msg = (
"fix",
{
"lat": lat,
"lon": lon,
"altitude": altitude,
"error_in_m": 0,
"source": "WEB",
"lock": True,
},
)
self.gps_queue.put(msg)
self.gps_queue.put(Location.make_fix(lat, lon, altitude, "WEB"))
logger.debug("Putting location msg on gps_queue: {msg}")

@auth_required
Expand Down
37 changes: 36 additions & 1 deletion python/PiFinder/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,24 @@ def __str__(self):
f"{f', last_lock={self.last_gps_lock}' if self.last_gps_lock else ''})"
)

@staticmethod
def make_fix(
lat: float, lon: float, altitude: float = 0, source: str = "MANUAL"
) -> tuple:
"""Build a GPS fix message tuple for the gps_queue."""
return (
"fix",
{
"lat": lat,
"lon": lon,
"altitude": altitude,
"error_in_m": 0,
"source": source,
"lock": True,
"lock_type": 2,
},
)

def reset(self):
self.lat = 0.0
self.lon = 0.0
Expand Down Expand Up @@ -264,6 +282,7 @@ def __init__(self):
self.__sqm_details: dict = {} # Full SQM calculation details for calibration
self.__datetime = None
self.__datetime_time = None
self.__datetime_manual = False # True when manually set, blocks GPS overrides
self.__screen = None
self.__solve_pixel = config.Config().get_option("solve_pixel")
self.__arch = None
Expand Down Expand Up @@ -416,11 +435,21 @@ def local_datetime(self):
return dt.astimezone(pytz.timezone("UTC"))
return dt.astimezone(pytz.timezone("UTC"))

def set_datetime(self, dt):
def set_datetime(self, dt, force=False):
if dt.tzname() is None:
utc_tz = pytz.timezone("UTC")
dt = utc_tz.localize(dt)

if force:
self.__datetime_time = time.time()
self.__datetime = dt
self.__datetime_manual = True
return

# Skip GPS time updates when time was manually set
if self.__datetime_manual:
return

if self.__datetime is None:
self.__datetime_time = time.time()
self.__datetime = dt
Expand All @@ -435,6 +464,12 @@ def set_datetime(self, dt):
self.__datetime_time = time.time()
self.__datetime = dt

def reset_datetime(self):
"""Clear manual datetime override, allowing GPS time updates again."""
self.__datetime = None
self.__datetime_time = None
self.__datetime_manual = False

def screen(self):
return self.__screen

Expand Down
82 changes: 78 additions & 4 deletions python/PiFinder/ui/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@
import logging
import gettext
import time
from datetime import datetime

import pytz

from typing import Any, TYPE_CHECKING
from PiFinder import utils, calc_utils
from PiFinder.locations import Location as SavedLocation
from PiFinder.state import Location
from PiFinder.ui.base import UIModule
from PiFinder.ui.textentry import UITextEntry
from PiFinder.catalogs import CatalogFilter
from PiFinder.composite_object import CompositeObject, MagnitudeObject

Expand Down Expand Up @@ -250,18 +256,68 @@ def get_wifi_mode(ui_module: UIModule) -> list[str]:
return [wfs.read()]


def set_location(ui_module: UIModule) -> None:
"""
Sets location from the coordinate entry UI.
Reads lat, lon, alt from item_definition (passed through the chain).
"""
lat = ui_module.item_definition.get("lat", 0.0)
lon = ui_module.item_definition.get("lon", 0.0)
alt = ui_module.item_definition.get("alt", 0)
logger.info(f"Setting location to: lat={lat}, lon={lon}, alt={alt}")

ui_module.command_queues["gps"].put(Location.make_fix(lat, lon, alt, "MANUAL"))
ui_module.message(
_("{lat:.2f}, {lon:.2f}\n{alt}m alt").format(lat=lat, lon=lon, alt=alt),
2,
)


def gps_reset(ui_module: UIModule) -> None:
ui_module.command_queues["gps"].put(("reset", {}))
ui_module.message("Location Reset", 2)


def datetime_reset(ui_module: UIModule) -> None:
ui_module.command_queues["gps"].put(("reset_datetime", {}))
ui_module.message("Time/Date Reset", 2)


def save_location(ui_module: UIModule) -> None:
"""Save current location — prompts for name via text entry."""
location = ui_module.shared_state.location()
if not location.lock:
ui_module.message(_("No location lock"), 2)
return

def _save(name):
new_loc = SavedLocation(
name=name,
latitude=location.lat,
longitude=location.lon,
height=location.altitude,
error_in_m=location.error_in_m,
source=location.source,
)
ui_module.config_object.locations.add_location(new_loc)
ui_module.config_object.save_locations()
ui_module.message(_("Saved\n{name}").format(name=name), 2)

num = len(ui_module.config_object.locations.locations) + 1
item_definition = {
"name": _("Location Name"),
"class": UITextEntry,
"mode": "text_entry",
"initial_text": _("Loc {number}").format(number=num),
"callback": _save,
}
ui_module.add_to_stack(item_definition)


def set_time(ui_module: UIModule, time_str: str) -> None:
"""
Sets the time from the time entry UI
"""
from datetime import datetime
import pytz

logger.info(f"Setting time to: {time_str}")

timezone_str = ui_module.shared_state.location().timezone
Expand All @@ -278,10 +334,28 @@ def set_time(ui_module: UIModule, time_str: str) -> None:
dt_with_date = datetime(now.year, now.month, now.day, dt.hour, dt.minute, dt.second)
dt_with_timezone = timezone.localize(dt_with_date)

ui_module.command_queues["gps"].put(("time", {"time": dt_with_timezone}))
ui_module.command_queues["gps"].put(("time_force", {"time": dt_with_timezone}))
ui_module.message(_("Time: {time}").format(time=time_str), 2)


def set_datetime(ui_module: UIModule, date_str: str) -> None:
"""
Sets both date and time from the date entry UI.
Reads the time_str from the item_definition (passed from UITimeEntry).
"""
time_str = ui_module.item_definition.get("time_str", "00:00:00")
logger.info(f"Setting datetime to: {date_str} {time_str}")

timezone_str = ui_module.shared_state.location().timezone
timezone = pytz.timezone(timezone_str)

dt = datetime.strptime(f"{date_str} {time_str}", "%Y-%m-%d %H:%M:%S")
dt_with_timezone = timezone.localize(dt)

ui_module.command_queues["gps"].put(("time_force", {"time": dt_with_timezone}))
ui_module.message(_("{date}\n{time}").format(date=date_str, time=time_str), 2)


def handle_radec_entry(ui_module: UIModule, ra_deg: float, dec_deg: float) -> None:
"""
Handles RA/DEC coordinate entry from the coordinate input UI
Expand Down
Loading
Loading