带你读《JavaScript机器人: 用Raspberry Pi、Arduino和BeagleBone构建NodeBots Make:JavaScript Robotics》之二:TypeBot-阿里云开发者社区

开发者社区> 华章出版社> 正文
登录阅读全文

带你读《JavaScript机器人: 用Raspberry Pi、Arduino和BeagleBone构建NodeBots Make:JavaScript Robotics》之二:TypeBot

简介: 本书将向你展示用Raspberry Pi、Arduino和BeagleBone构建NodeBots Make以及如何使用它来编写你所制作的东西。让使用JavaScript控制硬件变得简单而有趣。

|第2章|
点击查看第一章
点击查看第三章

TypeBot

Bryan Hughes

TypeBot是一款可以在键盘上打字的机器人。它是在2013年佛罗里达举办的JS开发者大会期间的一个黑客日被创造出来的,其开发者是一个三人团队,他们的灵感来自于拉克尔维(Raquel Velez)关于机器人学的演讲。回溯到21世纪早期,该团队的一些成员以前有过机器人方面的经验,但是没有人曾用过JavaScript作为机器人的脚本语言。对于团队来说,使用JavaScript轻松地构建机器人是一个令人欢呼的重要时刻,我们希望你也能感同身受。
对于TypeBot来说,Arduino Uno是一个理想的微控制器,这是因为它有很多用来控制伺服的引脚(见图2-1)。Johnny-Five现在在Arduino Uno上也十分成熟了,它同时也拥有着所有支持平台中的最佳支持。
image.png

图2-1 完整的手臂

那么,为什么要制造一个打字机器人?因为我们有这个开发能力,而且,这个机器人很酷。

2.1 材料清单

表2-1列出了构建TypeBot所需的材料。

表2-1 材料清单

image.png

如果你有一个Arduino伺服屏蔽,你可以使用它来代替面包板和跳线。Arduino伺服屏蔽是一个扩展板,可以卡在你的Arduino上。该板提供了可以直接将伺服插入的接头,无须手动连接所有接头。但是,如果伺服电线太短,你可能还需要一些跳线。
你还需要以下工具:

  • 热胶枪
  • 小型十字螺丝刀
  • 钻孔机

钻孔机用于在冰棒棍上钻孔,所以不需要有很大的功率。如果你没有钻孔机,也可以使用小刀或螺丝刀,但是它们不是很实用。如果你使用小刀,可以把它戳到冰棒棍上并用手指旋转它。旋转小刀,使它慢慢“钻出”冰棍棒。然而使用这种方式钻孔会加快刀片的磨损,因此不要使用昂贵的刀具。
了解伺服电机
我们将使用伺服电机(简称伺服)作为手臂的“关节”。伺服有很多类型,了解它们之间的差异非常重要。伺服一般可分为两类:标准型和连续型。
标准伺服是具有有限运动范围的伺服,以度为测量单位。使用标准伺服可以将信号发送至转换到具体位置的伺服。标准伺服通常用于远程控制(Remote Control,RC)汽车转向。标准伺服通常可以在180°弧度内设置任何位置,但90°伺服也很常见。
连续伺服可以自由旋转。使用连续伺服,你可以向伺服发送一个转换速度的信号。连续伺服通常用于RC车辆以控制驱动轮。
那么控制伺服的信号呢?所有伺服均由脉冲宽度调制信号或PWM驱动。伺服预计每50毫秒发出一次脉冲。脉冲宽度决定了标准伺服的位置,以及连续伺服的速度。宽度为5毫秒一直向左,宽度为15毫秒一直向右。
有时你会遇到一个与这些数字不完全匹配的伺服,所以你可能需要稍微调整一下这些数字。
本项目需要三个180°标准伺服,但如果你没有三个这样的伺服,那么三个伺服中有两个可以是90°伺服。本章的其余部分假设你有三个180°伺服。

2.2 剖析机器人手臂

