บทช่วยสอนการเขียนโปรแกรมหุ่นยนต์เบื้องต้น

เผยแพร่แล้ว: 2022-03-11
หมายเหตุบรรณาธิการ: เมื่อวันที่ 16 ตุลาคม 2018 บทความนี้ได้รับการปรับปรุงให้ทำงานร่วมกับเทคโนโลยีล่าสุด

มาเผชิญหน้ากัน หุ่นยนต์เจ๋งมาก พวกเขายังจะต้องบริหารโลกสักวันหนึ่ง และหวังว่าในเวลานั้นพวกเขาจะสงสารผู้สร้างเนื้อนุ่มที่น่าสงสารของพวกเขา (หรือที่รู้จักว่านักพัฒนาหุ่นยนต์) และช่วยเราสร้างพื้นที่ยูโทเปียที่เต็มไปด้วยความอุดมสมบูรณ์ ฉันล้อเล่นแน่นอน แต่แค่บางอย่าง

ในความทะเยอทะยานของฉันที่จะมีอิทธิพลเล็กน้อยในเรื่องนี้ ฉันได้เรียนหลักสูตรในทฤษฎีการควบคุมหุ่นยนต์อัตโนมัติเมื่อปีที่แล้ว ซึ่งจบลงด้วยการสร้างหุ่นยนต์จำลองแบบ Python ที่ทำให้ฉันฝึกทฤษฎีการควบคุมบนหุ่นยนต์ที่เคลื่อนย้ายได้และตั้งโปรแกรมได้ที่เรียบง่าย .

ในบทความนี้ ผมจะแสดงวิธีการใช้กรอบงานหุ่นยนต์ Python เพื่อพัฒนาซอฟต์แวร์ควบคุม อธิบายรูปแบบการควบคุมที่ฉันพัฒนาสำหรับหุ่นยนต์จำลองของฉัน แสดงให้เห็นว่ามันโต้ตอบกับสภาพแวดล้อมอย่างไรและบรรลุเป้าหมายอย่างไร และหารือเกี่ยวกับ ความท้าทายพื้นฐานของการเขียนโปรแกรมหุ่นยนต์ที่ฉันพบระหว่างทาง

เพื่อทำตามบทช่วยสอนนี้เกี่ยวกับการเขียนโปรแกรมหุ่นยนต์สำหรับผู้เริ่มต้น คุณควรมีความรู้พื้นฐานสองประการ:

  • คณิตศาสตร์—เราจะใช้ฟังก์ชันตรีโกณมิติและเวกเตอร์
  • Python—เนื่องจาก Python เป็นหนึ่งในภาษาการเขียนโปรแกรมโรบ็อตพื้นฐานที่ได้รับความนิยมมากกว่า—เราจะใช้ประโยชน์จากไลบรารีและฟังก์ชันพื้นฐานของ Python

ตัวอย่างโค้ดที่แสดงที่นี่เป็นเพียงส่วนหนึ่งของโปรแกรมจำลองทั้งหมด ซึ่งอาศัยคลาสและอินเทอร์เฟซ ดังนั้นเพื่อที่จะอ่านโค้ดโดยตรง คุณอาจต้องมีประสบการณ์ในการเขียนโปรแกรม Python และเชิงวัตถุ

สุดท้าย หัวข้อทางเลือกที่จะช่วยให้คุณทำตามบทช่วยสอนนี้ได้ดีขึ้นคือการรู้ว่าเครื่องบอกสถานะคืออะไร และเซ็นเซอร์วัดระยะและตัวเข้ารหัสทำงานอย่างไร

ความท้าทายของหุ่นยนต์ที่ตั้งโปรแกรมได้: การรับรู้เทียบกับความเป็นจริง และความเปราะบางของการควบคุม

ความท้าทายพื้นฐานของหุ่นยนต์ทั้งหมดคือ: เป็นไปไม่ได้เลยที่จะรู้สภาพที่แท้จริงของสิ่งแวดล้อม ซอฟต์แวร์ควบคุมหุ่นยนต์สามารถเดาได้เฉพาะสถานะของโลกแห่งความเป็นจริงตามการวัดที่ส่งกลับโดยเซ็นเซอร์เท่านั้น มันทำได้เพียงพยายามเปลี่ยนสถานะของโลกแห่งความจริงผ่านการสร้างสัญญาณควบคุม

ภาพกราฟิกนี้แสดงให้เห็นถึงปฏิสัมพันธ์ระหว่างหุ่นยนต์ทางกายภาพและการควบคุมคอมพิวเตอร์เมื่อฝึกการเขียนโปรแกรมหุ่นยนต์ Python

ซอฟต์แวร์ควบคุมหุ่นยนต์สามารถเดาได้เฉพาะสถานะของโลกแห่งความเป็นจริงตามการวัดที่ส่งกลับโดยเซ็นเซอร์เท่านั้น

ดังนั้น หนึ่งในขั้นตอนแรกในการออกแบบการควบคุมคือการสร้างสิ่งที่เป็นนามธรรมของโลกแห่งความเป็นจริง หรือที่เรียกว่า แบบจำลอง ซึ่งจะตีความการอ่านเซ็นเซอร์ของเราและตัดสินใจ ตราบใดที่โลกแห่งความจริงประพฤติตามสมมติฐานของแบบจำลอง เราก็สามารถคาดเดาได้ดีและพยายามควบคุม ทันทีที่โลกแห่งความเป็นจริงเบี่ยงเบนไปจากสมมติฐานเหล่านี้ เราไม่สามารถคาดเดาได้ดีอีกต่อไป และการควบคุมจะสูญเสียไป บ่อยครั้ง เมื่อสูญเสียการควบคุมไป จะไม่สามารถเรียกคืนได้อีก (เว้นแต่ผู้ใจดีบางคนจะฟื้นฟูมัน)

นี่เป็นหนึ่งในเหตุผลสำคัญที่ทำให้การเขียนโปรแกรมหุ่นยนต์เป็นเรื่องยาก เรามักจะเห็นวิดีโอของหุ่นยนต์วิจัยล่าสุดในห้องแล็บ แสดงความชำนาญ การนำทาง หรือการทำงานเป็นทีมที่ยอดเยี่ยม และเราอยากจะถามว่า "ทำไมจึงไม่ใช้สิ่งนี้ในโลกแห่งความเป็นจริง" ครั้งต่อไปที่คุณเห็นวิดีโอดังกล่าว ลองดูว่าสภาพแวดล้อมของห้องปฏิบัติการมีการควบคุมสูงเพียงใด ในกรณีส่วนใหญ่ หุ่นยนต์เหล่านี้สามารถทำงานที่น่าประทับใจเหล่านี้ได้ตราบเท่าที่สภาพแวดล้อมยังคงอยู่ภายในขอบเขตที่แคบของแบบจำลองภายใน ดังนั้น กุญแจดอกหนึ่งที่นำไปสู่ความก้าวหน้าของวิทยาการหุ่นยนต์คือการพัฒนาแบบจำลองที่ซับซ้อน ยืดหยุ่น และแข็งแกร่งยิ่งขึ้น—และความก้าวหน้าดังกล่าวขึ้นอยู่กับข้อจำกัดของทรัพยากรคอมพิวเตอร์ที่มีอยู่

กุญแจสำคัญประการหนึ่งในการพัฒนาหุ่นยนต์คือการพัฒนาโมเดลที่ซับซ้อน ยืดหยุ่น และทนทานมากขึ้น

