Microsoft Robotics Studio (MRDS) 基於 .NET/.NET CF,若機器人無法運行 .NET,需在 PC 端構建遠程控制服務。本文詳細講解四大核心環節。

若硬件已提供(如 NXT、iRobot Create),可跳過。否則需自行開發:循環輪詢傳感器 → 發送數據至 PC;接收電機指令 → 驅動硬件。
SerialPort serialPort = new SerialPort();
void Open(int comPort, int baudRate)
{
serialPort = new SerialPort("COM" + comPort, baudRate);
serialPort.Encoding = Encoding.Default;
serialPort.Parity = Parity.None;
serialPort.DataBits = 8;
serialPort.StopBits = StopBits.One;
serialPort.DataReceived += SerialPort_DataReceived;
serialPort.Open();
}
void SendData(byte[] buffer) { serialPort.Write(buffer, 0, buffer.Length); }
收到數據先發往內部端口 _myRobotInboundPort,再由獨占接收器更新狀態:
void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
_myRobotInboundPort.Post(sensorMsg); // 不直接改狀態
}
protected override void Start()
{
var main = ActivateDsspOperationHandlers();
main.CombineWith(new Interleave(
new TeardownReceiverGroup(),
new ExclusiveReceiverGroup(
Arbiter.ReceiveWithIterator(true, _myRobotInboundPort, MyRobotSensorMessageHandler)
),
new ConcurrentReceiverGroup()
));
}
private IEnumerator MyRobotSensorMessageHandler(SensorNotification msg)
{
_state.sensor = msg.sensor; // 獨占更新狀態
yield break;
}
標準訂閱推送所有傳感器數據,浪費頻寬。SelectiveSubscribe 允許訂閱者指定關注的傳感器名單(邏輯 OR:任一變化即通知)。
public class MyBrickServiceOperations : PortSet<
DsspDefaultLookup, DsspDefaultDrop, Get,
SelectiveSubscribe, Subscribe> {}
public class SelectiveSubscribe : Subscribe<
MySubscribeRequestType, PortSet<SubscribeResponseType, Fault>> {}
[DataContract]
public class MySubscribeRequestType : SubscribeRequestType
{
[DataMember] public List<string> Sensors; // 要訂閱的傳感器名稱
}
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public IEnumerator SelectiveSubscribeHandler(SelectiveSubscribe req)
{
var sub = new submgr.InsertSubscription(
new submgr.InsertSubscriptionMessage(req.Body.Subscriber, req.Body.Expiration, 0));
sub.Body.NotificationCount = req.Body.NotificationCount;
var filter = new List<submgr.QueryType>();
foreach (string s in req.Body.Sensors)
filter.Add(new submgr.QueryType(s.ToUpper())); // OR 邏輯
sub.Body.QueryList = filter.ToArray();
subMgrPort.Post(sub);
yield return Arbiter.Choice(sub.ResponsePort,
delegate(SubscribeResponseType rsp) { req.ResponsePort.Post(rsp); },
delegate(Fault f) { req.ResponsePort.Post(f); });
}
private IEnumerator MyRobotSensorMessageHandler(SensorNotification msg)
{
_state.sensor = msg.sensor;
var notify = new List<string> { msg.Name.ToUpper() };
// 通知普通訂閱者
subMgrPort.Post(new submgr.Submit(_state, DsspActions.ReplaceRequest));
// 通知選擇性訂閱者
subMgrPort.Post(new submgr.Submit(_state, DsspActions.ReplaceRequest, notify.ToArray()));
yield break;
}
使用 Alternate Contracts 借用現有協議(如 Motor、ContactSensorArray),無需複製定義文件。
[ServicePort("/RobotOne", AllowMultipleInstances = false)]
RobotOneOperations _mainPort = new RobotOneOperations();
[AlternateServicePort(AlternateContract = motor.Contract.Identifier)]
motor.MotorOperations _motorPort = new motor.MotorOperations();
SetMotorPower、Get、Subscribe 等)。DssInfo \o:"D:\RoboticsCommon" \s:Html bin\RoboticsCommon.dll
// 找到 Contract: https://schemas.microsoft.com/robotics/2006/05/motor.html
DssNewService.exe /service:MyRobotMotor /dir:samples\MyRobotMotor /alt:"https://schemas.microsoft.com/robotics/2006/05/motor.html"
[Partner("MyBrickService", Contract = brick.Contract.Identifier, ...)]
brick.MyBrickServiceOperations _brickPort = new brick.MyBrickServiceOperations();
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public IEnumerator SetMotorPowerHandler(motor.SetMotorPower msg)
{
double power = msg.Body.TargetPower;
if (_state.ReversePolarity) power *= -1;
_state.CurrentPower = power;
int native = (int)Math.Round(power * _state.PowerScalingFactor);
var cmd = new brick.SetMotor { PowerSetpoint = native };
yield return Arbiter.Choice(_brickPort.SendMotorCommand(cmd),
delegate(DefaultUpdateResponseType ok) { msg.ResponsePort.Post(ok); },
delegate(Fault f) { msg.ResponsePort.Post(f); });
}
private void SubscribeToNXT()
{
var notifyPort = new brick.MyBrickServiceOperations();
var req = new brick.MySubscribeRequestType { Sensors = new List<string>() };
foreach (var s in _state.Sensors) req.Sensors.Add("TOUCH" + s.Identifier);
Activate(Arbiter.Choice(_brickPort.SelectiveSubscribe(req, notifyPort),
delegate(SubscribeResponseType rsp) {
subscribed = true;
Activate(Arbiter.Receive(true, notifyPort, SensorNotificationHandler));
},
delegate(Fault f) { LogError("Subscription failed"); }));
}
private void SensorNotificationHandler(brick.Replace notify)
{
foreach (var s in _state.Sensors)
{
bool pressed = notify.Body.SensorPort[s.Identifier - 1] == 1;
if (s.Pressed != pressed)
{
s.Pressed = pressed; s.TimeStamp = DateTime.Now;
_subMgrPort.Post(new submgr.Submit(s, DsspActions.UpdateRequest));
}
}
}
繼承 ContactSensorArrayState,添加距離/閾值字段。主/備用端口分別掛載不同 Interleave,Get 等消息需分別實現處理器,備用端口返回基類狀態(顯式 Clone() 轉型避免序列化衝突)。
作者註:文章翻譯自 MSDN《Robotics Tutorial 6》,英文原文閱讀疲勞,翻譯以助理解。硬件經驗有限,歡迎指正。推薦 laneser 博客深度研究 MRDS。