TypeBot
Bryan Hughes
TypeBot是一款可以在鍵盤上打字的機器人。它是在2013年佛羅裡達舉辦的JS開發者大會期間的一個黑客日被創造出來的,其開發者是一個三人團隊,他們的靈感來自于拉克爾維(Raquel Velez)關于機器人學的演講。回溯到21世紀早期,該團隊的一些成員以前有過機器人方面的經驗,但是沒有人曾用過JavaScript作為機器人的腳本語言。對于團隊來說,使用JavaScript輕松地建構機器人是一個令人歡呼的重要時刻,我們希望你也能感同身受。
對于TypeBot來說,Arduino Uno是一個理想的微控制器,這是因為它有很多用來控制伺服的引腳(見圖2-1)。Johnny-Five現在在Arduino Uno上也十分成熟了,它同時也擁有着所有支援平台中的最佳支援。

圖2-1 完整的手臂
那麼,為什麼要制造一個打字機器人?因為我們有這個開發能力,而且,這個機器人很酷。
2.1 材料清單
表2-1列出了建構TypeBot所需的材料。
表2-1 材料清單
如果你有一個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顯示了各種運動階段。
圖2-2 手臂沿直線移動,全部伸展開
圖2-3 手臂沿直線移動,正在收縮
圖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所示。
圖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程式來保持肩部伺服處于中心。
圖2-6 肘關節和臂節
確定你的Arduino已按照“Arduino”中的訓示準備好,并連接配接到将運作Johnny-Five的計算機或裝置上。如果在運作此項目時遇到任何問題,請參閱“Installing Johnny-Five”。
首先建立一個名為TypeBot/的檔案夾。打開終端并導航到此檔案夾。例如,如果你使用的是OS X或Ubuntu系統,并在Documents /檔案夾中建立了此檔案夾,那麼請鍵入:
如果你的Windows、OS X或者Linux系統沒有安裝Node,那麼請參閱“Installing Node.js”。
打開這個檔案夾後,輸入以下内容安裝Johnny-Five:
在名為align.js 的檔案夾中建立一個新的文本檔案,然後将下面的代碼複制到文本中:
使用以下指令運作程式:
本書中全部示例的源代碼均可在GitHub上找到,網址為
https://github.com/rwaldron/javascript-robotics。
連接配接手臂時應該保持程式運作,以確定伺服保持居中。
通過将杆組底部的伺服臂滑動到外露的伺服齒輪上,将肘關節連接配接到肩部伺服。杆組應垂直于底部,肘關節懸挂在底部邊緣上。一旦到位,将手臂擰入肩部伺服來固定它。效果如圖2-7所示。
圖2-7 肘關節伺服的連接配接
2.3.3 腕部
下一個臂節與第一個臂節相似,但有一些變化。這個部分隻用了兩根冰棒棍,而不是三根。我們可以在這裡減少支撐,因為這個部分不需要支撐那麼多的重量。你甚至可以用一根冰棒棍。第二根冰棒棍可以幫助減少彎曲,但它确實增加了重量,這完全取決于你的個人偏好。請按照以下步驟操作:
1.将兩根冰棒棍黏在一起。
2.從杆組的一端鑽一個孔。伺服臂應盡可能靠近邊緣,不要懸挂。
3.使用與以前相同的方式将伺服臂粘貼到杆組上。
4.拿出最後一個伺服。
5.在用膠水黏着伺服臂的杆組的另一側和末端添加膠水。
6.将伺服按在杆組上,使伺服軸在杆組上朝上,如圖2-8所示。
圖2-8 腕部伺服的連接配接
現在将腕部連接配接到肘部伺服。除了應該将肘部連接配接到Arduino而不是肩部連接配接,使用完全相同的方式将肘部臂連接配接到底部。将腕部連接配接到肘部伺服使其直接指向空中,如圖2-9所示。
圖2-9 安裝腕節
2.3.4 手指
現在是時候建立手臂的最後一部分了,這也是最簡單的!這個手臂部分不需要太大的力量,是以我們隻使用一根冰棍棒。首先在距離末端大約一英寸處鑽一個孔,就像我們為腕部做的那樣。然後像以前一樣将伺服臂黏到冰棍棒上。
在這個過程中,唯一不同的操作就是将熱膠塗在手臂的另一端。塗在冰棍棒的末端有兩個原因。其一,我們不希望冰棍棒的末端刮傷鍵盤;其二,熱膠會給冰棍棒提供更多的抓地力。這樣做可以防止在我們試圖按下手臂時,它從鍵上滑落。手指應該如圖2-10所示。
現在連接配接手指。從Arduino上斷開肘部伺服并連接配接腕部伺服。運作程式使伺服居中,并将手指垂直于腕部且平行于地面。通過這一步操作,你現在擁有了一個完全構造的手臂!接下來隻剩下一件事了。
圖2-10 手指臂節
2.3.5 腦部
最後一步是将所有伺服連接配接到Arduino上并確定接線安全。建議将腕部伺服電線黏到腕部末端,如圖2-11所示。這将有助于防止腕部伺服電線在手臂移動時被卡住。
圖2-11 固定腕線
使用跳線或伺服電線延長線将伺服和Arduino連接配接到面包闆上,如Fritzing圖所示(見圖2-12)。
圖2-12 布線圖
你應該確定從手臂上脫落的電線足夠松弛,這樣手臂就有足夠的空間移動。最終完成後的手臂應該如圖2-13所示。
圖2-13 完整的手臂
現在我們讓機器人打字吧!
2.4 編寫軟體
我們将軟體分為兩部分:伺服控制和序列管理。伺服控制子產品在Johnny-Five伺服API的頂部添加了一個吸收層。我們使用這種抽象類型來輕松協調多個伺服的運動。
2.4.1 建立項目檔案
首先,需要建立包含代碼的檔案:
1.打開終端并導航回之前建立的TypeBot/檔案夾。
2.在此檔案夾中建立兩個檔案:typebot.js和servocontrol.js。這些檔案将包含兩個子產品的源代碼。
2.4.2 控制伺服
首先,建立控制子產品,添加例2-1中的
樣闆。
例2-1 伺服控制樣闆
用init和move兩種方法建立子產品,該子產品将調用typebot.js。它還導入了Johnny-Five庫并建立了一些全局狀态變量。
初始化負責建立Johnny-Five伺服執行個體并初始化全局狀态資訊。init參數包含三條資訊——每個伺服配置、伺服轉速和穩定時間。
- 每個伺服配置都用一個名稱辨別,并包含引腳編号、起始位置以及是否反轉伺服角度。
- 伺服速率将指定伺服可以旋轉的最大速度,以度/毫秒為機關,盡管它可能旋轉得更慢。
- 停留時間用于在伺服完成移動後添加額外的延遲。當手臂移動時,它會搖晃。一旦手臂停止移動,它通常會持續晃動一小段時間。停留時間可以讓手臂在進行下一步之前有機會停止顫抖。
以下是包含所有内容的配置對象的外觀(現在不需要将其添加到代碼清單中):
完整的init方法如例2-2所示。
例2-2 完整的init方法
①修改init函數來存儲選項。這樣我們以後可以在move函數中使用它們。
②讓我們自己初始化伺服。該代碼将循環周遊伺服對象中的每個伺服,并通過執行以下步驟對其進行初始化:
1.将起始位置存儲在全局位置變量中。
2.執行個體化Johnny-Five伺服執行個體并将其存儲在伺服全局變量中。
3.将伺服移至起始位置。
③添加一個逾時時間,給伺服時間移動到其起始位置。這行代碼将在1秒後調用callback,告訴typebot.js初始化完成。
現在開始第二個方法:move。該方法将為多個伺服系統采用一組目的地,并確定它們以協調的方式移動。我們将計算每個伺服需要旋轉的速度,這樣可以確定它們同時到達目的地。完整的move方法如
例2-3所示。
例2-3 完整的move方法
①首先找出需要移動到最遠的伺服。周遊每個變化,找出哪個伺服需要移動到最遠地點。
②為了提高代碼效率,檢查是否需要移動任何伺服。請注意,我們沒有直接調用callback函數,而是将它包裝在process.nextTick調用中。如果沒有更改,則會同步調用callback函數,但如果有更改,則會異步調用。這可能會引入難以發現的細微錯誤。有關此問題的更多資訊,請檢視代碼注釋中的連結。
③我們計算帶有最大增量的伺服移動到新目的地所需的時間。
此代碼計算在以選項中提供的速度移動時,此伺服移動到其位置所需的
時間。
我們使用剛剛計算的持續時間來移動所有伺服,這意味着其他伺服将以較慢的速度移動。還記得本章開頭介紹的筆記本電腦螢幕的限制嗎?這就是我們處理它的方式!事實證明,當手臂從靠近鍵盤中間頂部的鍵(如t、y等)開始時,手臂隻能擊中螢幕并移動到靠近鍵盤上邊緣的鍵(如q、p等)。以較慢的速度移動其他伺服可防止肘部和腕部旋轉太快并撞擊螢幕,因為在這些情況下肩部将始終比肘部和腕部旋轉得更快。
④在那之後,移動手臂。周遊所有伺服,更新它們的位置,然後移動它們。
⑤在代碼中的這個時間點,我們已經做了一切需要做的事情來告訴伺服移動到它們的位置。現在隻需要等待硬體來做它的事情。我們隻是等待duratiion毫秒傳遞,然後等待settleTime毫秒傳遞,并調用callback函數。
2.4.3 初始化
現在我們有了伺服子產品,在主子產品中啟動它。打開typebot.js并添加以下代碼:
接下來,建立一系列要鍵入的鍵。我們首次嘗試輸入的序列非常明顯:
現在建立伺服選項。首先,我們将伺服轉速定義為每毫秒0.05度,這稍微超過8轉。我們希望在對系統進行微調時以低伺服轉速開始。一旦一切都以這個速度工作,我們将它提升到每毫秒0.2度,或轉
轉(我最喜歡的轉速!):
接下來,我們将穩定率定義為0.25秒。如果發現手臂在鍵入時變得更不穩定,請嘗試增加此值:
現在建立伺服配置:
配置伺服使其從圖2-14所示的位置開始。
圖2-14 起點
如果運作了代碼,發現它與此位置不比對,請確定已正确設定isInverted标志。如果角度與圖形不完全比對,也不要擔心,隻要關閉它們就可以了。
我們需要建立的最後一個配置是鍵和伺服角度之間的映射:
這些數字來自哪裡?我們編寫了一個小程式,它通過執行一些三角函數來估計鍵的位置。要注意的是該程式隻能生成估值,因為伺服寬度/高度的微小變化,螺絲孔會略微錯位,伺服不精确為180°等。
最後一步是初始化Johnny-Five和伺服:
現在運作代碼。確定所有伺服都正确連接配接到Arduino,并将Arduino插入筆記本電腦上。運作節點typebot.js,手臂應該移動到它的起始位置!
2.4.4 按鍵排序
現在我們有了伺服子產品,在本章開始給出限制的情況下,如何使用它按鍵?我們将使用狀态機将按鍵分解為三個步驟:
1.将手指直接放在鍵的上方。
2.向下移動手指直到按下鍵。
3.将手指擡回在步驟1結束時手指所處的位置。
這三個步驟可以用圖2-15中所示的狀态機表示。
圖2-15
我們可以用事件循環和switch語句來表示這個狀态機。現在應該将例2-4中顯示的代碼添加到run方法中。
例2-4 run方法
①在這裡,我們定義了四種狀态:閑置、移動、按壓和釋放。
②為了記錄狀态機,我們使用三個全局變量:sequencePosition、state和key。sequencePosition記錄目前按下的序列中的按鍵。state記錄目前所在序列中的狀态。最後,key是KEYS數組中目前按鍵條目的别名。
③我們還建立了一個名為tick的函數,它通過事件循環處理單個程序。對于每個鍵,此方法将被調用四次,每個狀态一次。每個狀态負責過渡到下一個事件。在此處看到的代碼隻是這些case語句的存根。接下來将看到每個語句的完整定義。
應用程式在閑置狀态下啟動。當循環開始時,閑置狀态轉換為移動狀态,使用以下代碼替換先前的案例STATE_IDLE的定義:
①從序列中擷取下一個鍵并存儲它。如果沒有鍵,則表示我們已完成輸入并可退出。
②将狀态更改為移動并告訴伺服子產品移動伺服。在肘部和腕部添加一個偏移量,使它們在鍵盤上方懸停約一英寸。這可以防止手臂沿着鍵盤拖動。請注意,我們提供tick方法作為伺服控制移動方法的回調。這樣,一旦手臂完成移動,我們将在事件循環中勾選下一個循環。
下一個狀态是移動,看起來很像閑置狀态的後半部分。唯一的差別是我們移除偏移量,使手臂向下移動按鍵。選擇這些偏移量是因為它們導緻指尖幾乎直接向下移動,而這是之前的限制之一。以下内容替換了先前的案例STATE_MOVING的定義:
釋放狀态隻是颠倒了之前狀态中采取的操作:
最終狀态非常簡單。它将狀态更改回空閑狀态,以便我們可以再次循環:
在這種狀态下,我們必須直接調用tick函數,因為沒有告訴伺服移動。現在,完整的tick方法應該如例2-5所示。
例2-5 最終的tick方法
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,告訴它輸入的内容。