示例#1
0
        public void OnStart(object sender, IDictionary <string, object> parameters)
        {
            Task.Run(async() =>
            {
                _skillHelper = new SkillHelper(_misty);
                await Task.Delay(3000);

                // Load the path to follow.
                _followPath = new FollowPath(_misty, _skillHelper);
                List <IFollowPathCommand> commands = await _followPath.LoadCommandsAsync(PATH_FILE_NAME, MyDelegateAsync);

                // Follow the path
                await _followPath.DriveAsync(commands);

                // Dock
                await _skillHelper.DisableHazardSystemAsync();

                // Dock
#if MAP_DOCK
                bool started = await DockAlignAsync();
                if (started)
                {
                    await _skillHelper.TurnAsync(180);
                    _misty.DriveHeading(0, .6, 3000, true, OnResponse);
                    await Task.Delay(4000);
                    _misty.DriveHeading(0, .2, 1000, true, OnResponse);
                    await Task.Delay(2000);
                    _misty.PlayAudio("s_Awe.wav", 100, OnResponse);
                }
#else
                _docker = new ChargerDock(_misty, _skillHelper, HEAD_PITCH_OFFSET, HEAD_ROLL_OFFSET, HEAD_YAW_OFFSET);
                await _docker.DockAsync();
#endif
                await _skillHelper.EnableHazardSystemAsync();

                await Task.Delay(30000);

                Cleanup();
            });
        }