[หมายเหตุด้านข้าง: นักปรัชญาและนักจิตวิทยาเหมือนกันจะสังเกตว่าสิ่งมีชีวิตยังต้องทนทุกข์จากการพึ่งพาการรับรู้ภายในของตนเองว่าประสาทสัมผัสกำลังบอกอะไรกับพวกมัน ความก้าวหน้าหลายอย่างในวิทยาการหุ่นยนต์มาจากการสังเกตสิ่งมีชีวิตและดูว่าพวกมันตอบสนองต่อสิ่งเร้าที่คาดไม่ถึงอย่างไร คิดเกี่ยวกับมัน รูปแบบภายในของคุณของโลกคืออะไร? ต่างจากมดกับของปลาอย่างไร? (หวังว่า) อย่างไรก็ตาม เช่นเดียวกับมดและปลา มีแนวโน้มที่จะทำให้ความเป็นจริงบางอย่างของโลกดูเรียบง่ายเกินไป เมื่อสมมติฐานของคุณเกี่ยวกับโลกไม่ถูกต้อง อาจทำให้คุณเสี่ยงต่อการสูญเสียการควบคุมสิ่งต่างๆ บางครั้งเราเรียกสิ่งนี้ว่า “อันตราย” เช่นเดียวกับหุ่นยนต์ตัวน้อยของเราที่ต้องดิ้นรนเอาชีวิตรอดจากจักรวาลที่ไม่รู้จัก เราทุกคนก็เช่นกัน นี่เป็นข้อมูลเชิงลึกอันทรงพลังสำหรับนักวิทยาการหุ่นยนต์]

โปรแกรมจำลองหุ่นยนต์

โปรแกรมจำลองที่ฉันสร้างนั้นเขียนด้วยภาษา Python และได้รับการขนานนามว่า Sobot Rimulator อย่างชาญฉลาด คุณสามารถค้นหา v1.0.0 บน GitHub มีเสียงระฆังและนกหวีดไม่มาก แต่มันถูกสร้างขึ้นเพื่อทำสิ่งหนึ่งได้เป็นอย่างดี: ให้การจำลองที่แม่นยำของหุ่นยนต์เคลื่อนที่และให้กรอบงานง่ายๆ แก่นักหุ่นยนต์ที่ต้องการฝึกการเขียนโปรแกรมซอฟต์แวร์หุ่นยนต์ แม้ว่าจะดีกว่าเสมอที่จะมีหุ่นยนต์ตัวจริงให้เล่น แต่หุ่นยนต์จำลอง Python ที่ดีนั้นเข้าถึงได้ง่ายกว่ามากและเป็นจุดเริ่มต้นที่ดี

ในหุ่นยนต์ในโลกแห่งความเป็นจริง ซอฟต์แวร์ที่สร้างสัญญาณควบคุม ("ตัวควบคุม") จะต้องทำงานด้วยความเร็วสูงมาก และทำการคำนวณที่ซับซ้อน สิ่งนี้ส่งผลต่อการเลือกภาษาการเขียนโปรแกรมหุ่นยนต์ที่ดีที่สุด: โดยปกติ C++ จะใช้สำหรับสถานการณ์ประเภทนี้ แต่ในแอปพลิเคชันหุ่นยนต์ที่ง่ายกว่า Python เป็นการประนีประนอมที่ดีมากระหว่างความเร็วในการดำเนินการและความง่ายในการพัฒนาและทดสอบ

ซอฟต์แวร์ที่ฉันเขียนจำลองหุ่นยนต์วิจัยในชีวิตจริงที่เรียกว่า Khepera แต่สามารถปรับให้เข้ากับหุ่นยนต์เคลื่อนที่ประเภทต่างๆ ที่มีขนาดและเซ็นเซอร์ต่างกันได้ เนื่องจากฉันพยายามตั้งโปรแกรมตัวจำลองให้ใกล้เคียงกับความสามารถของหุ่นยนต์จริงมากที่สุด ตรรกะการควบคุมจึงสามารถโหลดลงในหุ่นยนต์ Khepera ตัวจริงได้ด้วยการรีแฟคเตอร์ขั้นต่ำ และมันจะทำงานเหมือนกับหุ่นยนต์จำลอง คุณลักษณะเฉพาะที่นำมาใช้อ้างอิงถึง Khepera III แต่สามารถปรับให้เข้ากับ Khepera IV ใหม่ได้อย่างง่ายดาย

กล่าวอีกนัยหนึ่ง การเขียนโปรแกรมหุ่นยนต์จำลองนั้นคล้ายคลึงกับการเขียนโปรแกรมหุ่นยนต์จริง นี่เป็นสิ่งสำคัญหากตัวจำลองจะนำไปใช้ในการพัฒนาและประเมินแนวทางซอฟต์แวร์ควบคุมที่แตกต่างกัน

ในบทช่วยสอนนี้ ฉันจะอธิบายสถาปัตยกรรมซอฟต์แวร์ควบคุมหุ่นยนต์ที่มาพร้อมกับ v1.0.0 ของ Sobot Rimulator และจัดเตรียมตัวอย่างจากแหล่ง Python (พร้อมการดัดแปลงเล็กน้อยเพื่อความชัดเจน) อย่างไรก็ตาม ฉันแนะนำให้คุณดำดิ่งลงไปในแหล่งที่มาและเข้าไปยุ่งวุ่นวาย เครื่องจำลองนี้ได้รับการแยกและใช้เพื่อควบคุมหุ่นยนต์เคลื่อนที่ต่างๆ รวมถึง Roomba2 จาก iRobot ในทำนองเดียวกันโปรดแยกโครงการและปรับปรุง

ตรรกะการควบคุมของหุ่นยนต์ถูกจำกัดในคลาส/ไฟล์ Python เหล่านี้:

  • models/supervisor.py —คลาสนี้รับผิดชอบการทำงานร่วมกันระหว่างโลกจำลองรอบๆ หุ่นยนต์และตัวหุ่นยนต์เอง มันพัฒนาเครื่องจักรสถานะหุ่นยนต์ของเราและเรียกตัวควบคุมสำหรับการคำนวณพฤติกรรมที่ต้องการ
  • models/supervisor_state_machine.py —คลาสนี้แสดงถึง สถานะ ต่างๆ ที่หุ่นยนต์สามารถเป็นได้ ขึ้นอยู่กับการตีความของเซ็นเซอร์
  • ไฟล์ในไดเร็กทอรี models/controllers คลาสเหล่านี้ใช้พฤติกรรมที่แตกต่างกันของโรบ็อตตามสภาวะที่ทราบของสภาพแวดล้อม โดยเฉพาะอย่างยิ่ง ตัวควบคุมเฉพาะจะถูกเลือกขึ้นอยู่กับเครื่องสถานะ

เป้าหมาย

หุ่นยนต์ก็เหมือนกับมนุษย์ ต้องมีจุดมุ่งหมายในชีวิต เป้าหมายของซอฟต์แวร์ของเราจะควบคุมหุ่นยนต์ตัวนี้ง่ายมาก: จะพยายามหาทางไปสู่จุดเป้าหมายที่กำหนดไว้ล่วงหน้า นี่เป็นคุณสมบัติพื้นฐานที่หุ่นยนต์เคลื่อนที่ควรมี ตั้งแต่รถยนต์ขับเคลื่อนอัตโนมัติไปจนถึงเครื่องดูดฝุ่นแบบหุ่นยนต์ พิกัดของเป้าหมายถูกตั้งโปรแกรมไว้ในซอฟต์แวร์ควบคุมก่อนที่หุ่นยนต์จะเปิดใช้งาน แต่สามารถสร้างได้จากแอปพลิเคชัน Python เพิ่มเติมที่ดูแลการเคลื่อนไหวของหุ่นยนต์ ตัวอย่างเช่น ลองนึกภาพว่ากำลังขับผ่านจุดอ้างอิงหลายจุด

