前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AirPods 监控头部运动,同步大象机器人myCobot 280协作机械臂

AirPods 监控头部运动,同步大象机器人myCobot 280协作机械臂

原创
作者头像
大象机器人
发布2024-02-26 11:15:03
1250
发布2024-02-26 11:15:03

本篇文章是经过作者kimizuka授权,进行编辑转载。

原文链接:AirPods → iPhoneアプリ → Express → python-shell → pymycobot → myCobotと繋いでいって、AirPodsの回転角とmyCobotの姿勢と同期する 🤖 - みかづきブログ・カスタム

引言

本篇文章主要记录,将 AirPods 通过 iPhone 应用连接到 Express,再通过 python-shell 连接到 pymycobot,最后与 myCobo 同步,实现 AirPods 的旋转角度与 myCobot 的姿态同步 。

虽然不确定是否有需求,但我会提供一个大致的源代码。

项目结构

这个项目主要运用到的技术有headphone-motion,web服务器,node.js的express框架,python-shell,pymycobot。

这里简要介绍一下这些技术。

  1. Headphone-Motion:Headphone Motion 是一个利用特定技术来追踪和利用用户头部运动的项目。虽然具体细节可能依赖于实现方式和所用的平台(如 iOS),它主要演示如何通过连接到设备的耳机(尤其是那些带有内置传感器的智能耳机)来捕获头部运动数据。我们看GitHub-anastasiadeana做的Headphone Motion Unity Plugin 比较直观的效果。GitHub - anastasiadevana/HeadphoneMotion: Unity plugin for Apple Headphone Motion API.

能够实时的追踪用户的头部运动,包括倾斜,旋转等动作。这也是本次项目较为核心的一个技术。

  1. web服务器:服务器的类型有很多种,它为其他应用程序或设备提供数据,服务或应用程序。服务器执行某些任务,如处理数据请求,托管网站,存储信息,运行企业应用程序。等等。本项目web服务器主要负责接受ios应用的头部运动数据,并且将这些数据传递给控制mycobot机械臂的脚本。
  2. express-node.js:Express 是一个快速、开放、最小化的 Web 应用程序框架,用于 Node.js。它被设计用来构建 Web 应用程序和 API。它允许开发者以非常快速和简便的方式设置中间件来响应 HTTP 请求,使得开发 Web 应用程序变得更加简单快捷。

GitHub - expressjs/express: Fast, unopinionated, minimalist web framework for node.

  1. pymycobot-python:pymycobot是一个Python库,专门为控制myCobot机械臂设计。这个库提供了一系列函数和接口,允许开发者通过Python脚本直接与myCobot机械臂进行通信和控制。使用pymycobot,开发者可以编写代码来控制机械臂的运动、调整其姿态、执行预设的动作序列等,使其在教育、研究、自动化等多种场景中具有广泛的应用可能性。

GitHub - elephantrobotics/pymycobot: This is a python API for ElephantRobotics product.

iOS 应用

这个应用是基于我之前创建的应用,使用react-native-haedphone-motion通过react Native IOS应用程序访问AirdPods中的传感器。一个有趣的项目,当你带着airpods长时间低头被检测的时候,就会有悲鸣的声音提醒你。

react-native-headphone-motionを使って、React Native製のiOSアプリでAirPods内のセンサにアクセスする 🎧 - みかづきブログ・カスタム

只是要注意更改点,我在 onDeviceMotionUpdates 中加入了向 Web 服务器发送 POST 请求的处理。另外,为了避免每次更新都发送 POST 请求给服务器带来负担,我设置了至少间隔 500ms 发送一次。

App.tsx(部分片段)