示例#2
0
        /// <summary>
        /// Drive straight for the specified distance in meters at a medium speed.
        /// Confirm movement with encoder values and retry if needed.
        /// If stopped due to a hazard wait a little bit for it to go away and then continue.
        /// </summary>
        /// <param name="distance"></param>
        /// <param name="slow"></param>
        public async Task <bool> DriveAsync(double distance, bool slow = false)
        {
            if (Math.Abs(distance) < 0.001)
            {
                return(true);
            }

            bool success = true;

            try
            {
                // Clear encoder values.
                int maxWait = 0;
                while (_leftEncoderValue != 0 && maxWait++ < 10)
                {
                    _misty.DriveHeading(0, 0, 100, false, OnResponse);
                    await Task.Delay(200);
                }
                if (_leftEncoderValue != 0)
                {
                    LogMessage("Failed to reset encoder values.");
                    return(false);
                }

                _leftEncoderValues  = new ConcurrentQueue <double>();
                _stopHazardLatching = false;
                bool driving     = true;
                bool sendCommand = true;

                maxWait = 0;
                while (driving)
                {
                    if (sendCommand)
                    {
                        // Arbitrary medium speed based upon distance.
                        int duration = (int)(500 + Math.Abs(distance) * 2500);
                        if (slow)
                        {
                            duration = 2 * duration;
                        }

                        LogMessage($"Sending drive command with a distance of {distance:f3} meters and a duration of {duration} ms.");
                        while (!_leftEncoderValues.IsEmpty)                         // Clearing the encoder queue
                        {
                            _leftEncoderValues.TryDequeue(out double r);
                        }
                        _misty.DriveHeading(0, Math.Abs(distance), duration, distance < 0, OnResponse);
                        sendCommand = false;
                        await Task.Delay(1000);
                    }

                    if (_abort)
                    {
                        return(false);
                    }
                    await Task.Delay(1000);

                    // Check that we really moved and if we're done.
                    // Drive command could have been dropped and/or a hazard could have stopped the robot.
                    double[] encoderValues = _leftEncoderValues.ToArray();
                    if (encoderValues.Length > 1)                     // encoder values should be arriving at 5Hz.
                    {
                        double distanceDriven = Math.Abs(encoderValues[encoderValues.Length - 1] / 1000.0);
                        //LogMessage($"Distance driven: {distanceDriven}.");
                        if (distanceDriven > Math.Abs(0.99 * distance) ||
                            Math.Abs(distanceDriven - Math.Abs(distance)) < 0.1)
                        {
                            LogMessage($"Completed drive with distance of {_leftEncoderValue / 1000.0:f3} meters.");
                            driving = false;
                        }
                        else
                        {
                            // Not there yet. Everything progressing okay?
                            if (_stopHazardLatching)
                            {
                                // We've stopped due to a hazard. Wait a bit for it to go away.
                                LogMessage("Drive command paused for hazard.");
                                _misty.PlayAudio("s_anger.wav", 100, OnResponse);
                                int maxHazardWait = 0;
                                while (_stopHazardState && maxHazardWait++ < 30)
                                {
                                    if (_abort)
                                    {
                                        return(false);
                                    }
                                    await Task.Delay(1000);
                                }
                                if (_stopHazardState)
                                {
                                    // Still in hazard state. Give up.
                                    LogMessage("Giving up on drive command due to persistent hazard condition.");
                                    success = false;
                                    driving = false;
                                }
                                else
                                {
                                    // We're out of hazard state.
                                    LogMessage("Out of hazard state.");
                                    distance           -= encoderValues[encoderValues.Length - 1] / 1000.0;
                                    _stopHazardLatching = false;
                                    sendCommand         = true;
                                }
                            }
                            if (encoderValues.Length > 2 && Math.Abs(encoderValues[encoderValues.Length - 1] - encoderValues[encoderValues.Length - 2]) < 0.0001)
                            {
                                // Encoder value not changing. Need to send drive command again.
                                LogMessage($"Encoder values not changing. Distance driven so far is {distanceDriven}.");
                                distance   -= encoderValues[encoderValues.Length - 1] / 1000.0;
                                sendCommand = true;
                            }
                        }
                    }
                    else
                    {
                        // Something is wrong if we get here.
                        // For now, just continue and hope for the best :).
                        LogMessage("Not receiving encoder messages. Can't verify drive commands.");
                    }
                }
            }
            catch (Exception ex)
            {
                LogMessage("Exception occurred within DriveAsync: " + ex.Message);
                success = false;
            }
            finally
            {
                _leftEncoderValues = null;
            }

            return(success);
        }
        private async Task <bool> ExecuteDockAsync(bool lastTry)
        {
            // Find the charger. It should be right in front of us.
            // Depending upon how far away from the charger we are we may need a different head pitch.
            // Start by assuming that we're fairly close to the charger which means we need to look more downward to see the charger.
            double headPitch = 35;
            await _skillHelper.MoveHeadAsync(headPitch, 0, 0);

            if (!await FindChargerAsync())
            {
                headPitch = 15;
                await _skillHelper.MoveHeadAsync(headPitch, 0, 0);

                if (!await FindChargerAsync())
                {
                    _skillHelper.MistySpeak("I can't find the charger.");
                    await Task.Delay(3000);

                    return(false);
                }
            }
            _skillHelper.MistySpeak("I see the charger.");

            _skillHelper.LogMessage($"Charger is located at [x, y, yaw] : [{_chargerPose.X:f3}, {_chargerPose.Y:f3}, {_chargerPose.Yaw.Degrees():f1}].");

            int retries = 0;

            while (_chargerPose.X - IDEAL_ALIGNMENT_DISTANCE > 0.04 || IDEAL_ALIGNMENT_DISTANCE - _chargerPose.X > 0.02 ||
                   Math.Abs(_chargerPose.Y) > 0.01 || Math.Abs(_chargerPose.Yaw.Degrees()) > 2.5)
            {
                var goalPose = new Simple2DPose()
                {
                    X   = IDEAL_ALIGNMENT_DISTANCE,
                    Y   = 0,
                    Yaw = 0
                };

                MoveSequence moveSequence = NavigationHelper.CalculateMoveSequence(_chargerPose, goalPose);

                _skillHelper.LogMessage($"Movement sequence to goal is turn {moveSequence.Turn1.Degrees():f0} degrees, drive {moveSequence.DriveDistance:f3} meters, " +
                                        $"and turn {moveSequence.Turn2.Degrees():f0} degrees.");

                if (!await _skillHelper.TurnAsync(moveSequence.Turn1.Degrees()))
                {
                    return(false);
                }
                if (_abort)
                {
                    return(false);
                }

                if (!await _skillHelper.DriveAsync(moveSequence.DriveDistance, true))
                {
                    return(false);
                }
                if (_abort)
                {
                    return(false);
                }

                if (!await _skillHelper.TurnAsync(moveSequence.Turn2.Degrees()))
                {
                    return(false);
                }
                if (_abort)
                {
                    return(false);
                }

                if (headPitch != 35)
                {
                    // In case we had to lift the head to originally find the charger.
                    headPitch = 35;
                    await _skillHelper.MoveHeadAsync(headPitch, 0, 0);
                }

                await Task.Delay(1000);

                if (!await CanSeeChargerAsync(1))
                {
                    _skillHelper.LogMessage("Can no longer see the charger.");
                    _skillHelper.MistySpeak("Uh oh. I can't see the charger any more.");
                    return(false);
                }

                _skillHelper.LogMessage($"Charger now located at [x, y, yaw] : [{_chargerPose.X:f3}, {_chargerPose.Y:f3}, {_chargerPose.Yaw.Degrees():f1}]. " +
                                        $"Error : [{(_chargerPose.X - goalPose.X):f3}, {(_chargerPose.Y - goalPose.Y):f3}, {(_chargerPose.Yaw.Degrees() - goalPose.Yaw.Degrees()):f1}].");

                if (retries++ > 5)
                {
                    _skillHelper.LogMessage("Failed to line up with the charger.");
                    _skillHelper.MistySpeak("I can't seem to line up right.");
                    await Task.Delay(5000);

                    return(false);
                }
            }

            _skillHelper.MistySpeak("I should be all lined up now. Going to turn around and back on to the charger.");

            // Turn around.
            // Note that we do 2 x 91 degrees. Misty tends to turn slightly less than requested and any turn where the absolute
            // value is over 180 degrees will get converted to a <180 turn in the other direction.
            _skillHelper.LogMessage("Turning 180 degrees to face away from charger.");
            if (!await _skillHelper.TurnAsync(91))
            {
                return(false);
            }
            if (_abort)
            {
                return(false);
            }
            if (!await _skillHelper.TurnAsync(91))
            {
                return(false);
            }
            if (_abort)
            {
                return(false);
            }

            // Back on to charger.
            _skillHelper.LogMessage($"Driving {IDEAL_ALIGNMENT_DISTANCE:f3} meters to back on to charger.");
            if (!await _skillHelper.DriveAsync(-IDEAL_ALIGNMENT_DISTANCE - 0.1, true))
            {
                return(false);
            }
            if (_abort)
            {
                return(false);
            }

            // Move forward slightly.
            _misty.DriveHeading(0, 0.01, 1000, false, OnResponse);

            // It can take several seconds for the charging indicator to update...
            _charging = false;
            DateTime start = DateTime.Now;

            while (!_charging && DateTime.Now.Subtract(start).TotalSeconds < 8)
            {
                await Task.Delay(250);
            }

            // If charging then the battery current is a positive number about 0.4 Amps
            if (_charging)
            {
                _skillHelper.MistySpeak("Ahh. I feel the power.");
                await Task.Delay(2000);
            }
            else
            {
                if (!lastTry)
                {
                    _skillHelper.MistySpeak("Hmm. I don't seem to be charging. I'm going to drive forward and try docking again.");
                    _skillHelper.LogMessage("Did not detect that we're charging. Driving forward and trying again.");

                    await _skillHelper.DriveAsync(IDEAL_ALIGNMENT_DISTANCE + 0.1);

                    if (_abort)
                    {
                        return(false);
                    }
                    await _skillHelper.TurnAsync(180);

                    return(false);
                }
            }

            return(true);
        }