อย่างไรก็ตาม เพื่อทำให้เรื่องยุ่งยากขึ้น สภาพแวดล้อมของหุ่นยนต์อาจเต็มไปด้วยสิ่งกีดขวาง หุ่นยนต์ต้องไม่ชนกับสิ่งกีดขวางระหว่างทางไปยังเป้าหมาย ดังนั้นหากหุ่นยนต์เจอสิ่งกีดขวาง มันจะต้องหาทางไปรอบๆ เพื่อที่จะไปให้ถึงเป้าหมายต่อไป

หุ่นยนต์ที่ตั้งโปรแกรมได้

หุ่นยนต์ทุกตัวมีความสามารถและข้อกังวลในการควบคุมที่แตกต่างกัน มาทำความคุ้นเคยกับหุ่นยนต์แบบตั้งโปรแกรมได้ของเรากันเถอะ

สิ่งแรกที่ควรทราบก็คือ ในคู่มือนี้ หุ่นยนต์ของเราจะเป็น หุ่นยนต์เคลื่อนที่อัตโนมัติ ซึ่งหมายความว่ามันจะเคลื่อนที่ไปมาในอวกาศอย่างอิสระและจะทำภายใต้การควบคุมของมันเอง ซึ่งตรงกันข้ามกับ หุ่นยนต์ควบคุมระยะไกล (ซึ่งไม่ใช่ระบบอัตโนมัติ) หรือแขนหุ่นยนต์ของโรงงาน (ซึ่งไม่ใช่อุปกรณ์เคลื่อนที่) หุ่นยนต์ของเราต้องคิดหาวิธีการบรรลุเป้าหมายและเอาตัวรอดในสภาพแวดล้อมของมันด้วยตัวมันเอง สิ่งนี้พิสูจน์ให้เห็นว่าเป็นความท้าทายที่ยากอย่างน่าประหลาดใจสำหรับโปรแกรมเมอร์หุ่นยนต์มือใหม่

อินพุตควบคุม: เซ็นเซอร์

มีหลายวิธีที่หุ่นยนต์สามารถติดตั้งเพื่อตรวจสอบสภาพแวดล้อมได้ สิ่งเหล่านี้อาจรวมถึงอะไรก็ได้ตั้งแต่เซ็นเซอร์ความใกล้ชิด เซ็นเซอร์วัดแสง กันชน กล้อง และอื่นๆ นอกจากนี้ หุ่นยนต์อาจสื่อสารกับเซ็นเซอร์ภายนอกที่ให้ข้อมูลที่พวกเขาไม่สามารถสังเกตได้โดยตรง

หุ่นยนต์อ้างอิงของเราติดตั้ง เซ็นเซอร์อินฟราเรด 9 ตัว โดยรุ่นใหม่กว่ามีเซ็นเซอร์อินฟราเรดแปดตัวและเซ็นเซอร์ระยะใกล้ 5 ตัวซึ่งจัดเรียงเป็น "กระโปรง" ในทุกทิศทาง มีเซ็นเซอร์ที่หันหน้าไปทางด้านหน้าของหุ่นยนต์มากกว่าด้านหลัง เนื่องจากโดยปกติแล้วหุ่นยนต์จะต้องรู้ว่าสิ่งใดอยู่ข้างหน้ามากกว่าสิ่งที่อยู่ด้านหลัง

นอกจากเซนเซอร์ระยะใกล้แล้ว หุ่นยนต์ยังมีตัวบอกตำแหน่ง ล้อคู่หนึ่ง ที่ติดตามการเคลื่อนที่ของล้อ สิ่งเหล่านี้ช่วยให้คุณติดตามจำนวนการหมุนของล้อแต่ละล้อ โดยการหมุนวงล้อไปข้างหน้าจนสุดหนึ่งครั้งจะมีค่า 2,765 ขีด หมุนไปในทิศทางตรงกันข้ามนับถอยหลัง ลดจำนวนเห็บแทนที่จะเพิ่ม คุณไม่ต้องกังวลเกี่ยวกับตัวเลขเฉพาะในบทช่วยสอนนี้ เนื่องจากซอฟต์แวร์ที่เราจะเขียนนั้นใช้ระยะทางที่เดินทางซึ่งแสดงเป็นเมตร ต่อมาผมจะแสดงวิธีการคำนวณจากเห็บด้วยฟังก์ชัน Python อย่างง่าย

เอาต์พุตควบคุม: Mobility

หุ่นยนต์บางตัวเคลื่อนที่ด้วยขา บางม้วนเหมือนลูกบอล บางคนถึงกับเลื้อยเหมือนงู

หุ่นยนต์ของเราเป็นหุ่นยนต์ขับเคลื่อนด้วยดิฟเฟอเรนเชียล หมายความว่ามันหมุนด้วยสองล้อ เมื่อล้อทั้งสองหมุนด้วยความเร็วเท่ากัน หุ่นยนต์จะเคลื่อนที่เป็นเส้นตรง เมื่อล้อเคลื่อนที่ด้วยความเร็วต่างกัน หุ่นยนต์จะหมุน ดังนั้น การควบคุมการเคลื่อนที่ของหุ่นยนต์ตัวนี้จึงลงมาเพื่อควบคุมอัตราการหมุนของสองล้อแต่ละล้ออย่างเหมาะสม

API

ใน Sobot Rimulator การแยกระหว่างหุ่นยนต์ "คอมพิวเตอร์" และโลกทางกายภาพ (จำลอง) นั้นรวมอยู่ในไฟล์ robot_supervisor_interface.py ซึ่งกำหนด API ทั้งหมดสำหรับการโต้ตอบกับเซ็นเซอร์และมอเตอร์ "หุ่นยนต์จริง":

  • read_proximity_sensors() ส่งคืนค่าอาร์เรย์เก้าค่าในรูปแบบเนทีฟของเซ็นเซอร์
  • read_wheel_encoders() คืนค่าอาร์เรย์ของค่าสองค่าที่ระบุขีดทั้งหมดตั้งแต่ start
  • set_wheel_drive_rates( v_l, v_r ) รับสองค่า (เป็นเรเดียนต่อวินาที) และตั้งค่าความเร็วซ้ายและขวาของล้อเป็นค่าสองค่านั้น

อินเทอร์เฟซนี้ใช้วัตถุหุ่นยนต์ภายในซึ่งให้ข้อมูลจากเซ็นเซอร์และความเป็นไปได้ในการเคลื่อนย้ายมอเตอร์หรือล้อ หากคุณต้องการสร้างโรบ็อตที่แตกต่างกัน คุณเพียงแค่จัดเตรียมคลาสโรบ็อต Python อื่นที่สามารถใช้โดยอินเทอร์เฟซเดียวกัน และโค้ดที่เหลือ (คอนโทรลเลอร์ หัวหน้างาน และโปรแกรมจำลอง) จะทำงานนอกกรอบ!

เครื่องจำลอง

ในขณะที่คุณใช้งานหุ่นยนต์จริงในโลกแห่งความเป็นจริงโดยไม่ให้ความสนใจกับกฎของฟิสิกส์ที่เกี่ยวข้องมากเกินไป คุณสามารถเพิกเฉยต่อการจำลองหุ่นยนต์และข้ามไปที่วิธีการตั้งโปรแกรมซอฟต์แวร์ตัวควบคุมได้โดยตรง เนื่องจากมันจะเกือบจะเหมือนกัน ระหว่างโลกแห่งความจริงกับการจำลอง แต่ถ้าคุณอยากรู้ ฉันจะแนะนำสั้น ๆ ที่นี่

