65.9K
CodeProject 正在变化。 阅读更多。
Home

UltraDynamo

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2012年10月13日

CPOL

9分钟阅读

viewsIcon

30493

Ultrabook vehicle dynamo

引言

[注意:由于英特尔于 2014 年 3 月关闭了 AppUp 商店,因此英特尔 AppUp 商店中该应用程序的下载链接已被删除。源代码仍可在我的竞赛后文章中找到。请参阅此链接。]

本文旨在概念性地探讨一个拟议应用程序,以展示超极本中嵌入的传感器设备的潜力。

对于喜欢赛车运动、也许会观看一级方程式等比赛的各位,您可能已经注意到屏幕上的车辆性能图形。

日产 GT-R 等现代跑车也配备了车载显示屏,向驾驶员传达车辆性能。

如果您是赛道日爱好者,或者只是一个普通的汽车爱好者(或者您所在地区怎么称呼他们),如果您可以将您的超极本带入车内,固定在仪表盘上等,然后让它显示性能特征,那会怎么样?

好了,这就是这款应用程序背后的概念。(是的,我是一个汽车爱好者!)

此应用程序的目标是什么

利用 GPS 传感器、加速度计、倾角仪等,应该可以向车内乘员显示诸如加速/减速数据、位置数据等信息,并且如果您知道车辆 + 乘员的重量,则应该可以计算功率特性。

您应该能够显示的一些统计数据是

  1. 加速/减速
  2. 过弯 G 力
  3. 0 到 60(62 英里/小时)/ 100 公里/小时(或自定义)
  4. 0 到 100 到 0
  5. 四分之一英里(或自定义)
  6. 当然还有其他的!

利用 GPS 数据,还应该可以将数据映射到您喜欢的地图工具,如谷歌地图/地球。

数据也可以存储用于历史参考,或在不同车辆之间进行比较,或在修改后进行比较。

显示

数据可以潜在地以多种格式显示,例如,数值数据、条形图、折线图、模拟仪表盘。

用户还可以构建自定义仪表盘来按他们认为合适的方式呈现数据。

Using the Code

项目更新 1(2012 年 10 月 24 日)

在过去一周里,我一直在断断续续地进行这个项目。主要的编码工作将在我一周后回家时进行,但那样会给下一个截止日期留下很紧张的时间,所以我现在需要做些事情!

最初,我正在使用 Visual Studio 2012 Pro 在标准笔记本电脑上开发代码,因此我无法测试传感器,不得不提出一种合适的方法,该方法可以允许在不依赖底层硬件的情况下测试 UI。迫不及待地想拿到超极本来实际看看事情是否朝着正确的方向发展,等等!

在初步创建一个空白的 Winforms C# 应用程序后,我必须修改项目文件以允许访问 Windows 8 传感器 API。这在几篇文章中都有介绍,但本质上,您需要在项目文件中添加一个新的属性组,其中包含

<propertygroup />
    <targetplatformversion />8.0</targetplatformversion />
</propertygroup />

完成此操作后,您可以添加对运行时文件的适当引用;Windows.Devices.Sensors.dllSystem.Runtime.InteropServices.WindowsRuntime.dll

传感器包装器

如上所述,我需要一种方法来测试 UI,而无需访问实时传感器数据。为了实现这一点,我创建了一些包装器类,它们充当 UI 和实际传感器之间的中间人。这允许我启用模拟模式并向代码注入测试数据。此外,如果任何传感器不可用,该特定传感器会自动切换到模拟模式。到目前为止,我已经为 AmbientLightSensorAccelerometerCompassGyrometerInclinometer 创建了包装器。还有待考察 GPS 传感器。

下面是包装器 MyLightSensor 的样子

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Sensors;

namespace UltraDynamo.Sensors
{
    public class MyLightSensor
    {        
        public static bool Available { get; private set; }
        public static bool Simulated { get; private set; }

        public static float LightReading {get; private set; }
        public static float rawLightReading {get; private set; }
        public static float simLightReading {get; private set; }

        //Source Sensor
        private static LightSensor lightSensor;

        //Events
        public static event ChangeHandler Change;
        public static EventArgs e = null;
        public delegate void ChangeHandler(MyLightSensor sender, EventArgs e);

        public MyLightSensor()
        {            
            //Base sensor
            lightSensor = LightSensor.GetDefault();

            if (lightSensor != null)
            {
                setAvailable(true);
                setSimulated(false);

                lightSensor.ReadingChanged += lightSensor_ReadingChanged;
            }
            else
            {
                setAvailable(false);
                setSimulated(true);
            }
        }

