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

本文介绍了如何通过iPhone应用,利用AirPods的头部运动数据,通过Express和python-shell连接到pymycobot,实现myCobot机械臂的姿态同步。过程包括获取头部运动数据、通过Web服务器转发至Python脚本控制myCobot。
摘要由CSDN通过智能技术生成

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

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

引言

本篇文章主要记录,将 AirPods 通过 iPhone 应用连接到 Express,再通过 python-shell 连接到 pymycobot,最后与 myCobot 同步,实现 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(部分片段)

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会更方便,但是我出于对速度的考虑,就直接这样实现了。

 Web服务器

我在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的来源可以是任何地方,所以我觉得建立这样一个服务器,将来可能会有用武之地。

源代码 

GitHub - kimizuka/mycobot-express at example/airpods

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值