ไฟล์ world.py เป็นคลาส Python ที่แสดงถึงโลกจำลอง โดยมีหุ่นยนต์และสิ่งกีดขวางอยู่ภายใน ฟังก์ชันขั้นตอนในคลาสนี้ดูแลการพัฒนาโลกที่เรียบง่ายของเราโดย:

  • การใช้กฎฟิสิกส์กับการเคลื่อนไหวของหุ่นยนต์
  • พิจารณาการชนกับสิ่งกีดขวาง
  • ให้ค่าใหม่สำหรับเซ็นเซอร์หุ่นยนต์

ในท้ายที่สุด มันเรียกผู้บังคับบัญชาหุ่นยนต์ที่รับผิดชอบในการดำเนินการซอฟต์แวร์สมองของหุ่นยนต์

ฟังก์ชันขั้นตอนดำเนินการในลูปเพื่อให้ robot.step_motion() เคลื่อนที่หุ่นยนต์โดยใช้ความเร็วล้อที่คำนวณโดยหัวหน้างานในขั้นตอนการจำลองก่อนหน้า

 # step the simulation through one time interval def step( self ): dt = self.dt # step all the robots for robot in self.robots: # step robot motion robot.step_motion( dt ) # apply physics interactions self.physics.apply_physics() # NOTE: The supervisors must run last to ensure they are observing the "current" world # step all of the supervisors for supervisor in self.supervisors: supervisor.step( dt ) # increment world time self.world_time += dt

apply_physics() จะอัปเดตค่าของเซ็นเซอร์ระยะใกล้ของหุ่นยนต์เป็นการภายใน เพื่อให้หัวหน้างานสามารถประเมินสภาพแวดล้อมในขั้นตอนการจำลองปัจจุบันได้ แนวความคิดเดียวกันกับตัวเข้ารหัส

โมเดลที่เรียบง่าย

อย่างแรก หุ่นยนต์ของเราจะมีโมเดลที่เรียบง่าย มันจะทำการสันนิษฐานมากมายเกี่ยวกับโลก สิ่งสำคัญบางประการ ได้แก่ :

  • ภูมิประเทศราบเรียบเสมอกัน
  • อุปสรรคไม่เคยกลม
  • ล้อไม่เคยลื่น
  • ไม่มีอะไรจะผลักหุ่นยนต์ไปรอบ ๆ
  • เซ็นเซอร์ไม่เคยล้มเหลวหรือให้การอ่านที่ผิดพลาด
  • ล้อจะหมุนเสมอเมื่อมีคนบอก

แม้ว่าสมมติฐานส่วนใหญ่จะสมเหตุสมผลในสภาพแวดล้อมที่เหมือนบ้าน แต่อาจมีอุปสรรคทรงกลมอยู่ ซอฟต์แวร์การหลีกเลี่ยงสิ่งกีดขวางของเรามีการใช้งานที่เรียบง่ายและปฏิบัติตามขอบเขตของสิ่งกีดขวางเพื่อที่จะหลีกเลี่ยงสิ่งกีดขวาง เราจะแนะนำผู้อ่านถึงวิธีปรับปรุงกรอบการควบคุมหุ่นยนต์ของเราด้วยการตรวจสอบเพิ่มเติมเพื่อหลีกเลี่ยงสิ่งกีดขวางที่เป็นวงกลม

วงควบคุม

ตอนนี้เราจะเข้าสู่แกนหลักของซอฟต์แวร์ควบคุมของเราและอธิบายพฤติกรรมที่เราต้องการตั้งโปรแกรมภายในหุ่นยนต์ คุณสามารถเพิ่มพฤติกรรมเพิ่มเติมลงในเฟรมเวิร์กนี้ได้ และคุณควรลองใช้แนวคิดของคุณเองหลังจากอ่านจบ! ซอฟต์แวร์หุ่นยนต์ตามพฤติกรรมได้รับการเสนอชื่อเมื่อ 20 ปีที่แล้ว และยังคงเป็นเครื่องมือที่ทรงพลังสำหรับหุ่นยนต์เคลื่อนที่ ตัวอย่างเช่น ในปี 2550 มีการใช้ชุดพฤติกรรมใน DARPA Urban Challenge ซึ่งเป็นการแข่งขันครั้งแรกสำหรับรถยนต์ขับเคลื่อนอัตโนมัติ!

หุ่นยนต์เป็นระบบไดนามิก สถานะของหุ่นยนต์ ค่าที่อ่านได้ของเซ็นเซอร์ และผลกระทบของสัญญาณควบคุมจะเปลี่ยนแปลงตลอดเวลา การควบคุมวิธีการดำเนินกิจกรรมเกี่ยวข้องกับสามขั้นตอนต่อไปนี้:

  1. ใช้สัญญาณควบคุม
  2. วัดผล.
  3. สร้างสัญญาณควบคุมใหม่ที่คำนวณเพื่อให้เราเข้าใกล้เป้าหมายมากขึ้น

ขั้นตอนเหล่านี้ซ้ำแล้วซ้ำอีกจนกว่าเราจะบรรลุเป้าหมาย ยิ่งเราทำสิ่งนี้ได้ต่อวินาทีมากเท่าไร การควบคุมระบบก็จะยิ่งดียิ่งขึ้นเท่านั้น หุ่นยนต์ Sobot Rimulator ทำซ้ำขั้นตอนเหล่านี้ 20 ครั้งต่อวินาที (20 Hz) แต่หุ่นยนต์จำนวนมากต้องทำหลายพันหรือล้านครั้งต่อวินาทีเพื่อให้มีการควบคุมที่เพียงพอ จำการแนะนำก่อนหน้าของเราเกี่ยวกับภาษาการเขียนโปรแกรมหุ่นยนต์ที่แตกต่างกันสำหรับระบบหุ่นยนต์ที่แตกต่างกันและข้อกำหนดด้านความเร็ว

โดยทั่วไป ทุกครั้งที่หุ่นยนต์ของเราทำการวัดด้วยเซ็นเซอร์ หุ่นยนต์จะใช้การวัดเหล่านี้เพื่อปรับปรุงการประมาณการภายในของสถานะของโลก ตัวอย่างเช่น ระยะทางจากเป้าหมาย โดยเปรียบเทียบสถานะนี้กับค่า อ้างอิง ของสิ่งที่ ต้องการ ให้รัฐเป็น (สำหรับระยะทาง ต้องการให้เป็นศูนย์) และคำนวณข้อผิดพลาดระหว่างสถานะที่ต้องการกับสถานะจริง เมื่อทราบข้อมูลนี้แล้ว การสร้างสัญญาณควบคุมใหม่สามารถลดปัญหาใน การลดข้อผิดพลาด ซึ่งในที่สุดจะเคลื่อนหุ่นยนต์ไปยังเป้าหมาย

A Nifty Trick: ทำให้โมเดลง่ายขึ้น

