使用 C# 控制 Myo Armband





5.00/5 (12投票s)
您是否对获得 Thalmic Labs 的 Myo Armband 感到兴奋?如果您是 C# 开发者,那么请查看这个开源库,它可以帮助您控制您的 Myo!这篇文章最初发布于 http://www.DevLeader.ca。
背景
Thalmic Labs 已经开始发货他们的 Myo Armband,该设备允许佩戴者通过手臂的动作和手势来控制各种集成技术。是不是很酷?我的朋友和我决定尝试一下,看看我们能做出什么。我们都是 C# 的拥护者,所以当我们看到 SDK 中唯一的 C# 支持是为 Unity 制作的,我们感到有些意外。我们决定自己动手,开源了一个 Myo C# 库。我们很高兴推出第一个版本的 MyoSharp!
底层的 Myo 组件是用 C++ 编写的,并且只有几个函数可以从库中访问。为了实现这一点,我们需要利用 C# 中的平台调用(PInvokes)来访问这些功能。一旦设置好 PInvokes,您就可以开始动手尝试了!
工作流程
设置 Myo 相当直接,但一开始并不明显。没有人指导我们了解不同组件是如何协同工作的(只能靠自己一点一点地摸索),所以我们不得不进行尝试。一旦我们弄清楚了整个流程,其实还是相当简单的。
- 第一步是与蓝牙模块建立通信通道。您不需要担心实现细节,因为这些都是由 Thalmic 的开发者用 C++ 完成的。通过 C# 调用正确的 PInvokes 方法,我们可以接入从蓝牙模块传输过来的“事件”流。
- 现在我们能够拦截事件了,我们需要能够识别 Myo。毕竟,与 Myo 交互是我们的主要目标!我们可以监听来自蓝牙模块的“配对”事件,该事件会在 Myo 配对时通知我们,并为我们提供设备的句柄。这个句柄用于识别特定 Myo 的事件或向特定 Myo 发送消息。
- 有一个连接事件,当 Myo 在与蓝牙模块配对后连接时会触发。Myo 可以被配对但断开连接。
- 现在我们可以唯一地识别一个 Myo 了,我们只需要做的事情就是拦截特定 Myo 的事件并理解设备发送过来的数据!方向改变?加速度改变?设备会发送大量信息回来,所以我们需要对其进行解释。
- 当 Myo 断开连接时,也会发送一个相应的事件。
开始使用 MyoSharp
我将从一些简单的代码开始,这些代码应该能说明开始使用 MyoSharp 有多容易。我将在代码之后立即解释其工作原理。
using System;
using MyoSharp.Device;
using MyoSharp.ConsoleSample.Internal;
namespace MyoSharp.ConsoleSample
{
/// <summary>
/// This example will show you the basics for setting up and working with
/// a Myo using MyoSharp. Primary communication with the device happens
/// over Bluetooth, but this C# wrapper hooks into the unmanaged Myo SDK to
/// listen on their "hub". The unmanaged hub feeds us information about
/// events, so a channel within MyoSharp is responsible for publishing
/// these events for other C# code to consume. A device listener uses a
/// channel to listen for pairing events. When a Myo pairs up, a device
/// listener publishes events for others to listen to. Once we have access
/// to a channel and a Myo handle (from something like a Pair event), we
/// can create our own Myo object. With a Myo object, we can do things like
/// cause it to vibrate or monitor for poses changes.
/// </summary>
internal class BasicSetupExample
{
#region Methods
private static void Main(string[] args)
{
// create a hub that will manage Myo devices for us
using (var hub = Hub.Create())
{
// listen for when the Myo connects
hub.MyoConnected += (sender, e) =>
{
Console.WriteLine("Myo {0} has connected!", e.Myo.Handle);
e.Myo.Vibrate(VibrationType.Short);
e.Myo.PoseChanged += Myo_PoseChanged;
};
// listen for when the Myo disconnects
hub.MyoDisconnected += (sender, e) =>
{
Console.WriteLine("Oh no! It looks like {0} arm Myo has disconnected!", e.Myo.Arm);
e.Myo.PoseChanged -= Myo_PoseChanged;
};
// wait on user input
ConsoleHelper.UserInputLoop(hub);
}
}
#endregion
#region Event Handlers
private static void Myo_PoseChanged(object sender, PoseEventArgs e)
{
Console.WriteLine("{0} arm Myo detected {1} pose!", e.Myo.Arm, e.Myo.Pose);
}
#endregion
}
}
在这个例子中,我们创建了一个 Hub 实例。Hub 会管理上线和下线的 Myo 设备集合,并通知感兴趣的监听器。在后台,Hub 会创建一个 Channel 实例,并将其传递给 DeviceListener 实例。Channel 和 DeviceListener 的组合允许我们接收设备上线时的通知,这是 Hub 实现的核心。如果您愿意,您可以完全绕过 Hub 类,创建自己的 Channel 和 DeviceListener 来自己管理 Myo。这完全取决于您。
在上面的代码中,我们已经连接了几个事件处理程序。有一个事件处理程序用于监听 Myo 设备何时连接,还有一个类似的事件处理程序用于监听设备何时断开连接。我们还连接了一个 Myo 设备实例的 `PoseChanged` 事件。每次硬件检测到用户做出不同的姿势时,这都会简单地在控制台输出一条消息。
当设备离线时,Hub 实际上会保留 Myo 对象的实例。这意味着,如果您有一个设备 A,并且您连接了它的 `PoseChanged` 事件,即使它离线并多次重新上线,您的事件仍然会连接到代表设备 A 的对象。这使得管理 Myo 变得更加容易,相比于每次设备上线和离线时都重新连接事件处理程序。当然,您可以自由使用我们的构建块来创建自己的实现,所以没有理由感到被迫采用这种模式。
值得一提的是,`UserInputLoop()` 方法仅用于保持程序运行。GitHub 上的示例代码允许您使用一些调试命令来读取一些 Myo 状态(如果您感兴趣)。否则,您可以简单地将此行替换为 `Console.ReadLine()` 以阻塞等待用户按 Enter 键。
姿势序列
在不深入研究加速度计、方向传感器和陀螺仪读数的情况下,我们正在寻找快速改进我们创建的基础 API 的方法。我们想要做的一个小改进是实现姿势序列的概念。Myo 在姿势改变时发送事件,但如果您想将这些姿势组合起来,开箱即用并没有直接的方法。通过姿势序列,您可以声明一系列姿势,并在用户完成序列时触发一个事件。
这里有一个例子:
using System;
using MyoSharp.Device;
using MyoSharp.ConsoleSample.Internal;
using MyoSharp.Poses;
namespace MyoSharp.ConsoleSample
{
/// <summary>
/// Myo devices can notify you every time the device detects that the user
/// is performing a different pose. However, sometimes it's useful to know
/// when a user has performed a series of poses. A
/// <see cref="PoseSequence"/> can monitor a Myo for a series of poses and
/// notify you when that sequence has completed.
/// </summary>
internal class PoseSequenceExample
{
#region Methods
private static void Main(string[] args)
{
// create a hub to manage Myos
using (var hub = Hub.Create())
{
// listen for when a Myo connects
hub.MyoConnected += (sender, e) =>
{
Console.WriteLine("Myo {0} has connected!", e.Myo.Handle);
// for every Myo that connects, listen for special sequences
var sequence = PoseSequence.Create(
e.Myo,
Pose.WaveOut,
Pose.WaveIn);
sequence.PoseSequenceCompleted += Sequence_PoseSequenceCompleted;
};
ConsoleHelper.UserInputLoop(hub);
}
}
#endregion
#region Event Handlers
private static void Sequence_PoseSequenceCompleted(object sender, PoseSequenceEventArgs e)
{
Console.WriteLine("{0} arm Myo has performed a pose sequence!", e.Myo.Arm);
e.Myo.Vibrate(VibrationType.Medium);
}
#endregion
}
}
与第一个示例相比,基本设置是相同的。我们创建了一个 Hub 来监听 Myo,当 Myo 连接时,我们将其连接到一个新的 `PoseSequence` 实例。如果您还记得第一个示例中的 Hub 类的工作方式,这将在每次 Myo 连接时挂载一个新的姿势序列(在这种情况下,实际上并不是理想的)。我们只是为了演示目的才采用了这个捷径。
在创建姿势序列时,我们只需要提供 Myo 和构成序列的姿势。在这个例子中,用户需要先挥动手臂向外,然后向内,姿势序列才会完成。提供了一个事件,当序列完成时会触发。如果用户多次向外和向内挥手,每次序列完成时都会触发该事件。您还会注意到,在我们的事件处理程序中,我们实际上向 Myo 发送了一个振动命令!大多数 Myo 交互都是读取 Myo 事件中的值,但在这种情况下,这是我们可以实际发送给它的命令之一。
保持姿势
Myo 设备事件流仅在设备检测到变化时发送姿势事件。当我们尝试使用初始 API 创建测试应用程序时,我们对无法在姿势被保持时触发某些操作感到沮丧。某些操作,如缩放、平移或调整某个内容的级别,最好与用户保持的姿势关联。否则,如果您想制作一个当用户握紧拳头时会放大的应用程序,用户就必须一遍又一遍地握紧拳头、放松、握紧拳头、放松……直到他们放大或缩小到足够远。这显然会影响用户体验,所以我们着手使这成为我们 API 的一个简单部分。
下面的代码与之前的示例设置类似,但引入了 `HeldPose` 类。
using System;
using MyoSharp.Device;
using MyoSharp.ConsoleSample.Internal;
using MyoSharp.Poses;
namespace MyoSharp.ConsoleSample
{
/// <summary>
/// Myo devices can notify you every time the device detects that the user
/// is performing a different pose. However, sometimes it's useful to know
/// when a user is still holding a pose and not just that they've
/// transitioned from one pose to another. The <see cref="HeldPose"/> class
/// monitors a Myo and notifies you as long as a particular pose is held.
/// </summary>
internal class HeldPoseExample
{
#region Methods
private static void Main(string[] args)
{
// create a hub to manage Myos
using (var hub = Hub.Create())
{
// listen for when a Myo connects
hub.MyoConnected += (sender, e) =>
{
Console.WriteLine("Myo {0} has connected!", e.Myo.Handle);
// setup for the pose we want to watch for
var pose = HeldPose.Create(e.Myo, Pose.Fist, Pose.FingersSpread);
// set the interval for the event to be fired as long as
// the pose is held by the user
pose.Interval = TimeSpan.FromSeconds(0.5);
pose.Start();
pose.Triggered += Pose_Triggered;
};
ConsoleHelper.UserInputLoop(hub);
}
}
#endregion
#region Event Handlers
private static void Pose_Triggered(object sender, PoseEventArgs e)
{
Console.WriteLine("{0} arm Myo is holding pose {1}!", e.Myo.Arm, e.Pose);
}
#endregion
}
}
当我们创建一个 `HeldPose` 实例时,我们可以传入一个或多个我们想要监视的、被保持的姿势。在上面的示例中,我们正在监视用户何时握紧拳头或何时展开手指。我们可以连接到 `HeldPose` 实例上的 `Triggered` 事件,我们在事件处理程序中获得的事件参数会告诉我们触发事件的实际姿势。
如果您回想一下我之前开始描述的缩放示例,我们可以有一个单一的事件处理程序,根据保持的姿势来处理放大和缩小。如果我们选择两个姿势,比如握拳表示放大,展开手指表示缩小,那么我们可以在事件处理程序中检查事件参数中的姿势并相应地调整缩放。当然,如果您愿意,您也可以创建两个 `HeldPose` 实例(每个姿势一个)并单独连接到事件。这将最终在后台创建两个定时器线程——每个 `HeldPose` 实例一个。
`HeldPose` 类还有一个间隔设置。这允许程序员调整在用户保持姿势的情况下,`Triggered` 事件希望触发的频率。例如,如果将间隔设置为两秒钟,只要姿势被保持,`Triggered` 事件就会每两秒触发一次。
翻滚、俯仰和偏航
来自 Myo 的数据可能会让人不知所措,除非您精通矢量数学和三角学。我们希望进一步改进和优化的是来自 Myo 的数据可用性。我们不希望每个程序员都必须编写类似的 C++ 代码来将 Myo 的值转换为可用于其应用程序的格式。相反,如果我们能将这些功能集成到 MyoSharp 中,那么每个人都将受益。
翻滚、俯仰和偏航是我们决定直接内置到 API 中的值。那么……这些到底是什么呢?这里有一张图表来帮助说明。
以下代码示例展示了如何连接到事件处理程序以获取翻滚、俯仰和偏航数据。
using System;
using MyoSharp.Device;
using MyoSharp.ConsoleSample.Internal;
namespace MyoSharp.ConsoleSample
{
/// <summary>
/// This example will show you how to hook onto the orientation events on
/// the Myo and pull roll, pitch and yaw values from it.
/// </summary>
internal class OrientationExample
{
#region Methods
private static void Main(string[] args)
{
// create a hub that will manage Myo devices for us
using (var hub = Hub.Create())
{
// listen for when the Myo connects
hub.MyoConnected += (sender, e) =>
{
Console.WriteLine("Myo {0} has connected!", e.Myo.Handle);
e.Myo.OrientationDataAcquired += Myo_OrientationDataAcquired;
};
// listen for when the Myo disconnects
hub.MyoDisconnected += (sender, e) =>
{
Console.WriteLine("Oh no! It looks like {0} arm Myo has disconnected!", e.Myo.Arm);
e.Myo.OrientationDataAcquired -= Myo_OrientationDataAcquired;
};
// wait on user input
ConsoleHelper.UserInputLoop(hub);
}
}
#endregion
#region Event Handlers
private static void Myo_OrientationDataAcquired(object sender, OrientationDataEventArgs e)
{
Console.Clear();
Console.WriteLine(@"Roll: {0}", e.Roll);
Console.WriteLine(@"Pitch: {0}", e.Pitch);
Console.WriteLine(@"Yaw: {0}", e.Yaw);
}
#endregion
}
}
当然,如果我们知道人们在使用方向数据方面有更多常见的用例,我们很乐意将这类东西直接内置到 MyoSharp 中,以便使每个人都能更轻松地使用。
结语
这就是利用 MyoSharp 创建自己的 C# 应用程序来使用 Myo 的一个快速概览!如前所述,MyoSharp 是开源的,我们非常欢迎贡献或建议。我们的目标是为我们的框架提供尽可能多的基础功能,但同时设计成开发者可以扩展每个单独的构建块。
这篇文章 使用 C# 控制 Myo Armband 最初发布于 Dev Leader。