使用 MRDS 控制的乐高 Mindstorms NXT 2.0 机器人






4.43/5 (4投票s)
一个使用 Microsoft Robotics Developer Studio 控制的乐高 Mindstorms NXT 2.0 机器人
引言
在本文中,我将解释如何使用 Microsoft Robotics Developer Studio (MRDS) 创建一个完全由计算机控制的乐高 Mindstorms NXT 2.0 机器人(带有安装在上面的摄像头)。
背景
如果你还没有使用 Studio 创建任何服务,我的上一篇文章 Microsoft Robotics Service for LEGO NXT 2.0 应该对你有所帮助。
要求
- 乐高 Mindstorms NXT 2.0 套件
- Microsoft Robotics Developer Studio
- 网络/IP 摄像头(我使用了我的 iPhone,并在上面安装了 WiFi Camera 应用)
- WPF Dashboard Controls
演示
看看这个视频。
Using the Code
创建一个 MRDS 服务并添加伙伴 - NxtDrive 和 NxtBattery。这将自动声明和实例化一个 BatteryOperations
对象和一个 DriveOperations
对象。请再为 DriveOperations
添加一个,如下面的代码所述
/// <summary>
/// NxtBattery partner
/// </summary>
[Partner("NxtBattery", Contract = battery.Contract.Identifier,
CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate)]
battery.BatteryOperations _nxtBatteryPort = new battery.BatteryOperations();
/// <summary>
/// NxtDrive partner
/// </summary>
[Partner("NxtDrive", Contract = drive.Contract.Identifier,
CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate)]
drive.DriveOperations _nxtDrivePort = new drive.DriveOperations();
drive.DriveOperations _nxtDriveNotify = new drive.DriveOperations();
向解决方案添加一个新的类库项目,并向其添加一个接口。 此项目需要在 MRDS 服务和 WPF UI 项目中引用(稍后将解释 UI 项目)。
public interface IMyLegoCarService
{
double GearPower { get; set; }
long LeftEncoderCurrent { get; set; }
long RightEncoderCurrent { get; set; }
double LeftPowerCurrent { get; set; }
double RightPowerCurrent { get; set; }
double BatteryPower { get; set; }
void Drive(DriveAction driveDirection);
void StopEngine();
}
更改 MRDS 服务,使其实现此接口。 除第一个属性 (GearPower
) 外的所有属性的值都将在服务中设置,并且它们将在 UI 层中检索和使用。
现在,添加一个新的 WPF 项目。 声明一个名为 Service
的 IMyLegoCarService
类型的属性,并添加一个构造函数,如下所示
public Dashboard(IMyLegoCarService service) : this()
{
Service = service;
Service.GearPower = 0;
brsr_ipcamera.Navigate(new Uri
("http://ipoftheiphoneorthewebserver/iphonecamera/index.htm"));
UpdateInitialOdometer();
uiTimer = new DispatcherTimer();
uiTimer.Interval = TimeSpan.FromSeconds(1);
uiTimer.Tick += uiTimer_Tick;
uiTimer.Start();
}
此构造函数应从 Service
的构造函数中调用,以便服务和 UI 位于同一线程上。 这里的“brsr_ipcamera
”是用于显示 IP 摄像头图像/视频的 Web 浏览器控件(在我的例子中是我的 iPhone)。 我向我的 Web 服务器添加了一个 HTML 页面,该页面仅显示来自摄像头的视频。 添加一个计时器控件以定期显示从服务检索的信息。 在这里,我使用了 WPF Dashboard Controls 的刻度盘控件作为速度计(用于左电机前进、左电机后退、右电机前进和右电机后退)、里程表控件作为里程表和进度条控件作为燃油表。 左/右功率电流属性用于初始化速度计。 左/右编码器属性用于初始化里程表,这些属性基本上为我们提供了伺服电机旋转的度数。 使用公式:distance = Convert.ToInt32(Math.Abs(currentEncoderCurrent) / 360 * 2 * 3.14 * 0.75
,我们可以计算覆盖的距离。 在这里,pi = 3.14,0.75 是车轮的半径。

回到服务。 声明和/或实例化以下类
wpf.WpfServicePort _wpfServicePort;
drive.SetDriveRequest _nxtSetDriveRequest = new drive.SetDriveRequest();
battery.BatteryState _nxtBatteryState;
WpfServicePort
用于调用 WPF UI,SetDriveRequest
用于旋转电机,BatteryState
用于获取电池信息。
向服务类型添加一个名为“TimerTick
”的端口,类似于自动创建的端口“Get
”、“Subscribe
”等。 现在,你的 serviceoperations
类声明将如下所示
[ServicePort]
public class MyLegoCarServiceOperations :
PortSet<DsspDefaultLookup, DsspDefaultDrop, Get, Subscribe, TimerTick>
{
}
public class TimerTick:Update<TimerTickRequest, PortSet<DefaultUpdateResponseType, Fault>>
{
public TimerTick()
: base(new TimerTickRequest())
{
}
}
[DataContract]
public class TimerTickRequest
{
}
修改服务的 start
方法,如下所示
protected override void Start()
{
SpawnIterator(DoStart);
}
private IEnumerator<ITask> DoStart()
{
DispatcherQueue queue = new DispatcherQueue();
this._wpfServicePort = wpf.WpfAdapter.Create(queue);
// invoke the UI
var runWindow = this._wpfServicePort.RunWindow(() => (Window)new Dashboard(this));
yield return (Choice)runWindow;
var exception = (Exception)runWindow;
if (exception != null)
{
LogError(exception);
StartFailed();
yield break;
}
// Subscribe to partners
var subscribe1 = this._nxtDrivePort.Subscribe(_nxtDriveNotify);
yield return (Choice)subscribe1;
_timerPort.Post(DateTime.Now);
// Activate independent tasks
Activate<ITask>(
Arbiter.Receive<drive.DriveEncodersUpdate>
(true, _nxtDriveNotify, DriveEncoderHandler),
Arbiter.Receive(true, _timerPort, TimerHandler)
);
// Start operation handlers and insert into directory service.
StartHandlers();
}
private void StartHandlers()
{
// Activate message handlers for this service and insert into the directory.
base.Start();
}
Timerport
用于定期检索电池信息。
[ServiceHandler(ServiceHandlerBehavior.Exclusive)] public IEnumerator<ITask> TimerTickHandler(TimerTick incrementTick) { incrementTick.ResponsePort.Post(DefaultUpdateResponseType.Instance); battery.Get batteryGet; yield return _nxtBatteryPort.Get(GetRequestType.Instance, out batteryGet); _nxtBatteryState = batteryGet.ResponsePort; if (_nxtBatteryState != null) { BatteryPower = _nxtBatteryState.PercentBatteryPower; } yield break; } void TimerHandler(DateTime signal) { _mainPort.Post(new TimerTick()); Activate( Arbiter.Receive(false, TimeoutPort(3000), delegate(DateTime time) { _timerPort.Post(time); } ) ); }
DriveEncoderUpdate
用于从伺服电机检索信息。
private void DriveEncoderHandler(drive.DriveEncodersUpdate statistics)
{
LeftEncoderCurrent = statistics.Body.LeftEncoderCurrent;
RightEncoderCurrent = statistics.Body.RightEncoderCurrent;
LeftPowerCurrent = statistics.Body.LeftPowerCurrent;
RightPowerCurrent = statistics.Body.RightPowerCurrent;
}
在公共类库项目中创建一个名为“DriveAction
”的 enum
。 这是为了处理键盘或 UI 层的单击事件。
public enum DriveAction
{
Front,
Back,
Left,
Right,
Stop
}
在服务中实现 Drive
方法。
public void Drive(DriveAction driveAction)
{
switch (driveAction)
{
case DriveAction.Front:
_nxtSetDriveRequest.LeftPower = -GearPower;
_nxtSetDriveRequest.RightPower = -GearPower;
_nxtDrivePort.DriveDistance(_nxtSetDriveRequest);
break;
case DriveAction.Back:
_nxtSetDriveRequest.LeftPower = GearPower;
_nxtSetDriveRequest.RightPower = GearPower;
_nxtDrivePort.DriveDistance(_nxtSetDriveRequest);
break;
case DriveAction.Left:
_nxtSetDriveRequest.LeftPower = -.4;
_nxtSetDriveRequest.RightPower = .4;
_nxtDrivePort.DriveDistance(_nxtSetDriveRequest);
break;
case DriveAction.Right:
_nxtSetDriveRequest.LeftPower = .4;
_nxtSetDriveRequest.RightPower = -.4;
_nxtDrivePort.DriveDistance(_nxtSetDriveRequest);
break;
case DriveAction.Stop:
_nxtDrivePort.AllStop(MotorStopState.Coast);
break;
default:
break;
}
}
希望这有助于创建一个用于控制你的机器人的服务。