        void lightSensor_ReadingChanged(LightSensor sender, LightSensorReadingChangedEventArgs args)
        {
             //throw new NotImplementedException();
            rawLightReading = args.Reading.IlluminanceInLux;
            if (!Simulated)
            {
                setLightLevel(rawLightReading);
            }

            //raise event
            if (Change != null)
                Change(this, e);
        }

        private void setLightLevel(float value)
        {
            LightReading = value;
        }

        private void setAvailable(bool available)
        {
            Available = available;

            //RaiseEvent
            if (Change != null)
                Change(this, e);
        }

        public void setSimulated(bool simulated)
        {
            Simulated = simulated;

            //RaiseEvent
            if (Change != null)
                Change(this, e);
        }

        public void setSimulatedValue(float value)
        {
            simLightReading = value;

            if (Simulated)
            {
                setLightLevel(simLightReading);
            }

            //RaiseEvent
            if (Change != null)
                Change(this, e);
        }
    }
}

如您所见,有许多 static 属性,显示 Sensor 当前是否被 Simulated 和/或 Available,以及 UsedRawSimulated 值。提供了几个方法来设置模拟数据值并在 SimulatedLive 模式之间切换。在代码中,您还可以看到实际的传感器字段 private static,用于访问实时数据,以及用于跟踪传感器数据更新的更改事件。

传感器模拟控制器

创建传感器包装器后,下一步是创建一些表单,可用于操纵传感器数据。下图显示了为 Inclinometer 创建的模拟表单

传感器数据表示

现在有了数据,我已经开始创建 UserControl,可以将其放置在应用程序的任何位置,或者放在独立的表单上供用户自定义布局,或者放在预定义的仪表盘上。下面显示了为 InclinometerPitchRollYaw 创建的三个用户控件,并显示了上面模拟表单中表示的数据。

图形不是我的强项,所以我使用了一些粗糙的图像处理来让核心功能正常工作,并且可以在将来的更新中进行改进。您还会注意到上面表单中的状态气泡位于左上角和右上角。左上角的蓝色气泡表示数据是模拟的。右上角的气泡显示实际传感器状态,红色=不可用,绿色=可用。

另一个创建的控件是指南针玫瑰指示器,如下所示

如果我们现在看一个更简单的控件,CompassHeading 控件,它接收指南针航向并将数值转换为“N, NE, E, SE, S, SW, W, NW”中的一个。您可以看到这个控件如何监听来自 Sensor 包装器的事件

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using UltraDynamo.Sensors;

namespace UltraDynamo.Controls
{
    public partial class UICompassHeadingLetters : UserControl
    {
        //Source Compass
        MyCompass myCompass;
        
        public UICompassHeadingLetters()
        {
            InitializeComponent();

            //Initialise a compass
            myCompass = new MyCompass();

            //Track changes on the compass
            //Add a compass to the mainform
            MyCompass.Change += new MyCompass.ChangeHandler(myCompass_Change);
            myCompass_Change(myCompass, new EventArgs());
        }

        void myCompass_Change(MyCompass sender, EventArgs e)
        {
            this.Refresh();
        }

        private void UICompassHeadingLetters_Paint(object sender, PaintEventArgs e)
        {
            String output = "N"; //Default to North removes need for two ifs below
            //Get the string representation of the heading
            double heading = MyCompass.Heading;

            if (heading >= 22.5 && heading < 67.5)
                output = "NE";

            if (heading >= 67.5 && heading < 112.5)
                output = "E";

            if (heading >= 112.5 && heading < 157.5)
                output = "SE";

            if (heading >= 157.5 && heading < 202.5)
                output = "S";

            if (heading >= 202.5 && heading < 247.5)
                output = "SW";

            if (heading >= 247.5 && heading < 292.5)
                output = "W";

            if (heading >= 292.5 && heading < 337.5)
                output = "NW";

            //Establish base fonts and graphics objects
            Graphics g = e.Graphics;

            Font f = this.Font;
            Font newFont = this.Font;
            SizeF testSize;
            float fHeight=0;
            float fWidth=0;
            bool fontGood = true;
            
            for (int newSize = 1; fontGood; newSize++)
            {
                //Increase the size and test height again)
                newFont=new Font(f.Name,newSize,f.Style);
                testSize = g.MeasureString(output,newFont);
            
                //Determine boundary size for the text
                fHeight = testSize.Height;
                fWidth = testSize.Width;

                //Test the text height and width against control size
                if ((fHeight > (this.Height)) | (fWidth > (this.Width)))
                {
                    fontGood = false;
                    newSize--;  //go back by 1 size, 
                                //recalculate size required for positioning central on control
                    if (newSize <6 ) { newSize=6;};
                    newFont = new Font(f.Name,newSize,f.Style);
                    testSize = g.MeasureString(output,newFont);
                    fHeight = testSize.Height;
                    fWidth = testSize.Width;
                }
            }            

            //Draw the text heading on the control
            g.DrawString(output, newFont, Brushes.Green, 
                new Point((this.Width - (int)fWidth) / 2, (this.Height - (int)fHeight) / 2));

            if (MyCompass.Simulated)
                g.FillEllipse(Brushes.Blue, 0, 0, 5, 5);

            //Add a Red/Green dot to show sensor availability in top right corner
            if (MyCompass.Available)
                g.FillEllipse(Brushes.Green, this.Width - 5, 0, 5, 5);
            else
                g.FillEllipse(Brushes.Red, this.Width - 5, 0, 5, 5);
        }
    }
}