示例#4
0
        private async Task <bool> ExecuteDockAsync()
        {
            _charging = false;

            // Set head position.
            await _skillHelper.MoveHeadAsync(_headPitchOffset, _headRollOffset, _headYawOffset);

            if (_abort)
            {
                return(false);
            }

            // Find the charger.
            if (!await FindChargerAsync())
            {
                return(false);
            }

            // Get Misty aligned with charger based upon charger pose X and Euler angle.
            int retries = 0;

            while (Math.Abs(_chargerPose.X) > ALIGNED_X || Math.Abs(_chargerPose.EulerYaw) > ALIGNED_EULER_YAW)
            {
                if (retries++ > ALIGN_MAX_RETRIES)
                {
                    _skillHelper.LogMessage("Failed to align with charger.");
                    return(false);
                }

                _skillHelper.LogMessage($"Charger position is [{_chargerPose.X:f3}, {_chargerPose.Y:f3}, {_chargerPose.Z:f3}] meters. Euler yaw is {_chargerPose.EulerYaw:f3} degrees.");
                await FaceChargerAsync(ALIGNED_X);

                if (_abort)
                {
                    return(false);
                }

                if (Math.Abs(_chargerPose.X) > ALIGNED_X || Math.Abs(_chargerPose.EulerYaw) > ALIGNED_EULER_YAW)
                {
                    await AlignWithChargerAsync();

                    if (Math.Abs(_chargerPose.Z - IDEAL_ALIGNMENT_DISTANCE) > .1)
                    {
                        await _skillHelper.DriveAsync(_chargerPose.Z - IDEAL_ALIGNMENT_DISTANCE);
                    }
                }
            }

            _skillHelper.LogMessage($"Charger position is [{_chargerPose.X:f3}, {_chargerPose.Y:f3}, {_chargerPose.Z:f3}] meters. Euler yaw is {_chargerPose.EulerYaw:f3} degrees.");

            // Backup and drive straight forward to check alignment.
            double chargerDistance = _chargerPose.Z;
            var    offsets         = new List <double>();
            await _skillHelper.DriveAsync(-1.0, true);

            await Task.Delay(1000);

            offsets.Add(_chargerPose.X);
            for (int i = 0; i < 6; i++)
            {
                await _skillHelper.DriveAsync(.25, true);

                await Task.Delay(1000);

                offsets.Add(_chargerPose.X);
            }
            foreach (var o in offsets)
            {
                _skillHelper.LogMessage(o.ToString("f3"));
            }
            double slope = (offsets[0] - offsets.Last()) / 1.5;
            double estimateFinalOffset = offsets.Last() - slope * 0.5;
            double offsetAngle         = -Math.Asin(estimateFinalOffset / 0.4) * 180.0 / Math.PI;

            _skillHelper.LogMessage($"Estimate final offset {estimateFinalOffset:f3} meters and {offsetAngle} degrees.");

            if (Math.Abs(offsetAngle) > FINAL_OFFSET_ANGLE_MAX)
            {
                _skillHelper.LogMessage("Offset angle is too large. Backing up and retrying dock process.");
                await _skillHelper.DriveAsync(-IDEAL_ALIGNMENT_DISTANCE + 0.25);

                return(false);
            }
            await _skillHelper.TurnAsync(offsetAngle);

            // Turn around.
            _skillHelper.LogMessage("Turning 180 degrees to face away from charger.");
            await _skillHelper.TurnAsync(-180);

            if (_abort)
            {
                return(false);
            }

            // Back on to charger.
            _skillHelper.LogMessage($"Driving {chargerDistance - 0.5 + CHARGER_DOCK_OVERSHOOT:f3} meters to back on to charger.");
            await _skillHelper.DriveAsync(-chargerDistance + 0.5 - CHARGER_DOCK_OVERSHOOT, true);

            if (_abort)
            {
                return(false);
            }

            // Check if we've ended up on top of the wedge.
            await Task.Delay(1000);

            _skillHelper.LogMessage($"Roll = {_skillHelper.ImuRoll:f3}. Pitch = {_skillHelper.ImuPitch:f3}.");
            if (Math.Abs(_skillHelper.ImuRoll) > MISTY_ON_WEDGE_ROLL || Math.Abs(AxisRotation(_initialPitch, _skillHelper.ImuPitch)) > MISTY_ON_WEDGE_PITCH)
            {
                // We're on the wedge. Drive away from the charger and try again.
                _skillHelper.LogMessage($"We appear to have driven on top of the alignment wedge. IMU roll is {_skillHelper.ImuRoll:f3}. IMU pitch is {_skillHelper.ImuPitch:f3}.");
                await _skillHelper.DriveAsync(IDEAL_ALIGNMENT_DISTANCE - 0.1);

                if (_abort)
                {
                    return(false);
                }
                await _skillHelper.TurnAsync(180);

                _chargerPose = null;
                return(false);
            }

            // Check that we're fully docked: back up a little bit to get more aligned.
            _misty.DriveHeading(0, 0.2, 500, true, OnResponse);

            // It can take several seconds for the charging indicator to update...
            await Task.Delay(7000);

            // Check that we're charging.
            if (!_charging)
            {
                _misty.PlayAudio("s_Anger3.wav", 100, OnResponse);
                await _skillHelper.DriveAsync(IDEAL_ALIGNMENT_DISTANCE - 0.1);

                if (_abort)
                {
                    return(false);
                }
                await _skillHelper.TurnAsync(180);

                _chargerPose = null;
                return(false);
            }

            _misty.PlayAudio("s_Ecstacy.wav", 100, OnResponse);

            return(true);
        }