เพื่อควบคุมหุ่นยนต์ที่เราต้องการจะตั้งโปรแกรม เราต้องส่งสัญญาณไปที่ล้อซ้ายเพื่อบอกว่าจะเลี้ยวเร็วแค่ไหน และสัญญาณแยกไปที่ล้อขวาเพื่อบอก ว่า จะเลี้ยวเร็วแค่ไหน ให้เรียกสัญญาณเหล่านี้ v L และ v R . อย่างไรก็ตาม การคิดอย่างต่อเนื่องในแง่ของ v L และ v R นั้นยุ่งยากมาก แทนที่จะถามว่า “เราต้องการให้ล้อซ้ายหมุนเร็วแค่ไหน และเราต้องการให้ล้อขวาหมุนเร็วแค่ไหน” มันเป็นเรื่องธรรมดามากกว่าที่จะถามว่า “เราต้องการให้หุ่นยนต์เคลื่อนที่ไปข้างหน้าเร็วแค่ไหน และเราต้องการให้หุ่นยนต์หมุนเร็วแค่ไหน หรือเปลี่ยนทิศทางของมัน” ให้เรียกพารามิเตอร์เหล่านี้ว่าความเร็ว v และความเร็วเชิงมุม (หมุน) ω (อ่านว่า “โอเมก้า”) ปรากฎว่าเราสามารถกำหนดแบบจำลองทั้งหมดของเราบน v และ ω แทนที่จะเป็น v L และ v R และเมื่อเราได้กำหนดวิธีที่เราต้องการให้หุ่นยนต์ที่ตั้งโปรแกรมไว้เคลื่อนที่แล้ว ให้แปลงค่าสองค่านี้ทางคณิตศาสตร์เป็น v L และ v R ที่เราต้องการ เพื่อควบคุมล้อหุ่นยนต์ได้จริง สิ่งนี้เรียกว่า unicycle model of control

ในการเขียนโปรแกรมหุ่นยนต์ สิ่งสำคัญคือต้องเข้าใจความแตกต่างระหว่างรุ่น unicycle และ differential drive

นี่คือโค้ด Python ที่ใช้การแปลงขั้นสุดท้ายใน supervisor.py โปรดทราบว่าถ้า ω เป็น 0 ล้อทั้งสองจะหมุนด้วยความเร็วเท่ากัน:

 # generate and send the correct commands to the robot def _send_robot_commands( self ): # ... v_l, v_r = self._uni_to_diff( v, omega ) self.robot.set_wheel_drive_rates( v_l, v_r ) def _uni_to_diff( self, v, omega ): # v = translational velocity (m/s) # omega = angular velocity (rad/s) R = self.robot_wheel_radius L = self.robot_wheel_base_length v_l = ( (2.0 * v) - (omega*L) ) / (2.0 * R) v_r = ( (2.0 * v) + (omega*L) ) / (2.0 * R) return v_l, v_r

สถานะการประมาณ: หุ่นยนต์ รู้จักตัวเอง

หุ่นยนต์จะต้องพยายามประเมินสภาพแวดล้อมและสถานะของตัวมันเองโดยใช้เซ็นเซอร์ของมัน การประมาณการเหล่านี้จะไม่มีวันสมบูรณ์แบบ แต่จะต้องค่อนข้างดีเพราะหุ่นยนต์จะใช้การตัดสินใจทั้งหมดจากการประมาณการเหล่านี้ โดยใช้เซ็นเซอร์วัดระยะและสัญลักษณ์ล้อเพียงอย่างเดียว จะต้องพยายามเดาสิ่งต่อไปนี้:

  • ทิศทางสู่อุปสรรค
  • ระยะห่างจากอุปสรรค
  • ตำแหน่งของหุ่นยนต์
  • ส่วนหัวของหุ่นยนต์

คุณสมบัติสองประการแรกถูกกำหนดโดยการอ่านค่าเซ็นเซอร์ระยะใกล้และค่อนข้างตรงไปตรงมา ฟังก์ชัน API read_proximity_sensors() จะคืนค่าอาร์เรย์ของค่าเก้าค่า หนึ่งค่าสำหรับแต่ละเซ็นเซอร์ เราทราบล่วงหน้าว่าการอ่านครั้งที่เจ็ดนั้นสอดคล้องกับเซ็นเซอร์ที่ชี้ไปทางขวา 75 องศาของหุ่นยนต์

ดังนั้น หากค่านี้แสดงการอ่านที่สัมพันธ์กับระยะทาง 0.1 เมตร แสดงว่ามีสิ่งกีดขวางห่างออกไป 0.1 เมตร ซึ่งอยู่ห่างออกไป 75 องศาทางด้านซ้าย หากไม่มีสิ่งกีดขวาง เซ็นเซอร์จะคืนค่าการอ่านที่ระยะสูงสุด 0.2 เมตร ดังนั้น หากเราอ่านค่า 0.2 เมตรบนเซ็นเซอร์เจ็ด เราจะถือว่าไม่มีสิ่งกีดขวางในทิศทางนั้นจริงๆ

เนื่องจากวิธีการทำงานของเซ็นเซอร์อินฟราเรด (การวัดการสะท้อนอินฟราเรด) ตัวเลขที่ส่งกลับจึงเป็นการเปลี่ยนแปลงแบบไม่เชิงเส้นของระยะทางจริงที่ตรวจพบ ดังนั้น ฟังก์ชัน Python สำหรับกำหนดระยะทางที่ระบุจะต้องแปลงค่าที่อ่านได้เหล่านี้เป็นเมตร สิ่งนี้ทำใน supervisor.py ดังนี้:

 # update the distances indicated by the proximity sensors def _update_proximity_sensor_distances( self ): self.proximity_sensor_distances = [ 0.02-( log(readval/3960.0) )/30.0 for readval in self.robot.read_proximity_sensors() ]

อีกครั้ง เรามีโมเดลเซ็นเซอร์เฉพาะในเฟรมเวิร์กของหุ่นยนต์ Python นี้ ในขณะที่ในโลกแห่งความเป็นจริง เซ็นเซอร์มาพร้อมกับซอฟต์แวร์ที่มาพร้อมซึ่งควรมีฟังก์ชันการแปลงที่คล้ายกันจากค่าที่ไม่เป็นเชิงเส้นเป็นเมตร

การกำหนดตำแหน่งและส่วนหัวของหุ่นยนต์ (เรียกรวมกันว่า ท่าทาง ในการเขียนโปรแกรมหุ่นยนต์) ค่อนข้างท้าทายกว่า หุ่นยนต์ของเราใช้ เครื่องวัดระยะทาง เพื่อประเมินท่าทางของมัน นี่คือที่มาของสัญลักษณ์ล้อ โดยการวัดจำนวนล้อแต่ละล้อที่หมุนไปนับตั้งแต่การวนซ้ำครั้งสุดท้ายของลูปควบคุม เป็นไปได้ที่จะได้รับค่าประมาณที่ดีว่าท่าทางของหุ่นยนต์เปลี่ยนไปอย่างไร—แต่ เฉพาะในกรณีที่การเปลี่ยนแปลงมีขนาดเล็ก เท่านั้น

นี่เป็นเหตุผลหนึ่งที่สำคัญที่จะต้องวนรอบการควบคุมซ้ำๆ ในหุ่นยนต์ในโลกแห่งความเป็นจริง ซึ่งมอเตอร์ที่เคลื่อนล้ออาจไม่สมบูรณ์แบบ ถ้าเรารอเวลานานเกินไปที่จะวัดเครื่องหมายล้อ ล้อทั้งสองสามารถทำอะไรได้มากทีเดียว และจะเป็นไปไม่ได้เลยที่จะประเมินว่าเราสิ้นสุดที่จุดใด

ด้วยซอฟต์แวร์จำลองสถานการณ์ปัจจุบันของเรา เราสามารถเรียกใช้การคำนวณหาระยะทางที่ 20 Hz ซึ่งเป็นความถี่เดียวกับตัวควบคุม แต่อาจเป็นความคิดที่ดีที่จะให้เธรด Python แยกทำงานเร็วขึ้นเพื่อตรวจจับการเคลื่อนไหวของทิกเกอร์ที่มีขนาดเล็กลง