代码语言:javascript
复制
useEffect(() => {  const delay = 500;  const handleDeviceMotionUpdates = onDeviceMotionUpdates((data) => {     // 如果距离上次时间不足500ms,则返回    if (Date.now() - lastUpdateTimeRef.current < delay) {      return;    }     // 向Web服务器POST传感器值    axios.post(String(process.env.API_URL), {       pitch: data.attitude.pitchDeg || 0,      roll: data.attitude.rollDeg || 0,      yaw: data.attitude.yawDeg || 0    }).then(() => {      lastUpdateTimeRef.current = Date.now();    }).catch((err) => {      console.error(err);      lastUpdateTimeRef.current = Date.now();    });     setPitch(data.attitude.pitch);    setPitchDeg(data.attitude.pitchDeg);    setRoll(data.attitude.roll);    setRollDeg(data.attitude.rollDeg);    setYaw(data.attitude.yaw);    setYawDeg(data.attitude.yawDeg);    setGravityX(data.gravity.x);    setGravityY(data.gravity.y);    setGravityZ(data.gravity.z);    setRotationRateX(data.rotationRate.x);    setRotationRateY(data.rotationRate.y);    setRotationRateZ(data.rotationRate.z);    setUserAccelerationX(data.userAcceleration.x);    setUserAccelerationY(data.userAcceleration.y);    setUserAccelerationZ(data.userAcceleration.z);  });   return () => {    handleDeviceMotionUpdates.remove();  };}, []);

POST请求中我使用了axios,它能够发送异步HTTP请求到REST端点并处理相应。因此,还需要添加模块导入。

代码语言:javascript
复制
import axios from 'axios';

完整的代码

代码语言:javascript
复制
import axios from 'axios'; // 为了简化POST请求而添加import React, {  useEffect,  useRef, // 为了保持500ms间隔而添加  useState,} from 'react';import {Button, SafeAreaView, StyleSheet, Text} from 'react-native';import {  requestPermission,  onDeviceMotionUpdates,  startListenDeviceMotionUpdates,  stopDeviceMotionUpdates,} from 'react-native-headphone-motion'; const API_URL = 'http://localhost:3000'; // 填入要POST的URL export default function App() {  const lastUpdateTimeRef = useRef<number>(0); // 为了保持最后一次更新的时间而添加  const [pitch, setPitch] = useState(0);  const [pitchDeg, setPitchDeg] = useState(0);  const [roll, setRoll] = useState(0);  const [rollDeg, setRollDeg] = useState(0);  const [yaw, setYaw] = useState(0);  const [yawDeg, setYawDeg] = useState(0);  const [gravityX, setGravityX] = useState(0);  const [gravityY, setGravityY] = useState(0);  const [gravityZ, setGravityZ] = useState(0);  const [rotationRateX, setRotationRateX] = useState(0);  const [rotationRateY, setRotationRateY] = useState(0);  const [rotationRateZ, setRotationRateZ] = useState(0);  const [userAccelerationX, setUserAccelerationX] = useState(0);  const [userAccelerationY, setUserAccelerationY] = useState(0);  const [userAccelerationZ, setUserAccelerationZ] = useState(0);   useEffect(() => {    const delay = 500; // 将更新间隔存入变量    const handleDeviceMotionUpdates = onDeviceMotionUpdates(data => {      if (Date.now() - lastUpdateTimeRef.current < delay) {        // 如果不满足更新间隔则返回        return;      }       // 向Web服务器POST传感器值      // 不管成功还是失败都更新lastUpdateTimeRef      // 出于某种原因,没有使用await      axios        .post(String(API_URL), {          pitch: data.attitude.pitchDeg || 0,          roll: data.attitude.rollDeg || 0,          yaw: data.attitude.yawDeg || 0,        })        .then(() => {          lastUpdateTimeRef.current = Date.now();        })        .catch(err => {          console.error(err);          lastUpdateTimeRef.current = Date.now();        });       setPitch(data.attitude.pitch);      setPitchDeg(data.attitude.pitchDeg);      setRoll(data.attitude.roll);      setRollDeg(data.attitude.rollDeg);      setYaw(data.attitude.yaw);      setYawDeg(data.attitude.yawDeg);      setGravityX(data.gravity.x);      setGravityY(data.gravity.y);      setGravityZ(data.gravity.z);      setRotationRateX(data.rotationRate.x);      setRotationRateY(data.rotationRate.y);      setRotationRateZ(data.rotationRate.z);      setUserAccelerationX(data.userAcceleration.x);      setUserAccelerationY(data.userAcceleration.y);      setUserAccelerationZ(data.userAcceleration.z);    });     return () => {      handleDeviceMotionUpdates.remove();    };  }, []);   return (    <SafeAreaView style={styles.container}>      <Button        title={'requestPermission'}        onPress={async () => {          await requestPermission();        }}      />      <Button        title={'startListenDeviceMotionUpdates'}        onPress={async () => {          await startListenDeviceMotionUpdates();        }}      />      <Button        title={'stopDeviceMotionUpdates'}        onPress={async () => {          await stopDeviceMotionUpdates();        }}      />      <Text>{lastUpdateTimeRef.current}</Text>      <Text>{`pitch: ${pitch}`}</Text>      <Text>{`pitchDeg: ${pitchDeg}`}</Text>      <Text>{`roll: ${roll}`}</Text>      <Text>{`rollDeg: ${rollDeg}`}</Text>      <Text>{`yaw: ${yaw}`}</Text>      <Text>{`yawDeg: ${yawDeg}`}</Text>      <Text>{`gravityX: ${gravityX}`}</Text>      <Text>{`gravityY: ${gravityY}`}</Text>      <Text>{`gravityZ: ${gravityZ}`}</Text>      <Text>{`rotationRateX: ${rotationRateX}`}</Text>      <Text>{`rotationRateY: ${rotationRateY}`}</Text>      <Text>{`rotationRateZ: ${rotationRateZ}`}</Text>      <Text>{`userAccelerationX: ${userAccelerationX}`}</Text>      <Text>{`userAccelerationY: ${userAccelerationY}`}</Text>      <Text>{`userAccelerationZ: ${userAccelerationZ}`}</Text>    </SafeAreaView>  );} const styles = StyleSheet.create({  container: {    flex: 1,    alignItems: 'center',    justifyContent: 'center',    backgroundColor: 'white',  },});

这段代码就是这样实现的,其实,如果能够在应用上指定API_URL会更方便,但是我出于对速度的考虑,就直接这样实现了。

 Web服务器

我在Mac上建立了一个本地服务器。

首先,为了操作myCobot,我进行了以下设置,主要是适配mac的电脑,安装机械臂的驱动,更新mycobot 280的固件等一些操作都在这篇文章当中。

myCobotをPythonから動かすための準備をする 🤖 - みかづきブログ・カスタム

我认为如果能用Python创建Web服务器会更顺畅,但基于我的技能集,使用Node.js创建是最快的方法,所以我打算使用Express快速搭建服务器。与myCobot的通信是通过Python进行的,所以这部分我决定使用python-shell来实现。

app.js

代码语言:javascript
复制
require('dotenv').config(); // 用于从外部传递myCobot的端口const express = require('express');const { PythonShell } = require('python-shell'); // 用于与myCobot通信const app = express();const http = require('http').Server(app); const duration = 100; // 如果应用端的延迟(500ms)设置得太小,就会出问题 app.use(express.json());app.post('/', (req, res) => {  try {    const angles = [0, 0, 0, 0, 0, 0];     // myCobot的关节信息请参考https://www.elephantrobotics.com/wp-content/uploads/2021/03/myCobot-User-Mannul-EN-V20210318.pdf第13页    // 数组按从下往上的顺序存放6个关节    // 每个关节都有确定的活动范围,要确保不超过这个范围        angles[0] = Math.max(-90, Math.min(req.body.yaw || 0, 90)); // J1    angles[3] = Math.max(-90, Math.min(req.body.pitch || 0, 90)); // J4    angles[5] = Math.max(-175, Math.min(req.body.roll || 0, 175)); // J6     // 通过USB连接的myCobot接收Python的指令    PythonShell.runString(      `from pymycobot.mycobot import MyCobot; MyCobot('${ process.env.MY_COBOT_PORT }').send_angles([${ angles }], ${ duration })`,      null,      (err) => err && console.error(err)    );  } catch (err) {    console.error(err);  }  res.send(200);}); try {  const angles = [0, 0, 0, 0, 0, 0];   // 启动时重置姿态  PythonShell.runString(    `from pymycobot.mycobot import MyCobot; MyCobot('${ process.env.MY_COBOT_PORT }').send_angles([${ angles }], ${ duration })`,    null,    (err) => err && console.error(err)  );} catch(err) {  console.error(err);} http.listen(3000, '0.0.0.0');

就是这样的感觉,因为要通过PythonShell执行pymycobot,所以需要在app.js的同一层级放置pymycobot的pymycobot目录。

GitHub - elephantrobotics/pymycobot: This is a python API for ElephantRobotics product.

准备好之后,将PC与myCobot连接,执行相应的操作即可启动Web服务器,并通过POST请求接收到的pitch、roll、yaw值传递给myCobot。虽然这次是从iPhone应用通过POST发送AirPods的传感器值,但POST的来源可以是任何地方,所以我觉得建立这样一个服务器,将来可能会有用武之地。

源代码 

GitHub - kimizuka/mycobot-express at example/airpods

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 项目结构
  • iOS 应用
  •  Web服务器
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档