diff --git a/dialogflow_task_executive/CMakeLists.txt b/dialogflow_task_executive/CMakeLists.txt
index 3fde0dc49..00578495f 100644
--- a/dialogflow_task_executive/CMakeLists.txt
+++ b/dialogflow_task_executive/CMakeLists.txt
@@ -10,9 +10,6 @@ if(NOT (gcc_dump_machine MATCHES "x86_64-.*" OR gcc_dump_machine MATCHES "aarch6
third_party/boringssl-with-bazel/src/crypto/hrss/asm/poly_rq_mul.S:306: Error: bad register name `%rbp'
third_party/boringssl-with-bazel/src/crypto/hrss/asm/poly_rq_mul.S:308: Error: bad register expression
third_party/boringssl-with-bazel/src/crypto/hrss/asm/poly_rq_mul.S:309: Error: bad register name `%rsp'")
- find_package(catkin)
- catkin_package()
- return()
endif()
find_package(catkin REQUIRED COMPONENTS
@@ -42,7 +39,11 @@ catkin_package(
CATKIN_DEPENDS message_runtime
)
-if("$ENV{ROS_DISTRO}" STREQUAL "indigo")
+if(NOT(gcc_dump_machine MATCHES "x86_64-.*" OR gcc_dump_machine MATCHES "aarch64-.*"))
+ message(WARNING "pip -i requirements.txt with grpcio works only with i686, so create dummy requirements.txt and run 'catkin_generate_virtualenv' for ${gcc_dump_machine}")
+ file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt "")
+ catkin_generate_virtualenv(CHECK_VENV FALSE)
+elseif("$ENV{ROS_DISTRO}" STREQUAL "indigo")
message(WARNING "following requirements.txt syntax is not support on 14.04")
message(WARNING "google-api-core[grpc]==1.31.5 # via google-cloud-language")
message(WARNING "so we intentionally use requirements.txt")
@@ -69,12 +70,10 @@ else()
)
endif()
-file(GLOB NODE_SCRIPTS_FILES node_scripts/*.py)
-
+file(GLOB PYTHON_SCRIPT_FILES node_scripts/*.py test/*.py)
catkin_install_python(
- PROGRAMS ${NODE_SCRIPTS_FILES}
- DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
-)
+ PROGRAMS ${PYTHON_SCRIPT_FILES}
+ DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
install(DIRECTORY launch
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
@@ -82,6 +81,13 @@ install(DIRECTORY launch
)
if(CATKIN_ENABLE_TESTING)
- find_package(catkin REQUIRED COMPONENTS roslaunch)
+ find_package(catkin REQUIRED COMPONENTS roslaunch rostest)
roslaunch_add_file_check(launch)
+ if(NOT (gcc_dump_machine MATCHES "x86_64-.*" OR gcc_dump_machine MATCHES "aarch64-.*"))
+ message(WARNING "pip -i requirements.txt work only with i686, so skipping test for ${gcc_dump_machine}")
+ else()
+ add_rostest(test/test_rospy_node.test
+ DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv ${PROJECT_NAME}_generate_messages
+ )
+ endif()
endif()
diff --git a/dialogflow_task_executive/package.xml b/dialogflow_task_executive/package.xml
index 331658dbf..14261a93c 100644
--- a/dialogflow_task_executive/package.xml
+++ b/dialogflow_task_executive/package.xml
@@ -18,6 +18,7 @@
catkin
catkin_virtualenv
message_generation
+ actionlib_msgs
std_msgs
roslaunch
app_manager
@@ -25,6 +26,7 @@
speech_recognition_msgs
topic_tools
+ rostest
requirements.txt
diff --git a/dialogflow_task_executive/requirements.txt.indigo b/dialogflow_task_executive/requirements.txt.indigo
index 31c82bf07..5a24a39cf 100644
--- a/dialogflow_task_executive/requirements.txt.indigo
+++ b/dialogflow_task_executive/requirements.txt.indigo
@@ -15,6 +15,7 @@ google-api-core==1.12.0
google-auth==1.6.3
google-auth-httplib2==0.0.3
googleapis-common-protos==1.6.0
+grpcio==1.24.0
httplib2==0.14.0
idna==2.6
protobuf==3.12.2
diff --git a/dialogflow_task_executive/test/test_rospy_node.py b/dialogflow_task_executive/test/test_rospy_node.py
new file mode 100644
index 000000000..318d6c8d6
--- /dev/null
+++ b/dialogflow_task_executive/test/test_rospy_node.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+import rospy
+import os, sys, unittest, rostest
+
+# https://stackoverflow.com/questions/10971033/backporting-python-3-openencoding-utf-8-to-python-2
+if sys.version_info[0] > 2:
+ # py3k
+ pass
+else:
+ # py2
+ import __builtin__
+ def open(filename, encoding=None):
+ return __builtin__.open(filename)
+
+pkg_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), os.pardir, os.pardir))
+pkg_name = os.path.basename(pkg_dir)
+
+class TestRospyNode(unittest.TestCase):
+
+ def __init__(self, *args):
+ unittest.TestCase.__init__(self, *args)
+
+ def test_rosnode(self):
+ __name__ = 'dummy'
+ for scripts_dir in ['scripts', 'node_scripts']:
+ full_scripts_dir = os.path.join(pkg_dir, scripts_dir)
+ if not os.path.exists(full_scripts_dir):
+ continue
+ for filename in [f for f in map(lambda x: os.path.join(full_scripts_dir, x), os.listdir(full_scripts_dir)) if os.path.isfile(f) and f.endswith('.py')]:
+ print("Check if {} is loadable".format(filename))
+ # https://stackoverflow.com/questions/4484872/why-doesnt-exec-work-in-a-function-with-a-subfunction
+ exec(open(filename, encoding='utf-8').read()) in globals(), locals()
+ self.assertTrue(True)
+
+if __name__ == '__main__':
+ rostest.rosrun('test_rospy_node', pkg_name, TestRospyNode, sys.argv)
diff --git a/dialogflow_task_executive/test/test_rospy_node.test b/dialogflow_task_executive/test/test_rospy_node.test
new file mode 100644
index 000000000..e47acaf68
--- /dev/null
+++ b/dialogflow_task_executive/test/test_rospy_node.test
@@ -0,0 +1,3 @@
+
+
+
diff --git a/google_chat_ros/CMakeLists.txt b/google_chat_ros/CMakeLists.txt
index 5242eb4a2..4ca3ca801 100644
--- a/google_chat_ros/CMakeLists.txt
+++ b/google_chat_ros/CMakeLists.txt
@@ -41,15 +41,14 @@ catkin_generate_virtualenv(
# euslisp
file(GLOB EUSLISP_SCRIPTS scripts/*.l)
install(FILES ${EUSLISP_SCRIPTS}
- DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
- )
+ DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/scripts/)
# python
-file(GLOB PYTHON_SCRIPTS scripts/*.py)
+file(GLOB PYTHON_SCRIPT_FILES scripts/*.py test/*.py)
catkin_install_python(
- PROGRAMS ${PYTHON_SCRIPTS}
- DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
- )
+ PROGRAMS ${PYTHON_SCRIPT_FILES}
+ DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
+
# python requirements
install(FILES requirements.txt
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
@@ -70,4 +69,7 @@ if(CATKIN_ENABLE_TESTING)
add_rostest(test/import.test
DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv
)
+ add_rostest(test/test_rospy_node.test
+ DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv
+ )
endif()
diff --git a/google_chat_ros/test/test_rospy_node.py b/google_chat_ros/test/test_rospy_node.py
new file mode 100644
index 000000000..5021e3a12
--- /dev/null
+++ b/google_chat_ros/test/test_rospy_node.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+import rospy
+import os, sys, unittest, rostest
+
+# https://stackoverflow.com/questions/10971033/backporting-python-3-openencoding-utf-8-to-python-2
+if sys.version_info[0] > 2:
+ # py3k
+ pass
+else:
+ # py2
+ import __builtin__
+ def open(filename, encoding=None):
+ return __builtin__.open(filename)
+
+pkg_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), os.pardir, os.pardir))
+pkg_name = os.path.basename(pkg_dir)
+
+class TestRospyNode(unittest.TestCase):
+
+ def __init__(self, *args):
+ unittest.TestCase.__init__(self, *args)
+
+ def test_rosnode(self):
+ __name__ = 'dummy'
+ for scripts_dir in ['scripts', 'node_scripts']:
+ full_scripts_dir = os.path.join(pkg_dir, scripts_dir)
+ if not os.path.exists(full_scripts_dir):
+ continue
+ for filename in [f for f in map(lambda x: os.path.join(full_scripts_dir, x), os.listdir(full_scripts_dir)) if os.path.isfile(f) and f.endswith('.py')]:
+ if filename.endswith('/helper.py'):
+ print("{} depends on dialogflow_task_executive. However, when we add this to the of package.xml, it appempts to build venv using 'dialogflow_task_executive/requirements.txt'. This requires having the same PYTHON_INTERPRETER for both dialogflow and chat ros package. The issue is that dialogflow_task_executive heavily relies on system Python modules, including ROS, making it difficult to use dialogflow with Python3 on Melodic".format(filename))
+ continue
+ print("Check if {} is loadable".format(filename))
+ # https://stackoverflow.com/questions/4484872/why-doesnt-exec-work-in-a-function-with-a-subfunction
+ exec(open(filename, encoding='utf-8').read()) in globals(), locals()
+ self.assertTrue(True)
+
+if __name__ == '__main__':
+ rostest.rosrun('test_rospy_node', pkg_name, TestRospyNode, sys.argv)
diff --git a/google_chat_ros/test/test_rospy_node.test b/google_chat_ros/test/test_rospy_node.test
new file mode 100644
index 000000000..2359baea5
--- /dev/null
+++ b/google_chat_ros/test/test_rospy_node.test
@@ -0,0 +1,3 @@
+
+
+
diff --git a/julius_ros/test/julius.test b/julius_ros/test/julius.test
index 630435bc0..bbb8eb6c6 100644
--- a/julius_ros/test/julius.test
+++ b/julius_ros/test/julius.test
@@ -15,7 +15,7 @@
-
+
dnn: $(arg dnn)
diff --git a/nfc_ros/CMakeLists.txt b/nfc_ros/CMakeLists.txt
index 759705a04..98e3d1c62 100644
--- a/nfc_ros/CMakeLists.txt
+++ b/nfc_ros/CMakeLists.txt
@@ -27,12 +27,19 @@ catkin_generate_virtualenv(
PYTHON_INTERPRETER python3
CHECK_VENV FALSE
)
-catkin_install_python(PROGRAMS
- node_scripts/nfc_ros_node.py
- DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
-)
+file(GLOB PYTHON_SCRIPT_FILES node_scripts/*.py test/*.py)
+catkin_install_python(
+ PROGRAMS ${PYTHON_SCRIPT_FILES}
+ DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
install(FILES requirements.txt
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
+
+if(CATKIN_ENABLE_TESTING)
+ find_package(rostest REQUIRED)
+ add_rostest(test/test_rospy_node.test
+ DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv
+ )
+endif()
endif()
diff --git a/nfc_ros/test/test_rospy_node.py b/nfc_ros/test/test_rospy_node.py
new file mode 100644
index 000000000..318d6c8d6
--- /dev/null
+++ b/nfc_ros/test/test_rospy_node.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+import rospy
+import os, sys, unittest, rostest
+
+# https://stackoverflow.com/questions/10971033/backporting-python-3-openencoding-utf-8-to-python-2
+if sys.version_info[0] > 2:
+ # py3k
+ pass
+else:
+ # py2
+ import __builtin__
+ def open(filename, encoding=None):
+ return __builtin__.open(filename)
+
+pkg_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), os.pardir, os.pardir))
+pkg_name = os.path.basename(pkg_dir)
+
+class TestRospyNode(unittest.TestCase):
+
+ def __init__(self, *args):
+ unittest.TestCase.__init__(self, *args)
+
+ def test_rosnode(self):
+ __name__ = 'dummy'
+ for scripts_dir in ['scripts', 'node_scripts']:
+ full_scripts_dir = os.path.join(pkg_dir, scripts_dir)
+ if not os.path.exists(full_scripts_dir):
+ continue
+ for filename in [f for f in map(lambda x: os.path.join(full_scripts_dir, x), os.listdir(full_scripts_dir)) if os.path.isfile(f) and f.endswith('.py')]:
+ print("Check if {} is loadable".format(filename))
+ # https://stackoverflow.com/questions/4484872/why-doesnt-exec-work-in-a-function-with-a-subfunction
+ exec(open(filename, encoding='utf-8').read()) in globals(), locals()
+ self.assertTrue(True)
+
+if __name__ == '__main__':
+ rostest.rosrun('test_rospy_node', pkg_name, TestRospyNode, sys.argv)
diff --git a/nfc_ros/test/test_rospy_node.test b/nfc_ros/test/test_rospy_node.test
new file mode 100644
index 000000000..564289a4b
--- /dev/null
+++ b/nfc_ros/test/test_rospy_node.test
@@ -0,0 +1,3 @@
+
+
+
diff --git a/respeaker_ros/CMakeLists.txt b/respeaker_ros/CMakeLists.txt
index 390a82e0f..9322760c9 100644
--- a/respeaker_ros/CMakeLists.txt
+++ b/respeaker_ros/CMakeLists.txt
@@ -6,6 +6,8 @@ find_package(catkin REQUIRED COMPONENTS
dynamic_reconfigure
)
+catkin_python_setup()
+
generate_dynamic_reconfigure_options(
cfg/Respeaker.cfg
)
@@ -15,10 +17,12 @@ catkin_package()
if($ENV{ROS_DISTRO} STRGREATER "melodic")
catkin_generate_virtualenv(
PYTHON_INTERPRETER python3
+ CHECK_VENV FALSE
)
else()
catkin_generate_virtualenv(
PYTHON_INTERPRETER python2
+ CHECK_VENV FALSE
)
endif()
@@ -29,13 +33,16 @@ install(FILES requirements.txt
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
-file(GLOB PYTHON_SCRIPTS scripts/*.py)
-catkin_install_python(PROGRAMS ${PYTHON_SCRIPTS}
- DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/scripts/)
+file(GLOB PYTHON_SCRIPT_FILES scripts/*.py test/*.py)
+catkin_install_python(PROGRAMS ${PYTHON_SCRIPT_FILES}
+ DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
if(CATKIN_ENABLE_TESTING)
find_package(rostest REQUIRED)
add_rostest(test/sample_respeaker.test
DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv
)
+ add_rostest(test/test_rospy_node.test
+ DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv
+ )
endif()
diff --git a/respeaker_ros/package.xml b/respeaker_ros/package.xml
index ac83b898a..01f255336 100644
--- a/respeaker_ros/package.xml
+++ b/respeaker_ros/package.xml
@@ -21,10 +21,8 @@
tf
python-numpy
python3-numpy
- python-pixel-ring-pip
python-pyaudio
python3-pyaudio
- python-pyusb-pip
jsk_tools
diff --git a/respeaker_ros/requirements.txt b/respeaker_ros/requirements.txt
index 7362afdbf..17615d307 100644
--- a/respeaker_ros/requirements.txt
+++ b/respeaker_ros/requirements.txt
@@ -1 +1,5 @@
SpeechRecognition==3.8.1
+# see https://github.com/jsk-ros-pkg/jsk_robot/pull/1797
+pyusb==1.1.1
+spidev==3.5 # pixel-ring requires spidev and spidev >=3.6 requires setuptools>=61.0. same as https://github.com/jsk-ros-pkg/jsk_robot/pull/1794
+pixel-ring==0.1.0
diff --git a/respeaker_ros/scripts/respeaker_gencfg.py b/respeaker_ros/scripts/respeaker_gencfg.py
index 63f6548fa..d49df86de 100644
--- a/respeaker_ros/scripts/respeaker_gencfg.py
+++ b/respeaker_ros/scripts/respeaker_gencfg.py
@@ -4,7 +4,7 @@
import os
import sys
-from respeaker_node import PARAMETERS, RespeakerInterface
+from respeaker_ros import PARAMETERS, RespeakerInterface
def main(out):
diff --git a/respeaker_ros/scripts/respeaker_node.py b/respeaker_ros/scripts/respeaker_node.py
index d9ec870aa..aff50ca6b 100644
--- a/respeaker_ros/scripts/respeaker_node.py
+++ b/respeaker_ros/scripts/respeaker_node.py
@@ -19,6 +19,12 @@
from geometry_msgs.msg import PoseStamped
from std_msgs.msg import Bool, Int32, ColorRGBA
from dynamic_reconfigure.server import Server
+
+# https://stackoverflow.com/questions/21367320/searching-for-equivalent-of-filenotfounderror-in-python-2
+try:
+ FileNotFoundError
+except NameError:
+ FileNotFoundError = IOError
try:
from pixel_ring import usb_pixel_ring_v2
except (IOError, FileNotFoundError) as e:
@@ -31,6 +37,7 @@
rospy.logerr(e)
raise RuntimeError("Need to run respeaker_gencfg.py first")
+from respeaker_ros import *
# suppress error messages from ALSA
# https://stackoverflow.com/questions/7088672/pyaudio-working-but-spits-out-error-messages-each-time
@@ -56,259 +63,6 @@ def ignore_stderr(enable=True):
yield
-# Partly copied from https://github.com/respeaker/usb_4_mic_array
-# parameter list
-# name: (id, offset, type, max, min , r/w, info)
-PARAMETERS = {
- 'AECFREEZEONOFF': (18, 7, 'int', 1, 0, 'rw', 'Adaptive Echo Canceler updates inhibit.', '0 = Adaptation enabled', '1 = Freeze adaptation, filter only'),
- 'AECNORM': (18, 19, 'float', 16, 0.25, 'rw', 'Limit on norm of AEC filter coefficients'),
- 'AECPATHCHANGE': (18, 25, 'int', 1, 0, 'ro', 'AEC Path Change Detection.', '0 = false (no path change detected)', '1 = true (path change detected)'),
- 'RT60': (18, 26, 'float', 0.9, 0.25, 'ro', 'Current RT60 estimate in seconds'),
- 'HPFONOFF': (18, 27, 'int', 3, 0, 'rw', 'High-pass Filter on microphone signals.', '0 = OFF', '1 = ON - 70 Hz cut-off', '2 = ON - 125 Hz cut-off', '3 = ON - 180 Hz cut-off'),
- 'RT60ONOFF': (18, 28, 'int', 1, 0, 'rw', 'RT60 Estimation for AES. 0 = OFF 1 = ON'),
- 'AECSILENCELEVEL': (18, 30, 'float', 1, 1e-09, 'rw', 'Threshold for signal detection in AEC [-inf .. 0] dBov (Default: -80dBov = 10log10(1x10-8))'),
- 'AECSILENCEMODE': (18, 31, 'int', 1, 0, 'ro', 'AEC far-end silence detection status. ', '0 = false (signal detected) ', '1 = true (silence detected)'),
- 'AGCONOFF': (19, 0, 'int', 1, 0, 'rw', 'Automatic Gain Control. ', '0 = OFF ', '1 = ON'),
- 'AGCMAXGAIN': (19, 1, 'float', 1000, 1, 'rw', 'Maximum AGC gain factor. ', '[0 .. 60] dB (default 30dB = 20log10(31.6))'),
- 'AGCDESIREDLEVEL': (19, 2, 'float', 0.99, 1e-08, 'rw', 'Target power level of the output signal. ', '[-inf .. 0] dBov (default: -23dBov = 10log10(0.005))'),
- 'AGCGAIN': (19, 3, 'float', 1000, 1, 'rw', 'Current AGC gain factor. ', '[0 .. 60] dB (default: 0.0dB = 20log10(1.0))'),
- 'AGCTIME': (19, 4, 'float', 1, 0.1, 'rw', 'Ramps-up / down time-constant in seconds.'),
- 'CNIONOFF': (19, 5, 'int', 1, 0, 'rw', 'Comfort Noise Insertion.', '0 = OFF', '1 = ON'),
- 'FREEZEONOFF': (19, 6, 'int', 1, 0, 'rw', 'Adaptive beamformer updates.', '0 = Adaptation enabled', '1 = Freeze adaptation, filter only'),
- 'STATNOISEONOFF': (19, 8, 'int', 1, 0, 'rw', 'Stationary noise suppression.', '0 = OFF', '1 = ON'),
- 'GAMMA_NS': (19, 9, 'float', 3, 0, 'rw', 'Over-subtraction factor of stationary noise. min .. max attenuation'),
- 'MIN_NS': (19, 10, 'float', 1, 0, 'rw', 'Gain-floor for stationary noise suppression.', '[-inf .. 0] dB (default: -16dB = 20log10(0.15))'),
- 'NONSTATNOISEONOFF': (19, 11, 'int', 1, 0, 'rw', 'Non-stationary noise suppression.', '0 = OFF', '1 = ON'),
- 'GAMMA_NN': (19, 12, 'float', 3, 0, 'rw', 'Over-subtraction factor of non- stationary noise. min .. max attenuation'),
- 'MIN_NN': (19, 13, 'float', 1, 0, 'rw', 'Gain-floor for non-stationary noise suppression.', '[-inf .. 0] dB (default: -10dB = 20log10(0.3))'),
- 'ECHOONOFF': (19, 14, 'int', 1, 0, 'rw', 'Echo suppression.', '0 = OFF', '1 = ON'),
- 'GAMMA_E': (19, 15, 'float', 3, 0, 'rw', 'Over-subtraction factor of echo (direct and early components). min .. max attenuation'),
- 'GAMMA_ETAIL': (19, 16, 'float', 3, 0, 'rw', 'Over-subtraction factor of echo (tail components). min .. max attenuation'),
- 'GAMMA_ENL': (19, 17, 'float', 5, 0, 'rw', 'Over-subtraction factor of non-linear echo. min .. max attenuation'),
- 'NLATTENONOFF': (19, 18, 'int', 1, 0, 'rw', 'Non-Linear echo attenuation.', '0 = OFF', '1 = ON'),
- 'NLAEC_MODE': (19, 20, 'int', 2, 0, 'rw', 'Non-Linear AEC training mode.', '0 = OFF', '1 = ON - phase 1', '2 = ON - phase 2'),
- 'SPEECHDETECTED': (19, 22, 'int', 1, 0, 'ro', 'Speech detection status.', '0 = false (no speech detected)', '1 = true (speech detected)'),
- 'FSBUPDATED': (19, 23, 'int', 1, 0, 'ro', 'FSB Update Decision.', '0 = false (FSB was not updated)', '1 = true (FSB was updated)'),
- 'FSBPATHCHANGE': (19, 24, 'int', 1, 0, 'ro', 'FSB Path Change Detection.', '0 = false (no path change detected)', '1 = true (path change detected)'),
- 'TRANSIENTONOFF': (19, 29, 'int', 1, 0, 'rw', 'Transient echo suppression.', '0 = OFF', '1 = ON'),
- 'VOICEACTIVITY': (19, 32, 'int', 1, 0, 'ro', 'VAD voice activity status.', '0 = false (no voice activity)', '1 = true (voice activity)'),
- 'STATNOISEONOFF_SR': (19, 33, 'int', 1, 0, 'rw', 'Stationary noise suppression for ASR.', '0 = OFF', '1 = ON'),
- 'NONSTATNOISEONOFF_SR': (19, 34, 'int', 1, 0, 'rw', 'Non-stationary noise suppression for ASR.', '0 = OFF', '1 = ON'),
- 'GAMMA_NS_SR': (19, 35, 'float', 3, 0, 'rw', 'Over-subtraction factor of stationary noise for ASR. ', '[0.0 .. 3.0] (default: 1.0)'),
- 'GAMMA_NN_SR': (19, 36, 'float', 3, 0, 'rw', 'Over-subtraction factor of non-stationary noise for ASR. ', '[0.0 .. 3.0] (default: 1.1)'),
- 'MIN_NS_SR': (19, 37, 'float', 1, 0, 'rw', 'Gain-floor for stationary noise suppression for ASR.', '[-inf .. 0] dB (default: -16dB = 20log10(0.15))'),
- 'MIN_NN_SR': (19, 38, 'float', 1, 0, 'rw', 'Gain-floor for non-stationary noise suppression for ASR.', '[-inf .. 0] dB (default: -10dB = 20log10(0.3))'),
- 'GAMMAVAD_SR': (19, 39, 'float', 1000, 0, 'rw', 'Set the threshold for voice activity detection.', '[-inf .. 60] dB (default: 3.5dB 20log10(1.5))'),
- # 'KEYWORDDETECT': (20, 0, 'int', 1, 0, 'ro', 'Keyword detected. Current value so needs polling.'),
- 'DOAANGLE': (21, 0, 'int', 359, 0, 'ro', 'DOA angle. Current value. Orientation depends on build configuration.')
-}
-
-
-class RespeakerInterface(object):
- VENDOR_ID = 0x2886
- PRODUCT_ID = 0x0018
- TIMEOUT = 100000
-
- def __init__(self):
- self.dev = usb.core.find(idVendor=self.VENDOR_ID,
- idProduct=self.PRODUCT_ID)
- if not self.dev:
- raise RuntimeError("Failed to find Respeaker device")
- rospy.loginfo("Initializing Respeaker device")
- try:
- self.dev.reset()
- except usb.core.USBError:
- rospy.logerr(
- "You may have to give the right permission on respeaker device. "
- "Please run the command as followings to register udev rules.\n"
- "$ roscd respeaker_ros \n"
- "$ sudo cp -f $(rospack find respeaker_ros)/config/60-respeaker.rules /etc/udev/rules.d/60-respeaker.rules \n"
- "$ sudo systemctl restart udev \n"
- "You may find further details at https://github.com/jsk-ros-pkg/jsk_3rdparty/blob/master/respeaker_ros/README.md"
- ) # NOQA
- raise
- self.pixel_ring = usb_pixel_ring_v2.PixelRing(self.dev)
- self.set_led_think()
- time.sleep(5) # it will take 5 seconds to re-recognize as audio device
- self.set_led_trace()
- rospy.loginfo("Respeaker device initialized (Version: %s)" % self.version)
-
- def __del__(self):
- try:
- self.close()
- except:
- pass
- finally:
- self.dev = None
-
- def write(self, name, value):
- try:
- data = PARAMETERS[name]
- except KeyError:
- return
-
- if data[5] == 'ro':
- raise ValueError('{} is read-only'.format(name))
-
- id = data[0]
-
- # 4 bytes offset, 4 bytes value, 4 bytes type
- if data[2] == 'int':
- payload = struct.pack(b'iii', data[1], int(value), 1)
- else:
- payload = struct.pack(b'ifi', data[1], float(value), 0)
-
- self.dev.ctrl_transfer(
- usb.util.CTRL_OUT | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE,
- 0, 0, id, payload, self.TIMEOUT)
-
- def read(self, name):
- try:
- data = PARAMETERS[name]
- except KeyError:
- return
-
- id = data[0]
-
- cmd = 0x80 | data[1]
- if data[2] == 'int':
- cmd |= 0x40
-
- length = 8
-
- try:
- response = self.dev.ctrl_transfer(
- usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE,
- 0, cmd, id, length, self.TIMEOUT)
- except usb.core.USBError as e:
- rospy.logerr(e)
- rospy.signal_shutdown('Shutdown this node because of USBError')
-
- if sys.version_info.major == 2:
- response = struct.unpack(b'ii', response.tostring())
- else:
- response = struct.unpack(b'ii', response.tobytes())
-
- if data[2] == 'int':
- result = response[0]
- else:
- result = response[0] * (2.**response[1])
-
- return result
-
- def set_led_think(self):
- self.pixel_ring.set_brightness(10)
- self.pixel_ring.think()
-
- def set_led_trace(self):
- self.pixel_ring.set_brightness(20)
- self.pixel_ring.trace()
-
- def set_led_color(self, r, g, b, a):
- self.pixel_ring.set_brightness(int(20 * a))
- self.pixel_ring.set_color(r=int(r*255), g=int(g*255), b=int(b*255))
-
- def set_vad_threshold(self, db):
- self.write('GAMMAVAD_SR', db)
-
- def is_voice(self):
- return self.read('VOICEACTIVITY')
-
- @property
- def direction(self):
- return self.read('DOAANGLE')
-
- @property
- def version(self):
- return self.dev.ctrl_transfer(
- usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE,
- 0, 0x80, 0, 1, self.TIMEOUT)[0]
-
- def close(self):
- """
- close the interface
- """
- usb.util.dispose_resources(self.dev)
-
-
-class RespeakerAudio(object):
- def __init__(self, on_audio, channel=0, suppress_error=True):
- self.on_audio = on_audio
- with ignore_stderr(enable=suppress_error):
- self.pyaudio = pyaudio.PyAudio()
- self.channels = None
- self.channel = channel
- self.device_index = None
- self.rate = 16000
- self.bitwidth = 2
- self.bitdepth = 16
-
- # find device
- count = self.pyaudio.get_device_count()
- rospy.logdebug("%d audio devices found" % count)
- for i in range(count):
- info = self.pyaudio.get_device_info_by_index(i)
- name = info["name"].encode("utf-8")
- chan = info["maxInputChannels"]
- rospy.logdebug(" - %d: %s" % (i, name))
- if name.lower().find(b"respeaker") >= 0:
- self.channels = chan
- self.device_index = i
- rospy.loginfo("Found %d: %s (channels: %d)" % (i, name, chan))
- break
- if self.device_index is None:
- rospy.logwarn("Failed to find respeaker device by name. Using default input")
- info = self.pyaudio.get_default_input_device_info()
- self.channels = info["maxInputChannels"]
- self.device_index = info["index"]
-
- if self.channels != 6:
- rospy.logwarn("%d channel is found for respeaker" % self.channels)
- rospy.logwarn("You may have to update firmware.")
- self.channel = min(self.channels - 1, max(0, self.channel))
-
- self.stream = self.pyaudio.open(
- input=True, start=False,
- format=pyaudio.paInt16,
- channels=self.channels,
- rate=self.rate,
- frames_per_buffer=1024,
- stream_callback=self.stream_callback,
- input_device_index=self.device_index,
- )
-
- def __del__(self):
- self.stop()
- try:
- self.stream.close()
- except:
- pass
- finally:
- self.stream = None
- try:
- self.pyaudio.terminate()
- except:
- pass
-
- def stream_callback(self, in_data, frame_count, time_info, status):
- # split channel
- data = np.frombuffer(in_data, dtype=np.int16)
- chunk_per_channel = int(len(data) / self.channels)
- data = np.reshape(data, (chunk_per_channel, self.channels))
- chan_data = data[:, self.channel]
- # invoke callback
- self.on_audio(chan_data.tobytes())
- return None, pyaudio.paContinue
-
- def start(self):
- if self.stream.is_stopped():
- self.stream.start_stream()
-
- def stop(self):
- if self.stream.is_active():
- self.stream.stop_stream()
-
-
class RespeakerNode(object):
def __init__(self):
rospy.on_shutdown(self.on_shutdown)
diff --git a/respeaker_ros/setup.py b/respeaker_ros/setup.py
new file mode 100644
index 000000000..579e8aa00
--- /dev/null
+++ b/respeaker_ros/setup.py
@@ -0,0 +1,9 @@
+from distutils.core import setup
+from catkin_pkg.python_setup import generate_distutils_setup
+
+d = generate_distutils_setup(
+ packages=['respeaker_ros'],
+ package_dir={'': 'src'}
+)
+
+setup(**d)
diff --git a/respeaker_ros/src/respeaker_ros/__init__.py b/respeaker_ros/src/respeaker_ros/__init__.py
new file mode 100644
index 000000000..8ebce95ee
--- /dev/null
+++ b/respeaker_ros/src/respeaker_ros/__init__.py
@@ -0,0 +1,253 @@
+# Partly copied from https://github.com/respeaker/usb_4_mic_array
+# parameter list
+# name: (id, offset, type, max, min , r/w, info)
+PARAMETERS = {
+ 'AECFREEZEONOFF': (18, 7, 'int', 1, 0, 'rw', 'Adaptive Echo Canceler updates inhibit.', '0 = Adaptation enabled', '1 = Freeze adaptation, filter only'),
+ 'AECNORM': (18, 19, 'float', 16, 0.25, 'rw', 'Limit on norm of AEC filter coefficients'),
+ 'AECPATHCHANGE': (18, 25, 'int', 1, 0, 'ro', 'AEC Path Change Detection.', '0 = false (no path change detected)', '1 = true (path change detected)'),
+ 'RT60': (18, 26, 'float', 0.9, 0.25, 'ro', 'Current RT60 estimate in seconds'),
+ 'HPFONOFF': (18, 27, 'int', 3, 0, 'rw', 'High-pass Filter on microphone signals.', '0 = OFF', '1 = ON - 70 Hz cut-off', '2 = ON - 125 Hz cut-off', '3 = ON - 180 Hz cut-off'),
+ 'RT60ONOFF': (18, 28, 'int', 1, 0, 'rw', 'RT60 Estimation for AES. 0 = OFF 1 = ON'),
+ 'AECSILENCELEVEL': (18, 30, 'float', 1, 1e-09, 'rw', 'Threshold for signal detection in AEC [-inf .. 0] dBov (Default: -80dBov = 10log10(1x10-8))'),
+ 'AECSILENCEMODE': (18, 31, 'int', 1, 0, 'ro', 'AEC far-end silence detection status. ', '0 = false (signal detected) ', '1 = true (silence detected)'),
+ 'AGCONOFF': (19, 0, 'int', 1, 0, 'rw', 'Automatic Gain Control. ', '0 = OFF ', '1 = ON'),
+ 'AGCMAXGAIN': (19, 1, 'float', 1000, 1, 'rw', 'Maximum AGC gain factor. ', '[0 .. 60] dB (default 30dB = 20log10(31.6))'),
+ 'AGCDESIREDLEVEL': (19, 2, 'float', 0.99, 1e-08, 'rw', 'Target power level of the output signal. ', '[-inf .. 0] dBov (default: -23dBov = 10log10(0.005))'),
+ 'AGCGAIN': (19, 3, 'float', 1000, 1, 'rw', 'Current AGC gain factor. ', '[0 .. 60] dB (default: 0.0dB = 20log10(1.0))'),
+ 'AGCTIME': (19, 4, 'float', 1, 0.1, 'rw', 'Ramps-up / down time-constant in seconds.'),
+ 'CNIONOFF': (19, 5, 'int', 1, 0, 'rw', 'Comfort Noise Insertion.', '0 = OFF', '1 = ON'),
+ 'FREEZEONOFF': (19, 6, 'int', 1, 0, 'rw', 'Adaptive beamformer updates.', '0 = Adaptation enabled', '1 = Freeze adaptation, filter only'),
+ 'STATNOISEONOFF': (19, 8, 'int', 1, 0, 'rw', 'Stationary noise suppression.', '0 = OFF', '1 = ON'),
+ 'GAMMA_NS': (19, 9, 'float', 3, 0, 'rw', 'Over-subtraction factor of stationary noise. min .. max attenuation'),
+ 'MIN_NS': (19, 10, 'float', 1, 0, 'rw', 'Gain-floor for stationary noise suppression.', '[-inf .. 0] dB (default: -16dB = 20log10(0.15))'),
+ 'NONSTATNOISEONOFF': (19, 11, 'int', 1, 0, 'rw', 'Non-stationary noise suppression.', '0 = OFF', '1 = ON'),
+ 'GAMMA_NN': (19, 12, 'float', 3, 0, 'rw', 'Over-subtraction factor of non- stationary noise. min .. max attenuation'),
+ 'MIN_NN': (19, 13, 'float', 1, 0, 'rw', 'Gain-floor for non-stationary noise suppression.', '[-inf .. 0] dB (default: -10dB = 20log10(0.3))'),
+ 'ECHOONOFF': (19, 14, 'int', 1, 0, 'rw', 'Echo suppression.', '0 = OFF', '1 = ON'),
+ 'GAMMA_E': (19, 15, 'float', 3, 0, 'rw', 'Over-subtraction factor of echo (direct and early components). min .. max attenuation'),
+ 'GAMMA_ETAIL': (19, 16, 'float', 3, 0, 'rw', 'Over-subtraction factor of echo (tail components). min .. max attenuation'),
+ 'GAMMA_ENL': (19, 17, 'float', 5, 0, 'rw', 'Over-subtraction factor of non-linear echo. min .. max attenuation'),
+ 'NLATTENONOFF': (19, 18, 'int', 1, 0, 'rw', 'Non-Linear echo attenuation.', '0 = OFF', '1 = ON'),
+ 'NLAEC_MODE': (19, 20, 'int', 2, 0, 'rw', 'Non-Linear AEC training mode.', '0 = OFF', '1 = ON - phase 1', '2 = ON - phase 2'),
+ 'SPEECHDETECTED': (19, 22, 'int', 1, 0, 'ro', 'Speech detection status.', '0 = false (no speech detected)', '1 = true (speech detected)'),
+ 'FSBUPDATED': (19, 23, 'int', 1, 0, 'ro', 'FSB Update Decision.', '0 = false (FSB was not updated)', '1 = true (FSB was updated)'),
+ 'FSBPATHCHANGE': (19, 24, 'int', 1, 0, 'ro', 'FSB Path Change Detection.', '0 = false (no path change detected)', '1 = true (path change detected)'),
+ 'TRANSIENTONOFF': (19, 29, 'int', 1, 0, 'rw', 'Transient echo suppression.', '0 = OFF', '1 = ON'),
+ 'VOICEACTIVITY': (19, 32, 'int', 1, 0, 'ro', 'VAD voice activity status.', '0 = false (no voice activity)', '1 = true (voice activity)'),
+ 'STATNOISEONOFF_SR': (19, 33, 'int', 1, 0, 'rw', 'Stationary noise suppression for ASR.', '0 = OFF', '1 = ON'),
+ 'NONSTATNOISEONOFF_SR': (19, 34, 'int', 1, 0, 'rw', 'Non-stationary noise suppression for ASR.', '0 = OFF', '1 = ON'),
+ 'GAMMA_NS_SR': (19, 35, 'float', 3, 0, 'rw', 'Over-subtraction factor of stationary noise for ASR. ', '[0.0 .. 3.0] (default: 1.0)'),
+ 'GAMMA_NN_SR': (19, 36, 'float', 3, 0, 'rw', 'Over-subtraction factor of non-stationary noise for ASR. ', '[0.0 .. 3.0] (default: 1.1)'),
+ 'MIN_NS_SR': (19, 37, 'float', 1, 0, 'rw', 'Gain-floor for stationary noise suppression for ASR.', '[-inf .. 0] dB (default: -16dB = 20log10(0.15))'),
+ 'MIN_NN_SR': (19, 38, 'float', 1, 0, 'rw', 'Gain-floor for non-stationary noise suppression for ASR.', '[-inf .. 0] dB (default: -10dB = 20log10(0.3))'),
+ 'GAMMAVAD_SR': (19, 39, 'float', 1000, 0, 'rw', 'Set the threshold for voice activity detection.', '[-inf .. 60] dB (default: 3.5dB 20log10(1.5))'),
+ # 'KEYWORDDETECT': (20, 0, 'int', 1, 0, 'ro', 'Keyword detected. Current value so needs polling.'),
+ 'DOAANGLE': (21, 0, 'int', 359, 0, 'ro', 'DOA angle. Current value. Orientation depends on build configuration.')
+}
+
+
+class RespeakerInterface(object):
+ VENDOR_ID = 0x2886
+ PRODUCT_ID = 0x0018
+ TIMEOUT = 100000
+
+ def __init__(self):
+ self.dev = usb.core.find(idVendor=self.VENDOR_ID,
+ idProduct=self.PRODUCT_ID)
+ if not self.dev:
+ raise RuntimeError("Failed to find Respeaker device")
+ rospy.loginfo("Initializing Respeaker device")
+ try:
+ self.dev.reset()
+ except usb.core.USBError:
+ rospy.logerr(
+ "You may have to give the right permission on respeaker device. "
+ "Please run the command as followings to register udev rules.\n"
+ "$ roscd respeaker_ros \n"
+ "$ sudo cp -f $(rospack find respeaker_ros)/config/60-respeaker.rules /etc/udev/rules.d/60-respeaker.rules \n"
+ "$ sudo systemctl restart udev \n"
+ "You may find further details at https://github.com/jsk-ros-pkg/jsk_3rdparty/blob/master/respeaker_ros/README.md"
+ ) # NOQA
+ raise
+ self.pixel_ring = usb_pixel_ring_v2.PixelRing(self.dev)
+ self.set_led_think()
+ time.sleep(5) # it will take 5 seconds to re-recognize as audio device
+ self.set_led_trace()
+ rospy.loginfo("Respeaker device initialized (Version: %s)" % self.version)
+
+ def __del__(self):
+ try:
+ self.close()
+ except:
+ pass
+ finally:
+ self.dev = None
+
+ def write(self, name, value):
+ try:
+ data = PARAMETERS[name]
+ except KeyError:
+ return
+
+ if data[5] == 'ro':
+ raise ValueError('{} is read-only'.format(name))
+
+ id = data[0]
+
+ # 4 bytes offset, 4 bytes value, 4 bytes type
+ if data[2] == 'int':
+ payload = struct.pack(b'iii', data[1], int(value), 1)
+ else:
+ payload = struct.pack(b'ifi', data[1], float(value), 0)
+
+ self.dev.ctrl_transfer(
+ usb.util.CTRL_OUT | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE,
+ 0, 0, id, payload, self.TIMEOUT)
+
+ def read(self, name):
+ try:
+ data = PARAMETERS[name]
+ except KeyError:
+ return
+
+ id = data[0]
+
+ cmd = 0x80 | data[1]
+ if data[2] == 'int':
+ cmd |= 0x40
+
+ length = 8
+
+ try:
+ response = self.dev.ctrl_transfer(
+ usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE,
+ 0, cmd, id, length, self.TIMEOUT)
+ except usb.core.USBError as e:
+ rospy.logerr(e)
+ rospy.signal_shutdown('Shutdown this node because of USBError')
+
+ if sys.version_info.major == 2:
+ response = struct.unpack(b'ii', response.tostring())
+ else:
+ response = struct.unpack(b'ii', response.tobytes())
+
+ if data[2] == 'int':
+ result = response[0]
+ else:
+ result = response[0] * (2.**response[1])
+
+ return result
+
+ def set_led_think(self):
+ self.pixel_ring.set_brightness(10)
+ self.pixel_ring.think()
+
+ def set_led_trace(self):
+ self.pixel_ring.set_brightness(20)
+ self.pixel_ring.trace()
+
+ def set_led_color(self, r, g, b, a):
+ self.pixel_ring.set_brightness(int(20 * a))
+ self.pixel_ring.set_color(r=int(r*255), g=int(g*255), b=int(b*255))
+
+ def set_vad_threshold(self, db):
+ self.write('GAMMAVAD_SR', db)
+
+ def is_voice(self):
+ return self.read('VOICEACTIVITY')
+
+ @property
+ def direction(self):
+ return self.read('DOAANGLE')
+
+ @property
+ def version(self):
+ return self.dev.ctrl_transfer(
+ usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE,
+ 0, 0x80, 0, 1, self.TIMEOUT)[0]
+
+ def close(self):
+ """
+ close the interface
+ """
+ usb.util.dispose_resources(self.dev)
+
+
+class RespeakerAudio(object):
+ def __init__(self, on_audio, channel=0, suppress_error=True):
+ self.on_audio = on_audio
+ with ignore_stderr(enable=suppress_error):
+ self.pyaudio = pyaudio.PyAudio()
+ self.channels = None
+ self.channel = channel
+ self.device_index = None
+ self.rate = 16000
+ self.bitwidth = 2
+ self.bitdepth = 16
+
+ # find device
+ count = self.pyaudio.get_device_count()
+ rospy.logdebug("%d audio devices found" % count)
+ for i in range(count):
+ info = self.pyaudio.get_device_info_by_index(i)
+ name = info["name"].encode("utf-8")
+ chan = info["maxInputChannels"]
+ rospy.logdebug(" - %d: %s" % (i, name))
+ if name.lower().find(b"respeaker") >= 0:
+ self.channels = chan
+ self.device_index = i
+ rospy.loginfo("Found %d: %s (channels: %d)" % (i, name, chan))
+ break
+ if self.device_index is None:
+ rospy.logwarn("Failed to find respeaker device by name. Using default input")
+ info = self.pyaudio.get_default_input_device_info()
+ self.channels = info["maxInputChannels"]
+ self.device_index = info["index"]
+
+ if self.channels != 6:
+ rospy.logwarn("%d channel is found for respeaker" % self.channels)
+ rospy.logwarn("You may have to update firmware.")
+ self.channel = min(self.channels - 1, max(0, self.channel))
+
+ self.stream = self.pyaudio.open(
+ input=True, start=False,
+ format=pyaudio.paInt16,
+ channels=self.channels,
+ rate=self.rate,
+ frames_per_buffer=1024,
+ stream_callback=self.stream_callback,
+ input_device_index=self.device_index,
+ )
+
+ def __del__(self):
+ self.stop()
+ try:
+ self.stream.close()
+ except:
+ pass
+ finally:
+ self.stream = None
+ try:
+ self.pyaudio.terminate()
+ except:
+ pass
+
+ def stream_callback(self, in_data, frame_count, time_info, status):
+ # split channel
+ data = np.frombuffer(in_data, dtype=np.int16)
+ chunk_per_channel = int(len(data) / self.channels)
+ data = np.reshape(data, (chunk_per_channel, self.channels))
+ chan_data = data[:, self.channel]
+ # invoke callback
+ self.on_audio(chan_data.tobytes())
+ return None, pyaudio.paContinue
+
+ def start(self):
+ if self.stream.is_stopped():
+ self.stream.start_stream()
+
+ def stop(self):
+ if self.stream.is_active():
+ self.stream.stop_stream()
+
+
diff --git a/respeaker_ros/test/test_rospy_node.py b/respeaker_ros/test/test_rospy_node.py
new file mode 100644
index 000000000..00c66c784
--- /dev/null
+++ b/respeaker_ros/test/test_rospy_node.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+import rospy
+import os, sys, unittest, rostest
+
+# https://stackoverflow.com/questions/10971033/backporting-python-3-openencoding-utf-8-to-python-2
+if sys.version_info[0] > 2:
+ # py3k
+ pass
+else:
+ # py2
+ import __builtin__
+ def open(filename, encoding=None):
+ return __builtin__.open(filename)
+
+pkg_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), os.pardir, os.pardir))
+pkg_name = os.path.basename(pkg_dir)
+
+class TestRospyNode(unittest.TestCase):
+
+ def __init__(self, *args):
+ unittest.TestCase.__init__(self, *args)
+
+ def test_rosnode(self):
+ __name__ = 'dummy'
+ for scripts_dir in ['scripts', 'node_scripts']:
+ full_scripts_dir = os.path.join(pkg_dir, scripts_dir)
+ if not os.path.exists(full_scripts_dir):
+ continue
+ for filename in [f for f in map(lambda x: os.path.join(full_scripts_dir, x), os.listdir(full_scripts_dir)) if os.path.isfile(f) and f.endswith('.py')]:
+ print("Check if {} is loadable".format(filename))
+ # https://stackoverflow.com/questions/4484872/why-doesnt-exec-work-in-a-function-with-a-subfunction
+ try:
+ exec(open(filename, encoding='utf-8').read()) in globals(), locals()
+ except RuntimeError as e:
+ print("Catch runtime error ({}), check if this is expect".format(e.args))
+ self.assertTrue(e.args[0] == 'Check the device is connected and recognized')
+ self.assertTrue(True)
+
+if __name__ == '__main__':
+ rostest.rosrun('test_rospy_node', pkg_name, TestRospyNode, sys.argv)
diff --git a/respeaker_ros/test/test_rospy_node.test b/respeaker_ros/test/test_rospy_node.test
new file mode 100644
index 000000000..1e57fff43
--- /dev/null
+++ b/respeaker_ros/test/test_rospy_node.test
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ros_google_cloud_language/CMakeLists.txt b/ros_google_cloud_language/CMakeLists.txt
index 2503d2b25..edf58076f 100644
--- a/ros_google_cloud_language/CMakeLists.txt
+++ b/ros_google_cloud_language/CMakeLists.txt
@@ -44,6 +44,9 @@ catkin_package(
)
catkin_generate_virtualenv(
+ # specify python version
+ # https://github.com/jsk-ros-pkg/jsk_3rdparty/pull/367/files
+ PYTHON_INTERPRETER "python$ENV{ROS_PYTHON_VERSION}"
# Disable creating a unit test to verify that package requirements are locked.
# # commented out for python3, which uses concurrent.futures
# # futures==3.3.0
@@ -52,12 +55,10 @@ catkin_generate_virtualenv(
include_directories()
-file(GLOB NODE_SCRIPTS_FILES node_scripts/*.py)
-file(GLOB SCRIPTS_FILES scripts/*.py)
+file(GLOB PYTHON_SCRIPT_FILES node_scripts/*.py test/*.py)
catkin_install_python(
- PROGRAMS ${NODE_SCRIPTS_FILES} ${SCRIPTS_FILES}
- DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
-)
+ PROGRAMS ${PYTHON_SCRIPT_FILES}
+ DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
install(DIRECTORY launch samples
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
@@ -68,3 +69,7 @@ install(FILES requirements.txt
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
+if(CATKIN_ENABLE_TESTING)
+ find_package(rostest REQUIRED)
+ add_rostest(test/test_rospy_node.test)
+endif()
diff --git a/ros_google_cloud_language/test/test_rospy_node.py b/ros_google_cloud_language/test/test_rospy_node.py
new file mode 100644
index 000000000..318d6c8d6
--- /dev/null
+++ b/ros_google_cloud_language/test/test_rospy_node.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+import rospy
+import os, sys, unittest, rostest
+
+# https://stackoverflow.com/questions/10971033/backporting-python-3-openencoding-utf-8-to-python-2
+if sys.version_info[0] > 2:
+ # py3k
+ pass
+else:
+ # py2
+ import __builtin__
+ def open(filename, encoding=None):
+ return __builtin__.open(filename)
+
+pkg_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), os.pardir, os.pardir))
+pkg_name = os.path.basename(pkg_dir)
+
+class TestRospyNode(unittest.TestCase):
+
+ def __init__(self, *args):
+ unittest.TestCase.__init__(self, *args)
+
+ def test_rosnode(self):
+ __name__ = 'dummy'
+ for scripts_dir in ['scripts', 'node_scripts']:
+ full_scripts_dir = os.path.join(pkg_dir, scripts_dir)
+ if not os.path.exists(full_scripts_dir):
+ continue
+ for filename in [f for f in map(lambda x: os.path.join(full_scripts_dir, x), os.listdir(full_scripts_dir)) if os.path.isfile(f) and f.endswith('.py')]:
+ print("Check if {} is loadable".format(filename))
+ # https://stackoverflow.com/questions/4484872/why-doesnt-exec-work-in-a-function-with-a-subfunction
+ exec(open(filename, encoding='utf-8').read()) in globals(), locals()
+ self.assertTrue(True)
+
+if __name__ == '__main__':
+ rostest.rosrun('test_rospy_node', pkg_name, TestRospyNode, sys.argv)
diff --git a/ros_google_cloud_language/test/test_rospy_node.test b/ros_google_cloud_language/test/test_rospy_node.test
new file mode 100644
index 000000000..8efef5fac
--- /dev/null
+++ b/ros_google_cloud_language/test/test_rospy_node.test
@@ -0,0 +1,3 @@
+
+
+
diff --git a/ros_speech_recognition/CMakeLists.txt b/ros_speech_recognition/CMakeLists.txt
index 003eb530c..722faba02 100644
--- a/ros_speech_recognition/CMakeLists.txt
+++ b/ros_speech_recognition/CMakeLists.txt
@@ -24,11 +24,10 @@ else()
)
endif()
-file(GLOB SCRIPT_PROGRAMS scripts/*.py)
+file(GLOB PYTHON_SCRIPT_FILES scripts/*.py test/*.py)
catkin_install_python(
- PROGRAMS ${SCRIPT_PROGRAMS}
- DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/scripts/
-)
+ PROGRAMS ${PYTHON_SCRIPT_FILES}
+ DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
install(DIRECTORY launch
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
@@ -42,4 +41,7 @@ if(CATKIN_ENABLE_TESTING)
DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv
)
roslaunch_add_file_check(launch/speech_recognition.launch)
+ add_rostest(test/test_rospy_node.test
+ DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv
+ )
endif()
diff --git a/ros_speech_recognition/test/test_rospy_node.py b/ros_speech_recognition/test/test_rospy_node.py
new file mode 100644
index 000000000..318d6c8d6
--- /dev/null
+++ b/ros_speech_recognition/test/test_rospy_node.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+import rospy
+import os, sys, unittest, rostest
+
+# https://stackoverflow.com/questions/10971033/backporting-python-3-openencoding-utf-8-to-python-2
+if sys.version_info[0] > 2:
+ # py3k
+ pass
+else:
+ # py2
+ import __builtin__
+ def open(filename, encoding=None):
+ return __builtin__.open(filename)
+
+pkg_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), os.pardir, os.pardir))
+pkg_name = os.path.basename(pkg_dir)
+
+class TestRospyNode(unittest.TestCase):
+
+ def __init__(self, *args):
+ unittest.TestCase.__init__(self, *args)
+
+ def test_rosnode(self):
+ __name__ = 'dummy'
+ for scripts_dir in ['scripts', 'node_scripts']:
+ full_scripts_dir = os.path.join(pkg_dir, scripts_dir)
+ if not os.path.exists(full_scripts_dir):
+ continue
+ for filename in [f for f in map(lambda x: os.path.join(full_scripts_dir, x), os.listdir(full_scripts_dir)) if os.path.isfile(f) and f.endswith('.py')]:
+ print("Check if {} is loadable".format(filename))
+ # https://stackoverflow.com/questions/4484872/why-doesnt-exec-work-in-a-function-with-a-subfunction
+ exec(open(filename, encoding='utf-8').read()) in globals(), locals()
+ self.assertTrue(True)
+
+if __name__ == '__main__':
+ rostest.rosrun('test_rospy_node', pkg_name, TestRospyNode, sys.argv)
diff --git a/ros_speech_recognition/test/test_rospy_node.test b/ros_speech_recognition/test/test_rospy_node.test
new file mode 100644
index 000000000..4491c81f6
--- /dev/null
+++ b/ros_speech_recognition/test/test_rospy_node.test
@@ -0,0 +1,3 @@
+
+
+
diff --git a/sesame_ros/CMakeLists.txt b/sesame_ros/CMakeLists.txt
index 60f97a853..b154ba71a 100644
--- a/sesame_ros/CMakeLists.txt
+++ b/sesame_ros/CMakeLists.txt
@@ -32,13 +32,18 @@ endif()
include_directories()
-file(GLOB NODE_SCRIPTS_FILES node_scripts/*)
-file(GLOB SCRIPTS_FILES scripts/*)
+file(GLOB PYTHON_SCRIPT_FILES node_scripts/* scripts/* test/*.py)
catkin_install_python(
- PROGRAMS ${NODE_SCRIPTS_FILES} ${SCRIPTS_FILES}
- DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
-)
+ PROGRAMS ${PYTHON_SCRIPT_FILES}
+ DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
install(FILES requirements.txt
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
+
+if(CATKIN_ENABLE_TESTING)
+ find_package(rostest REQUIRED)
+ add_rostest(test/test_rospy_node.test
+ DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv
+ )
+endif()
diff --git a/sesame_ros/test/test_rospy_node.py b/sesame_ros/test/test_rospy_node.py
new file mode 100644
index 000000000..318d6c8d6
--- /dev/null
+++ b/sesame_ros/test/test_rospy_node.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+import rospy
+import os, sys, unittest, rostest
+
+# https://stackoverflow.com/questions/10971033/backporting-python-3-openencoding-utf-8-to-python-2
+if sys.version_info[0] > 2:
+ # py3k
+ pass
+else:
+ # py2
+ import __builtin__
+ def open(filename, encoding=None):
+ return __builtin__.open(filename)
+
+pkg_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), os.pardir, os.pardir))
+pkg_name = os.path.basename(pkg_dir)
+
+class TestRospyNode(unittest.TestCase):
+
+ def __init__(self, *args):
+ unittest.TestCase.__init__(self, *args)
+
+ def test_rosnode(self):
+ __name__ = 'dummy'
+ for scripts_dir in ['scripts', 'node_scripts']:
+ full_scripts_dir = os.path.join(pkg_dir, scripts_dir)
+ if not os.path.exists(full_scripts_dir):
+ continue
+ for filename in [f for f in map(lambda x: os.path.join(full_scripts_dir, x), os.listdir(full_scripts_dir)) if os.path.isfile(f) and f.endswith('.py')]:
+ print("Check if {} is loadable".format(filename))
+ # https://stackoverflow.com/questions/4484872/why-doesnt-exec-work-in-a-function-with-a-subfunction
+ exec(open(filename, encoding='utf-8').read()) in globals(), locals()
+ self.assertTrue(True)
+
+if __name__ == '__main__':
+ rostest.rosrun('test_rospy_node', pkg_name, TestRospyNode, sys.argv)
diff --git a/sesame_ros/test/test_rospy_node.test b/sesame_ros/test/test_rospy_node.test
new file mode 100644
index 000000000..5d9f11ce9
--- /dev/null
+++ b/sesame_ros/test/test_rospy_node.test
@@ -0,0 +1,3 @@
+
+
+