ด้านล่างนี้คือฟังก์ชันการวัดระยะทางเต็มรูปแบบใน supervisor.py ที่อัปเดตการประมาณการท่าทางของหุ่นยนต์ โปรดทราบว่าท่าทางของหุ่นยนต์ประกอบด้วยพิกัด x และ y และที theta ส่วนหัว ซึ่งวัดเป็นเรเดียนจากแกน X บวก ค่าบวก x อยู่ทางทิศตะวันออก และ ค่าบวก y อยู่ทางทิศเหนือ ดังนั้น หัวเรื่อง 0 แสดงว่าหุ่นยนต์หันหน้าไปทางทิศตะวันออกโดยตรง หุ่นยนต์จะถือว่าท่าเริ่มต้นของมันคือ (0, 0), 0 เสมอ

 # update the estimated position of the robot using it's wheel encoder readings def _update_odometry( self ): R = self.robot_wheel_radius N = float( self.wheel_encoder_ticks_per_revolution ) # read the wheel encoder values ticks_left, ticks_right = self.robot.read_wheel_encoders() # get the difference in ticks since the last iteration d_ticks_left = ticks_left - self.prev_ticks_left d_ticks_right = ticks_right - self.prev_ticks_right # estimate the wheel movements d_left_wheel = 2*pi*R*( d_ticks_left / N ) d_right_wheel = 2*pi*R*( d_ticks_right / N ) d_center = 0.5 * ( d_left_wheel + d_right_wheel ) # calculate the new pose prev_x, prev_y, prev_theta = self.estimated_pose.scalar_unpack() new_x = prev_x + ( d_center * cos( prev_theta ) ) new_y = prev_y + ( d_center * sin( prev_theta ) ) new_theta = prev_theta + ( ( d_right_wheel - d_left_wheel ) / self.robot_wheel_base_length ) # update the pose estimate with the new values self.estimated_pose.scalar_update( new_x, new_y, new_theta ) # save the current tick count for the next iteration self.prev_ticks_left = ticks_left self.prev_ticks_right = ticks_right

ตอนนี้หุ่นยนต์ของเราสามารถประมาณการโลกแห่งความเป็นจริงได้ดีแล้ว ลองใช้ข้อมูลนี้เพื่อให้บรรลุเป้าหมายของเรา

ที่เกี่ยวข้อง: การ สอนฟิสิกส์วิดีโอเกม - การตรวจจับการชนกันของวัตถุที่เป็นของแข็ง

วิธีการเขียนโปรแกรม Python Robot: พฤติกรรมไปสู่เป้าหมาย

จุดประสงค์สูงสุดในการดำรงอยู่ของหุ่นยนต์ตัวน้อยของเราในบทช่วยสอนการเขียนโปรแกรมนี้คือเพื่อไปให้ถึงเป้าหมาย แล้วเราจะให้ล้อหมุนไปถึงตรงนั้นได้อย่างไร? เริ่มต้นด้วยการทำให้โลกทัศน์ของเราเรียบง่ายขึ้นเล็กน้อย และถือว่าไม่มีอุปสรรคขวางทาง

สิ่งนี้จะกลายเป็นงานง่าย ๆ และสามารถตั้งโปรแกรมใน Python ได้อย่างง่ายดาย ถ้าเราก้าวไปข้างหน้าในขณะที่หันหน้าเข้าหาเป้าหมาย เราจะไปถึงที่นั่น ต้องขอบคุณการวัดระยะทางของเรา ทำให้เรารู้ว่าพิกัดและทิศทางปัจจุบันของเราคืออะไร เรายังรู้พิกัดของเป้าหมายด้วยเพราะว่าพวกมันถูกตั้งโปรแกรมไว้ล่วงหน้า ดังนั้น โดยใช้พีชคณิตเชิงเส้นเพียงเล็กน้อย เราจึงสามารถกำหนดเวกเตอร์จากตำแหน่งของเราไปยังเป้าหมายได้ เช่นเดียวกับใน go_to_goal_controller.py :

 # return a go-to-goal heading vector in the robot's reference frame def calculate_gtg_heading_vector( self ): # get the inverse of the robot's pose robot_inv_pos, robot_inv_theta = self.supervisor.estimated_pose().inverse().vector_unpack() # calculate the goal vector in the robot's reference frame goal = self.supervisor.goal() goal = linalg.rotate_and_translate_vector( goal, robot_inv_theta, robot_inv_pos ) return goal

โปรดทราบว่าเราได้รับเวกเตอร์ไปยังเป้าหมาย ในหน้าต่างอ้างอิงของหุ่นยนต์ และไม่อยู่ในพิกัดโลก ถ้าเป้าหมายอยู่บนแกน X ในหน้าต่างอ้างอิงของหุ่นยนต์ นั่นหมายความว่ามันอยู่ตรงหน้าหุ่นยนต์โดยตรง ดังนั้น มุมของเวกเตอร์นี้จากแกน X คือความแตกต่างระหว่างส่วนหัวของเรากับส่วนหัวที่เราต้องการ กล่าวอีกนัยหนึ่ง มันคือ ข้อผิดพลาด ระหว่างสถานะปัจจุบันของเรากับสิ่งที่เราต้องการให้สถานะปัจจุบันของเราเป็น ดังนั้นเราจึงต้องการ ปรับอัตราการเลี้ยว ω เพื่อให้มุมระหว่างทิศทางและเป้าหมายเปลี่ยนเป็น 0 เราต้องการลดข้อผิดพลาดให้น้อยที่สุด:

 # calculate the error terms theta_d = atan2( self.gtg_heading_vector[1], self.gtg_heading_vector[0] ) # calculate angular velocity omega = self.kP * theta_d

self.kP ในตัวอย่างข้างต้นของการใช้งาน Python ของคอนโทรลเลอร์คือการเพิ่มการควบคุม เป็นสัมประสิทธิ์ที่กำหนดความเร็วที่เราหมุน ตามสัดส่วน ว่าไกลจากเป้าหมายที่เรากำลังเผชิญอยู่ หากข้อผิดพลาดในหัวข้อของเราคือ 0 อัตราการเลี้ยวก็จะเป็น 0 ด้วย ในฟังก์ชัน Python จริงในไฟล์ go_to_goal_controller.py คุณจะเห็นผลลัพธ์ที่คล้ายกันมากขึ้น เนื่องจากเราใช้ตัวควบคุม PID แทนค่าสัมประสิทธิ์สัดส่วนแบบง่าย

ตอนนี้เรามีความเร็วเชิงมุมแล้ว ω เราจะหาความเร็วไปข้างหน้า v ได้อย่างไร? หลักการทั่วไปที่ดีคือกฎที่คุณอาจทราบโดยสัญชาตญาณ: หากเราไม่เลี้ยว เราสามารถเดินหน้าด้วยความเร็วเต็มที่ แล้วยิ่งเราเลี้ยวเร็วขึ้น เราก็ยิ่งควรช้าลง โดยทั่วไปสิ่งนี้จะช่วยให้ระบบของเรามีเสถียรภาพและดำเนินการภายในขอบเขตของแบบจำลองของเรา ดังนั้น v เป็นฟังก์ชันของ ω . ใน go_to_goal_controller.py สมการคือ:

 # calculate translational velocity # velocity is v_max when omega is 0, # drops rapidly to zero as |omega| rises v = self.supervisor.v_max() / ( abs( omega ) + 1 )**0.5

