茶壶机器人
这款基于树莓派Zero W的机器人,集成了摄像头技术,通过串行链路与Arduino Pro Mini板协同工作,驱动八个设计的舵机,赋予了它无与伦比的灵活性和动态表现。并且以游戏中的茶壶形象为灵感,赋予机器人独特的茶壶造型。
标签
Arduino
DIY
Raspberry
refdesignshare
更新2024-07-15
46

项目链接:Chapelier Fou

3D模型链接:eyepot/models at master · paullouisageneau/eyepot · GitHub

Github链接:GitHub - paullouisageneau/eyepot: A four-legged teapot robot

介绍

在这个快速变化的世界里,机器人技术不断突破我们的想象。但Eyepot不仅仅是一个技术展示,它是一个将传统与现代、实用与趣味完美结合的艺术品。这款机器人的设计灵感来源于我们熟悉的茶壶,但它的功能却远远超出了传统茶具的范畴。

Eyepot搭载了先进的 Raspberry Pi Zero W 和高清摄像头,赋予了它敏锐的视觉感知能力。通过精密的 Arduino Pro Mini控制,它能够灵活地驱动八只微型舵机,实现四条腿的协调运动。这不仅仅是技术上的挑战,更是对机器人设计美学的一次大胆尝试。

材料

让我们从设计和打印零件开始,使用OpenSCAD和白色PLA打印。

3D 模型视图

如果你玩过电子游戏《Alice: Madness Returns》,你可能会认出游戏中的某个敌人是灵感来源之一。形状和腿的数量虽然不同,但基本上它仍然是一个长着单眼的茶壶形状!你可能还会认出著名的犹他茶壶,我已经用它来制作过跳舞的茶壶。

《Alice: Madness Returns》截图

安装

打印好所有东西后就可以开始组装机器人了!每条腿有两个关节:膝盖可以水平旋转,脚踝可以垂直旋转。

安装了膝关节舵机的腿

平台上面安装了四个脚踝的舵机。中心的隔间将安装分配板,用于连接这八个舵机。在处理舵机时有一个小技巧:不要先拧紧手臂,等它们连接好之后,你可以先将它们转到中位位置,然后再拧紧螺丝。

安装了四个脚踝舵机并连接了一条腿

连接四条腿的平台

用一块锯成正确尺寸和引脚的原型板,制作了一块配电板来连接八个舵机。引脚焊接在电路板的背面,因此每个舵机都连接到电源线、地线和单独的控制线。十根电线通向头部:一根用于电源,一根用于接地,八根控制线用于驱动每个舵机。

连接到配电板的舵机

电源线、接地线和控制线穿过机器人的颈部,连接在茶壶形的头部。

用于顶部伸出的伺服系统的电源、接地和控制线

颈部不能移动,简单地用胶粘在底部的平台上和顶部的茶壶底座上。它中间的孔让电线能够穿过到达头部。

颈部用电线粘在平台上

头部的底部用电线粘在脖子上

现在可以连接舵机线了,对于电源和接地,我制作了一个小的分配板,以便干净地连接杜邦线。2S LiPo电池7.4V的电压(实际上从充满电时的8.4V到7V)通过开关稳压器(又名 UBEC)调节至 5V。由于选择让舵机由调节的5V电压供电,我们需要正确地确定UBEC的尺寸:每个9g的舵机在旋转时消耗大约250mA(在失速时甚至可能高达600mA),Pi Zero W在视频捕获和启用Wifi的情况下消耗高达250mA的电流,而Arduino Pro Mini仅消耗约5mA。因此,这里至少需要3A的额定值。

组件间连接示意图

在连接Arduino Pro Mini时需要注意一些细节:由于它是3.3V版本,因此将5V输入连接到RAW。如果是 5V,则将直接将 5V 输入连接到 VCC,否则由于稳压器中的压差,电路板中的电压将低于 5V。

Arduino Pro Mini板和电池连接到舵机

现在是将舵机设置为虚拟Arduino程序的平均角度(90度)的好时机,然后拧紧螺丝,使手臂保持在中间位置。在检查舵机一切正常后,我们可以夹住底盖。

盖子关闭的底视图

相机支架粘在头部的茶壶形顶部内。它允许用一对螺丝将 Raspberry Pi 相机固定在眼睛中。

摄像头定位在头部的顶部内

Raspberry Pi Zero W被螺丝固定在头部的底座上。舵机的电线有空间适应在板子下方。

所有东西都已连接好,头部准备好安装

最后,安装头部的茶壶形状顶部,在安装前别忘了先连接摄像头,并用盖子将茶壶盖紧。

完成的 Eyepot 机器人

在下文中将介绍如何给机器人编程:

Eyepot 的工作原理是将 Raspberry Pi Zero W 和通过串行链路连接的 Arduino Pro Mini 结合使用。因此,首先编写Arduino代码,然后为Raspberry Pi编写Python程序进行远程控制。

Arduino程序

Arduino Pro Mini负责驱动腿部的八个舵机。指定目标角度的命令通过串行链路从 Raspberry Pi 发送。

自定义串行协议基于文本,非常简单。它可以在调试时手动轻松输入,但它仍然足够紧凑,即使在低比特率下也能实现较短的传输时间。每行包含一个单字符命令、一个可选空格以及一个可选的以10进制整数形式的参数。实现的命令如下:

  • 0 到 7:存储相应舵机的目标角度(0 到 7)
  • R:将存储的目标角度重置为每个舵机的默认值
  • C:提交存储的目标角度(为每个舵机应用存储的角度)

因此,例如,移动一条腿的命令,其臀部和膝盖舵机分别连接到引脚0和4:

090
445
C

Arduino程序读取串行上的命令并应用它们。舵机的默认位置被定义为机器人站立的姿势,而且程序还具有可调节的偏移量,以精确设置每个舵机的中心位置。

#include <Servo.h>

// ---------- Pin setup ----------
const int servoPins[8] = {5, 6, 7, 8, 9, 10, 11, 12};
const int offsetAngles[8] = { 0, 0, -10, -5, -10, 5, 10, -5 };
const int defaultAngles[8] = { 135, 135, 45, 45, 90, 90, 90, 90 };

Servo servos[8];
unsigned int angles[8];

String inputString = "";

void setup()
{
// Init serial
Serial.begin(9600);

// Init servos
for(int i = 0; i < 8; ++i)
{
angles[i] = defaultAngles[i];
servos[i].attach(servoPins[i]);
servos[i].write(offsetAngles[i] + angles[i]);
}
}

void loop()
{
// Read commands on serial
while(Serial.available())
{
char chr = (char)Serial.read();
if(chr != '\n')
{
if(chr != '\r')
inputString+= chr;
}
else if(inputString.length() > 0)
{
// Parse command
char cmd = toupper(inputString[0]);
String param;
int pos = 1;
while(pos < inputString.length() && inputString[pos] == ' ') ++pos;
param = inputString.substring(pos);

// Execute command
if(cmd >= '0' && cmd < '8') // Servo
{
int i = int(cmd) - int('0');
int angle = param.toInt();
angles[i] = angle;
}
else if(cmd == 'R') // Reset
{
for(int i = 0; i < 8; ++i)
angles[i] = defaultAngles[i];
}
else if(cmd == 'C') // Commit
{
for(int i = 0; i < 8; ++i)
servos[i].write(offsetAngles[i] + angles[i]);
}

inputString = "";
}
}
}

控制程序

Raspberry Pi Zero W,安装Raspbian lite发行版,运行一个实现步行循环的Python程序。

顺便说一句,由于PI Zero W只提供Wifi作为网络接口,没有屏幕可能有点棘手,但如果按照正确的步骤,这个过程实际上并不复杂:

  • 将 SD 卡放入计算机中
  • 写入镜像文件:# dd if=raspbian-stretch-lite.img of=/dev/mmcblk0 bs=4096
  • 挂载引导分区: # mkdir /mnt/boot && mount /dev/mmcblk0p1 /mnt/boot
  • 放置一个空的 ssh 文件以启用 SSH: # touch /mnt/boot/ssh
  • 放置包含以下内容的 /mnt/boot/wpa_supplicant.conf 文件以启用 Wifi:
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=FR

network={
ssid="NETWORK_NAME"
psk="NETWORK_KEY"
}
  • 卸载分区:# umount /dev/mmcblk0p1
  • 将 SD 卡放入 Raspberry Pi 中。启动时,它应该会自动连接到Wifi网络,然后您可以使用其IP地址通过SSH连接到它,该地址可以从路由器DHCP服务器的设备列表中检索。默认用户是 pi,默认密码是 raspberry。你当然应该快速改变它。

现在,回到正题:Python控制程序通过串行链路将伺服角度发送到Arduino板。特别是,Control 类封装了不同的动作。

class Control:

def __init__(self, device = '/dev/serial0', baudrate = 9600):
self.ser = serial.Serial(device, baudrate)

# leg angle > 0 => down
def leg(self, i, angle):
self.move(i, 90+angle if i < 2 else 90-angle)

# hip angle > 0 => front
def hip(self, i, angle):
self.move(4+i, 90+angle if i < 2 else 90-angle)

def move(self, servo, angle):
if servo >= 0 and servo < 8:
angle = min(max(angle, 0), 180)
self.send('{:s}{:d}'.format(chr(ord('0')+servo), int(angle)))

def commit(self):
self.send('C')

def send(self, command):
self.ser.write((command + '\n').encode())

