开发者

WP7: Convert Accelometer and Compass data to mock Motion API

I'm writing a small sample application for the Windows Phone 7.1 (Mango) and want to use the Combined Motion API to display the motion of the device. I need to write mock classes to be able to test my application when using the emulator which does not support all sensors of the device.

I already wrote a simple mock class to simulate a compass (it just simulates a rotating device) and for the accelerometer which is开发者_开发知识库 actually available in the emulator.

Now I would have to write a new mock object for the Motion API but I hope that I could calculate the values that are used for the Motion object using the values from compass and accelerometer. Unfortunately I found no sample for a simple conversion that is already doing this.

Does anybody know a code sample that does this conversion? As complex as this will be I wouldn't like to do this by myself, if there is already a solution.


I've run into the same issue again now that Windows Phone 8 is out and I don't have a handset to test with yet.

Much like the answer to WP7 Mock Microsoft.Devices.Sensors.Compass when using the emulator, I've created a wrapper class with the same methods as the Motion API. When the actual Motion API is supported it is used. Otherwise, in debug mode, mock data is returned that varies the roll, pitch, and yaw.


MotionWrapper

    /// <summary>
    /// Provides Windows Phone applications information about the device’s orientation and motion.
    /// </summary>
    public class MotionWrapper //: SensorBase<MotionReading> // No public constructors, nice one.
    {
        private Motion motion;

        public event EventHandler<SensorReadingEventArgs<MockMotionReading>> CurrentValueChanged;

        #region Properties
        /// <summary>
        /// Gets or sets the preferred time between Microsoft.Devices.Sensors.SensorBase<TSensorReading>.CurrentValueChanged events.
        /// </summary>
        public virtual TimeSpan TimeBetweenUpdates
        {
            get
            {
                return motion.TimeBetweenUpdates;
            }
            set
            {
                motion.TimeBetweenUpdates = value;
            }
        }

        /// <summary>
        /// Gets or sets whether the device on which the application is running supports the sensors required by the Microsoft.Devices.Sensors.Motion class.
        /// </summary>
        public static bool IsSupported
        {
            get
            {
#if(DEBUG)
                return true;
#else
                return Motion.IsSupported;
#endif

            }
        }
        #endregion

        #region Constructors
        protected MotionWrapper()
        {
        }

        protected MotionWrapper(Motion motion)
        {
            this.motion = motion;
            this.motion.CurrentValueChanged += motion_CurrentValueChanged;
        }
        #endregion

        /// <summary>
        /// Get an instance of the MotionWrappper that supports the Motion API
        /// </summary>
        /// <returns></returns>
        public static MotionWrapper Instance()
        {
#if(DEBUG)
            if (!Motion.IsSupported)
            {
                return new MockMotionWrapper();
            }
#endif
            return new MotionWrapper(new Motion());
        }

        /// <summary>
        /// The value from the underlying Motion API has changed. Relay it on within a MockMotionReading.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void motion_CurrentValueChanged(object sender, SensorReadingEventArgs<MotionReading> e)
        {
            var f = new SensorReadingEventArgs<MockMotionReading>();
            f.SensorReading = new MockMotionReading(e.SensorReading);
   RaiseValueChangedEvent(sender, f);
        }

        protected void RaiseValueChangedEvent(object sender, SensorReadingEventArgs<MockMotionReading> e)
        {
            if (CurrentValueChanged != null)
            {
                CurrentValueChanged(this, e);
            }
        }

        /// <summary>
        /// Starts acquisition of data from the sensor.
        /// </summary>
        public virtual void Start()
        {
            motion.Start();
        }

        /// <summary>
        /// Stops acquisition of data from the sensor.
        /// </summary>
        public virtual void Stop()
        {
            motion.Stop();
        }
    }