示例#5
0
        /// <summary>
        /// Drive straight for the specified distance in meters at a medium speed.
        /// Confirm movement with encoder values and retry if needed.
        /// If stopped due to a hazard wait a little bit for it to go away and then continue.
        /// </summary>
        /// <param name="distance"></param>
        /// <param name="slow"></param>
        public async Task <bool> DriveAsync(double distance, bool slow = false)
        {
            if (Math.Abs(distance) < 0.003)
            {
                // Can't drive this short a distance
                return(true);
            }

            if (DateTime.Now.Subtract(LastEncoderMessageReceived).TotalSeconds > 1)
            {
                LogMessage($"Cannot carry out a drive command because encoder messages are not being received. Last encoder message received at {LastEncoderMessageReceived}.");
                MistySpeak("Encoder messages are not being received. Path following aborted.");
                return(false);
            }

            bool success = true;

            try
            {
                // Clear encoder values by sending a drive command with a distance of 0.
                int encoderResets = 0;
                while (_leftEncoderValue != 0 && encoderResets++ < 10)
                {
                    _misty.DriveHeading(0, 0, 100, false, OnResponse);
                    await Task.Delay(500);
                }
                if (_leftEncoderValue != 0)
                {
                    LogMessage($"Failed to reset encoder values. Last encoder value received at {LastEncoderMessageReceived} with a value of {_leftEncoderValue}.");
                    return(false);
                }

                _leftEncoderValues  = new ConcurrentQueue <double>();
                _stopHazardLatching = false;
                bool     driving     = true;
                bool     sendCommand = true;
                int      duration    = 0;
                int      retries     = 0;
                DateTime start       = DateTime.Now;

                while (driving)
                {
                    if (sendCommand)
                    {
                        // Arbitrary medium speed based upon distance.
                        duration = (int)(500 + Math.Abs(distance) * 3000);
                        if (slow)
                        {
                            duration = 3 * duration;
                        }

                        while (!_leftEncoderValues.IsEmpty)                         // Clearing the encoder queue
                        {
                            _leftEncoderValues.TryDequeue(out double r);
                        }
                        LogMessage($"Sending drive command with a distance of {distance:f3} meters and a duration of {duration} ms.");
                        _misty.DriveHeading(0, Math.Abs(distance), duration, distance < 0, OnResponse);
                        start       = DateTime.Now;
                        sendCommand = false;
                        await Task.Delay(1000);
                    }

                    if (_abort)
                    {
                        return(false);
                    }
                    await Task.Delay(1500);

                    // Check that we really moved and if we're done.
                    // Drive command could have been dropped and/or a hazard could have stopped the robot.
                    double[] encoderValues = _leftEncoderValues.ToArray();
                    if (encoderValues.Length > 1)                     // encoder values should be arriving at 5Hz.
                    {
                        double distanceDriven = Math.Abs(encoderValues[encoderValues.Length - 1] / 1000.0);
                        if (distanceDriven > Math.Abs(0.99 * distance) || Math.Abs(distanceDriven - Math.Abs(distance)) < 0.1)
                        {
                            LogMessage($"Completed drive with distance of {_leftEncoderValue / 1000.0:f3} meters.");
                            driving = false;
                        }
                        else
                        {
                            // THE FOLLOWING CODE AROUND HAZARDS IS CURRENTLY INACTIVE AS THE HAZARD SYSTEM IS DISABLED BY THE SKILL
                            // Not there yet. Everything progressing okay?
                            if (_stopHazardLatching)
                            {
                                // We've stopped due to a hazard. Wait a bit for it to go away.
                                LogMessage("Drive command paused for hazard.");
                                _misty.PlayAudio("s_Anger.wav", 100, OnResponse);
                                int maxHazardWait = 0;
                                while (_stopHazardState && maxHazardWait++ < 30)
                                {
                                    if (_abort)
                                    {
                                        return(false);
                                    }
                                    await Task.Delay(1000);
                                }
                                if (_stopHazardState)
                                {
                                    // Still in hazard state. Give up.
                                    LogMessage("Giving up on drive command due to persistent hazard condition.");
                                    success = false;
                                    driving = false;
                                }
                                else
                                {
                                    // We're out of hazard state.
                                    LogMessage("Out of hazard state.");
                                    distance           -= encoderValues[encoderValues.Length - 1] / 1000.0;
                                    _stopHazardLatching = false;
                                    sendCommand         = true;
                                }
                            }
                            if (encoderValues[encoderValues.Length - 1] == 0)
                            {
                                // Encoder values are not changing. Try sending drive command again.
                                if (retries++ > 5)
                                {
                                    // Don't try forever.
                                    LogMessage("Unable to complete drive command successfully.");
                                    success = false;
                                    driving = false;
                                }
                                else
                                {
                                    sendCommand = true;
                                }
                            }
                            // Don't wait forever.
                            if (DateTime.Now.Subtract(start).TotalMilliseconds > (5 + 2 * duration))
                            {
                                LogMessage($"Time out waiting for completion of drive. Completed distance of {_leftEncoderValue / 1000.0:f3} meters.");
                                success = false;
                                driving = false;
                            }
                        }
                    }
                    else
                    {
                        LogMessage($"Not receiving encoder messages. Last encoder value recieved at {_lastEncoderValue}.");
                        success = false;
                    }
                }
            }
            catch (Exception ex)
            {
                LogMessage("Exception occurred within DriveAsync: " + ex.Message);
                success = false;
            }
            finally
            {
                _leftEncoderValues = null;
            }

            return(success);
        }