开发的下一阶段将是研究一些实时趋势、模拟仪表盘以及最初概述简报中提到的其他一些计划功能。

这是更新 1 的结束。到目前为止一切顺利(我认为!)。

项目更新 2(2012 年 11 月 13 日)

在过去的几周里,我一直在忙于这个应用程序。

遇到了无数问题,考虑到时间限制,一切都感觉有点仓促,需要“赶工”。遇到的一些问题包括

  • 典型的交叉线程问题
  • 传感器事件冲突
  • 硬件/软件中的固有错误
  • 代码签名和包创建

那么我进展如何?

对一些事件进行了大量重写,以解决交叉线程问题。我仍然希望进一步改进这方面,但在此阶段不能冒风险打破东西。所以也许以后再说。

上面显示的模拟代码在初始测试中非常有用,但鉴于传感器代码的重写,它不再起作用,因此已将其放在后台,直到以后可以重新审视。

GPS 子系统似乎也存在硬件/驱动程序级别的错误。因此,不幸的是,直到这个问题解决之前,我都无法完全测试或实现介绍列表中所述的功能。但是,该应用程序在初始版本中仍然具有有用的功能。

创建了一个基本的概述仪表盘,显示了所有主要传感器元素以及一个净马力表。马力是通过加速度计和车辆(及其乘员)的重量计算得出的。这是净马力,不考虑传动系统损失或阻力损失。这些也许可以在以后进行计算。仪表盘如下所示

还有一些实时趋势可以显示最近发生的情况,例如,下面的加速度计趋势。

我在获取免费代码签名证书时遇到了问题,仍在等待验证电话。所以我自己通过 GlobalSign 获取了一个。然后出现了如何设置 Visual Studio 在后期生成事件中构建包、对其进行代码签名的问题。InstallShield 对我来说也是全新的,因此将所有内容打包好、签名安装程序等都有些令人头疼。紧随其后的是一些获取 MSI 安装程序的麻烦,这些安装程序是从设置包装器中提取出来的。

以后有很多可能值得写的东西,但在此期间,我觉得我头都炸了,这便是更新 2 的结束!

项目更新 3(2012 年 11 月 28 日)

毕竟,在 14 日提交应用程序的麻烦,然后等待其发布数周,我只是想添加一个小更新,因为我注意到我还没有在其他地方真正提到它。在上面的仪表盘显示中,您会注意到右侧的表盘指示器显示净马力。这是一个计算值,基于

  1. 车辆重量
  2. 加速度和
  3. 车辆速度

如果您参考维基百科文章并查看牵引马力的计算,您可以看到它们之间的关系。

詹姆斯·瓦特在 18 世纪引入了这一功率单位,他声称 1 马力是在 1 秒内将 550 磅的重量提升所需的功率。可以使用所示参数和超极本传感器的数据进行计算,如下所示

Net HP = (Weight (in lbs) x Acceleration (g) x Speed (mph) ) / 375

重量由用户在应用程序的配置页面上提供,其他数据是传感器派生的。375 是一个常数,源自 550 磅/秒到英里/小时的转换。

很容易添加传动系统损失和阻力系数的计算常数,但这留待以后处理。

发布应用程序后,我注意到在控件和显示器上,我忘记启用“双缓冲”,因此在更新期间会出现轻微闪烁。发布后,我已经开始重构事件系统和应用程序的各种其他部分,因此希望在将来的更新中将它们发布出来。

其他蓝图想法

还可以进一步扩展核心功能,以与车辆的 OBDII 或 CAN 总线接口,以利用其他参考数据,例如,车辆参数,如速度、档位、油门角度。

这超出了最初的范围,但只是一个想法。

关注点

这应该为超极本的功能提供充足的研究机会,并提供一些有趣的东西来让我深入研究。

历史

  • 2012 年 10 月 13 日 - 概念应用程序描述初稿
  • 2012 年 10 月 24 日 - 添加了项目更新 1
  • 2012 年 11 月 13 日 - 添加了项目更新 2
  • 2013 年 11 月 28 日 - 添加了项目更新 3
© . All rights reserved.