MockMotionWrapper

    /// <summary>
    /// Provides Windows Phone applications mock information about the device’s orientation and motion.
    /// </summary>
    public class MockMotionWrapper : MotionWrapper
    {
        /// <summary>
        /// Use a timer to trigger simulated data updates.
        /// </summary>
        private DispatcherTimer timer;

        private MockMotionReading lastCompassReading = new MockMotionReading(true);

        #region Properties
        /// <summary>
        /// Gets or sets the preferred time between Microsoft.Devices.Sensors.SensorBase<TSensorReading>.CurrentValueChanged events.
        /// </summary>
        public override TimeSpan TimeBetweenUpdates
        {
            get
            {
                return timer.Interval;
            }
            set
            {
                timer.Interval = value;
            }
        }
        #endregion

        #region Constructors
        public MockMotionWrapper()
        {
            timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(30);
            timer.Tick += new EventHandler(timer_Tick);
        }
        #endregion

        void timer_Tick(object sender, EventArgs e)
        {
            var reading = new Microsoft.Devices.Sensors.SensorReadingEventArgs<MockMotionReading>();
            lastCompassReading = new MockMotionReading(lastCompassReading);
            reading.SensorReading = lastCompassReading;

            //if (lastCompassReading.HeadingAccuracy > 20)
            //{
            //    RaiseValueChangedEvent(this, new CalibrationEventArgs());
            //}

            RaiseValueChangedEvent(this, reading);
        }

        /// <summary>
        /// Starts acquisition of data from the sensor.
        /// </summary>
        public override void Start()
        {
            timer.Start();
        }

        /// <summary>
        /// Stops acquisition of data from the sensor.
        /// </summary>
        public override void Stop()
        {
            timer.Stop();
        }

    }

MockMotionReading

//Microsoft.Devices.Sensors.MotionReading
/// <summary>
/// Contains information about the orientation and movement of the device.
/// </summary>
public struct MockMotionReading : Microsoft.Devices.Sensors.ISensorReading
{
    public static bool RequiresCalibration = false;

    #region Properties
    /// <summary>
    /// Gets the attitude (yaw, pitch, and roll) of the device, in radians.
    /// </summary>
    public MockAttitudeReading Attitude { get; internal set; }

    /// <summary>
    ///  Gets the linear acceleration of the device, in gravitational units.
    /// </summary>
    public Vector3 DeviceAcceleration { get; internal set; }

    /// <summary>
    /// Gets the rotational velocity of the device, in radians per second.
    /// </summary>
    public Vector3 DeviceRotationRate { get; internal set; }

    /// <summary>
    /// Gets the gravity vector associated with the Microsoft.Devices.Sensors.MotionReading.
    /// </summary>
    public Vector3 Gravity { get; internal set; }

    /// <summary>
    /// Gets a timestamp indicating the time at which the accelerometer reading was
    ///     taken. This can be used to correlate readings across sensors and provide
    ///     additional input to algorithms that process raw sensor data.
    /// </summary>
    public DateTimeOffset Timestamp { get; internal set; }
    #endregion

    #region Constructors

    /// <summary>
    /// Initialize an instance from an actual MotionReading
    /// </summary>
    /// <param name="cr"></param>
    public MockMotionReading(MotionReading cr)
        : this()
    {
        this.Attitude = new MockAttitudeReading(cr.Attitude);
        this.DeviceAcceleration = cr.DeviceAcceleration;
        this.DeviceRotationRate = cr.DeviceRotationRate;
        this.Gravity = cr.Gravity;
        this.Timestamp = cr.Timestamp;
    }

    /// <summary>
    /// Create an instance initialized with testing data
    /// </summary>
    /// <param name="test"></param>
    public MockMotionReading(bool test) 
        : this()
    {
        float pitch = 0.01f;
        float roll = 0.02f;
        float yaw = 0.03f;

        this.Attitude = new MockAttitudeReading()
        {
            Pitch = pitch,
            Roll = roll,
            Yaw = yaw,
            RotationMatrix = Matrix.CreateFromYawPitchRoll(yaw, pitch, roll),  
            Quaternion = Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll), 

            Timestamp = DateTimeOffset.Now
        };

