UltraDynamo
Ultrabook vehicle dynamo
引言
[注意:由于英特尔于 2014 年 3 月关闭了 AppUp 商店,因此英特尔 AppUp 商店中该应用程序的下载链接已被删除。源代码仍可在我的竞赛后文章中找到。请参阅此链接。]
本文旨在概念性地探讨一个拟议应用程序,以展示超极本中嵌入的传感器设备的潜力。
对于喜欢赛车运动、也许会观看一级方程式等比赛的各位,您可能已经注意到屏幕上的车辆性能图形。
日产 GT-R 等现代跑车也配备了车载显示屏,向驾驶员传达车辆性能。
如果您是赛道日爱好者,或者只是一个普通的汽车爱好者(或者您所在地区怎么称呼他们),如果您可以将您的超极本带入车内,固定在仪表盘上等,然后让它显示性能特征,那会怎么样?
好了,这就是这款应用程序背后的概念。(是的,我是一个汽车爱好者!)
此应用程序的目标是什么
利用 GPS 传感器、加速度计、倾角仪等,应该可以向车内乘员显示诸如加速/减速数据、位置数据等信息,并且如果您知道车辆 + 乘员的重量,则应该可以计算功率特性。
您应该能够显示的一些统计数据是
- 加速/减速
- 过弯 G 力
- 0 到 60(62 英里/小时)/ 100 公里/小时(或自定义)
- 0 到 100 到 0
- 四分之一英里(或自定义)
- 当然还有其他的!
利用 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.dll 和 System.Runtime.InteropServices.WindowsRuntime.dll。
传感器包装器
如上所述,我需要一种方法来测试 UI,而无需访问实时传感器数据。为了实现这一点,我创建了一些包装器类,它们充当 UI 和实际传感器之间的中间人。这允许我启用模拟模式并向代码注入测试数据。此外,如果任何传感器不可用,该特定传感器会自动切换到模拟模式。到目前为止,我已经为 AmbientLightSensor
、Accelerometer
、Compass
、Gyrometer
和 Inclinometer
创建了包装器。还有待考察 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
,以及 Used
、Raw
和 Simulated
值。提供了几个方法来设置模拟数据值并在 Simulated
和 Live
模式之间切换。在代码中,您还可以看到实际的传感器字段 private static
,用于访问实时数据,以及用于跟踪传感器数据更新的更改事件。
传感器模拟控制器
创建传感器包装器后,下一步是创建一些表单,可用于操纵传感器数据。下图显示了为 Inclinometer
创建的模拟表单
传感器数据表示
现在有了数据,我已经开始创建 UserControl
,可以将其放置在应用程序的任何位置,或者放在独立的表单上供用户自定义布局,或者放在预定义的仪表盘上。下面显示了为 Inclinometer
的 Pitch
、Roll
和 Yaw
创建的三个用户控件,并显示了上面模拟表单中表示的数据。
图形不是我的强项,所以我使用了一些粗糙的图像处理来让核心功能正常工作,并且可以在将来的更新中进行改进。您还会注意到上面表单中的状态气泡位于左上角和右上角。左上角的蓝色气泡表示数据是模拟的。右上角的气泡显示实际传感器状态,红色=不可用,绿色=可用。
另一个创建的控件是指南针玫瑰指示器,如下所示
如果我们现在看一个更简单的控件,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 日提交应用程序的麻烦,然后等待其发布数周,我只是想添加一个小更新,因为我注意到我还没有在其他地方真正提到它。在上面的仪表盘显示中,您会注意到右侧的表盘指示器显示净马力。这是一个计算值,基于
- 车辆重量
- 加速度和
- 车辆速度
如果您参考维基百科文章并查看牵引马力的计算,您可以看到它们之间的关系。
詹姆斯·瓦特在 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