/// <summary>
        /// 
        /// </summary>
        /// <param name="ddg"></param>
        /// <param name="speaker"></param>
        /// <param name="trackFileName">can be null, for a saved track</param>
        public BehaviorRouteFollowing(IDriveGeometry ddg, ISpeaker speaker, string trackFileName)
            : base(ddg)
        {
            this.speaker = speaker;

            BehaviorActivateCondition = bd =>
            {
                return nextWp != null;
            };

            BehaviorDeactivateCondition = bd =>
            {
                return nextWp == null;
            };

            if (String.IsNullOrWhiteSpace(trackFileName))
            {
                speaker.Speak("Loading saved track");
                try
                {
                    missionTrack = null;

                    // Load stored waypoints:
                    // on the PC, from   C:\Users\sergei\AppData\Local\Packages\RobotPluckyPackage_sjh4qacv6p1wm\LocalState\MyTrack.xml
                    //            RPi:   \\172.16.1.175\c$\Data\Users\DefaultAccount\AppData\Local\Packages\RobotPluckyPackage_sjh4qacv6p1wm\LocalState
                    Track track = SerializableStorage<Track>.Load(savedTrackFileName).Result;

                    if (track != null)
                    {
                        missionTrack = track;
                        speaker.Speak("Loaded file " + missionTrack.Count + " trackpoints");
                    }
                }
                catch (Exception ex)
                {
                    speaker.Speak("could not load saved track file");
                }

                if(missionTrack == null)
                {
                    speaker.Speak("failed to load saved track file");
                    missionTrack = new Track();
                }
                nextWp = missionTrack.nextTargetWp;
            }
            else
            {
                speaker.Speak("Loading file " + trackFileName);

                missionTrack = new Track();

                try
                {
                    missionTrack.Init(trackFileName);

                    speaker.Speak("Loaded file " + missionTrack.Count + " trackpoints");

                    nextWp = missionTrack.nextTargetWp;
                }
                catch (Exception ex)
                {
                    speaker.Speak("could not load planned track file");
                }
            }
        }
        /// <summary>
        /// Computes goalBearingDegrees based on current location and the next trackpoint
        /// </summary>
        /// <returns></returns>
        public override IEnumerator<ISubsumptionTask> Execute()
        {
            FiredOn = true;

            while (!MustExit && !MustTerminate)
            {
                nextWp = missionTrack.nextTargetWp;

                while (!MustActivate && !MustExit && !MustTerminate)   // wait for activation 
                {
                    yield return RobotTask.Continue;    // dormant state - no need to calculate
                }

                // we have next trackpoint to go to:

                if (MustExit || MustTerminate)
                {
                    break;
                }

                //int i = 0;
                while (!MustDeactivate && !MustExit && !MustTerminate)
                {
                    nextWp = missionTrack.nextTargetWp;

                    if(nextWp == null)
                    {
                        break;
                    }

                    Direction dirToWp = nextWp.directionToWp(behaviorData.robotPose.geoPosition, behaviorData.robotPose.direction);
                    Distance distToWp = nextWp.distanceToWp(behaviorData.robotPose.geoPosition);

                    if (distToWp.Meters < 2.0d)
                    {
                        nextWp.trackpointState = TrackpointState.Passed;     // will be ignored on the next cycle
                        speaker.Speak("Waypoint " + nextWp.number + " passed");
                    }
                    else if (nextWp.estimatedTimeOfArrival.HasValue
                        && (DateTime.Now - nextWp.estimatedTimeOfArrival.Value).TotalSeconds > WAYPOINT_CANTREACH_SECONDS)
                    {
                        nextWp.trackpointState = TrackpointState.CouldNotReach;     // will be ignored on the next cycle
                        speaker.Speak("Waypoint " + nextWp.number + " could not reach");
                    }
                    else
                    {
                        goalBearingRelativeDegrees = dirToWp.bearingRelative.Value;       // update robotState
                        goalDistanceMeters = distToWp.Meters;

                        Debug.WriteLine(string.Format("IP: Trackpoint {0}  distToWp.Meters= {1}", nextWp.number, distToWp.Meters));

                        if (!nextWp.estimatedTimeOfArrival.HasValue)
                        {
                            nextWp.trackpointState = TrackpointState.SelectedAsTarget;
                            double maxVelocityMSec = ToVelocity(behaviorData.robotState.powerLevelPercent);  // m/sec                    
                            double timeToReachSec = distToWp.Meters / maxVelocityMSec;
                            nextWp.estimatedTimeOfArrival = DateTime.Now.AddSeconds(timeToReachSec);
                            speaker.Speak("Next trackpoint " + Math.Round(distToWp.Meters) + " meters away. I expect reaching it in " + Math.Round(timeToReachSec) + " seconds");
                        }
                    }

                    yield return RobotTask.Continue;
                }

                speaker.Speak("No more trackpoints");
                goalBearingRelativeDegrees = null;      // BehaviorGoToAngle will stop the robot
                goalDistanceMeters = null;
            }

            FiredOn = false;

            Debug.WriteLine("BehaviorRouteFollowing: " + (MustExit ? "MustExit" : "completed"));

            yield break;
        }