def walk(self, forward, sideward, rotation):
fu = [1, 1, 1, 1]
su = [-1, 1, 1, -1]
ru = [1, 1, -1, -1]
v = [f*forward + s*sideward + r*rotation for f, s, r in zip(fu, su, ru)]
self._pattern2(20, -5, v, 0.2)

最终,我们需要实际的步行周期。它需要四个参数:

  • Ldown:腿向下时的膝盖角度
  • LUP:腿抬起时的膝盖角度
  • Hangles:描述每条腿贡献的脚踝角度矢量
  • step:以秒为单位的步长持续时间

每个步长包括两种不同的腿部位置。对于每种位置,为需要改变的腿部应用新的角度,提交新的角度,然后暂停。

def _pattern2(self, ldown, lup, hangles, step):
self.leg(0, lup)
self.leg(1, ldown)
self.leg(2, ldown)
self.leg(3, lup)
self.commit()
time.sleep(step/2)

self.hip(0, hangles[0])
self.hip(1, 0)
self.hip(2, 0)
self.hip(3, hangles[3])
self.commit()
time.sleep(step/2)

self.leg(0, ldown)
self.leg(3, ldown)
self.commit()
time.sleep(step/2)

self.hip(0, 0)
self.hip(1, - hangles[1])
self.hip(2, - hangles[2])
self.hip(3, 0)
self.commit()
time.sleep(step/2)

self.leg(0, ldown)
self.leg(1, lup)
self.leg(2, lup)
self.leg(3, ldown)
self.commit()
time.sleep(step/2)

self.hip(0, 0)
self.hip(1, hangles[1])
self.hip(2, hangles[2])
self.hip(3, 0)
self.commit()
time.sleep(step/2)

self.leg(1, ldown)
self.leg(2, ldown)
self.commit()
time.sleep(step/2)

self.hip(0, - hangles[0])
self.hip(1, 0)
self.hip(2, 0)
self.hip(3, - hangles[3])
self.commit()
time.sleep(step/2)

远程控制

目前,机器人将连接到Wifi,并配置为可通过网络浏览器远程访问。该网页将显示视频反馈并允许控制机器人。

为此,需要结合几个组件:

  • 使用Flask Python框架创建托管控制代码的应用程序
  • 作为TLS终端和反向代理的NGINX HTTP服务器
  • 使用gstreamer设置视频流管道
  • 使用Janus WebRTC网关,使浏览器能够通过WebRTC流式传输实时视频

Eyepot 内部软件组件之间的关系示意图

首先,将主机名更改为 eyepot:

$ sudo hostnamectl set-hostname eyepot

出于安全原因,现代浏览器将拒绝在来自不安全来源的页面上运行 WebRTC 会话(原因是网络上的任何人都可以注入 JavaScript 代码来收听他人的麦克风或网络摄像头)。对我们来说,问题在于此规则也在本地网络上强制执行。因此,需要生成一个TLS证书,并部署一个HTTPS服务器。

您可以使用 openssl 生成自签名 TLS X.509 证书:

$ openssl req -x509 -newkey rsa:4096 -days 365 -nodes -keyout eyepot.key -out eyepot.pem -subj '/CN=eyepot'

使用 NGINX 作为字体端 HTTPS 服务器,但另一个支持代理传递的服务器(如 Apache 或 Lighttpd)也可以使用。

$ sudo apt install nginx-light

安装 nginx 后,必须将其配置为 Janus(侦听端口 8088)和 Flask 应用程序(侦听端口 8080)的反向代理,并在 /etc/nginx/sites-enabled/default 中启用 SSL/TLS:

server {
listen 80 default_server;
listen [::]:80 default_server;

listen 443 ssl default_server;
listen [::]:443 ssl default_server;

server_name eyepot;

ssl_certificate eyepot.pem;
ssl_certificate_key eyepot.key;

location /janus {
proxy_pass http://localhost:8088;
}

location / {
proxy_pass http://localhost:8080;
}
}

现在可以启动 nginx:

$ sudo systemctl enable nginx
$ sudo systemctl start nginx

Janus 在 Debian Stretch 中不可用,而 Raspbian 目前基于该 Stretch。因此,按照说明编译和安装 Janus。在这里不需要对 WebRTC 数据通道的支持,因此不需要安装 usrsctp。

$ sudo apt install git build-essential libmicrohttpd-dev libjansson-dev libnice-dev libssl-dev libsrtp-dev libsofia-sip-ua-dev libglib2.0-dev libopus-dev libogg-dev libcurl4-openssl-dev liblua5.3-dev pkg-config gengetopt libtool automake

这里有一点需要注意:Debian Stretch 发布了 libsrtp 的 1.4.5 版本,但 Janus 需要 1.5.x、1.6.x 或 2.x 版本,必须手动编译它。