ข้อเสนอแนะให้ละเอียดเกี่ยวกับสูตรนี้คือการพิจารณาว่าเรามักจะช้าลงเมื่อใกล้เป้าหมายเพื่อที่จะไปถึงมันด้วยความเร็วเป็นศูนย์ สูตรนี้จะเปลี่ยนไปอย่างไร? มันต้องรวมถึงการแทนที่ v_max() ด้วยบางสิ่งที่เป็นสัดส่วนกับระยะทาง ตกลง เราเกือบจะเสร็จสิ้นการควบคุมลูปเดียวแล้ว สิ่งเดียวที่ต้องทำคือเปลี่ยนพารามิเตอร์รุ่น unicycle ทั้งสองนี้เป็นความเร็วของล้อที่แตกต่างกัน และส่งสัญญาณไปยังล้อ นี่คือตัวอย่างวิถีโคจรของหุ่นยนต์ภายใต้ตัวควบคุม go-to-goal โดยไม่มีสิ่งกีดขวาง:

นี่คือตัวอย่างวิถีโคจรของหุ่นยนต์ที่ตั้งโปรแกรมไว้

ดังที่เราเห็น เวกเตอร์ที่นำไปสู่เป้าหมายเป็นข้อมูลอ้างอิงที่มีประสิทธิภาพสำหรับเราในการคำนวณการควบคุมของเรา เป็นการแสดงภายในว่า “เราต้องการไปที่ไหน” ดังที่เราจะได้เห็นกัน ความแตกต่างที่สำคัญเพียงอย่างเดียวระหว่างการมุ่งสู่เป้าหมายกับพฤติกรรมอื่นๆ ก็คือ การมุ่งไปสู่เป้าหมายในบางครั้งอาจเป็นความคิดที่ไม่ดี เราจึงต้องคำนวณเวกเตอร์อ้างอิงที่แตกต่างกัน

วิธีการเขียนโปรแกรม Python Robot: พฤติกรรมการหลีกเลี่ยงอุปสรรค

การมุ่งสู่เป้าหมายเมื่อมีอุปสรรคในทิศทางนั้นเป็นประเด็น แทนที่จะวิ่งเข้าหาสิ่งต่าง ๆ ในแบบของเรา ให้ลองตั้งโปรแกรมกฎหมายควบคุมที่ทำให้หุ่นยนต์หลีกเลี่ยงกฎเหล่านี้

เพื่อทำให้สถานการณ์ง่ายขึ้น ตอนนี้ ให้ลืมเป้าหมายอย่างสมบูรณ์ แล้วทำตามวัตถุประสงค์ของเรา: เมื่อไม่มีสิ่งกีดขวางอยู่ข้างหน้า ให้ก้าวไปข้างหน้า เมื่อเจออุปสรรค ให้หันหนี จนกว่ามันจะไม่อยู่ตรงหน้าเราอีกต่อไป

ดังนั้น เมื่อไม่มีสิ่งกีดขวางอยู่ตรงหน้า เราต้องการให้เวกเตอร์อ้างอิงชี้ไปข้างหน้าอย่างง่ายๆ จากนั้น ω จะเป็นศูนย์ และ v จะเป็นความเร็วสูงสุด อย่างไรก็ตาม ทันทีที่เราตรวจพบสิ่งกีดขวางด้วยเซนเซอร์ระยะใกล้ เราต้องการให้เวกเตอร์อ้างอิงชี้ไปในทิศทางใดก็ตามที่อยู่ห่างจากสิ่งกีดขวาง ซึ่งจะทำให้ ω พุ่งขึ้นเพื่อหันเหเราออกจากสิ่งกีดขวาง และทำให้ v ดรอปเพื่อให้แน่ใจว่าเราจะไม่บังเอิญไปชนกับสิ่งกีดขวางในกระบวนการ

วิธีที่ดีในการสร้างเวกเตอร์อ้างอิงที่ต้องการคือการเปลี่ยนค่าความใกล้เคียง 9 ค่าเป็นเวกเตอร์ แล้วหาผลรวมแบบถ่วงน้ำหนัก เมื่อตรวจไม่พบสิ่งกีดขวาง เวกเตอร์จะรวมสมมาตร ส่งผลให้เวกเตอร์อ้างอิงชี้ไปข้างหน้าตามที่ต้องการ แต่ถ้าเซ็นเซอร์เปิด เช่น ด้านขวาจับสิ่งกีดขวาง มันจะส่งผลให้เวกเตอร์ขนาดเล็กลงสู่ผลรวม และผลลัพธ์จะเป็นเวกเตอร์อ้างอิงที่เลื่อนไปทางซ้าย

สำหรับหุ่นยนต์ทั่วไปที่มีตำแหน่งเซ็นเซอร์ต่างกัน สามารถใช้แนวคิดเดียวกันได้ แต่อาจต้องมีการเปลี่ยนแปลงน้ำหนักและ/หรือการดูแลเพิ่มเติมเมื่อเซ็นเซอร์มีความสมมาตรที่ด้านหน้าและด้านหลังของหุ่นยนต์ เนื่องจากผลรวมที่ถ่วงน้ำหนักอาจกลายเป็นศูนย์ .

เมื่อตั้งโปรแกรมอย่างถูกต้อง หุ่นยนต์สามารถหลีกเลี่ยงอุปสรรคที่ซับซ้อนเหล่านี้ได้

นี่คือรหัสที่ทำสิ่งนี้ใน avoid_obstacles_controller.py :

 # sensor gains (weights) self.sensor_gains = [ 1.0+( (0.4*abs(p.theta)) / pi ) for p in supervisor.proximity_sensor_placements() ] # ... # return an obstacle avoidance vector in the robot's reference frame # also returns vectors to detected obstacles in the robot's reference frame def calculate_ao_heading_vector( self ): # initialize vector obstacle_vectors = [ [ 0.0, 0.0 ] ] * len( self.proximity_sensor_placements ) ao_heading_vector = [ 0.0, 0.0 ] # get the distances indicated by the robot's sensor readings sensor_distances = self.supervisor.proximity_sensor_distances() # calculate the position of detected obstacles and find an avoidance vector robot_pos, robot_theta = self.supervisor.estimated_pose().vector_unpack() for i in range( len( sensor_distances ) ): # calculate the position of the obstacle sensor_pos, sensor_theta = self.proximity_sensor_placements[i].vector_unpack() vector = [ sensor_distances[i], 0.0 ] vector = linalg.rotate_and_translate_vector( vector, sensor_theta, sensor_pos ) obstacle_vectors[i] = vector # store the obstacle vectors in the robot's reference frame # accumulate the heading vector within the robot's reference frame ao_heading_vector = linalg.add( ao_heading_vector, linalg.scale( vector, self.sensor_gains[i] ) ) return ao_heading_vector, obstacle_vectors

การใช้ ao_heading_vector ที่เป็นผลลัพธ์เป็นข้อมูลอ้างอิงสำหรับหุ่นยนต์เพื่อพยายามจับคู่ นี่คือผลลัพธ์ของการรันซอฟต์แวร์หุ่นยนต์ในการจำลองโดยใช้เพียงตัวควบคุมการหลีกเลี่ยงสิ่งกีดขวาง โดยไม่สนใจจุดเป้าหมายอย่างสมบูรณ์ หุ่นยนต์กระดอนไปรอบๆ อย่างไร้จุดหมาย แต่มันไม่เคยชนกับสิ่งกีดขวาง และยังสามารถเคลื่อนที่ผ่านพื้นที่แคบๆ บางแห่งได้:

หุ่นยนต์ตัวนี้สามารถหลีกเลี่ยงสิ่งกีดขวางภายในโปรแกรมจำลองหุ่นยนต์ Python ได้สำเร็จ

วิธีการเขียนโปรแกรม Python Robot: Hybrid Automata (เครื่องสถานะพฤติกรรม)