我们都知道手臂是如何工作的,对吗?它们由人类大脑控制,不会思考。我们的大脑构造复杂,除了控制手臂,它也可以控制所有令人烦躁的小细节。拾取物体这一行为对我们来说非常容易。我们只需想一下要做什么,神经系统就会为我们做剩下的事情。
然而,机器人手臂不像人类大脑控制手臂那样强大。这意味着我们必须自己处理所有细节。我们将会发现,选择的手臂布局将决定运动中的约束类型。

2.2.1 设计手臂

TypeBot将使用带有三个伺服的手臂作为关节,我们将其称为“肩膀”“肘部”和“手腕”。
肘部和手腕将通过冰棒棍连接。伺服相互间隔180°安装,这样它们可以进行相对运动。以相同的速率移动伺服将使指尖沿直线移动。图2-2至2-4显示了各种运动阶段。

image.png


图2-2 手臂沿直线移动,全部伸展开


image.png


图2-3 手臂沿直线移动,正在收缩


image.png


图2-4 手臂沿直线移动,收缩完毕

然后将该结构连接到肩部伺服上,并且手臂部分从其余部分旋转90°。第三个伺服用于设定沿着其他两个伺服系统伸展和收缩的线的角度。这允许机器人指尖移动到键盘上的任何位置。

2.2.2 约束手臂

大多数机器人非常笨拙,我们不希望机器人敲击我们的键盘(特别是当键盘内置在性能良好的笔记本电脑中时)。因此,我们需要协调机器人手臂的运动,就像在网页中编排复杂的动画一样。如果你仔细思考一下,这其实就是最终的动画,因为它是现实中的运动。
我们可以利用一些很棒的数学公式(这些公式被称为“约束编程”)来创建一个保证永不击中任何东西的最佳动画。我们也可以随时提出一些想法,并利用额外的时间向所有朋友展示TypeBot,这听起来更有趣!
那么我们担心的是哪种约束呢?我们假设你将在笔记本电脑键盘上使用TypeBot,这是因为与独立键盘相比,它有一些额外的约束。
第一个约束是我们不想通过过度伸展手臂来击中笔记本电脑的屏幕。扩展肘部关节的最大量根据底部伺服的角度而变化。这意味着不能只说“伸展不要超过×度。”幸运的是,这种约束直接关系到我们需要多长时间才能伸出手臂以达到键本身。如果你向下看笔记本电脑上的键盘,并假装触控板的中心是中央伺服,那么你可以看到6键比1键更接近中心。这意味着当手臂沿着键盘左右移动时,手臂需要伸展和收缩。如果我们没有计时手臂的伸展和肩部的旋转,那么它会撞到笔记本电脑屏幕。
第二个约束是我们不希望在键之间移动时将手臂拖过键盘。毕竟这不是一个Swype虚拟键盘!我们必须在这里确保一些事情。当机器人手臂旋转时,我们需要确保手指安全地悬停在键盘上方。我们还需要确保,当按下一个键时,手臂能干净利落地下降到键上(即垂直于键盘)。如果不这样做,手臂可能会有从键上滑下来的危险。
那么我们如何解决这些约束呢?现在告诉你就没那么有趣了,所以请继续阅读吧!

2.3 构建硬件

现在让我们动手来构建机器人手臂吧!

2.3.1 底座和肩膀

首先获得木质底座和其中一个伺服。使用热胶将伺服连接到底座。如果想稍后重复使用该伺服,不用担心,热胶很容易剥落。按照以下步骤操作:
1.在伺服底部涂上一层胶水。确保使用足量胶水,因为我们不希望伺服和底座之间有任何气隙。
2.将伺服压在底座上,使伺服轴距离其中一个边缘约1/3。
3.根据需要快速定位伺服。
4.让它静置30秒左右,让胶水凝固。
接下来,我们需要加强伺服与底座的黏合。还记得我们如何使用沉重的木质底座稳定手臂的移动吗?所有这些力量现在都体现在这个胶合接头上,所以需要黏性大的胶水。就好像你正在填充淋浴或修剪蛋糕底部的糖霜一样,在伺服和底座之间添加一圈热胶。连接好的伺服应如图2-5所示。

image.png


图2-5 带肩部的底座

2.3.2 肘部

