Compare commits

4 Commits

25 changed files with 4051 additions and 394 deletions
-4
View File
@@ -1,4 +0,0 @@
#!/bin/bash
pactl set-source-volume alsa_input.pci-0000_00_1b.0.analog-stereo 32%
#python3.11 naowalk.py --ip spike.local --port 9561 --mic-gain 9 --video-scale 3.0
python3.11 naowalk.py --ip 127.0.0.1 --port 9561 --mic-gain 9 --video-scale 3.0 --nao-ssh-port 2222 --media-relay 127.0.0.1:8000
Binary file not shown.
@@ -0,0 +1,66 @@
---
title: "Automate NAO video server deployment and cleanup"
tags: []
author: claude
count: 2
exporter: 3.1.0
date: 2026-07-21T22-15-10+0000
url: https://claude.ai/chat/44ac3c8f-f04d-4b23-a71d-f35c28b50a86
---
# Automate NAO video server deployment and cleanup
## Table of Contents
- [1: make naowalk automatically scp the video server to the nao's home dire…](#chat-1)
- [2: lets gut the original audio and video code out, i want it all going th…](#chat-2)
## chat-1
> make naowalk automatically scp the video server to the nao's home directory, and run, then terminate the video server on close
Now I'll add a launcher class that deploys and runs `nao_video_server.py` over SSH, then wire it into startup/shutdown.
Now update the `NaoTeleop.__init__` signature and wire in the deploy call before video/audio init:
Now add teardown in the shutdown block:
Now add the CLI flags and pass them through:
Lets update the `--media-relay` help text, since it currently implies the server must already be running manually:
Now let's sanity-check the file compiles and the diff looks right:
Diff looks clean. Now let's copy the finished file to outputs and present it.
___
###### [top](#table-of-contents)
## chat-2
> lets gut the original audio and video code out, i want it all going through the relay, including my mic. Also lets see if we can improve the latency on the nao's mic audio
Good, this stays unchanged (`start()`/`stop()` API preserved). Now let's check the HUD and shutdown/argparse sections:
That's fine to leave as-is (harmless dead branch for "connecting"). Now the shutdown block and CLI/argparse section:
Now let's do the bigger refactor. Starting with `nao_video_server.py` — adding the bidirectional talk channel and tightening audio buffering for latency:
Given the scope of changes, I'll rewrite `nao_video_server.py` in full rather than patching piecemeal:
Now let's do the equivalent large refactor on `naowalk.py`, section by section. First, the protocol constants and header comment:
Now let's tune `SoundReceiver` for lower latency:
Now update `feed()` to actively trim the backlog instead of just dropping one chunk on overflow:
Now let's replace `LiveMicRelay` with the new `PushToTalk` class that talks over the media relay instead of SSH:
Now I'll replace the entire `LiveMicRelay` class with `PushToTalk`:
Now let's update `NaoTeleop.__init__` and the video/audio init methods:
Now I'll replace this whole block with the simplified, relay-only version:
___
###### [top](#table-of-contents)
+20
View File
@@ -0,0 +1,20 @@
# Loaded by naowalk.py's gesture loader (_load_gestures). The filename's
# leading digit ("1") is which number key triggers this. Every gesture
# file just needs a run(motion, tts) function - motion is the connected
# ALMotion proxy, tts is ALTextToSpeech. Optionally set NAME below to
# control the display name shown in the on-screen controls hint.
NAME = "Wave"
def run(motion, tts):
motion.setStiffnesses("RArm", 1.0)
names = ["RShoulderPitch", "RShoulderRoll", "RElbowYaw", "RElbowRoll", "RWristYaw", "RHand"]
angles = [[-1.5]*5, [-0.4,-0.7,-0.4,-0.7,-0.4], [0.6,1.1,0.6,1.1,0.6],
[0.8,0.3,0.8,0.3,0.8], [0.0,1.5,0.0,-1.5,0.0], [1.0]*5]
times = [[0.5,1.0,1.5,2.0,2.5]] * 6
motion.angleInterpolation(names, angles, times, True)
neutral = [1.5, -0.15, 1.2, 0.5, 0.0, 0.0]
motion.angleInterpolationWithSpeed(names, neutral, 0.3)
# Relaxed stiffness after completion
motion.setStiffnesses("RArm", 0.3)
+23
View File
@@ -0,0 +1,23 @@
NAME = "Cena"
def run(motion, tts):
tts.say("You can't see me")
motion.setStiffnesses("RArm", 1.0)
names = ["RShoulderPitch", "RShoulderRoll", "RElbowYaw", "RElbowRoll", "RWristYaw", "RHand"]
# Setup for tight, face-level sweep
tight_setup = [-0.4, -0.2, 0.0, 1.2, 1.5, 1.0]
motion.angleInterpolation(names, tight_setup, [0.5]*6, True)
# The horizontal "backhand" sweep
motion.angleInterpolation(["RElbowYaw"], [0.8], 0.4, True)
motion.angleInterpolation(["RElbowYaw"], [-0.8], 0.6, True)
motion.angleInterpolation(["RElbowYaw"], [0.0], 0.4, True)
# Return to neutral
neutral = [1.5, -0.15, 1.2, 0.5, 0.0, 0.0]
motion.angleInterpolationWithSpeed(names, neutral, 0.3)
# Relaxed stiffness after completion
motion.setStiffnesses("RArm", 0.3)
+47
View File
@@ -0,0 +1,47 @@
import threading
NAME = "6-7"
def run(motion, tts):
# Threaded so the TTS call doesn't block the arm motion below.
threading.Thread(target=tts.say, args=("six seven",), daemon=True).start()
motion.setStiffnesses(["RArm", "LArm"], 1.0)
# Lock both arms up in front of the body, hands held out like a scale.
# ElbowYaw is rotated ~90° off zero here - that's what points the elbow's
# bend axis so flexing tips the forearm forward/down instead of curling
# it in across the chest. These joints don't move again until the return.
hold_names = [
"RShoulderPitch", "RShoulderRoll", "RElbowYaw", "RWristYaw", "RHand",
"LShoulderPitch", "LShoulderRoll", "LElbowYaw", "LWristYaw", "LHand"
]
hold_angles = [
0.2, -0.2, 1.4, 1.57, 0.6,
0.2, 0.2, -1.4, -1.57, 0.6
]
motion.angleInterpolationWithSpeed(hold_names, hold_angles, 0.25)
# Only the elbows seesaw now - a small tip back and forth, one arm
# rising while the other falls, like weighing something in each hand
# ("six... seven..."). Small amplitude so it stays a wobble, not a fold.
elbow_names = ["RElbowRoll", "LElbowRoll"]
r_low, r_high = 0.6, 1.0
l_low, l_high = -0.6, -1.0
elbow_angles = [
[r_low, r_high, r_low, r_high, r_low, r_high],
[l_high, l_low, l_high, l_low, l_high, l_low],
]
times = [[0.35, 0.7, 1.05, 1.4, 1.75, 2.1]] * 2
motion.angleInterpolation(elbow_names, elbow_angles, times, True)
# Return everything to neutral
neutral_names = hold_names + elbow_names
neutral = [1.5, -0.15, 1.2, 0.0, 0.0,
1.5, 0.15, -1.2, 0.0, 0.0,
0.5, -0.5]
motion.angleInterpolationWithSpeed(neutral_names, neutral, 0.3)
# Relaxed stiffness after completion
motion.setStiffnesses(["RArm", "LArm"], 0.3)
+22
View File
@@ -0,0 +1,22 @@
import os
NAME = "Gangnam Style"
# The Choregraphe project lives in a sibling folder with the same name
# as this file (minus the .py) - e.g. gestures/4_gangnam_style/ next
# to gestures/4_gangnam_style.py - containing manifest.xml and a
# GangnamStyle/behavior.xar subfolder, exactly what Choregraphe's
# "Export as package" produces.
PACKAGE_DIR = os.path.splitext(os.path.abspath(__file__))[0]
BEHAVIOR_SUBDIR = "GangnamStyle"
def run(motion, tts, teleop):
# Deploys (first run only, cached after) + runs the actual
# Choregraphe behavior on the robot - this blocks until it
# finishes, same as the old hand-coded version did.
teleop.behavior_runner.ensure_running(PACKAGE_DIR, BEHAVIOR_SUBDIR)
# The .xar moves legs/hips, unlike the old arms-only version, so
# bring him back to a known walk-ready stance afterward.
teleop.idle_crouch()
File diff suppressed because it is too large Load Diff
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
<?xml version='1.0' encoding='UTF-8'?>
<package uuid=".lastUploadedChoregrapheBehavior" version="0.0.0" installOnlyAtStartup="false">
<names>
<name lang="en_US">.lastUploadedChoregrapheBehavior</name>
<name lang="en_US-x-scrald">.lastUploadedChoregrapheBehavior</name>
</names>
<descriptionLanguages>
<language>en_US</language>
</descriptionLanguages>
<contents>
<behaviorContent path="GangnamStyle">
<nature>interactive</nature>
<permissions/>
</behaviorContent>
</contents>
</package>
+22
View File
@@ -0,0 +1,22 @@
import os
NAME = "Tai Chi"
# The Choregraphe project lives in a sibling folder with the same name
# as this file (minus the .py) - e.g. gestures/5_tai_chi/ next to
# gestures/5_tai_chi.py - containing manifest.xml and a
# behavior_1/behavior.xar subfolder, exactly what Choregraphe's
# "Export as package" produces.
PACKAGE_DIR = os.path.splitext(os.path.abspath(__file__))[0]
BEHAVIOR_SUBDIR = "behavior_1"
def run(motion, tts, teleop):
# Deploys (first run only, cached after) + runs the actual
# Choregraphe behavior on the robot - this blocks until it
# finishes, same pattern as the Gangnam Style gesture.
teleop.behavior_runner.ensure_running(PACKAGE_DIR, BEHAVIOR_SUBDIR)
# Tai chi moves legs/hips through slow stances, so bring him back
# to a known walk-ready stance afterward.
teleop.idle_crouch()
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Package name="5_tai_chi" format_version="5">
<Manifest src="manifest.xml" />
<BehaviorDescriptions>
<BehaviorDescription name="behavior" src="tai_chi" xar="behavior.xar" />
</BehaviorDescriptions>
<Dialogs />
<Resources />
<Topics />
<IgnoredPaths />
<Translations auto-fill="en_US">
<Translation name="translation_en_US" src="translations/translation_en_US.ts" language="en_US" />
</Translations>
</Package>
+18
View File
@@ -0,0 +1,18 @@
<?xml version='1.0' encoding='UTF-8'?>
<package version="0.0.0" uuid="5_tai_chi-0cc7f3" installOnlyAtStartup="false">
<names>
<name lang="en_US">Untitled</name>
</names>
<supportedLanguages>
<language>en_US</language>
</supportedLanguages>
<descriptionLanguages>
<language>en_US</language>
</descriptionLanguages>
<contents>
<behaviorContent path="tai_chi">
<nature>interactive</nature>
<permissions/>
</behaviorContent>
</contents>
</package>
+857
View File
@@ -0,0 +1,857 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ChoregrapheProject xmlns="http://www.ald.softbankrobotics.com/schema/choregraphe/project.xsd" xar_version="3">
<Box name="root" id="-1" localization="8" tooltip="Root box of Choregraphe&apos;s behavior. Highest level possible." x="0" y="0">
<bitmap>media/images/box/root.png</bitmap>
<script language="4">
<content>
<![CDATA[]]>
</content>
</script>
<Input name="onLoad" type="1" type_size="1" nature="0" inner="1" tooltip="Signal sent when diagram is loaded." id="1" />
<Input name="onStart" type="1" type_size="1" nature="2" inner="0" tooltip="Box behavior starts when a signal is received on this input." id="2" />
<Input name="onStop" type="1" type_size="1" nature="3" inner="0" tooltip="Box behavior stops when a signal is received on this input." id="3" />
<Output name="onStopped" type="1" type_size="1" nature="1" inner="0" tooltip="Signal sent when box behavior is finished." id="4" />
<Timeline enable="0">
<BehaviorLayer name="behavior_layer1">
<BehaviorKeyframe name="keyframe1" index="1">
<Diagram>
<Box name="Tai Chi Chuan" id="11" localization="8" tooltip="Arms part of Tai Chi Chuan dance." x="198" y="164">
<bitmap>media/images/box/movement/dance.png</bitmap>
<script language="4">
<content>
<![CDATA[]]>
</content>
</script>
<Input name="onLoad" type="1" type_size="1" nature="0" inner="1" tooltip="Signal sent when diagram is loaded." id="1" />
<Input name="onStart" type="1" type_size="1" nature="2" inner="0" tooltip="Box behavior starts when a signal is received on this input." id="2" />
<Input name="onStop" type="1" type_size="1" nature="3" inner="0" tooltip="Box behavior stops when a signal is received on this input." id="3" />
<Output name="onStopped" type="1" type_size="1" nature="1" inner="0" tooltip="Signal sent when box behavior is finished." id="4" />
<Timeline enable="1" fps="5" start_frame="1" end_frame="-1" size="250">
<BehaviorLayer name="LED">
<BehaviorKeyframe name="keyframe15" index="15">
<Diagram>
<Box name="Light_ConcentratedEyes" id="1" localization="8" tooltip="Set an animated gaze which expresses concentration." x="231" y="105">
<bitmap>media/images/box/interaction/LED.png</bitmap>
<script language="4">
<content>
<![CDATA[class MyClass(GeneratedClass):
def __init__(self):
try: # disable autoBind;
GeneratedClass.__init__(self, False);
except TypeError: # if NAOqi < 1.14;
GeneratedClass.__init__( self );
def onLoad(self):
self.bIsRunning = False;
self.leds = self.session().service("ALLeds")
def onUnload(self):
self.onInput_onStop(); # will stop current loop execution
def onInput_onStart(self):
#self.log( self.getName() + ": start - begin" );
if( self.bIsRunning ):
#print( self.getName() + ": already started => nothing" );
return;
self.bIsRunning = True;
self.bMustStop = False;
rDuration = 1.0;
self.leds.fadeRGB( "FaceLedsTop", 0x0000ff, rDuration, _async=True );
self.leds.fadeRGB( "FaceLedsInternal", 0x00ff00, rDuration, _async=True );
self.leds.fadeRGB( "FaceLedsBottom", 0x0000ff, rDuration, _async=True );
self.leds.fadeRGB( "FaceLedsExternal", 0x0000ff, rDuration );
while( not self.bMustStop ):
rTime=1.0
self.leds.fadeRGB( "FaceLedsInternal", 0x000000, rTime );
if( self.bMustStop ):
break;
self.leds.fadeRGB( "FaceLedsInternal", 0x00ff00, rDuration );
# end while
self.bIsRunning = False;
self.onStopped();
def onInput_onStop(self):
self.bMustStop = True; # will stop current loop execution]]>
</content>
</script>
<Input name="onLoad" type="1" type_size="1" nature="0" inner="1" tooltip="Signal sent when diagram is loaded." id="1" />
<Input name="onStart" type="1" type_size="1" nature="2" inner="0" tooltip="Box behavior starts when a signal is received on this input." id="2" />
<Input name="onStop" type="1" type_size="1" nature="3" inner="0" tooltip="Box behavior stops when a signal is received on this input." id="3" />
<Output name="onStopped" type="1" type_size="1" nature="1" inner="0" tooltip="Signal sent when box behavior is finished." id="4" />
</Box>
<Box name="Tocolor" id="2" localization="8" tooltip="tags:&#x0A;LED" x="490" y="183">
<bitmap>media/images/box/interaction/LED.png</bitmap>
<script language="4">
<content>
<![CDATA[class MyClass(GeneratedClass):
def __init__(self):
GeneratedClass.__init__(self)
def onLoad(self):
#~ puts code for box initialization here
self.leds = self.session().service("ALLeds")
def onUnload(self):
#~ puts code for box cleanup here
self.leds.reset("EarLeds")
pass
def onInput_onStart(self):
rDuration = 0.2
self.leds.fadeRGB( "BrainLeds", 0xffffff, rDuration)
self.onStopped()
def onInput_onStop(self):
self.onUnload() #~ it is usually a good idea to call onUnload of this box in a onStop method, as the code written in onUnload is used to finish the working of the box as well
pass]]>
</content>
</script>
<Input name="onLoad" type="1" type_size="1" nature="0" inner="1" tooltip="Signal sent when diagram is loaded." id="1" />
<Input name="onStart" type="1" type_size="1" nature="2" inner="0" tooltip="Box behavior starts when a signal is received on this input." id="2" />
<Input name="onStop" type="1" type_size="1" nature="3" inner="0" tooltip="Box behavior stops when a signal is received on this input." id="3" />
<Output name="onStopped" type="1" type_size="1" nature="1" inner="0" tooltip="Signal sent when box behavior is finished." id="4" />
<Resource name="Left eye leds" type="Lock" timeout="1" />
<Resource name="Right eye leds" type="Lock" timeout="1" />
</Box>
<Link inputowner="1" indexofinput="2" outputowner="0" indexofoutput="1" />
</Diagram>
</BehaviorKeyframe>
<BehaviorKeyframe name="Eye48" index="247">
<Diagram>
<Box name="Blink" id="3" localization="8" tooltip="tags:&#x0A;LED" x="339" y="161">
<bitmap>media/images/box/interaction/LED.png</bitmap>
<script language="4">
<content>
<![CDATA[class MyClass(GeneratedClass):
def __init__(self):
GeneratedClass.__init__(self)
def onLoad(self):
#~ puts code for box initialization here
self.leds = self.session().service("ALLeds")
def onUnload(self):
#~ puts code for box cleanup here
pass
def onInput_onStart(self):
rDuration = 0.05;
self.leds.fadeRGB( "FaceLed0", 0x000000, rDuration, _async=True );
self.leds.fadeRGB( "FaceLed1", 0x000000, rDuration, _async=True );
self.leds.fadeRGB( "FaceLed2", 0xffffff, rDuration, _async=True );
self.leds.fadeRGB( "FaceLed3", 0x000000, rDuration, _async=True );
self.leds.fadeRGB( "FaceLed4", 0x000000, rDuration, _async=True );
self.leds.fadeRGB( "FaceLed5", 0x000000, rDuration, _async=True );
self.leds.fadeRGB( "FaceLed6", 0xffffff, rDuration, _async=True );
self.leds.fadeRGB( "FaceLed7", 0x000000, rDuration );
time.sleep( 0.1 );
rDuration = 0.05;
self.leds.fadeRGB( "FaceLeds", 0xffffff, rDuration );
self.onStopped()
def onInput_onStop(self):
self.onUnload() #~ it is usually a good idea to call onUnload of this box in a onStop method, as the code written in onUnload is used to finish the working of the box as well
pass]]>
</content>
</script>
<Input name="onLoad" type="1" type_size="1" nature="0" inner="1" tooltip="Signal sent when diagram is loaded." id="1" />
<Input name="onStart" type="1" type_size="1" nature="2" inner="0" tooltip="Box behavior starts when a signal is received on this input." id="2" />
<Input name="onStop" type="1" type_size="1" nature="3" inner="0" tooltip="Box behavior stops when a signal is received on this input." id="3" />
<Output name="onStopped" type="1" type_size="1" nature="1" inner="0" tooltip="Signal sent when box behavior is finished." id="4" />
<Resource name="Left eye leds" type="Lock" timeout="1" />
<Resource name="Right eye leds" type="Lock" timeout="1" />
</Box>
<Link inputowner="3" indexofinput="2" outputowner="0" indexofoutput="1" />
</Diagram>
</BehaviorKeyframe>
</BehaviorLayer>
<BehaviorLayer name="music">
<BehaviorKeyframe name="keyframe12" index="12">
<Diagram>
<Box name="Play Sound" id="1" localization="8" tooltip="Play a sound file. Select the file in parameters. The format of the file can be wav or ogg (on virtual robot) and also mp3 (on a real robot).&#x0A;&#x0A;Note: There can be some delay to play ogg or mp3 (on a robot) files. We advise you to use wav if&#x0A;you want a fast play of the file." x="193" y="90">
<bitmap>media/images/box/interaction/play_music.png</bitmap>
<script language="4">
<content>
<![CDATA[class MyClass(GeneratedClass):
def __init__(self):
GeneratedClass.__init__(self, False)
def onLoad(self):
self.bIsRunning = False
def onUnload(self):
self.bIsRunning = False
def onInput_onStart(self):
self.bIsRunning = True
def onInput_onStop(self):
if( self.bIsRunning ):
self.onUnload()
self.onStopped()]]>
</content>
</script>
<Input name="onLoad" type="1" type_size="1" nature="0" inner="1" tooltip="Signal sent when Diagram is loaded." id="1" />
<Input name="onStart" type="1" type_size="1" nature="2" inner="0" tooltip="Starts the music." id="2" />
<Input name="onStop" type="1" type_size="1" nature="3" inner="0" tooltip="Stops the music." id="3" />
<Output name="onStopped" type="1" type_size="1" nature="1" inner="0" tooltip="Signal sent when box behavior is finished or stopped." id="4" />
<Parameter name="File name" inherits_from_parent="0" content_type="4" value="/sounds/mikhael-landscape-paisaje.ogg" default_value="" tooltip="Name of the file you want to play.&#x0A;&#x0A;Note: You can click on the folder icon to browse the project content or import&#x0A;new files to the project." id="5" />
<Parameter name="Begin position (s)" inherits_from_parent="0" content_type="2" value="0" default_value="0" min="0" max="600" tooltip="Position in seconds where the playing must start." id="6" />
<Parameter name="Volume (%)" inherits_from_parent="0" content_type="1" value="100" default_value="100" min="0" max="100" tooltip="Volume the file is played with." id="7" />
<Parameter name="Balance L/R" inherits_from_parent="0" content_type="2" value="0" default_value="0" min="-1" max="1" tooltip="Value which determines if the sound is played more on the robot&apos;s left or right.&#x0A;You can particularly set it to:&#x0A;- -1 to play only on the left loudspeaker.&#x0A;- 0 to play on both loudspeakers.&#x0A;- 1 to play only on the right loudspeaker." id="8" />
<Parameter name="Play in loop" inherits_from_parent="0" content_type="0" value="0" default_value="0" tooltip="This parameter allows to play the file in loop. The playing will start each time at&#x0A;the beginning of the file." id="9" />
<Timeline enable="0">
<BehaviorLayer name="behavior_layer1">
<BehaviorKeyframe name="keyframe1" index="1">
<Diagram>
<Box name="Play Sound File" id="2" localization="8" tooltip="Play the sound." x="442" y="70">
<bitmap>media/images/box/interaction/play_music.png</bitmap>
<script language="4">
<content>
<![CDATA[import time
class MyClass(GeneratedClass):
def __init__(self):
GeneratedClass.__init__(self, False)
def onLoad(self):
self.player = self.session().service('ALAudioPlayer')
self.playerStop = self.session().service('ALAudioPlayer') #Create another service as wait is blocking if audioout is remote
self.bIsRunning = False
self.ids = []
def onUnload(self):
for id in self.ids:
try:
self.playerStop.stop(id)
except:
pass
while( self.bIsRunning ):
time.sleep( 0.2 )
def onInput_onStart(self, p):
self.bIsRunning = True
try:
if (self.getParameter("Play in loop")) :
id = self.player.pCall("playFileInLoop",p,self.getParameter("Volume (%)")/100.,self.getParameter("Balance L/R"))
else :
id = self.player.pCall("playFileFromPosition",p,self.getParameter("Begin position (s)"),self.getParameter("Volume (%)")/100.,self.getParameter("Balance L/R"))
self.ids.append(id)
self.player.wait(id)
finally:
try:
self.ids.remove(id)
except:
pass
if( self.ids == [] ):
self.onStopped() # activate output of the box
self.bIsRunning = False
def onInput_onStop(self):
self.onUnload()]]>
</content>
</script>
<Input name="onLoad" type="1" type_size="1" nature="0" inner="1" tooltip="Signal sent when Diagram is loaded." id="1" />
<Input name="onStart" type="3" type_size="1" nature="2" inner="0" tooltip="Box behavior starts when a signal is received on this Input." id="2" />
<Input name="onStop" type="1" type_size="1" nature="3" inner="0" tooltip="Box behavior stops when a signal is received on this Input." id="3" />
<Output name="onStopped" type="1" type_size="1" nature="1" inner="0" tooltip="Signal sent when Box behavior is finished." id="4" />
<Parameter name="Begin position (s)" inherits_from_parent="1" content_type="2" value="0" default_value="0" min="0" max="600" tooltip="Position in seconds where the playing must start." id="5" />
<Parameter name="Volume (%)" inherits_from_parent="1" content_type="1" value="100" default_value="100" min="0" max="100" tooltip="Volume the file is played with." id="6" />
<Parameter name="Balance L/R" inherits_from_parent="1" content_type="2" value="0" default_value="0" min="-1" max="1" tooltip="Value which determines if the sound is played more on the robot&apos;s left or right.&#x0A;You can particularly set it to:&#x0A;- -1 to play only on the left loudspeaker.&#x0A;- 0 to play on both loudspeakers.&#x0A;- 1 to play only on the right loudspeaker." id="7" />
<Parameter name="Play in loop" inherits_from_parent="1" content_type="0" value="0" default_value="0" tooltip="This parameter allows to play the file in loop. The playing will start each time at&#x0A;the beginning of the file." id="8" />
</Box>
<Box name="Get Attached File" id="1" localization="8" tooltip="Use this box to choose an attached file in its parameters. The filename will be sent on&#x0A;the output when the input is stimulated." x="216" y="71">
<bitmap>media/images/box/folder.png</bitmap>
<script language="4">
<content>
<![CDATA[class MyClass(GeneratedClass):
def __init__(self):
GeneratedClass.__init__(self, False)
def onLoad(self):
pass
def onInput_onStart(self):
self.onStopped(self.behaviorAbsolutePath() + self.getParameter("File name"))]]>
</content>
</script>
<Input name="onLoad" type="1" type_size="1" nature="0" inner="1" tooltip="Signal sent when diagram is loaded." id="1" />
<Input name="onStart" type="1" type_size="1" nature="1" inner="0" tooltip="To send the filepath on the output." id="2" />
<Output name="onStopped" type="3" type_size="1" nature="2" inner="0" tooltip="The filepath of the selected resource file." id="3" />
<Parameter name="File name" inherits_from_parent="1" content_type="4" value="" default_value="" tooltip="Name of the file which is going to be sent on the box output.&#x0A;&#x0A;Note: You can click on the folder icon to browse the project content or import&#x0A;new files to the project." id="4" />
</Box>
<Link inputowner="0" indexofinput="4" outputowner="2" indexofoutput="4" />
<Link inputowner="1" indexofinput="2" outputowner="0" indexofoutput="2" />
<Link inputowner="2" indexofinput="2" outputowner="1" indexofoutput="3" />
</Diagram>
</BehaviorKeyframe>
</BehaviorLayer>
</Timeline>
<Resource name="Audio player" type="Lock" timeout="0" />
</Box>
<Link inputowner="1" indexofinput="2" outputowner="0" indexofoutput="1" />
</Diagram>
</BehaviorKeyframe>
</BehaviorLayer>
<ActuatorList model="nao">
<ActuatorCurve name="value" actuator="HeadYaw" mute="0" unit="0">
<Key frame="15" value="0" />
<Key frame="25" value="4.82967e-06" />
<Key frame="35" value="4.82968e-06" />
<Key frame="45" value="4.82968e-06" />
<Key frame="55" value="-2.73208e-05" />
<Key frame="65" value="18" />
<Key frame="75" value="-17" />
<Key frame="85" value="-68" />
<Key frame="95" value="-16" />
<Key frame="105" value="12" />
<Key frame="118" value="90" />
<Key frame="131" value="12" />
<Key frame="142" value="8" />
<Key frame="152" value="0" />
<Key frame="162" value="-8" />
<Key frame="172" value="16" />
<Key frame="185" value="-12" />
<Key frame="198" value="-90" />
<Key frame="211" value="-12" />
<Key frame="222" value="-8" />
<Key frame="231" value="0" />
<Key frame="250" value="0" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="HeadPitch" mute="0" unit="0">
<Key frame="15" value="0" />
<Key frame="25" value="5.12931e-06" />
<Key frame="35" value="-2.73208e-05" />
<Key frame="45" value="5.0962e-06" />
<Key frame="55" value="6.01466e-06" />
<Key frame="65" value="19" />
<Key frame="75" value="18" />
<Key frame="85" value="5.26559e-06" />
<Key frame="95" value="-19" />
<Key frame="105" value="8" />
<Key frame="118" value="-5" />
<Key frame="131" value="8" />
<Key frame="142" value="22" />
<Key frame="152" value="32" />
<Key frame="162" value="22" />
<Key frame="172" value="-19" />
<Key frame="185" value="8" />
<Key frame="198" value="-5" />
<Key frame="211" value="8" />
<Key frame="222" value="22" />
<Key frame="231" value="0" />
<Key frame="250" value="-10.0411" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LShoulderPitch" mute="0" unit="0">
<Key frame="15" value="90" />
<Key frame="25" value="110" />
<Key frame="35" value="120" />
<Key frame="45" value="90" />
<Key frame="55" value="0" />
<Key frame="65" value="21" />
<Key frame="75" value="20" />
<Key frame="85" value="11" />
<Key frame="95" value="-46" />
<Key frame="105" value="-10" />
<Key frame="118" value="-17" />
<Key frame="131" value="-10" />
<Key frame="142" value="30" />
<Key frame="152" value="27" />
<Key frame="162" value="19" />
<Key frame="172" value="-27" />
<Key frame="185" value="4" />
<Key frame="198" value="-4" />
<Key frame="211" value="4" />
<Key frame="222" value="19" />
<Key frame="231" value="97" />
<Key frame="250" value="83.0164" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LShoulderRoll" mute="0" unit="0">
<Key frame="15" value="10" />
<Key frame="25" value="20" />
<Key frame="35" value="10" />
<Key frame="45" value="10" />
<Key frame="55" value="10" />
<Key frame="65" value="40" />
<Key frame="75" value="0" />
<Key frame="85" value="5" />
<Key frame="95" value="10" />
<Key frame="105" value="23" />
<Key frame="118" value="66" />
<Key frame="131" value="23" />
<Key frame="142" value="23" />
<Key frame="152" value="10" />
<Key frame="162" value="0" />
<Key frame="172" value="23" />
<Key frame="185" value="0" />
<Key frame="198" value="0" />
<Key frame="211" value="0" />
<Key frame="222" value="12" />
<Key frame="231" value="54" />
<Key frame="250" value="12.7723" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LElbowYaw" mute="0" unit="0">
<Key frame="15" value="-90" />
<Key frame="25" value="-90" />
<Key frame="35" value="-90" />
<Key frame="45" value="-90" />
<Key frame="55" value="-90" />
<Key frame="65" value="-22" />
<Key frame="75" value="0" />
<Key frame="85" value="0" />
<Key frame="95" value="0" />
<Key frame="105" value="0" />
<Key frame="118" value="0" />
<Key frame="131" value="0" />
<Key frame="142" value="0" />
<Key frame="152" value="12" />
<Key frame="162" value="11" />
<Key frame="172" value="-24" />
<Key frame="185" value="-24" />
<Key frame="198" value="-5" />
<Key frame="211" value="-24" />
<Key frame="222" value="11" />
<Key frame="227" value="-21.7" />
<Key frame="231" value="-14" />
<Key frame="250" value="-68.8135" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LElbowRoll" mute="0" unit="0">
<Key frame="15" value="0" />
<Key frame="25" value="-40" />
<Key frame="35" value="-60" />
<Key frame="45" value="0" />
<Key frame="55" value="0" />
<Key frame="65" value="-95" />
<Key frame="75" value="-55" />
<Key frame="85" value="-85" />
<Key frame="95" value="-58" />
<Key frame="105" value="-58" />
<Key frame="118" value="0" />
<Key frame="131" value="-58" />
<Key frame="142" value="-58" />
<Key frame="152" value="-51" />
<Key frame="162" value="-49" />
<Key frame="172" value="-64" />
<Key frame="185" value="-49" />
<Key frame="198" value="-72" />
<Key frame="211" value="-49" />
<Key frame="222" value="-49" />
<Key frame="227" value="-57" />
<Key frame="231" value="-81.4" />
<Key frame="250" value="-23.6757" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LWristYaw" mute="0" unit="0">
<Key frame="15" value="-88" />
<Key frame="250" value="5.78932" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LHand" mute="0" unit="1">
<Key frame="15" value="0" />
<Key frame="250" value="0.291787" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RShoulderPitch" mute="0" unit="0">
<Key frame="15" value="90" />
<Key frame="25" value="110" />
<Key frame="35" value="120" />
<Key frame="45" value="90" />
<Key frame="55" value="0" />
<Key frame="65" value="10" />
<Key frame="75" value="35" />
<Key frame="85" value="60" />
<Key frame="95" value="-27" />
<Key frame="105" value="4" />
<Key frame="118" value="-4" />
<Key frame="131" value="4" />
<Key frame="142" value="19" />
<Key frame="152" value="27" />
<Key frame="162" value="30" />
<Key frame="172" value="-46" />
<Key frame="185" value="-10" />
<Key frame="198" value="-17" />
<Key frame="211" value="-10" />
<Key frame="222" value="30" />
<Key frame="231" value="97" />
<Key frame="250" value="82.7438" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RShoulderRoll" mute="0" unit="0">
<Key frame="15" value="-10" />
<Key frame="25" value="-10" />
<Key frame="35" value="-20" />
<Key frame="45" value="-10" />
<Key frame="55" value="-9.99896" />
<Key frame="65" value="-4" />
<Key frame="75" value="-48" />
<Key frame="85" value="-87" />
<Key frame="95" value="-23" />
<Key frame="105" value="0" />
<Key frame="118" value="0" />
<Key frame="131" value="0" />
<Key frame="142" value="0" />
<Key frame="152" value="-10" />
<Key frame="162" value="-23" />
<Key frame="172" value="-10" />
<Key frame="185" value="-23" />
<Key frame="198" value="-66" />
<Key frame="211" value="-23" />
<Key frame="222" value="-32" />
<Key frame="231" value="-54" />
<Key frame="250" value="-12.6315" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RElbowYaw" mute="0" unit="0">
<Key frame="15" value="90" />
<Key frame="25" value="90" />
<Key frame="35" value="90" />
<Key frame="45" value="90" />
<Key frame="55" value="90" />
<Key frame="65" value="11" />
<Key frame="75" value="20" />
<Key frame="85" value="90" />
<Key frame="95" value="24" />
<Key frame="105" value="24" />
<Key frame="118" value="5" />
<Key frame="131" value="24" />
<Key frame="142" value="-11" />
<Key frame="152" value="-12" />
<Key frame="162" value="0" />
<Key frame="172" value="0" />
<Key frame="185" value="0" />
<Key frame="198" value="0" />
<Key frame="211" value="0" />
<Key frame="222" value="0" />
<Key frame="227" value="19.6" />
<Key frame="231" value="14" />
<Key frame="250" value="68.8565" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RElbowRoll" mute="0" unit="0">
<Key frame="15" value="0" />
<Key frame="25" value="40" />
<Key frame="35" value="60" />
<Key frame="45" value="1.47493e-05" />
<Key frame="55" value="0" />
<Key frame="65" value="71" />
<Key frame="75" value="94" />
<Key frame="85" value="4" />
<Key frame="95" value="64" />
<Key frame="105" value="49" />
<Key frame="118" value="72" />
<Key frame="131" value="49" />
<Key frame="142" value="49" />
<Key frame="152" value="51" />
<Key frame="162" value="58" />
<Key frame="172" value="58" />
<Key frame="185" value="58" />
<Key frame="198" value="2" />
<Key frame="211" value="58" />
<Key frame="222" value="58" />
<Key frame="227" value="64.9" />
<Key frame="231" value="78.3" />
<Key frame="250" value="23.387" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RWristYaw" mute="0" unit="0">
<Key frame="15" value="88" />
<Key frame="250" value="6.23278" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RHand" mute="0" unit="1">
<Key frame="15" value="0" />
<Key frame="250" value="0.301984" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LHipYawPitch" mute="0" unit="0">
<Key frame="15" value="-6.2" />
<Key frame="25" value="-6.9" />
<Key frame="35" value="-6.94104" />
<Key frame="45" value="-7.04938" />
<Key frame="55" value="-8.2" />
<Key frame="65" value="-9.6" />
<Key frame="75" value="-5.7" />
<Key frame="85" value="0" />
<Key frame="95" value="0" />
<Key frame="105" value="0" />
<Key frame="118" value="0" />
<Key frame="131" value="0" />
<Key frame="142" value="-0.566539" />
<Key frame="152" value="0" />
<Key frame="162" value="0" />
<Key frame="172" value="0" />
<Key frame="185" value="0" />
<Key frame="198" value="0" />
<Key frame="211" value="0" />
<Key frame="222" value="-3.9" />
<Key frame="231" value="0" />
<Key frame="250" value="-9.74084" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LHipRoll" mute="0" unit="0">
<Key frame="15" value="-3" />
<Key frame="25" value="-7" />
<Key frame="35" value="-11.4235" />
<Key frame="45" value="5.96288" />
<Key frame="55" value="6" />
<Key frame="65" value="-10" />
<Key frame="75" value="10" />
<Key frame="85" value="24.1" />
<Key frame="95" value="30.3" />
<Key frame="105" value="28.7" />
<Key frame="118" value="32.4" />
<Key frame="131" value="28.7" />
<Key frame="142" value="19.9829" />
<Key frame="152" value="0" />
<Key frame="162" value="-15" />
<Key frame="167" value="14.4" />
<Key frame="172" value="15" />
<Key frame="185" value="8" />
<Key frame="198" value="40" />
<Key frame="211" value="8" />
<Key frame="222" value="-15" />
<Key frame="231" value="0" />
<Key frame="250" value="6.82441" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LHipPitch" mute="0" unit="0">
<Key frame="15" value="0" />
<Key frame="25" value="0" />
<Key frame="35" value="-19.8395" />
<Key frame="45" value="-0.0382666" />
<Key frame="55" value="0" />
<Key frame="65" value="-40" />
<Key frame="75" value="-35" />
<Key frame="85" value="-60" />
<Key frame="95" value="-60" />
<Key frame="105" value="-60" />
<Key frame="118" value="-60" />
<Key frame="131" value="-60" />
<Key frame="142" value="-59.6787" />
<Key frame="152" value="-50" />
<Key frame="162" value="-42.5" />
<Key frame="172" value="-7" />
<Key frame="185" value="-50" />
<Key frame="198" value="0" />
<Key frame="211" value="-50" />
<Key frame="222" value="-37.5" />
<Key frame="231" value="-60" />
<Key frame="250" value="7.30057" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LKneePitch" mute="0" unit="0">
<Key frame="15" value="0" />
<Key frame="25" value="0" />
<Key frame="35" value="42.8009" />
<Key frame="45" value="0.0951225" />
<Key frame="55" value="-5.69182e-06" />
<Key frame="65" value="80" />
<Key frame="75" value="70" />
<Key frame="85" value="120" />
<Key frame="95" value="120" />
<Key frame="105" value="120" />
<Key frame="118" value="120" />
<Key frame="131" value="120" />
<Key frame="142" value="120.887" />
<Key frame="152" value="100" />
<Key frame="162" value="85" />
<Key frame="172" value="7" />
<Key frame="185" value="100" />
<Key frame="198" value="0" />
<Key frame="211" value="100" />
<Key frame="222" value="75" />
<Key frame="231" value="120" />
<Key frame="250" value="-5.09006" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LAnklePitch" mute="0" unit="0">
<Key frame="15" value="5.75265e-06" />
<Key frame="25" value="0" />
<Key frame="35" value="-22.1511" />
<Key frame="45" value="-0.569877" />
<Key frame="55" value="0" />
<Key frame="65" value="-37.1" />
<Key frame="75" value="-35" />
<Key frame="85" value="-60" />
<Key frame="95" value="-60" />
<Key frame="105" value="-60" />
<Key frame="118" value="-60" />
<Key frame="131" value="-60" />
<Key frame="142" value="-59.6787" />
<Key frame="152" value="-50" />
<Key frame="162" value="-42.5" />
<Key frame="172" value="0" />
<Key frame="185" value="5.75265e-06" />
<Key frame="198" value="30" />
<Key frame="211" value="5.75265e-06" />
<Key frame="217" value="-31.8" />
<Key frame="222" value="-37.5" />
<Key frame="231" value="-60" />
<Key frame="250" value="5.00876" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="LAnkleRoll" mute="0" unit="0">
<Key frame="15" value="3" />
<Key frame="25" value="7" />
<Key frame="35" value="9.83048" />
<Key frame="45" value="-6.94104" />
<Key frame="55" value="-6" />
<Key frame="65" value="10" />
<Key frame="75" value="-15" />
<Key frame="85" value="3.6" />
<Key frame="95" value="4.2" />
<Key frame="105" value="0.5" />
<Key frame="118" value="1.8" />
<Key frame="131" value="0.5" />
<Key frame="142" value="6.97225" />
<Key frame="152" value="0" />
<Key frame="162" value="-13.8" />
<Key frame="167" value="-31.9" />
<Key frame="172" value="-24.3" />
<Key frame="185" value="-20" />
<Key frame="198" value="0" />
<Key frame="211" value="-20" />
<Key frame="222" value="-17.9" />
<Key frame="231" value="0" />
<Key frame="250" value="-6.34798" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RHipRoll" mute="0" unit="0">
<Key frame="15" value="3" />
<Key frame="25" value="-7" />
<Key frame="35" value="-24.8982" />
<Key frame="45" value="-6.04821" />
<Key frame="55" value="-6" />
<Key frame="65" value="-20" />
<Key frame="75" value="-45" />
<Key frame="85" value="-31" />
<Key frame="95" value="-12.7" />
<Key frame="105" value="-25.6" />
<Key frame="118" value="-40" />
<Key frame="131" value="-25.6" />
<Key frame="142" value="15.2077" />
<Key frame="152" value="0" />
<Key frame="162" value="-20" />
<Key frame="172" value="-30.9" />
<Key frame="185" value="-29.3" />
<Key frame="198" value="-34.2" />
<Key frame="211" value="-35" />
<Key frame="222" value="-30.5" />
<Key frame="231" value="0" />
<Key frame="250" value="-6.82407" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RHipPitch" mute="0" unit="0">
<Key frame="15" value="0" />
<Key frame="25" value="0" />
<Key frame="35" value="0.134711" />
<Key frame="45" value="0.21464" />
<Key frame="55" value="0" />
<Key frame="65" value="-40" />
<Key frame="75" value="-10" />
<Key frame="85" value="-6" />
<Key frame="95" value="-7" />
<Key frame="105" value="-50" />
<Key frame="118" value="0" />
<Key frame="131" value="-50" />
<Key frame="142" value="-45.3545" />
<Key frame="152" value="-50" />
<Key frame="162" value="-60" />
<Key frame="172" value="-60" />
<Key frame="185" value="-60" />
<Key frame="198" value="-60" />
<Key frame="211" value="-60" />
<Key frame="222" value="-60" />
<Key frame="231" value="-60" />
<Key frame="250" value="7.30058" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RKneePitch" mute="0" unit="0">
<Key frame="15" value="0" />
<Key frame="25" value="0" />
<Key frame="35" value="0.178188" />
<Key frame="45" value="-0.410883" />
<Key frame="55" value="0" />
<Key frame="65" value="80" />
<Key frame="75" value="20" />
<Key frame="85" value="7" />
<Key frame="95" value="7" />
<Key frame="105" value="100" />
<Key frame="118" value="0" />
<Key frame="131" value="100" />
<Key frame="142" value="90.0035" />
<Key frame="152" value="100" />
<Key frame="162" value="120" />
<Key frame="172" value="120" />
<Key frame="185" value="120" />
<Key frame="198" value="120" />
<Key frame="211" value="120" />
<Key frame="222" value="120" />
<Key frame="231" value="120" />
<Key frame="250" value="-5.26102" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RAnklePitch" mute="0" unit="0">
<Key frame="15" value="5.75266e-06" />
<Key frame="25" value="0" />
<Key frame="35" value="0.266079" />
<Key frame="45" value="-0.349163" />
<Key frame="55" value="0" />
<Key frame="65" value="-40" />
<Key frame="75" value="-10" />
<Key frame="85" value="0" />
<Key frame="95" value="0" />
<Key frame="105" value="5.75266e-06" />
<Key frame="118" value="30" />
<Key frame="131" value="5.75266e-06" />
<Key frame="142" value="-45.8771" />
<Key frame="152" value="-50" />
<Key frame="162" value="-60" />
<Key frame="172" value="-60" />
<Key frame="185" value="-60" />
<Key frame="198" value="-60" />
<Key frame="211" value="-60" />
<Key frame="222" value="-60" />
<Key frame="231" value="-60" />
<Key frame="250" value="5.00876" />
</ActuatorCurve>
<ActuatorCurve name="value" actuator="RAnkleRoll" mute="0" unit="0">
<Key frame="15" value="-3" />
<Key frame="25" value="7.5" />
<Key frame="35" value="22.6325" />
<Key frame="45" value="6.68218" />
<Key frame="55" value="6" />
<Key frame="65" value="16.9" />
<Key frame="75" value="35.6" />
<Key frame="85" value="45" />
<Key frame="95" value="42.6" />
<Key frame="105" value="25" />
<Key frame="118" value="0" />
<Key frame="131" value="25" />
<Key frame="142" value="9.83317" />
<Key frame="152" value="0" />
<Key frame="162" value="-10" />
<Key frame="172" value="-2.41531" />
<Key frame="185" value="-3.18747" />
<Key frame="198" value="-0.718272" />
<Key frame="211" value="-0.558977" />
<Key frame="222" value="-1.55043" />
<Key frame="231" value="0" />
<Key frame="250" value="6.34774" />
</ActuatorCurve>
<ActuatorCurve name="RHipYawPitch (RHipYawPitch)" actuator="RHipYawPitch" mute="0" unit="0">
<Key frame="15" value="-6.2" />
<Key frame="25" value="-6.9" />
<Key frame="35" value="-7.55628" />
<Key frame="45" value="-7.04938" />
<Key frame="55" value="-8.2" />
<Key frame="65" value="-9.6" />
<Key frame="142" value="0.178188" />
<Key frame="250" value="-9.74084" />
</ActuatorCurve>
</ActuatorList>
</Timeline>
<Resource name="Standing" type="Lock" timeout="1" />
<Resource name="All motors" type="Lock" timeout="1" />
</Box>
<Link inputowner="11" indexofinput="2" outputowner="0" indexofoutput="2" />
<Link inputowner="0" indexofinput="4" outputowner="11" indexofoutput="4" />
</Diagram>
</BehaviorKeyframe>
</BehaviorLayer>
</Timeline>
</Box>
</ChoregrapheProject>
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US"/>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+185 -51
View File
@@ -3,8 +3,19 @@
nao_video_server.py - RUNS ON THE ROBOT, under its own Python 2.7. nao_video_server.py - RUNS ON THE ROBOT, under its own Python 2.7.
Opens a qi session to NAOqi over localhost (zero network hop - it's Opens a qi session to NAOqi over localhost (zero network hop - it's
the same box) and serves ONE persistent, length-framed TCP stream the same box) and serves TWO persistent, length-framed TCP streams -
carrying both compressed video (JPEG) and compressed audio (mu-law). one for compressed video (JPEG), one for compressed audio (mu-law) -
each on its own port/connection. They used to share one connection,
but a single big video frame mid-send could block audio behind it for
its whole transfer time, which is a bad tradeoff for something as
latency-sensitive as audio. Splitting them means a slow video frame
can never delay audio.
The audio connection is bidirectional: this side keeps writing the
robot's own mic (TYPE_AUDIO) to the client, while a reader thread on
the same socket listens for TYPE_TALK chunks pushed the other way
(push-to-talk audio from the client's mic) and plays them on the
robot's speaker via TalkPlayer. Video stays one-way, server->client.
Why not HTTP: the phone tunnel only forwards specific TCP ports, so Why not HTTP: the phone tunnel only forwards specific TCP ports, so
this still rides over TCP - but a 5-byte binary header has none of this still rides over TCP - but a 5-byte binary header has none of
@@ -18,18 +29,21 @@ whole time. Subscribing to ALAudioDevice locally (same zero-hop trick
as the camera) means only mu-law-compressed bytes ever cross the as the camera) means only mu-law-compressed bytes ever cross the
tunnel, half the size of raw PCM. tunnel, half the size of raw PCM.
Wire format (all integers big-endian, via `struct`): Wire format, identical on both connections (all integers big-endian,
1 byte type 0x00 HELLO 0x01 VIDEO 0x02 AUDIO via `struct`):
1 byte type 0x00 HELLO 0x01 VIDEO 0x02 AUDIO 0x03 TALK
4 bytes length 4 bytes length
N bytes payload N bytes payload
HELLO payload: JSON {"rate": 16000, "channels": 1} HELLO payload: JSON, informational only
VIDEO payload: JPEG bytes VIDEO payload: JPEG bytes (video connection, server->client only)
AUDIO payload: mu-law encoded 8-bit samples (mono) AUDIO payload: mu-law 8-bit samples, mono (audio connection, server->client - robot's mic)
TALK payload: mu-law 8-bit samples, mono (audio connection, client->server - push-to-talk)
Under load (slow/lossy tunnel), video quality and frame rate step Under load (slow/lossy tunnel), video quality and frame rate step
down automatically (see QUALITY_TIERS) and recover once sends are down automatically (see QUALITY_TIERS) and recover once sends are
fast again. Audio is prioritized over video each pass, since gaps in fast again - and since encoding is throttled to match, this also cuts
audio are far more noticeable than a slightly stale picture. CPU use on a congested link instead of just wasting it on frames that
would've been discarded anyway.
Needs the SDK's own env vars, since the bindings aren't on a login Needs the SDK's own env vars, since the bindings aren't on a login
shell's default path: shell's default path:
@@ -45,6 +59,7 @@ import collections
import json import json
import socket import socket
import struct import struct
import subprocess
import threading import threading
import time import time
@@ -54,7 +69,9 @@ import qi
# ---- tunables ---- # ---- tunables ----
NAOQI_PORT = 9561 # local qi session port (confirmed working) NAOQI_PORT = 9561 # local qi session port (confirmed working)
TCP_PORT = 8000 # forward this one port through the phone tunnel VIDEO_PORT = 8000 # forward both of these through the phone tunnel
AUDIO_PORT = 8001 # kept on its own connection so a slow video
# frame can never block/delay audio delivery
CAMERA_RESOLUTION = 1 # 0=kQQVGA(160x120) 1=kQVGA(320x240) 2=kVGA(640x480) CAMERA_RESOLUTION = 1 # 0=kQQVGA(160x120) 1=kQVGA(320x240) 2=kVGA(640x480)
CAMERA_COLORSPACE = 11 # kRGBColorSpace CAMERA_COLORSPACE = 11 # kRGBColorSpace
CAMERA_FPS = 20 CAMERA_FPS = 20
@@ -78,6 +95,7 @@ FAST_STREAK_TO_RECOVER = 30 # consecutive quick sends before stepping back
TYPE_HELLO = 0x00 TYPE_HELLO = 0x00
TYPE_VIDEO = 0x01 TYPE_VIDEO = 0x01
TYPE_AUDIO = 0x02 TYPE_AUDIO = 0x02
TYPE_TALK = 0x03
def encode_ulaw(pcm16): def encode_ulaw(pcm16):
@@ -90,6 +108,17 @@ def encode_ulaw(pcm16):
return (((y + 1.0) / 2.0) * 255.0).round().astype(np.uint8) return (((y + 1.0) / 2.0) * 255.0).round().astype(np.uint8)
def decode_ulaw(q):
"""uint8 mu-law samples -> int16 PCM. Inverse of encode_ulaw()
above, used to play back incoming push-to-talk (TYPE_TALK) chunks.
Must match naowalk.py's encode_ulaw() exactly, or push-to-talk
just produces noise on the robot's speaker."""
mu = 255.0
y = (q.astype(np.float64) / 255.0) * 2.0 - 1.0
x = np.sign(y) * (1.0 / mu) * (np.power(1.0 + mu, np.abs(y)) - 1.0)
return np.clip(x * 32768.0, -32768, 32767).astype(np.int16)
class FrameGrabber(object): class FrameGrabber(object):
"""Background thread: keeps the single most recent JPEG-encoded """Background thread: keeps the single most recent JPEG-encoded
frame available, at whatever quality tier the link currently frame available, at whatever quality tier the link currently
@@ -209,66 +238,165 @@ class MicGrabber(object):
pass pass
class MediaServer(object): class TalkPlayer(object):
"""Accepts one client at a time and interleaves video + audio onto """Decodes incoming TYPE_TALK chunks (push-to-talk audio pushed
its socket. Audio is drained first every pass since gaps in it are from the client) and plays them on the robot's speaker.
much more noticeable than the picture being a beat stale."""
def __init__(self, grabber, mic): Uses a persistent `aplay` subprocess fed raw PCM over its stdin -
self.grabber = grabber the same playback mechanism the old SSH `nc -l | aplay` relay
self.mic = mic used, just sourced from decoded network chunks instead of a raw
pipe. The process is kept open across calls (silence between
presses is fine); it's only recreated if it exited or hasn't
been started yet."""
def serve_forever(self, host, port): def __init__(self, rate=AUDIO_RATE):
self.rate = rate
self._proc = None
self._lock = threading.Lock()
def _ensure_proc(self):
if self._proc is None or self._proc.poll() is not None:
self._proc = subprocess.Popen(
["aplay", "-q", "-t", "raw", "-f", "S16_LE",
"-c", str(AUDIO_CHANNELS), "-r", str(self.rate)],
stdin=subprocess.PIPE)
def play(self, ulaw_payload):
pcm = decode_ulaw(np.frombuffer(ulaw_payload, dtype=np.uint8))
with self._lock:
try:
self._ensure_proc()
self._proc.stdin.write(pcm.tobytes())
self._proc.stdin.flush()
except Exception as e:
print("talk playback error: %s" % e)
self._proc = None
def close(self):
with self._lock:
if self._proc is not None:
try:
self._proc.stdin.close()
self._proc.terminate()
except Exception:
pass
self._proc = None
def _recv_exact(sock, n):
"""Read exactly n bytes from a TCP socket (recv() can return short
reads/chunks at any point - never assume one call gives you n).
Mirror of naowalk.py's _recv_exact()."""
buf = b""
while len(buf) < n:
chunk = sock.recv(n - len(buf))
if not chunk:
raise IOError("media relay connection closed")
buf += chunk
return buf
def _send(conn, msg_type, payload):
conn.sendall(struct.pack(">BI", msg_type, len(payload)))
conn.sendall(payload)
def serve_video(grabber, host, port):
"""Video gets its own connection/thread - a slow sendall() here
(a big JPEG frame over a bad link) must never be able to delay
audio, which used to share this same socket and paid for it in
latency."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port)) sock.bind((host, port))
sock.listen(1) sock.listen(1)
print("media server listening on :%d" % port) print("video server listening on :%d" % port)
while True: while True:
conn, addr = sock.accept() conn, addr = sock.accept()
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
conn.settimeout(5.0) conn.settimeout(5.0)
print("client connected: %s" % (addr,)) print("video client connected: %s" % (addr,))
last_jpeg = None
last_send = 0.0
try: try:
self._serve_client(conn) _send(conn, TYPE_HELLO, json.dumps({"stream": "video"}).encode("utf-8"))
while True:
jpeg = grabber.latest()
_, min_interval = grabber.quality_and_interval()
now = time.time()
if jpeg is not None and jpeg is not last_jpeg and now - last_send >= min_interval:
t0 = time.time()
_send(conn, TYPE_VIDEO, jpeg)
grabber.report_send_time(time.time() - t0)
last_jpeg = jpeg
last_send = now
else:
time.sleep(0.01)
except Exception as e: except Exception as e:
print("client disconnected (%s)" % e) print("video client disconnected (%s)" % e)
finally: finally:
try: try:
conn.close() conn.close()
except Exception: except Exception:
pass pass
def _serve_client(self, conn):
self._send(conn, TYPE_HELLO, def _talk_reader(conn, player):
json.dumps({"rate": AUDIO_RATE, "channels": AUDIO_CHANNELS}).encode("utf-8")) """Runs alongside the audio writer loop on the same connection -
last_jpeg = None the audio socket is bidirectional now, so this thread's the read
last_video_send = 0.0 half: it pulls TYPE_TALK frames pushed from the client (push-to-
talk) and hands each decoded chunk to TalkPlayer. conn has a
socket timeout set by the caller, so a quiet stretch between
button presses just means occasional socket.timeout - not a
disconnect - and the loop keeps going."""
try:
while True: while True:
did_something = False try:
header = _recv_exact(conn, 5)
except socket.timeout:
continue
msg_type, length = struct.unpack(">BI", header)
payload = _recv_exact(conn, length)
if msg_type == TYPE_TALK:
player.play(payload)
except Exception as e:
print("talk reader stopped (%s)" % e)
if self.mic is not None:
for chunk in self.mic.pop_all():
self._send(conn, TYPE_AUDIO, chunk)
did_something = True
jpeg = self.grabber.latest() def serve_audio(mic, player, host, port):
_, min_interval = self.grabber.quality_and_interval() """Audio's own connection/thread. Checked eagerly (short sleep,
now = time.time() not paced like video) since gaps are far more noticeable than a
if jpeg is not None and jpeg is not last_jpeg and now - last_video_send >= min_interval: video frame being a beat stale."""
t0 = time.time() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._send(conn, TYPE_VIDEO, jpeg) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.grabber.report_send_time(time.time() - t0) sock.bind((host, port))
last_jpeg = jpeg sock.listen(1)
last_video_send = now print("audio server listening on :%d" % port)
did_something = True while True:
conn, addr = sock.accept()
if not did_something: conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
time.sleep(0.01) conn.settimeout(5.0)
print("audio client connected: %s" % (addr,))
def _send(self, conn, msg_type, payload): reader_thread = threading.Thread(target=_talk_reader, args=(conn, player))
conn.sendall(struct.pack(">BI", msg_type, len(payload))) reader_thread.daemon = True
conn.sendall(payload) reader_thread.start()
try:
_send(conn, TYPE_HELLO,
json.dumps({"rate": AUDIO_RATE, "channels": AUDIO_CHANNELS}).encode("utf-8"))
while True:
chunks = mic.pop_all() if mic is not None else []
if chunks:
for chunk in chunks:
_send(conn, TYPE_AUDIO, chunk)
else:
time.sleep(0.005)
except Exception as e:
print("audio client disconnected (%s)" % e)
finally:
try:
conn.close()
except Exception:
pass
def main(): def main():
@@ -284,15 +412,21 @@ def main():
except Exception as e: except Exception as e:
print("mic capture unavailable (%s) - video only" % e) print("mic capture unavailable (%s) - video only" % e)
server = MediaServer(grabber, mic) video_thread = threading.Thread(target=serve_video, args=(grabber, "0.0.0.0", VIDEO_PORT))
video_thread.daemon = True
video_thread.start()
player = TalkPlayer()
try: try:
server.serve_forever("0.0.0.0", TCP_PORT) serve_audio(mic, player, "0.0.0.0", AUDIO_PORT)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
finally: finally:
grabber.close() grabber.close()
if mic: if mic:
mic.close() mic.close()
player.close()
if __name__ == "__main__": if __name__ == "__main__":
+607 -327
View File
File diff suppressed because it is too large Load Diff
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
nmcli c up naorobotwifi\ \(WAP\) && nmcli c up BC-VR\ \(USBWIFI\)
Submodule other-repos/nao-dances added at 5cae0354f5
+2 -1
View File
@@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
pactl set-source-volume alsa_input.pci-0000_00_1b.0.analog-stereo 32% pactl set-source-volume alsa_input.pci-0000_00_1b.0.analog-stereo 32%
#python3.11 naowalk.py --ip spike.local --port 9561 --mic-gain 9 --video-scale 3.0 #python3.11 naowalk.py --ip spike.local --port 9561 --mic-gain 9 --video-scale 3.0
python3.11 naowalk.py --ip 127.0.0.1 --port 9561 --mic-gain 9 --video-scale 3.0 --nao-ssh-port 2222 --media-relay 127.0.0.1:8000 #python3.11 naowalk.py --ip 127.0.0.1 --port 9561 --mic-gain 9 --video-scale 3.0 --nao-ssh-port 2222 --media-relay 127.0.0.1:8000
python3.11 naowalk.py --ip spike.local --port 9561 --mic-gain 9 --video-scale 3.0 --nao-ssh-port 22 --media-relay spike.local:8000