$ sudo apt purge libsrtp0 libsrtp0-dev
$ wget https://github.com/cisco/libsrtp/archive/v2.1.0.tar.gz
$ tar xfv v2.1.0.tar.gz
$ cd libsrtp-2.1.0
$ ./configure --enable-openssl --prefix=/usr/local
$ make shared_library
$ sudo make install
$ cd ..

然后,可以正确安装Janus。

$ git clone https://github.com/meetecho/janus-gateway.git
$ cd janus-gateway
$ sh autogen.sh
$ ./configure --disable-docs --prefix=/usr/local
$ make
$ sudo make install
$ sudo cp -r /usr/local/etc/janus /etc/

默认情况下,Janus 在 HTTP 的端口 8088 上绑定。使用其流式传输插件来流式传输视频,因此,在安装后,需要在配置文件 /etc/janus/janus.plugin.streaming.cfg 中注册视频源:

[gst-rpi]
type = rtp
id = 1
description = Raspberry Pi H264 streaming
audio = no
video = yes
videoport = 8000
videopt = 100
videortpmap = H264/90000
videofmtp = profile-level-id=42e028\;packetization-mode=1

现在可以在后台启动 Janus:

$ janus -F /etc/janus &

现在,需要在端口 8000 上实际流式传输以 H.264 编码的视频。raspivid 程序从 Raspberry Pi 相机捕获视频,"-o -"选项将H.264帧输出到标准输出。然后,可以将它们通过管道传输到 gstreamer 管道中以对视频进行打包,并最终将其发送到 UDP 端口 8000。raspivid 上的参数来自一些经验调整,特别是intra,它指定了关键帧之间的帧数:如果设置得太高,视频在Chrome中容易出现随机冻结。

请注意,gst-launch 应该是一个调试工具,我们实际上应该使用 gstreamer库创建一个程序。但是,这足以满足这里的需求。

$ raspivid -n -t 0 -w 640 -h 480 -fps 30 -b 2000000 --profile baseline --intra 15 -o - | gst-launch-1.0 -v fdsrc ! h264parse ! rtph264pay config-interval=1 pt=100 ! udpsink host=127.0.0.1 port=8000 sync=false &

在发送到浏览器的网页中,在导入 janus.js、流插件的streaming.js和 Janus 所需的 WebRTC 适配器的adapter.js后,需要配置 Janus 会话。这是一个没有错误处理的简化版本:

// Initialize the library
Janus.init({debug: "all", callback: function() {
// Make sure the browser supports WebRTC
if(!Janus.isWebrtcSupported()) {
alert("No WebRTC support");
return;
}
// Create session
var janus = new Janus({
server: window.location.protocol + "//" + window.location.host + "/janus",
success: function() {
// Attach to streaming plugin
janus.attach({
plugin: "janus.plugin.streaming",
opaqueId: Janus.randomString(12),
success: function(pluginHandle) {
var streaming = pluginHandle;
var body = { "request": "watch", id: 1 };
streaming.send({ "message": body });
},
onmessage: function(msg, jsep) {
if(jsep !== undefined && jsep !== null) {
// Answer
streaming.createAnswer({
jsep: jsep,
media: { audioSend: false, videoSend: false }, // Receive only
success: function(jsep) {
var body = { "request": "start" };
streaming.send({"message": body, "jsep": jsep});
},
});
}
},
onremotestream: function(stream) {
var videoElement = document.getElementById("video");
Janus.attachMediaStream(videoElement, stream);
},
});
},
});
}});

还需要根据按下的按钮或键发送控制状态,在路由 /move 上使用一个非常简单的 API 和 POST:

var message = JSON.stringify({
'state': state,
});
var xhr = new XMLHttpRequest();
xhr.open('POST', '/move');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(message);

最终,可以编写 Flask 应用程序来接收控制状态。它被传递给循环当前状态的线程。提醒一下,可以在 GitHub 上找到整个 Flask 应用程序以及该项目的其余源代码。

import control
import controlthread

from flask import Flask, request, Response, send_file, url_for

app = Flask(__name__)
app.config.from_object(__name__)

ctrl = control.Control('/dev/serial0', 9600)
ctrlThread = controlthread.ControlThread(ctrl)
ctrlThread.start()

@app.route('/', methods=['GET'])
def home():
return send_file('static/index.html')

@app.route('/move', methods=['POST'])
def move():
data = request.get_json()
ctrlThread.state = data["state"] if data["state"] else 'idle'
return Response('', 200)

随着其他一切都在后台运行,可以使用:

$ python3 app.py

然后,通过将兼容 WebRTC 的浏览器(如 Firefox 或 Chromium)指向 https://eyepot/,现在可以通过视频反馈来控制机器人!

附件下载
eyepot.ino
团队介绍
Paul-Louis Ageneau
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号