接下来,我们将建立第一个臂节和肘关节。这个部分需要很坚固,因为它将支撑整个手臂的重量。不幸的是,该臂节也成角度,使得手臂的力量沿着杆的平坦、柔性一侧。以下是应遵循的步骤。
1.为了确保手臂坚固,取出三根冰棒棍,将它们用热胶黏在一起。再次确保在棍棒之间加入大量热胶,这样就没有气隙了。
2.让它静置至少30秒,等待胶水凝固。
3.然后在它的中心位置用钻孔机钻出一个孔。这个孔需要足够大,能够使伺服手臂的螺钉穿过。1/4英寸钻头应该足以使螺钉头部能够穿过小孔了。
4.取出伺服臂,在其上加一层轻薄的热胶。如果可以,尽量不要在螺钉穿过的中心孔上涂胶。
5.将伺服臂连接到杆组上,使手臂中的孔与刚刚钻好的孔对齐:
a.如果你在杆组中间的孔中涂上胶水,那么可以用伺服臂螺丝将它拧入胶水中进行钻孔。
b.继续拧紧,直到胶水松动,然后用螺丝刀将螺丝从另一端推出。
6.取出另一个伺服作为肘关节。
7.添加足够多的热胶,防止从上一步到杆组一端的气隙。胶水应放置在粘贴伺服臂的杆组的另一侧。
8.将伺服按到杆组的侧面,使轴垂直于杆的末端,如图2-6所示。
现在将肘关节连接到底座。将肩部伺服插头上的白线连接到Arduino上面的引脚3,将红线连接到5V,将黑线连接到GND(接地)。为了确保肘关节与手臂对齐,需要写一个小巧的Johnny-Five程序来保持肩部伺服处于中心。

image.png


图2-6 肘关节和臂节

确保你的Arduino已按照“Arduino”中的指示准备好,并连接到将运行Johnny-Five的计算机或设备上。如果在运行此项目时遇到任何问题,请参阅“Installing Johnny-Five”。
首先创建一个名为TypeBot/的文件夹。打开终端并导航到此文件夹。例如,如果你使用的是OS X或Ubuntu系统,并在Documents /文件夹中创建了此文件夹,那么请键入:
image.png
如果你的Windows、OS X或者Linux系统没有安装Node,那么请参阅“Installing Node.js”。
打开这个文件夹后,输入以下内容安装Johnny-Five:
image.png
在名为align.js 的文件夹中创建一个新的文本文件,然后将下面的代码复制到文本中:
image.png
使用以下命令运行程序:
image.png
本书中全部示例的源代码均可在GitHub上找到,网址为https://github.com/rwaldron/javascript-robotics
连接手臂时应该保持程序运行,以确保伺服保持居中。
通过将杆组底部的伺服臂滑动到外露的伺服齿轮上,将肘关节连接到肩部伺服。杆组应垂直于底部,肘关节悬挂在底部边缘上。一旦到位,将手臂拧入肩部伺服来固定它。效果如图2-7所示。

image.png


图2-7 肘关节伺服的连接

2.3.3 腕部

下一个臂节与第一个臂节相似,但有一些变化。这个部分只用了两根冰棒棍,而不是三根。我们可以在这里减少支撑,因为这个部分不需要支撑那么多的重量。你甚至可以用一根冰棒棍。第二根冰棒棍可以帮助减少弯曲,但它确实增加了重量,这完全取决于你的个人偏好。请按照以下步骤操作:
1.将两根冰棒棍黏在一起。
2.从杆组的一端钻一个孔。伺服臂应尽可能靠近边缘,不要悬挂。
3.使用与以前相同的方式将伺服臂粘贴到杆组上。
4.拿出最后一个伺服。
5.在用胶水黏着伺服臂的杆组的另一侧和末端添加胶水。
6.将伺服按在杆组上,使伺服轴在杆组上朝上,如图2-8所示。

image.png


图2-8 腕部伺服的连接

现在将腕部连接到肘部伺服。除了应该将肘部连接到Arduino而不是肩部连接,使用完全相同的方式将肘部臂连接到底部。将腕部连接到肘部伺服使其直接指向空中,如图2-9所示。

