本篇文章是经过作者kimizuka授权,进行编辑转载。
本篇文章主要记录,将 AirPods 通过 iPhone 应用连接到 Express,再通过 python-shell 连接到 pymycobot,最后与 myCobo 同步,实现 AirPods 的旋转角度与 myCobot 的姿态同步 。
虽然不确定是否有需求,但我会提供一个大致的源代码。
这个项目主要运用到的技术有headphone-motion,web服务器,node.js的express框架,python-shell,pymycobot。
这里简要介绍一下这些技术。
能够实时的追踪用户的头部运动,包括倾斜,旋转等动作。这也是本次项目较为核心的一个技术。
GitHub - expressjs/express: Fast, unopinionated, minimalist web framework for node.
GitHub - elephantrobotics/pymycobot: This is a python API for ElephantRobotics product.
这个应用是基于我之前创建的应用,使用react-native-haedphone-motion通过react Native IOS应用程序访问AirdPods中的传感器。一个有趣的项目,当你带着airpods长时间低头被检测的时候,就会有悲鸣的声音提醒你。
react-native-headphone-motionを使って、React Native製のiOSアプリでAirPods内のセンサにアクセスする 🎧 - みかづきブログ・カスタム
只是要注意更改点,我在 onDeviceMotionUpdates 中加入了向 Web 服务器发送 POST 请求的处理。另外,为了避免每次更新都发送 POST 请求给服务器带来负担,我设置了至少间隔 500ms 发送一次。
App.tsx(部分片段)
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端点并处理相应。因此,还需要添加模块导入。
import axios from 'axios';
完整的代码
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会更方便,但是我出于对速度的考虑,就直接这样实现了。
我在Mac上建立了一个本地服务器。
首先,为了操作myCobot,我进行了以下设置,主要是适配mac的电脑,安装机械臂的驱动,更新mycobot 280的固件等一些操作都在这篇文章当中。
myCobotをPythonから動かすための準備をする 🤖 - みかづきブログ・カスタム
我认为如果能用Python创建Web服务器会更顺畅,但基于我的技能集,使用Node.js创建是最快的方法,所以我打算使用Express快速搭建服务器。与myCobot的通信是通过Python进行的,所以这部分我决定使用python-shell来实现。
app.js
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的来源可以是任何地方,所以我觉得建立这样一个服务器,将来可能会有用武之地。
源代码
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。