จนถึงตอนนี้ เราได้อธิบายพฤติกรรมสองอย่าง—การมุ่งสู่เป้าหมายและการหลีกเลี่ยงอุปสรรค—แยกกัน ทั้งสองทำหน้าที่ได้อย่างน่าชื่นชม แต่เพื่อให้บรรลุเป้าหมายในสภาพแวดล้อมที่เต็มไปด้วยอุปสรรค เราจำเป็นต้องรวมเข้าด้วยกัน

โซลูชันที่เราจะพัฒนานั้นอยู่ในกลุ่มเครื่องจักรที่มีการกำหนด ออโตมาตาไฮบริด ที่ให้เสียงที่ไพเราะอย่างยิ่ง หุ่นยนต์ไฮบริดถูกตั้งโปรแกรมด้วยพฤติกรรมหรือโหมดต่างๆ ที่แตกต่างกัน รวมถึงเครื่องควบคุมสถานะ เครื่องควบคุมสถานะจะเปลี่ยนจากโหมดหนึ่งไปอีกโหมดหนึ่งในช่วงเวลาที่ไม่ต่อเนื่อง (เมื่อบรรลุเป้าหมายหรือสภาพแวดล้อมเปลี่ยนไปอย่างกะทันหันมากเกินไป) ในขณะที่พฤติกรรมแต่ละอย่างใช้เซ็นเซอร์และล้อเพื่อตอบสนองต่อการเปลี่ยนแปลงสภาพแวดล้อมอย่างต่อเนื่อง โซลูชันนี้เรียกว่า ไฮบริด เนื่องจากมีวิวัฒนาการทั้งในรูปแบบที่ไม่ต่อเนื่องและต่อเนื่อง

กรอบงานหุ่นยนต์ Python ของเราใช้เครื่องสถานะในไฟล์ supervisor_state_machine.py

ตรรกะง่ายๆ ที่มาพร้อมพฤติกรรมที่มีประโยชน์สองอย่างของเรา คือ: เมื่อตรวจไม่พบสิ่งกีดขวาง ให้ใช้พฤติกรรมที่มุ่งไปสู่เป้าหมาย เมื่อตรวจพบสิ่งกีดขวาง ให้เปลี่ยนไปใช้พฤติกรรมการหลีกเลี่ยงสิ่งกีดขวางจนกว่าจะตรวจไม่พบสิ่งกีดขวางอีกต่อไป

อย่างไรก็ตาม ตรรกะนี้จะก่อให้เกิดปัญหามากมาย สิ่งที่ระบบนี้มักจะทำเมื่อเจอสิ่งกีดขวางคือการเบี่ยงตัวออกจากมัน จากนั้นทันทีที่มันเคลื่อนตัวออกห่างจากมัน ให้หันหลังกลับทันทีแล้ววิ่งเข้าไปอีกครั้ง ผลที่ได้คือการวนซ้ำไม่รู้จบของการสลับอย่างรวดเร็วที่ทำให้หุ่นยนต์ไร้ประโยชน์ In the worst case, the robot may switch between behaviors with every iteration of the control loop—a state known as a Zeno condition .

There are multiple solutions to this problem, and readers that are looking for deeper knowledge should check, for example, the DAMN software architecture.

What we need for our simple simulated robot is an easier solution: One more behavior specialized with the task of getting around an obstacle and reaching the other side.

Python Robot Programming Methods: Follow-Wall Behavior

Here's the idea: When we encounter an obstacle, take the two sensor readings that are closest to the obstacle and use them to estimate the surface of the obstacle. Then, simply set our reference vector to be parallel to this surface. Keep following this wall until A) the obstacle is no longer between us and the goal, and B) we are closer to the goal than we were when we started. Then we can be certain we have navigated the obstacle properly.

With our limited information, we can't say for certain whether it will be faster to go around the obstacle to the left or to the right. To make up our minds, we select the direction that will move us closer to the goal immediately. To figure out which way that is, we need to know the reference vectors of the go-to-goal behavior and the avoid-obstacle behavior, as well as both of the possible follow-wall reference vectors. Here is an illustration of how the final decision is made (in this case, the robot will choose to go left):

Utilizing a few types of behaviors, the programmed robot avoids obstacles and continues onward.

Determining the follow-wall reference vectors turns out to be a bit more involved than either the avoid-obstacle or go-to-goal reference vectors. Take a look at the Python code in follow_wall_controller.py to see how it's done.

Final Control Design

The final control design uses the follow-wall behavior for almost all encounters with obstacles. However, if the robot finds itself in a tight spot, dangerously close to a collision, it will switch to pure avoid-obstacles mode until it is a safer distance away, and then return to follow-wall. Once obstacles have been successfully negotiated, the robot switches to go-to-goal. Here is the final state diagram, which is programmed inside the supervisor_state_machine.py :

This diagram illustrates the switching between robotics programming behaviors to achieve a goal and avoid obstacles.

Here is the robot successfully navigating a crowded environment using this control scheme:

The robot simulator has successfully allowed the robot software to avoid obstacles and achieve its original purpose.

An additional feature of the state machine that you can try to implement is a way to avoid circular obstacles by switching to go-to-goal as soon as possible instead of following the obstacle border until the end (which does not exist for circular objects!)

Tweak, Tweak, Tweak: Trial and Error

The control scheme that comes with Sobot Rimulator is very finely tuned. It took many hours of tweaking one little variable here, and another equation there, to get it to work in a way I was satisfied with. Robotics programming often involves a great deal of plain old trial-and-error. Robots are very complex and there are few shortcuts to getting them to behave optimally in a robot simulator environment…at least, not much short of outright machine learning, but that's a whole other can of worms.

Robotics often involves a great deal of plain old trial-and-error.

I encourage you to play with the control variables in Sobot Rimulator and observe and attempt to interpret the results. Changes to the following all have profound effects on the simulated robot's behavior:

  • The error gain kP in each controller
  • The sensor gains used by the avoid-obstacles controller
  • The calculation of v as a function of ω in each controller
  • The obstacle standoff distance used by the follow-wall controller
  • The switching conditions used by supervisor_state_machine.py
  • Pretty much anything else

When Programmable Robots Fail

We've done a lot of work to get to this point, and this robot seems pretty clever. Yet, if you run Sobot Rimulator through several randomized maps, it won't be long before you find one that this robot can't deal with. Sometimes it drives itself directly into tight corners and collides. Sometimes it just oscillates back and forth endlessly on the wrong side of an obstacle. Occasionally it is legitimately imprisoned with no possible path to the goal. After all of our testing and tweaking, sometimes we must come to the conclusion that the model we are working with just isn't up to the job, and we have to change the design or add functionality.

In the mobile robot universe, our little robot's “brain” is on the simpler end of the spectrum. Many of the failure cases it encounters could be overcome by adding some more advanced software to the mix. More advanced robots make use of techniques such as mapping , to remember where it's been and avoid trying the same things over and over; heuristics , to generate acceptable decisions when there is no perfect decision to be found; and machine learning , to more perfectly tune the various control parameters governing the robot's behavior.

A Sample of What's to Come

Robots are already doing so much for us, and they are only going to be doing more in the future. While even basic robotics programming is a tough field of study requiring great patience, it is also a fascinating and immensely rewarding one.

In this tutorial, we learned how to develop reactive control software for a robot using the high-level programming language Python. But there are many more advanced concepts that can be learned and tested quickly with a Python robot framework similar to the one we prototyped here. I hope you will consider getting involved in the shaping of things to come!


Acknowledgement: I would like to thank Dr. Magnus Egerstedt and Jean-Pierre de la Croix of the Georgia Institute of Technology for teaching me all this stuff, and for their enthusiasm for my work on Sobot Rimulator.

Related: OpenCV Tutorial: Real-time Object Detection Using MSER in iOS