image.png


图2-9 安装腕节

2.3.4 手指

现在是时候建立手臂的最后一部分了,这也是最简单的!这个手臂部分不需要太大的力量,所以我们只使用一根冰棍棒。首先在距离末端大约一英寸处钻一个孔,就像我们为腕部做的那样。然后像以前一样将伺服臂黏到冰棍棒上。
在这个过程中,唯一不同的操作就是将热胶涂在手臂的另一端。涂在冰棍棒的末端有两个原因。其一,我们不希望冰棍棒的末端刮伤键盘;其二,热胶会给冰棍棒提供更多的抓地力。这样做可以防止在我们试图按下手臂时,它从键上滑落。手指应该如图2-10所示。
现在连接手指。从Arduino上断开肘部伺服并连接腕部伺服。运行程序使伺服居中,并将手指垂直于腕部且平行于地面。通过这一步操作,你现在拥有了一个完全构造的手臂!接下来只剩下一件事了。

image.png


图2-10 手指臂节

2.3.5 脑部

最后一步是将所有伺服连接到Arduino上并确保接线安全。建议将腕部伺服电线黏到腕部末端,如图2-11所示。这将有助于防止腕部伺服电线在手臂移动时被卡住。

image.png


图2-11 固定腕线


使用跳线或伺服电线延长线将伺服和Arduino连接到面包板上,如Fritzing图所示(见图2-12)。

image.png


图2-12 布线图

你应该确保从手臂上脱落的电线足够松弛,这样手臂就有足够的空间移动。最终完成后的手臂应该如图2-13所示。

image.png


图2-13 完整的手臂

现在我们让机器人打字吧!

2.4 编写软件

我们将软件分为两部分:伺服控制和序列管理。伺服控制模块在Johnny-Five伺服API的顶部添加了一个吸收层。我们使用这种抽象类型来轻松协调多个伺服的运动。

2.4.1 创建项目文件

首先,需要创建包含代码的文件:
1.打开终端并导航回之前创建的TypeBot/文件夹。
2.在此文件夹中创建两个文件:typebot.js和servocontrol.js。这些文件将包含两个模块的源代码。

2.4.2 控制伺服

首先,创建控制模块,添加例2-1中的
样板。
例2-1 伺服控制样板
image.png
用init和move两种方法创建模块,该模块将调用typebot.js。它还导入了Johnny-Five库并创建了一些全局状态变量。
初始化负责创建Johnny-Five伺服实例并初始化全局状态信息。init参数包含三条信息——每个伺服配置、伺服转速和稳定时间。

  • 每个伺服配置都用一个名称标识,并包含引脚编号、起始位置以及是否反转伺服角度。
  • 伺服速率将指定伺服可以旋转的最大速度,以度/毫秒为单位,尽管它可能旋转得更慢。
  • 停留时间用于在伺服完成移动后添加额外的延迟。当手臂移动时,它会摇晃。一旦手臂停止移动,它通常会持续晃动一小段时间。停留时间可以让手臂在进行下一步之前有机会停止颤抖。

