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 @@ + + +