Compare commits

6 Commits

29 changed files with 4426 additions and 390 deletions
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.
+433
View File
@@ -0,0 +1,433 @@
# -*- coding: utf-8 -*-
"""
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
the same box) and serves TWO persistent, length-framed TCP streams -
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
this still rides over TCP - but a 5-byte binary header has none of
HTTP's chunked/multipart-boundary parsing overhead, and it's trivial
to keep alive and reconnect on a flaky link.
Why mic capture lives here now: it used to be shipped raw (16-bit PCM,
uncompressed) straight through NAOqi's own pub/sub over the qi RPC
session the client holds - i.e. across the tunnel, uncompressed, the
whole time. Subscribing to ALAudioDevice locally (same zero-hop trick
as the camera) means only mu-law-compressed bytes ever cross the
tunnel, half the size of raw PCM.
Wire format, identical on both connections (all integers big-endian,
via `struct`):
1 byte type 0x00 HELLO 0x01 VIDEO 0x02 AUDIO 0x03 TALK
4 bytes length
N bytes payload
HELLO payload: JSON, informational only
VIDEO payload: JPEG bytes (video connection, server->client only)
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
down automatically (see QUALITY_TIERS) and recover once sends are
fast again - and since encoding is throttled to match, this also cuts
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
shell's default path:
export PYTHONPATH=/opt/aldebaran/lib/python2.7/site-packages
export LD_LIBRARY_PATH=/opt/aldebaran/lib
python2 nao_video_server.py
Stdlib only (BaseHTTPServer/Flask are gone entirely now) plus qi,
cv2, and numpy, which are already confirmed present on the robot.
"""
import collections
import json
import socket
import struct
import subprocess
import threading
import time
import cv2
import numpy as np
import qi
# ---- tunables ----
NAOQI_PORT = 9561 # local qi session port (confirmed working)
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_COLORSPACE = 11 # kRGBColorSpace
CAMERA_FPS = 20
AUDIO_RATE = 16000
AUDIO_CHANNELS = 1
MIC_CHANNEL = 1 # 1=left 2=right 3=front 4=rear (ALAudioDevice channel index)
AUDIO_QUEUE_MAX = 60 # chunks buffered before we start dropping the oldest
# (jpeg_quality, min_seconds_between_frames) - best link first, worst last.
# The server steps down a tier when a video send is slow, and back up
# after a run of fast ones.
QUALITY_TIERS = [
(70, 0.05),
(55, 0.07),
(40, 0.10),
(25, 0.15),
]
SLOW_SEND_THRESHOLD = 0.20 # seconds - a send this slow means congestion
FAST_STREAK_TO_RECOVER = 30 # consecutive quick sends before stepping back up
TYPE_HELLO = 0x00
TYPE_VIDEO = 0x01
TYPE_AUDIO = 0x02
TYPE_TALK = 0x03
def encode_ulaw(pcm16):
"""int16 numpy array -> uint8 numpy array, mu-law companded.
Must match decode_ulaw() in naowalk.py exactly, or the far end
just hears noise - keep the two in sync if you touch this."""
mu = 255.0
x = np.clip(pcm16.astype(np.float64) / 32768.0, -1.0, 1.0)
y = np.sign(x) * np.log1p(mu * np.abs(x)) / np.log1p(mu)
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):
"""Background thread: keeps the single most recent JPEG-encoded
frame available, at whatever quality tier the link currently
supports. A stalled capture never blocks a client's read."""
def __init__(self, session):
self.video = session.service("ALVideoDevice")
self.client = self.video.subscribeCamera(
"MediaServer", 0, CAMERA_RESOLUTION, CAMERA_COLORSPACE, CAMERA_FPS)
self._lock = threading.Lock()
self._jpeg = None
self._tier = 0
self._fast_streak = 0
self._stop = threading.Event()
self._thread = threading.Thread(target=self._loop)
self._thread.daemon = True
self._thread.start()
def _loop(self):
last_encode = 0.0
while not self._stop.is_set():
try:
image = self.video.getImageRemote(self.client)
if not image or len(image) < 7:
continue
# getImageRemote() blocks until the next camera frame is
# ready (~1/CAMERA_FPS), so this loop is already paced -
# but under a congested tier we send far less often than
# that, so skip the (expensive) convert+encode work for
# frames we're just going to overwrite unsent anyway.
quality, min_interval = self.quality_and_interval()
now = time.time()
if now - last_encode < min_interval:
continue
w, h = image[0], image[1]
arr = np.frombuffer(bytearray(image[6]), dtype=np.uint8).reshape((h, w, 3))
bgr = cv2.cvtColor(arr, cv2.COLOR_RGB2BGR)
ok, buf = cv2.imencode(".jpg", bgr, [cv2.IMWRITE_JPEG_QUALITY, quality])
if ok:
with self._lock:
self._jpeg = buf.tobytes()
last_encode = now
except Exception as e:
print("frame grab error: %s" % e)
time.sleep(0.2)
def quality_and_interval(self):
with self._lock:
return QUALITY_TIERS[self._tier]
def report_send_time(self, elapsed):
"""Called by the writer thread after each video send so we can
adapt to how the link is actually behaving right now."""
with self._lock:
if elapsed > SLOW_SEND_THRESHOLD:
self._fast_streak = 0
if self._tier < len(QUALITY_TIERS) - 1:
self._tier += 1
print("link looks congested - dropping to quality tier %d" % self._tier)
else:
self._fast_streak += 1
if self._fast_streak >= FAST_STREAK_TO_RECOVER and self._tier > 0:
self._tier -= 1
self._fast_streak = 0
print("link recovered - raising to quality tier %d" % self._tier)
def latest(self):
with self._lock:
return self._jpeg
def close(self):
self._stop.set()
try:
self.video.unsubscribe(self.client)
except Exception:
pass
class MicGrabber(object):
"""Subscribes to the robot's own mic locally (zero-hop, same trick
as FrameGrabber) and mu-law-encodes each chunk as it arrives. This
replaces the old design where raw PCM was pushed to the client
directly through NAOqi's pub/sub over the (tunneled) qi session."""
def __init__(self, session, channel=MIC_CHANNEL):
self.audio = session.service("ALAudioDevice")
self._svc_name = "MicRelay"
self._lock = threading.Lock()
self._chunks = collections.deque(maxlen=AUDIO_QUEUE_MAX)
session.registerService(self._svc_name, self)
time.sleep(0.5) # let NAOqi's service directory propagate the
# registration before ALAudioDevice looks it up
# by name, or subscribe() fails to find it
self.audio.setClientPreferences(self._svc_name, AUDIO_RATE, channel, 0)
self.audio.subscribe(self._svc_name)
def processRemote(self, nbOfChannels, nbOfSamplesByChannel, timeStamp, buffer):
try:
pcm = np.frombuffer(bytes(buffer), dtype=np.int16)
chunk = encode_ulaw(pcm).tobytes()
with self._lock:
self._chunks.append(chunk)
except Exception as e:
print("mic encode error: %s" % e)
def pop_all(self):
"""Drain everything queued right now, oldest first."""
with self._lock:
out = list(self._chunks)
self._chunks.clear()
return out
def close(self):
try:
self.audio.unsubscribe(self._svc_name)
except Exception:
pass
class TalkPlayer(object):
"""Decodes incoming TYPE_TALK chunks (push-to-talk audio pushed
from the client) and plays them on the robot's speaker.
Uses a persistent `aplay` subprocess fed raw PCM over its stdin -
the same playback mechanism the old SSH `nc -l | aplay` relay
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 __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.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
sock.listen(1)
print("video server listening on :%d" % port)
while True:
conn, addr = sock.accept()
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
conn.settimeout(5.0)
print("video client connected: %s" % (addr,))
last_jpeg = None
last_send = 0.0
try:
_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:
print("video client disconnected (%s)" % e)
finally:
try:
conn.close()
except Exception:
pass
def _talk_reader(conn, player):
"""Runs alongside the audio writer loop on the same connection -
the audio socket is bidirectional now, so this thread's the read
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:
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)
def serve_audio(mic, player, host, port):
"""Audio's own connection/thread. Checked eagerly (short sleep,
not paced like video) since gaps are far more noticeable than a
video frame being a beat stale."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
sock.listen(1)
print("audio server listening on :%d" % port)
while True:
conn, addr = sock.accept()
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
conn.settimeout(5.0)
print("audio client connected: %s" % (addr,))
reader_thread = threading.Thread(target=_talk_reader, args=(conn, player))
reader_thread.daemon = True
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():
session = qi.Session()
session.connect("tcp://127.0.0.1:%d" % NAOQI_PORT)
print("connected to local NAOqi on port %d" % NAOQI_PORT)
grabber = FrameGrabber(session)
mic = None
try:
mic = MicGrabber(session)
print("mic capture ready (mu-law, %dHz)" % AUDIO_RATE)
except Exception as e:
print("mic capture unavailable (%s) - video only" % e)
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:
serve_audio(mic, player, "0.0.0.0", AUDIO_PORT)
except KeyboardInterrupt:
pass
finally:
grabber.close()
if mic:
mic.close()
player.close()
if __name__ == "__main__":
main()
-101
View File
@@ -1,101 +0,0 @@
#!/usr/bin/env python
# -*- encoding: UTF-8 -*-
"""
Standalone NAO audio diagnostic - deliberately bypasses naowalk.py entirely.
Run this FIRST when troubleshooting "I can't hear the robot's mic". It tests
each link in the chain independently, so a failure tells you exactly where
the problem is instead of "somewhere in 300 lines of PyAudio/threading code":
TEST 1 - Front mic hardware energy.
Uses ALAudioDevice's built-in energy meter. No PyAudio, no
network audio streaming, no laptop audio stack involved at all.
This is the lowest-level check of "does the mic itself work".
TEST 2 - Local on-robot recording.
Records straight to a .wav file on the robot's own disk, then
you pull it off and play it normally. This bypasses the live
network stream, the SoundReceiver/queue/threading code, and
your laptop's PyAudio output path - all at once.
TEST 3 - Robot's own speaker.
Plays a tone through the ROBOT's speaker (not your laptop), to
confirm robot-side audio output is fine, independent of any of
the mic-listening code.
If TEST 1 shows no energy change when you make noise: mic hardware/mute/
environment issue, upstream of any code in this project.
If TEST 1 works but TEST 2's file is silent: something is wrong with the
recording/robot-audio-stack itself, still nothing to do with naowalk.py.
If TEST 2's file has clear audio: the mic and robot are fine, and the bug
is specifically in naowalk.py's live-streaming path (network delivery,
PyAudio device selection, etc.) - go back to the diagnostics already
built into naowalk.py (device list / test beep / peak level) to isolate it
further.
Usage:
python3 nao_audio_diagnostic.py --ip spike.local --port 9559
"""
import qi
import argparse
import time
parser = argparse.ArgumentParser()
parser.add_argument("--ip", type=str, default="127.0.0.1")
parser.add_argument("--port", type=int, default=9559)
args = parser.parse_args()
session = qi.Session()
session.connect(f"tcp://{args.ip}:{args.port}")
print(f"✅ Connected to {args.ip}")
audio = session.service("ALAudioDevice")
# --- TEST 1: mic hardware energy, all four channels ------------------------
print("\n=== TEST 1: Mic hardware energy (all 4 mics) ===")
print("Make noise near the robot's head (clap, talk right up close to it) for 10s...")
audio.enableEnergyComputation()
front_readings, rear_readings, left_readings, right_readings = [], [], [], []
for i in range(10):
f, r, l, rt = (audio.getFrontMicEnergy(), audio.getRearMicEnergy(),
audio.getLeftMicEnergy(), audio.getRightMicEnergy())
front_readings.append(f); rear_readings.append(r); left_readings.append(l); right_readings.append(rt)
print(f" t={i}s front={f:.2f} rear={r:.2f} left={l:.2f} right={rt:.2f}")
time.sleep(1)
audio.disableEnergyComputation()
results = {"front": front_readings, "rear": rear_readings, "left": left_readings, "right": right_readings}
flat = [name for name, vals in results.items() if max(vals) - min(vals) < 2]
if len(flat) == 4:
print("⚠️ ALL FOUR mics are flat. This points to something systemic - e.g. audio "
"inputs closed (closeAudioInputs() called somewhere and never reopened), "
"or a low-level hardware/driver fault - rather than one damaged mic.")
elif flat:
print(f"⚠️ Flat mic(s): {', '.join(flat)}. The others responded normally, which "
f"points to a fault specific to {', '.join(flat)} rather than the audio "
f"pipeline as a whole (that pipeline is clearly working for the others).")
else:
print("✅ All four mics responded to noise - hardware capture is fine across the board.")
# --- TEST 2: local on-robot recording ---------------------------------------
print("\n=== TEST 2: Local on-robot recording ===")
remote_path = "/home/nao/mic_test.wav"
print(f"Recording 5s directly to {remote_path} on the robot itself "
f"(this bypasses the network stream and your laptop's audio stack entirely)...")
audio.startMicrophonesRecording(remote_path)
time.sleep(5)
audio.stopMicrophonesRecording()
print(f"Done. Pull it off and play it with:")
print(f" scp nao@{args.ip}:{remote_path} .")
print(f" (then open mic_test.wav in any media player - it's 4-channel, so check all channels)")
print("If that file has clearly audible sound, the mic and robot are fine and the")
print("bug is specifically in naowalk.py's live-streaming path.")
# --- TEST 3: robot's own speaker, via TTS (version-agnostic) ---------------
print("\n=== TEST 3: Robot's own speaker ===")
input("Press Enter to have the robot say a test phrase out loud...")
session.service("ALTextToSpeech").say("Testing 1 2 3, can you hear me.")
print("If you heard the robot speak just now, its speaker output is fine "
"(this is unrelated to hearing its mic on your laptop, but rules out a robot-wide audio fault).")
print("\nDone. Report back which test(s) failed and we'll know exactly where to look.")
+6 -3
View File
@@ -9,15 +9,17 @@ import time
import threading import threading
class NaoMusicPlayer: class NaoMusicPlayer:
def __init__(self, session, nao_ip): def __init__(self, session, nao_ip, ssh_port=22):
self.session = session self.session = session
self.player = session.service("ALAudioPlayer") self.player = session.service("ALAudioPlayer")
self.nao_ip = nao_ip self.nao_ip = nao_ip
self.ssh_port = ssh_port
self.music_dir = "/home/nao/music" self.music_dir = "/home/nao/music"
self.local_path = "/tmp/song.mp3" self.local_path = "/tmp/song.mp3"
self.remote_path = f"{self.music_dir}/song.mp3" self.remote_path = f"{self.music_dir}/song.mp3"
try: try:
subprocess.run(["sshpass", "-p", "nao", "ssh", f"nao@{nao_ip}", f"mkdir -p {self.music_dir}"], subprocess.run(["sshpass", "-p", "nao", "ssh", "-p", str(self.ssh_port),
f"nao@{nao_ip}", f"mkdir -p {self.music_dir}"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except: pass except: pass
self.current_file = None self.current_file = None
@@ -110,7 +112,8 @@ class NaoMusicPlayer:
t = threading.Thread(target=ticker, daemon=True) t = threading.Thread(target=ticker, daemon=True)
t.start() t.start()
scp_result = subprocess.run( scp_result = subprocess.run(
["sshpass", "-p", "nao", "scp", self.local_path, f"nao@{self.nao_ip}:{self.remote_path}"], ["sshpass", "-p", "nao", "scp", "-P", str(self.ssh_port),
self.local_path, f"nao@{self.nao_ip}:{self.remote_path}"],
capture_output=True, text=True, timeout=60 capture_output=True, text=True, timeout=60
) )
stop_ticker.set() stop_ticker.set()
+737 -285
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
+3 -1
View File
@@ -1,3 +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 spike.local --port 9561 --mic-gain 9 --video-scale 3.0 --nao-ssh-port 22 --media-relay spike.local:8000
+1
View File
@@ -0,0 +1 @@
ssh -N -L 9561:192.168.0.100:9561 -L 2222:192.168.0.100:22 -L 8000:192.168.0.100:8000 root@10.196.124.46