This commit is contained in:
2026-04-05 15:20:28 -04:00
parent 5dbbeb3394
commit 02d9e06077
4 changed files with 81 additions and 22 deletions

View File

@@ -18,7 +18,6 @@ from dotenv import load_dotenv
with warnings.catch_warnings():
# Ignore: "Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater."
warnings.simplefilter("ignore", category=UserWarning)
warnings.simplefilter("ignore", category=UserWarning)
import fastapi
import uvicorn
from fastapi import FastAPI, APIRouter, Request, Response
@@ -53,6 +52,10 @@ from pymobiledevice3.services.dvt.instruments.location_simulation import (
LocationSimulation,
)
from pymobiledevice3.services.dvt.instruments.dvt_provider import DvtProvider
try:
from pymobiledevice3.services.simulate_location import DtSimulateLocation
except Exception:
DtSimulateLocation = None
from pymobiledevice3.tunneld.server import TunneldCore, TunnelTask
from .icloud_monitor import FindMyMonitor
@@ -575,7 +578,11 @@ class TunneldRunnerSio:
logger.exception("Simulation worker crashed")
await self.context.sio.emit(
"appError",
{"type": "simulation_crash", "udid": self.context.udid},
{
"type": "simulation_crash",
"udid": self.context.udid,
"error": traceback.format_exc(),
},
namespace="/",
)
finally:
@@ -891,7 +898,7 @@ class TunneldRunnerSio:
if not self.context.test_mode and self.context.tunnel is not None:
async with (
DvtProvider(self.context.tunnel) as dvt,
LocationSimulationQueue(dvt) as locate_simulation,
LocationSimulationQueue(dvt, self.context) as locate_simulation,
):
await locate_simulation.clear()
@@ -1718,9 +1725,48 @@ class LocationSimulationQueue(LocationSimulation):
def __init__(self, dvt, context: LocationSimulationState):
super().__init__(dvt)
self.context = context
self._dt_simulate_location = None
self._prefer_dt_simulate_location = False
self._noise_task: Optional[asyncio.Task] = None
self._noise_loc_id: Optional[str] = None
async def _get_dt_simulate_location(self):
if DtSimulateLocation is None:
raise RuntimeError("DtSimulateLocation is not available in this pymobiledevice3 build")
if self._dt_simulate_location is None:
lockdown = getattr(self.provider, "lockdown", None)
if lockdown is None:
raise RuntimeError("DVT provider does not expose lockdown provider for fallback simulation")
self._dt_simulate_location = DtSimulateLocation(lockdown)
return self._dt_simulate_location
async def set(self, latitude: float, longitude: float) -> None:
if not self._prefer_dt_simulate_location:
try:
await super().set(latitude, longitude)
return
except Exception:
logger.exception(
"DVT location set failed; switching to DtSimulateLocation fallback"
)
self._prefer_dt_simulate_location = True
fallback = await self._get_dt_simulate_location()
await fallback.set(latitude, longitude)
async def clear(self) -> None:
dvt_clear_error = None
if not self._prefer_dt_simulate_location:
try:
await super().clear()
except Exception as e:
dvt_clear_error = e
logger.warning("DVT location clear failed: %s", e)
if self._prefer_dt_simulate_location:
fallback = await self._get_dt_simulate_location()
await fallback.clear()
elif dvt_clear_error is not None:
raise dvt_clear_error
@staticmethod
def _add_gps_noise(lat: float, lon: float, std_dev_meters: float = 5.0) -> tuple[float, float]:
"""Apply Gaussian jitter in meters and convert to lat/lon deltas."""
@@ -1776,6 +1822,18 @@ class LocationSimulationQueue(LocationSimulation):
name=f"simulation-noise-{loc_id}",
)
def _update_queue_data(self):
data = {
"simulation_queue": {
"active": self.context.simulation_active,
"data": self.context.simulation_queue_data,
"order": self.context.simulation_queue_order,
"state": self.context.simulation_queue_state,
"worker_task": self.context.simulation_task.get_name() if self.context.simulation_task else None,
}
}
self.context.sio.emit("queue_data_update", {"data": data}, namespace="/")
async def play_queue(
self, disable_sleep: bool = False, timing_randomness_range: int = 0
) -> None:
@@ -1861,7 +1919,7 @@ class LocationSimulationQueue(LocationSimulation):
if self.context.loc_id is not None:
self.context.simulation_queue_data[self.context.loc_id]["end"] = datetime.now(
timezone.utc).isoformat()
update_queue_data()
self._update_queue_data()
await self._stop_noise_task()
await self.set(new_latitude, new_longitude)