import Toybox.Lang; import Toybox.Position; import Toybox.Timer; // Single-shot GPS acquisition with a hard timeout. Uses both // enableLocationEvents (fires on real hardware) and a polling // fallback via Position.getInfo() (works in the simulator where // "Set Position" doesn't trigger location events). class GpsService { private var _callback as Method(result as Dictionary or Null) as Void; private var _timer as Timer.Timer; private var _pollTimer as Timer.Timer; private var _finished as Boolean = false; private var _bestFix as Dictionary or Null = null; function initialize(callback as Method(result as Dictionary or Null) as Void) { _callback = callback; _timer = new Timer.Timer(); _pollTimer = new Timer.Timer(); } function start() as Void { Position.enableLocationEvents( Position.LOCATION_CONTINUOUS, method(:_onPosition) ); // Poll Position.getInfo() every 500ms as fallback for simulator. _pollTimer.start(method(:_poll), 500, true); _timer.start(method(:_onTimeout), Config.GPS_TIMEOUT_MS, false); } function _poll() as Void { if (_finished) { return; } var info = Position.getInfo(); if (info != null && info.position != null) { _processInfo(info); } } function _onPosition(info as Position.Info) as Void { if (_finished) { return; } _processInfo(info); } private function _processInfo(info as Position.Info) as Void { if (info == null || info.position == null) { return; } var degrees = info.position.toDegrees(); var quality = (info.accuracy != null) ? info.accuracy : 0; // Always keep the best quality fix. if (_bestFix == null || quality >= (_bestFix["q"] as Number)) { _bestFix = { "lat" => degrees[0].toFloat(), "lon" => degrees[1].toFloat(), "acc" => quality.toFloat(), "q" => quality }; } } function _onTimeout() as Void { _finish(_bestFix); } function _finish(result as Dictionary or Null) as Void { if (_finished) { return; } _finished = true; _timer.stop(); _pollTimer.stop(); Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:_onPosition)); _callback.invoke(result); } }