以下是包含所有内容的配置对象的外观(现在不需要将其添加到代码清单中):
image.png
image.png
完整的init方法如例2-2所示。
例2-2 完整的init方法
image.png
①修改init函数来存储选项。这样我们以后可以在move函数中使用它们。
②让我们自己初始化伺服。该代码将循环遍历伺服对象中的每个伺服,并通过执行以下步骤对其进行初始化:
1.将起始位置存储在全局位置变量中。
2.实例化Johnny-Five伺服实例并将其存储在伺服全局变量中。
3.将伺服移至起始位置。
③添加一个超时时间,给伺服时间移动到其起始位置。这行代码将在1秒后调用callback,告诉typebot.js初始化完成。
现在开始第二个方法:move。该方法将为多个伺服系统采用一组目的地,并确保它们以协调的方式移动。我们将计算每个伺服需要旋转的速度,这样可以确保它们同时到达目的地。完整的move方法如
例2-3所示。
例2-3 完整的move方法
image.png
①首先找出需要移动到最远的伺服。遍历每个变化,找出哪个伺服需要移动到最远地点。
②为了提高代码效率,检查是否需要移动任何伺服。请注意,我们没有直接调用callback函数,而是将它包装在process.nextTick调用中。如果没有更改,则会同步调用callback函数,但如果有更改,则会异步调用。这可能会引入难以发现的细微错误。有关此问题的更多信息,请查看代码注释中的链接。
③我们计算带有最大增量的伺服移动到新目的地所需的时间。
此代码计算在以选项中提供的速度移动时,此伺服移动到其位置所需的
时间。
我们使用刚刚计算的持续时间来移动所有伺服,这意味着其他伺服将以较慢的速度移动。还记得本章开头介绍的笔记本电脑屏幕的约束吗?这就是我们处理它的方式!事实证明,当手臂从靠近键盘中间顶部的键(如t、y等)开始时,手臂只能击中屏幕并移动到靠近键盘上边缘的键(如q、p等)。以较慢的速度移动其他伺服可防止肘部和腕部旋转太快并撞击屏幕,因为在这些情况下肩部将始终比肘部和腕部旋转得更快。
④在那之后,移动手臂。遍历所有伺服,更新它们的位置,然后移动它们。
⑤在代码中的这个时间点,我们已经做了一切需要做的事情来告诉伺服移动到它们的位置。现在只需要等待硬件来做它的事情。我们只是等待duratiion毫秒传递,然后等待settleTime毫秒传递,并调用callback函数。

2.4.3 初始化

现在我们有了伺服模块,在主模块中启动它。打开typebot.js并添加以下代码:
image.png
接下来,创建一系列要键入的键。我们首次尝试输入的序列非常明显:
image.png
现在创建伺服选项。首先,我们将伺服转速定义为每毫秒0.05度,这稍微超过8转。我们希望在对系统进行微调时以低伺服转速开始。一旦一切都以这个速度工作,我们将它提升到每毫秒0.2度,或转image.png
转(我最喜欢的转速!):
image.png
接下来,我们将稳定率定义为0.25秒。如果发现手臂在键入时变得更不稳定,请尝试增加此值:
image.png
现在创建伺服配置:
image.png
配置伺服使其从图2-14所示的位置开始。

image.png


图2-14 起点

如果运行了代码,发现它与此位置不匹配,请确保已正确设置isInverted标志。如果角度与图形不完全匹配,也不要担心,只要关闭它们就可以了。
我们需要创建的最后一个配置是键和伺服角度之间的映射:
image.png
这些数字来自哪里?我们编写了一个小程序,它通过执行一些三角函数来估计键的位置。要注意的是该程序只能生成估值,因为伺服宽度/高度的微小变化,螺丝孔会略微错位,伺服不精确为180°等。
最后一步是初始化Johnny-Five和伺服:
image.png
现在运行代码。确保所有伺服都正确连接到Arduino,并将Arduino插入笔记本电脑上。运行节点typebot.js,手臂应该移动到它的起始位置!

2.4.4 按键排序

现在我们有了伺服模块,在本章开始给出约束的情况下,如何使用它按键?我们将使用状态机将按键分解为三个步骤:
1.将手指直接放在键的上方。
2.向下移动手指直到按下键。
3.将手指抬回在步骤1结束时手指所处的位置。
这三个步骤可以用图2-15中所示的状态机表示。

image.png


图2-15