        // TODO: pull data from the Accelerometer
        this.Gravity = new Vector3(0, 0, 1f);
    }

    /// <summary>
    /// Create a new mock instance based on the previous mock instance
    /// </summary>
    /// <param name="lastCompassReading"></param>
    public MockMotionReading(MockMotionReading lastCompassReading)
        : this()
    {
        // Adjust the pitch, roll, and yaw as required.

        // -90 to 90 deg
        float pitchDegrees = MathHelper.ToDegrees(lastCompassReading.Attitude.Pitch) - 0.5f;
        //pitchDegrees = ((pitchDegrees + 90) % 180) - 90;

        // -90 to 90 deg
        float rollDegrees = MathHelper.ToDegrees(lastCompassReading.Attitude.Roll);
        //rollDegrees = ((rollDegrees + 90) % 180) - 90;

        // 0 to 360 deg
        float yawDegrees = MathHelper.ToDegrees(lastCompassReading.Attitude.Yaw) + 0.5f;
        //yawDegrees = yawDegrees % 360;

        float pitch = MathHelper.ToRadians(pitchDegrees);
        float roll = MathHelper.ToRadians(rollDegrees);
        float yaw = MathHelper.ToRadians(yawDegrees);

        this.Attitude = new MockAttitudeReading()
        {
            Pitch = pitch,
            Roll = roll,
            Yaw = yaw,
            RotationMatrix = Matrix.CreateFromYawPitchRoll(yaw, pitch, roll),
            Quaternion = Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll),

            Timestamp = DateTimeOffset.Now
        };

        this.DeviceAcceleration = lastCompassReading.DeviceAcceleration;
        this.DeviceRotationRate = lastCompassReading.DeviceRotationRate;
        this.Gravity = lastCompassReading.Gravity;
        Timestamp = DateTime.Now;

    }
    #endregion



}

MockAttitudeReading

public struct MockAttitudeReading : ISensorReading
{
    public MockAttitudeReading(AttitudeReading attitudeReading) : this()
    {
        Pitch = attitudeReading.Pitch;
        Quaternion = attitudeReading.Quaternion;
        Roll = attitudeReading.Roll;
        RotationMatrix = attitudeReading.RotationMatrix;
        Timestamp = attitudeReading.Timestamp;
        Yaw = attitudeReading.Yaw;
    }

    /// <summary>
    /// Gets the pitch of the attitude reading in radians.
    /// </summary>
    public float Pitch { get; set; }

    /// <summary>
    /// Gets the quaternion representation of the attitude reading.
    /// </summary>
    public Quaternion Quaternion { get; set; }

    /// <summary>
    /// Gets the roll of the attitude reading in radians.
    /// </summary>
    public float Roll { get; set; }

    /// <summary>
    /// Gets the matrix representation of the attitude reading.
    /// </summary>
    public Matrix RotationMatrix { get; set; }

    /// <summary>
    /// Gets a timestamp indicating the time at which the accelerometer reading was
    ///     taken. This can be used to correlate readings across sensors and provide
    ///     additional input to algorithms that process raw sensor data.
    /// </summary>
    public DateTimeOffset Timestamp { get; set; }

    /// <summary>
    /// Gets the yaw of the attitude reading in radians.
    /// </summary>
    public float Yaw { get; set; }
}


A good starting point for doing your Mockup work is to have a look at the Motion API and how it works internally and which parameters are used by the API core:

http://msdn.microsoft.com/en-us/library/hh202984%28VS.92%29.aspx

Before you go on, have in mind that the Motion API is complex mathematical model which combines the inputs of the different phone sensors. You can find many different models to calculate motion by combining acceleration, position and rotation.. one good description you can find in this article:

http://www.instructables.com/id/Accelerometer-Gyro-Tutorial/

So in fact, you have to use the equations and functions shown in the article above and then calculate the values on your own.

It is everything else than a simple thing, but possible this way.

I hope i was able to help you :) and let the community know, if you've done it. I think a codeplex project would be nice to write a kind of mocking utilites for windows phone motion API.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