我们可以用事件循环和switch语句来表示这个状态机。现在应该将例2-4中显示的代码添加到run方法中。
例2-4 run方法
image.png
image.png
①在这里,我们定义了四种状态:闲置、移动、按压和释放。
②为了记录状态机,我们使用三个全局变量:sequencePosition、state和key。sequencePosition记录当前按下的序列中的按键。state记录当前所在序列中的状态。最后,key是KEYS数组中当前按键条目的别名。
③我们还创建了一个名为tick的函数,它通过事件循环处理单个进程。对于每个键,此方法将被调用四次,每个状态一次。每个状态负责过渡到下一个事件。在此处看到的代码只是这些case语句的存根。接下来将看到每个语句的完整定义。
应用程序在闲置状态下启动。当循环开始时,闲置状态转换为移动状态,使用以下代码替换先前的案例STATE_IDLE的定义:
image.png
image.png
①从序列中获取下一个键并存储它。如果没有键,则表示我们已完成输入并可退出。
②将状态更改为移动并告诉伺服模块移动伺服。在肘部和腕部添加一个偏移量,使它们在键盘上方悬停约一英寸。这可以防止手臂沿着键盘拖动。请注意,我们提供tick方法作为伺服控制移动方法的回调。这样,一旦手臂完成移动,我们将在事件循环中勾选下一个循环。
下一个状态是移动,看起来很像闲置状态的后半部分。唯一的区别是我们移除偏移量,使手臂向下移动按键。选择这些偏移量是因为它们导致指尖几乎直接向下移动,而这是之前的约束之一。以下内容替换了先前的案例STATE_MOVING的定义:
image.png
释放状态只是颠倒了之前状态中采取的操作:
image.png
最终状态非常简单。它将状态更改回空闲状态,以便我们可以再次循环:
image.png
在这种状态下,我们必须直接调用tick函数,因为没有告诉伺服移动。现在,完整的tick方法应该如例2-5所示。
例2-5 最终的tick方法
image.png
image.png

2.4.5 首次运行

你准备好观看手臂打字了吗?现在开始吧!首先,你应该在没有键盘的情况下运行手臂,以防手臂由于某种原因而变得不听使唤。将所有硬件连接到你的计算机上,然后运行节点类型bot.js。手臂应该在空中打字。
如果没有什么明显错误的话,那么就可以尝试在真正的键盘上打字了。将键盘放在手臂下方,使肩部伺服的中心与B键对齐。再次运行节点typebot.js并观察其打字。
如果你非常幸运,那么这次就是它第一次工作。然而,也可能碰不到正确的键。这没关系,因为我们可以微调手臂。

2.4.6 微调手臂

微调一个键的最简单方法是为释放状态案例条目添加注释。这样,手臂将会初始化,移动到第一个键上方,按下键,释放键,然后停止。然后,你可以将感兴趣的字母设置为序列中的第一个条目。遍历每个键并调整角度,直到正确按下键为止。可能需要在所有三个方向上调整手臂以确保良好的按键效果。
一旦你对手臂按下所有键的方式感到满意,那么请启用释放状态案例条目。现在TypeBot应该能够输出“helloworld”。
现在可以输出第11个字母了!开始以0.05为增量增加速度。每次增加速度时,运行TypeBot并观察其执行情况。随着速度的提高,位置上出现的小错误会变得更加明显。使用前面描述的方法微调这些键,直到手臂以新的速度工作。冲洗并重复,直到将速度提高到0.2。
如果你喜欢冒险,可以继续尝试提高速度,但要注意,现在会开始遇到手臂本身的物理限制。手臂快速运动完全有可能造成其支离破碎。

2.5 进一步探索

既然你已经有一个正常运行的TypeBot,那么你还可以用它做什么呢?
TypeBot最初是为了在键盘上打字而创建的,但是弹钢琴怎么样呢?如果加强手臂的力量,它应该可以毫不费力地按下钢琴键!如果将几个手臂组合在一起,就可以演奏(缓慢的)钢琴曲。
当前的实现需要进行大量微调才能使其正常工作。一个更智能的机器人应该会自我校准。可以在手指末端添加一个摄像头,在视频输入上运行光学字符识别(Optical Character Recognition,OCR)以自行查找字母。“node-tesseract”NPM模块在Node.js中提供OCR功能。
对你来说太容易了?这里有一些非常复杂的内容。输入TypeBot将键入的消息有点多余。简单地通过语音告诉TypeBot应输入什么不是更有趣吗?你可以使用Web Speech API(http://bit.ly/1Arwoyl)来实现。首先需要向TypeBot添加一个Web服务器,该服务器会提供一个网页来处理语音到文本的转写,然后将文本发送回TypeBot,告诉它输入的内容。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: