diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 223614529..b4cdb25fa 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -6,6 +6,8 @@ on: env: DISPLAY: ':0.0' + # workaround required for checkout@v3, https://github.com/actions/checkout/issues/1590 + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true jobs: ros: @@ -38,7 +40,11 @@ jobs: CATKIN_PARALLEL_JOBS: "-i" - container: ${{ matrix.CONTAINER }} + container: + image: ${{ matrix.CONTAINER }} + volumes: + - /tmp/node20:/__e/node20 + steps: - name: Install latest git ( use sudo for ros-ubuntu ) run: | @@ -61,6 +67,21 @@ jobs: git config --global --add safe.directory $GITHUB_WORKSPACE fi + - name: Try to replace `node` with an glibc 2.17 + shell: bash + run: | + if [ "${{ matrix.CONTAINER }}" = "jskrobotics/ros-ubuntu:14.04" ]; then + export USER=$(whoami) + sudo chmod 777 -R /__e/node20 + sudo chown -R $USER /__e/node20 + fi + ls -lar /__e/node20 && + sudo apt-get install -y curl && + curl -Lo /tmp/node.tar.gz https://unofficial-builds.nodejs.org/download/release/v20.17.0/node-v20.17.0-linux-x64-glibc-217.tar.gz && + cd /__e/node20 && + tar -x --strip-components=1 -f /tmp/node.tar.gz && + ls -lar /__e/node20/bin/ + - name: Checkout uses: actions/checkout@v3.0.2 @@ -149,14 +170,14 @@ jobs: export EXTRA_DEB=${{ matrix.EXTRA_DEB }} export ROS_DISTRO=${{ matrix.ROS_DISTRO }} export ROS_PARALLEL_TEST_JOBS="-j2" - export CATKIN_PARALLEL_JOBS="-i" + export CATKIN_PARALLEL_JOBS="-i -j2" export ROSDEP_ADDITIONAL_OPTIONS="-n -q -r --ignore-src --skip-keys=python-google-cloud-texttospeech-pip --skip-keys=python-dialogflow-pip" # Skip installation of grpcio by pip because it causes error export BEFORE_SCRIPT="sudo pip install virtualenv==15.1.0" export USE_TRAVIS=true export USE_DOCKER=false export DOCKER_IMAGE=${{ matrix.CONTAINER }} export NOT_TEST_INSTALL=true - export ROS_PARALLEL_JOBS="--make-args LANG=C.UTF-8 LC_ALL=C.UTF-8" + export ROS_PARALLEL_JOBS="-j8 --make-args LANG=C.UTF-8 LC_ALL=C.UTF-8" set +o nounset export CI_SOURCE_PATH=$(pwd) @@ -183,3 +204,64 @@ jobs: if: always() run: | rm -fr ${{ matrix.ROS_DISTRO }}-${{ github.run_number }}-${{ github.run_attempt }}-${{ github.run_id }} || echo "OK" + +# ROS-O setup https://github.com/v4hn/ros-o-builder/blob/jammy-one/README.md#install-instructions + ros-o: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - DISTRO: ubuntu:22.04 + ROS_REPOSITORY_URL: https://raw.githubusercontent.com/v4hn/ros-o-builder/jammy-one/repository + + container: ${{ matrix.DISTRO }} + + env: + DEBIAN_FRONTEND : noninteractive + + steps: + - name: Chcekout Source + uses: actions/checkout@v3.0.2 + + - name: Setup ROS-O deb repository + run: | + set -x + apt update && apt install -qq -y ca-certificates + echo "deb [trusted=yes] ${{ matrix.ROS_REPOSITORY_URL }}/ ./" | tee /etc/apt/sources.list.d/ros-o-builder.list + apt update + apt install -qq -y python3-rosdep2 + echo "yaml ${{ matrix.ROS_REPOSITORY_URL }}/local.yaml debian" | tee /etc/ros/rosdep/sources.list.d/1-ros-o-builder.list + rosdep update + # disable installing recommends as ros-o-builder do + echo 'APT::Install-Recommends "false";' | tee /etc/apt/apt.conf.d/99norecommends + + - name: Setup catkin-tools + run: | + set -x + # setup catkin tools + apt install -qq -y python3-pip + pip3 install catkin-tools + # setup build tools + apt install -qq -y cmake build-essential catkin ros-one-rosbash + + - name: Setup Workspace + run: | + source /opt/ros/one/setup.bash + set -x + # setup workspace + mkdir -p ~/ws/src + cd ~/ws/src + ln -sf $GITHUB_WORKSPACE . + rosdep install -qq -r -y --from-path . --ignore-src || echo "OK" + shell: bash + + - name: Compile Packages + run: | + source /opt/ros/one/setup.bash + set -x + cd ~/ws/ + catkin build --no-status -sv ${{ matrix.CATKIN_OPTIONS }} --cmake-args -DCATKIN_ENABLE_TESTING=OFF -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON ${{ matrix.CMAKE_OPTIONS }} + shell: bash + diff --git a/.travis b/.travis index 4e8b3cf26..b223c886b 160000 --- a/.travis +++ b/.travis @@ -1 +1 @@ -Subproject commit 4e8b3cf2637d593d05106e84edf2e4532677d37c +Subproject commit b223c886bcda0d96c6c03b0f0ae07da768a00155 diff --git a/.travis.rosinstall.indigo b/.travis.rosinstall.indigo index b07215c12..19545013d 100644 --- a/.travis.rosinstall.indigo +++ b/.travis.rosinstall.indigo @@ -9,5 +9,5 @@ # we need to avoid pip upgrade - git: local-name: locusrobotics/catkin_virtualenv - uri: https://github.com/locusrobotics/catkin_virtualenv - version: 0.2.2 + uri: https://github.com/locusrobotics/catkin_virtualenv-release + version: release/melodic/catkin_virtualenv/0.2.2-0 diff --git a/3rdparty/downward/CMakeLists.txt b/3rdparty/downward/CMakeLists.txt index 5d153d878..e270fdb1b 100644 --- a/3rdparty/downward/CMakeLists.txt +++ b/3rdparty/downward/CMakeLists.txt @@ -14,7 +14,7 @@ externalproject_add(downward URL http://cdn.rawgit.com/jsk-ros-pkg/archives/master/Fast-Downward-f33d3b65601f.tar.gz TIMEOUT 120 CONFIGURE_COMMAND "" - BUILD_COMMAND cd src && sed -i "s@^CXXFLAGS =$@CXXFLAGS = ${CXXFLAGS_NOETIC} -Wno-maybe-uninitialized@" search/Makefile && ./build_all DOWNWARD_BITWIDTH=native && patch -p3 < ${PROJECT_SOURCE_DIR}/fix_time_clock.patch + BUILD_COMMAND cd src && sed -i "s@^CXXFLAGS =$@CXXFLAGS = ${CXXFLAGS_NOETIC} -Wno-deprecated-copy -Wno-maybe-uninitialized@" search/Makefile && patch -p3 < ${PROJECT_SOURCE_DIR}/fix_std_vector_namespace.patch && ./build_all DOWNWARD_BITWIDTH=native && patch -p3 < ${PROJECT_SOURCE_DIR}/fix_time_clock.patch INSTALL_COMMAND bash -c "cp -rf --parents src/{validate,plan,preprocess/preprocess,search/downward*,search/unitcost,search/portfolio.py,translate} ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_SHARE_DESTINATION}" BUILD_IN_SOURCE 1 ) diff --git a/3rdparty/downward/fix_std_vector_namespace.patch b/3rdparty/downward/fix_std_vector_namespace.patch new file mode 100644 index 000000000..487dcd17b --- /dev/null +++ b/3rdparty/downward/fix_std_vector_namespace.patch @@ -0,0 +1,10 @@ +--- src/downward/src/search/landmarks/landmark_factory_zhu_givan.h.bak 2024-11-13 13:20:10.508657847 +0900 ++++ src/downward/src/search/landmarks/landmark_factory_zhu_givan.h 2024-11-13 13:20:12.591485789 +0900 +@@ -7,6 +7,7 @@ + #include "../globals.h" + + using namespace __gnu_cxx; ++using std::vector; + + class LandmarkFactoryZhuGivan : public LandmarkFactory { + private: diff --git a/3rdparty/downward/package.xml b/3rdparty/downward/package.xml index aa752c913..c6fa3ac05 100644 --- a/3rdparty/downward/package.xml +++ b/3rdparty/downward/package.xml @@ -17,6 +17,7 @@ python python3 flex + libfl-dev bison gawk rostest diff --git a/3rdparty/ff/Makefile b/3rdparty/ff/Makefile index 3a7de76b4..b691ceb70 100644 --- a/3rdparty/ff/Makefile +++ b/3rdparty/ff/Makefile @@ -16,7 +16,7 @@ MAKE_ARGS = ADDONS=-DYY_SKIP_YYWRAP installed:$(SOURCE_DIR)/unpacked rm -f $(SOURCE_DIR)/ff - (cd $(SOURCE_DIR) && $(MAKE) clean && $(MAKE) CFLAGS='-O6 -g -ansi -Wno-array-bounds -Wno-endif-labels -Wno-format-overflow -Wno-implicit-function-declaration -Wno-int-in-bool-context -Wno-unused-function -Wno-unused-variable -fno-builtin-strncpy -fno-builtin-strcpy -fno-builtin-strlen -fno-builtin-strcat -fno-builtin-memset ' $(MAKE_ARGS)) + (cd $(SOURCE_DIR) && $(MAKE) clean && $(MAKE) CFLAGS='-O6 -g -ansi -Wno-array-bounds -Wno-endif-labels -Wno-format-overflow -Wno-implicit-function-declaration -Wno-int-in-bool-context -Wno-unused-function -Wno-unused-variable -fno-builtin-strncpy -fno-builtin-strcpy -fno-builtin-strlen -fno-builtin-strcat -fno-builtin-memset -fcommon ' $(MAKE_ARGS)) mkdir -p bin cp $(SOURCE_DIR)/ff bin/ touch installed diff --git a/3rdparty/ffha/Makefile b/3rdparty/ffha/Makefile index 9c6a1168c..b2d063231 100644 --- a/3rdparty/ffha/Makefile +++ b/3rdparty/ffha/Makefile @@ -22,7 +22,7 @@ $(SOURCE_DIR)/patched:$(SOURCE_DIR)/unpacked touch $(SOURCE_DIR)/patched installed:$(SOURCE_DIR)/patched - (cd $(SOURCE_DIR) && $(MAKE) clean && $(MAKE) CFLAGS='-O6 -g -ansi -Wno-array-bounds -Wno-endif-labels -Wno-format-overflow -Wno-implicit-function-declaration -Wno-int-in-bool-context -Wno-unused-function -Wno-unused-variable -fno-builtin-strncpy -fno-builtin-strcpy -fno-builtin-strlen -fno-builtin-strcat -fno-builtin-memset ' $(MAKE_ARGS)) + (cd $(SOURCE_DIR) && $(MAKE) clean && $(MAKE) CFLAGS='-O6 -g -ansi -Wno-array-bounds -Wno-endif-labels -Wno-format-overflow -Wno-implicit-function-declaration -Wno-int-in-bool-context -Wno-unused-function -Wno-unused-variable -fno-builtin-strncpy -fno-builtin-strcpy -fno-builtin-strlen -fno-builtin-strcat -fno-builtin-memset -fcommon ' $(MAKE_ARGS)) mkdir -p bin cp $(SOURCE_DIR)/ffha bin/ touch installed diff --git a/3rdparty/libsiftfast/Makefile b/3rdparty/libsiftfast/Makefile index b330cb1c7..70c618026 100644 --- a/3rdparty/libsiftfast/Makefile +++ b/3rdparty/libsiftfast/Makefile @@ -24,7 +24,7 @@ clone: $(SVN_DIR) patch: clone cd $(SVN_DIR) && svn revert --recursive . && for patch in $(SOURCE_DIR)/patches/0*.patch; do patch -p0 -f -E < $$patch; done # for python3 - if [ "${ROS_DISTRO}" \> "melodic" ]; then cd $(SVN_DIR) && svn revert --recursive . && for patch in $(SOURCE_DIR)/patches/1*.patch; do patch -p0 -f -E < $$patch; done; fi + if [ "${ROS_DISTRO}" != "kinetic" -a "${ROS_DISTRO}" != "melodic" ]; then cd $(SVN_DIR) && svn revert --recursive . && for patch in $(SOURCE_DIR)/patches/1*.patch; do patch -p0 -f -E < $$patch; done; fi libsiftfast: patch cd $(SVN_DIR)/$(BUILDDIR) && BOOST_INCLUDEDIR=$(BOOST_INCLUDE_DIRS) BOOST_LIBRARYDIR=$(BOOST_LIBRARY_DIRS) cmake -DCMAKE_INSTALL_PREFIX=$(INSTALL_DIR) -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) .. && make $(ROS_PARALLEL_JOBS) install diff --git a/3rdparty/libsiftfast/patches/10.boost_python38.patch b/3rdparty/libsiftfast/patches/10.boost_python38.patch index 36dfe4c89..451fc743f 100644 --- a/3rdparty/libsiftfast/patches/10.boost_python38.patch +++ b/3rdparty/libsiftfast/patches/10.boost_python38.patch @@ -2,12 +2,14 @@ Index: CMakeLists.txt =================================================================== --- CMakeLists.txt (revision 54) +++ CMakeLists.txt (working copy) -@@ -155,7 +155,8 @@ +@@ -155,7 +155,10 @@ if( NOT $ENV{BOOST_LIBRARYDIR} STREQUAL "" ) set(Boost_LIBRARY_DIRS $ENV{BOOST_LIBRARYDIR}) endif() -find_package(Boost COMPONENTS python) -+find_package(Boost REQUIRED COMPONENTS system python38) ++set(Python3_FIND_STRATEGY VERSION) ++find_package(Python3 COMPONENTS Interpreter Development) ++find_package(Boost REQUIRED COMPONENTS system python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}) +add_definitions("-Wno-narrowing") if( Boost_FOUND ) @@ -17,7 +19,7 @@ Index: CMakeLists.txt # set(BUILD_SIFTFASTPY) -if( Boost_PYTHON_FOUND ) -+if( Boost_PYTHON38_FOUND ) ++if( Boost_PYTHON${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}_FOUND ) find_package(PythonLibs) if( PYTHONLIBS_FOUND OR PYTHON_LIBRARIES ) diff --git a/3rdparty/nlopt/CMakeLists.txt b/3rdparty/nlopt/CMakeLists.txt index f9cc4f0fa..9d36d0e93 100644 --- a/3rdparty/nlopt/CMakeLists.txt +++ b/3rdparty/nlopt/CMakeLists.txt @@ -8,6 +8,8 @@ add_custom_target(libnlopt_cxx ALL DEPENDS ${CATKIN_DEVEL_PREFIX}/lib/libnlopt_cxx.so) add_custom_command(OUTPUT ${CATKIN_DEVEL_PREFIX}/lib/libnlopt_cxx.so + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/build + COMMAND cp ${PROJECT_SOURCE_DIR}/build/nlopt-2.3.tar.gz ${CMAKE_CURRENT_BINARY_DIR}/build COMMAND cp ${PROJECT_SOURCE_DIR}/nlopt-2.3.tar.gz.md5sum ${CMAKE_CURRENT_BINARY_DIR} COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/tmp COMMAND cmake -E chdir ${CMAKE_CURRENT_BINARY_DIR} make -f ${PROJECT_SOURCE_DIR}/Makefile DSTDIR=${CMAKE_CURRENT_BINARY_DIR}/tmp MK_DIR=${mk_PREFIX}/share/mk diff --git a/3rdparty/zdepth/CMakeLists.txt b/3rdparty/zdepth/CMakeLists.txt index 52bb0d590..08b996951 100644 --- a/3rdparty/zdepth/CMakeLists.txt +++ b/3rdparty/zdepth/CMakeLists.txt @@ -3,18 +3,18 @@ project(zdepth) include(ExternalProject) -if("$ENV{ROS_DISTRO}" STRGREATER "kinetic") +if(("$ENV{ROS_DISTRO}" STREQUAL "indigo") OR ("$ENV{ROS_DISTRO}" STREQUAL "kinetic")) + install(CODE "message(WARNING \"Skipping zdepth install because ROS is too old\")") +else() ExternalProject_Add( zdepth GIT_REPOSITORY https://github.com/catid/Zdepth.git GIT_TAG ac7c6d8e944d07be2404e5a1eaa04562595f3756 GIT_SHALLOW TRUE - PATCH_COMMAND cat ${PROJECT_SOURCE_DIR}/fix_cmakelists.patch | patch -p1 + PATCH_COMMAND cat ${PROJECT_SOURCE_DIR}/fix_cmakelists.patch | patch -p1 --forward || true # If there is no --forward, the build process hangs because the patch command waits interactively for input on whether a file that has already been patched can be patched again. If there is no || true , the patch command returns non-zero status if you try to use it on the patched file, then catkin build failes CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} INSTALL_COMMAND echo "install" ) install(CODE "execute_process(COMMAND make install WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/zdepth-prefix/src/zdepth-build)") -else() - install(CODE "message(WARNING \"Skipping zdepth install because ROS is too old\")") endif() diff --git a/dialogflow_task_executive/CMakeLists.txt b/dialogflow_task_executive/CMakeLists.txt index 9e67eb43a..eec641946 100644 --- a/dialogflow_task_executive/CMakeLists.txt +++ b/dialogflow_task_executive/CMakeLists.txt @@ -55,16 +55,19 @@ elseif("$ENV{ROS_DISTRO}" STREQUAL "indigo") COMMAND cp ${CMAKE_CURRENT_BINARY_DIR}/requirements.txt.indigo ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) - catkin_generate_virtualenv(PYTHON_INTERPRETER python2) -elseif("$ENV{ROS_DISTRO}" STRGREATER "melodic") + file(DOWNLOAD http://curl.haxx.se/ca/cacert.pem ${CMAKE_BINARY_DIR}/cacert.pem) + catkin_generate_virtualenv(PYTHON_INTERPRETER python2 + # https://stackoverflow.com/questions/25981703/pip-install-fails-with-connection-error-ssl-certificate-verify-failed + EXTRA_PIP_ARGS -vvv --cert=${CMAKE_BINARY_DIR}/cacert.pem) +elseif("$ENV{ROS_DISTRO}" STREQUAL "kinetic" OR "$ENV{ROS_DISTRO}" STREQUAL "melodic") catkin_generate_virtualenv( - INPUT_REQUIREMENTS requirements.in.noetic - PYTHON_INTERPRETER python3 + INPUT_REQUIREMENTS requirements.in + PYTHON_INTERPRETER python2 ) else() catkin_generate_virtualenv( - INPUT_REQUIREMENTS requirements.in - PYTHON_INTERPRETER python2 + INPUT_REQUIREMENTS requirements.in.noetic + PYTHON_INTERPRETER python3 ) endif() @@ -73,7 +76,7 @@ catkin_install_python( PROGRAMS ${PYTHON_SCRIPT_FILES} DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) -install(DIRECTORY launch +install(DIRECTORY launch apps samples config DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} USE_SOURCE_PERMISSIONS ) diff --git a/dialogflow_task_executive/requirements.in.noetic b/dialogflow_task_executive/requirements.in.noetic index 7fe2d496f..63615e454 100644 --- a/dialogflow_task_executive/requirements.in.noetic +++ b/dialogflow_task_executive/requirements.in.noetic @@ -3,3 +3,4 @@ google-api-core[grpc]==1.33.0 grpcio-status==1.48.1 googleapis-common-protos[grpc]==1.56.2 protobuf==3.20.1 # fix Could not find a version that matches protobuf<4.0.0dev,<5.0.0dev,>=3.15.0,>=3.20.1,>=4.21.3 (from google-api-core[grpc]==1.33.1->dialogflow==1.1.1->-r requirements.in (line 1)) +grpcio==1.54.0 # via google-api-core, googleapis-common-protos, grpcio-status diff --git a/dialogflow_task_executive/test/test_rospy_node.py b/dialogflow_task_executive/test/test_rospy_node.py index 318d6c8d6..a56f75054 100644 --- a/dialogflow_task_executive/test/test_rospy_node.py +++ b/dialogflow_task_executive/test/test_rospy_node.py @@ -27,11 +27,15 @@ def test_rosnode(self): 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')]: + for filename in [f for f in map(lambda x: 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) + import subprocess + try: + ret = subprocess.check_output(['rosrun', pkg_name, filename], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + print("Catch runtime error ({}), check if this is expect".format(e.output)) + self.assertTrue('Check the device is connected and recognized' in e.output) + 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 index e47acaf68..c692f54ca 100644 --- a/dialogflow_task_executive/test/test_rospy_node.test +++ b/dialogflow_task_executive/test/test_rospy_node.test @@ -1,3 +1,3 @@ - + diff --git a/gdrive_ros/CMakeLists.txt b/gdrive_ros/CMakeLists.txt index 68e8904bb..dab4abaef 100644 --- a/gdrive_ros/CMakeLists.txt +++ b/gdrive_ros/CMakeLists.txt @@ -23,10 +23,20 @@ catkin_package( message_runtime ) +if("$ENV{ROS_DISTRO}" STREQUAL "indigo") +file(DOWNLOAD http://curl.haxx.se/ca/cacert.pem ${CMAKE_BINARY_DIR}/cacert.pem) catkin_generate_virtualenv( PYTHON_INTERPRETER python3 CHECK_VENV FALSE + # https://stackoverflow.com/questions/25981703/pip-install-fails-with-connection-error-ssl-certificate-verify-failed + EXTRA_PIP_ARGS -vvv --cert=${CMAKE_BINARY_DIR}/cacert.pem ) +else() +catkin_generate_virtualenv( + PYTHON_INTERPRETER python3 + CHECK_VENV FALSE + ) +endif() catkin_install_python(PROGRAMS node_scripts/gdrive_server_node.py node_scripts/sample_gdrive_rospy_client.py diff --git a/jsk_3rdparty/package.xml b/jsk_3rdparty/package.xml index b878b227b..ac9194395 100644 --- a/jsk_3rdparty/package.xml +++ b/jsk_3rdparty/package.xml @@ -55,7 +55,7 @@ zdepth - collada_urdf_jsk_patch + laser_filters_jsk_patch diff --git a/jsk_ros_patch/collada_urdf_jsk_patch/CMakeLists.txt b/jsk_ros_patch/collada_urdf_jsk_patch/CMakeLists.txt index 322560351..65643bfb6 100644 --- a/jsk_ros_patch/collada_urdf_jsk_patch/CMakeLists.txt +++ b/jsk_ros_patch/collada_urdf_jsk_patch/CMakeLists.txt @@ -5,12 +5,19 @@ project(collada_urdf_jsk_patch) if(("$ENV{ROS_DISTRO}" STREQUAL "groovy") OR ("$ENV{ROS_DISTRO}" STREQUAL "hydro")) set(SOURCE_DISTRO hydro-devel) set(GIT_REPO robot_model) -elseif ("$ENV{ROS_DISTRO}" STRLESS "melodic") +elseif (("$ENV{ROS_DISTRO}" STREQUAL "indigo") OR ("$ENV{ROS_DISTRO}" STREQUAL "jade") OR ("$ENV{ROS_DISTRO}" STREQUAL "kinetic")) set(SOURCE_DISTRO bd4fc369d56eaa0c31d8cb17677e00b9d9685de6) # 1.11.13, before strip indigo set(GIT_REPO robot_model) -elseif ("$ENV{ROS_DISTRO}" STREQUAL "melodic" OR "$ENV{ROS_DISTRO}" STRGREATER "melodic") +elseif (("$ENV{ROS_DISTRO}" STREQUAL "melodic") OR ("$ENV{ROS_DISTRO}" STREQUAL "noetic")) set(SOURCE_DISTRO 923c5d33bd245e82134e8ae02e4c9d379e80eb27) # 1.12.12 set(GIT_REPO collada_urdf) +else() + find_package(catkin REQUIRED) + catkin_package(CATKIN_DEPENDS) + file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/CATKIN_IGNORE) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CATKIN_IGNORE #catkin_lint: ignore_once missing_file + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) + return() endif() if ("$ENV{ROS_DISTRO}" STREQUAL "kinetic" OR "$ENV{ROS_DISTRO}" STRGREATER "kinetic") set(CXXFLAGS CXXFLAGS=-std=gnu++11) diff --git a/julius_ros/test/julius.test b/julius_ros/test/julius.test index 06cfce3b1..361fc0259 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 98e3d1c62..488994054 100644 --- a/nfc_ros/CMakeLists.txt +++ b/nfc_ros/CMakeLists.txt @@ -20,7 +20,7 @@ catkin_package( CATKIN_DEPENDS message_runtime ) -if ("$ENV{ROS_DISTRO}" MATCHES "indigo") +if ("$ENV{ROS_DISTRO}" MATCHES "indigo" OR "$ENV{ROS_DISTRO}" MATCHES "kinetic") message(WARNING "nfc_ros requires python3.6 or newer. For indigo, virtualenv generation is skipped.") else() catkin_generate_virtualenv( @@ -36,7 +36,7 @@ install(FILES requirements.txt DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} ) -if(CATKIN_ENABLE_TESTING) +if(CATKIN_ENABLE_TESTING AND ("$ENV{ROS_DISTRO}" MATCHES "indigo" OR "$ENV{ROS_DISTRO}" MATCHES "kinetic")) find_package(rostest REQUIRED) add_rostest(test/test_rospy_node.test DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv diff --git a/respeaker_ros/CMakeLists.txt b/respeaker_ros/CMakeLists.txt index 9322760c9..0eb66f1df 100644 --- a/respeaker_ros/CMakeLists.txt +++ b/respeaker_ros/CMakeLists.txt @@ -14,14 +14,21 @@ generate_dynamic_reconfigure_options( catkin_package() -if($ENV{ROS_DISTRO} STRGREATER "melodic") +if("$ENV{ROS_DISTRO}" STREQUAL "kinetic" OR "$ENV{ROS_DISTRO}" STREQUAL "melodic") catkin_generate_virtualenv( - PYTHON_INTERPRETER python3 + PYTHON_INTERPRETER python2 CHECK_VENV FALSE ) -else() +elseif("$ENV{ROS_DISTRO}" STREQUAL "indigo") + file(DOWNLOAD http://curl.haxx.se/ca/cacert.pem ${CMAKE_BINARY_DIR}/cacert.pem) catkin_generate_virtualenv( PYTHON_INTERPRETER python2 + # https://stackoverflow.com/questions/25981703/pip-install-fails-with-connection-error-ssl-certificate-verify-failed + EXTRA_PIP_ARGS -vvv --cert=${CMAKE_BINARY_DIR}/cacert.pem + ) +else() + catkin_generate_virtualenv( + PYTHON_INTERPRETER python3 CHECK_VENV FALSE ) endif() diff --git a/ros_google_cloud_language/test/test_rospy_node.py b/ros_google_cloud_language/test/test_rospy_node.py old mode 100755 new mode 100644 diff --git a/ros_speech_recognition/CMakeLists.txt b/ros_speech_recognition/CMakeLists.txt index 9810c3377..ba2d07b93 100644 --- a/ros_speech_recognition/CMakeLists.txt +++ b/ros_speech_recognition/CMakeLists.txt @@ -29,7 +29,16 @@ if($ENV{ROS_DISTRO} STREQUAL "indigo" OR COMMAND cp ${CMAKE_CURRENT_BINARY_DIR}/requirements.txt.indigo ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) - catkin_generate_virtualenv(PYTHON_INTERPRETER python2) + if("$ENV{ROS_DISTRO}" STREQUAL "indigo") + file(DOWNLOAD http://curl.haxx.se/ca/cacert.pem ${CMAKE_BINARY_DIR}/cacert.pem) + catkin_generate_virtualenv( + PYTHON_INTERPRETER python2 + # https://stackoverflow.com/questions/25981703/pip-install-fails-with-connection-error-ssl-certificate-verify-failed + EXTRA_PIP_ARGS -vvv --cert=${CMAKE_BINARY_DIR}/cacert.pem + ) + else() + catkin_generate_virtualenv(PYTHON_INTERPRETER python2) + endif() else() catkin_generate_virtualenv( PYTHON_INTERPRETER python3 diff --git a/ros_speech_recognition/README.md b/ros_speech_recognition/README.md index ec116f7df..fba1d1dc2 100644 --- a/ros_speech_recognition/README.md +++ b/ros_speech_recognition/README.md @@ -41,7 +41,7 @@ roslaunch ros_speech_recognition parrotry.launch language:=ja-JP ### Publishing Topics -* `~voice_topic` (`sound_recognition_msgs/SpeechReconitionCandidates`) +* `~voice_topic` (`speech_recognition_msgs/SpeechRecognitionCandidates`) Speech recognition candidates topic name. diff --git a/rosping/CMakeLists.txt b/rosping/CMakeLists.txt index de150bbae..82ea618a2 100644 --- a/rosping/CMakeLists.txt +++ b/rosping/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 2.8.3) project(rosping) +SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--disable-new-dtags") + find_package(catkin REQUIRED COMPONENTS roscpp std_msgs rostest) # Set the build type. Options are: diff --git a/sesame_ros/CMakeLists.txt b/sesame_ros/CMakeLists.txt index b154ba71a..f9ac9a4ae 100644 --- a/sesame_ros/CMakeLists.txt +++ b/sesame_ros/CMakeLists.txt @@ -20,13 +20,22 @@ catkin_package( CATKIN_DEPENDS message_runtime ) -if($ENV{ROS_DISTRO} STRGREATER "melodic") +if($ENV{ROS_DISTRO} STREQUAL "kinetic" OR $ENV{ROS_DISTRO} STREQUAL "melodic") catkin_generate_virtualenv( - PYTHON_INTERPRETER python3 + PYTHON_INTERPRETER python2 + CHECK_VENV FALSE ) -else() +elseif("$ENV{ROS_DISTRO}" STREQUAL "indigo") + file(DOWNLOAD http://curl.haxx.se/ca/cacert.pem ${CMAKE_BINARY_DIR}/cacert.pem) catkin_generate_virtualenv( PYTHON_INTERPRETER python2 + # https://stackoverflow.com/questions/25981703/pip-install-fails-with-connection-error-ssl-certificate-verify-failed + EXTRA_PIP_ARGS -vvv --cert=${CMAKE_BINARY_DIR}/cacert.pem + ) +else() + catkin_generate_virtualenv( + PYTHON_INTERPRETER python3 + CHECK_VENV FALSE ) endif() diff --git a/sesame_ros/requirements.txt b/sesame_ros/requirements.txt index 7bb97d5c5..c76134ce7 100644 --- a/sesame_ros/requirements.txt +++ b/sesame_ros/requirements.txt @@ -1,6 +1,5 @@ asn1crypto==1.3.0 certifi==2020.6.20 -cffi==1.14.0 chardet==3.0.4 cryptography==2.3 enum34==1.1.10 diff --git a/switchbot_ros/CMakeLists.txt b/switchbot_ros/CMakeLists.txt index e5426bb6a..cac584f7d 100644 --- a/switchbot_ros/CMakeLists.txt +++ b/switchbot_ros/CMakeLists.txt @@ -5,6 +5,7 @@ find_package( catkin REQUIRED COMPONENTS message_generation actionlib_msgs + catkin_virtualenv ) catkin_python_setup() @@ -13,6 +14,11 @@ add_message_files( FILES Device.msg DeviceArray.msg + Meter.msg + PlugMini.msg + Hub2.msg + Bot.msg + StripLight.msg ) add_action_files( @@ -27,6 +33,21 @@ generate_messages( catkin_package() +if("$ENV{ROS_DISTRO}" STREQUAL "indigo") +file(DOWNLOAD http://curl.haxx.se/ca/cacert.pem ${CMAKE_BINARY_DIR}/cacert.pem) +catkin_generate_virtualenv( + PYTHON_INTERPRETER python3 + CHECK_VENV FALSE + # https://stackoverflow.com/questions/25981703/pip-install-fails-with-connection-error-ssl-certificate-verify-failed + EXTRA_PIP_ARGS -vvv --cert=${CMAKE_BINARY_DIR}/cacert.pem +) +else() +catkin_generate_virtualenv( + PYTHON_INTERPRETER python3 + CHECK_VENV FALSE +) +endif() + include_directories() # install diff --git a/switchbot_ros/launch/switchbot.launch b/switchbot_ros/launch/switchbot.launch index b2a4d2aba..cb9a68a96 100644 --- a/switchbot_ros/launch/switchbot.launch +++ b/switchbot_ros/launch/switchbot.launch @@ -1,11 +1,28 @@ + + + + token: $(arg token) + secret: $(arg secret) + + + + token: $(arg token) + secret: $(arg secret) + device_name: $(arg pub_device_name) + rate: $(arg pub_status_rate) + + + diff --git a/switchbot_ros/msg/Bot.msg b/switchbot_ros/msg/Bot.msg new file mode 100644 index 000000000..74955d462 --- /dev/null +++ b/switchbot_ros/msg/Bot.msg @@ -0,0 +1,10 @@ +string DEVICEMODE_PRESS = "pressMode" +string DEVICEMODE_SWITCH = "switchMode" +string DEVICEMODE_CUSTOMIZE = "customizeMode" + +Header header # timestamp + +float64 battery # the current battery level, 0-100 + +bool power # ON/OFF state True/False +string device_mode # pressMode, switchMode, or customizeMode diff --git a/switchbot_ros/msg/Hub2.msg b/switchbot_ros/msg/Hub2.msg new file mode 100644 index 000000000..dfdf40b4f --- /dev/null +++ b/switchbot_ros/msg/Hub2.msg @@ -0,0 +1,6 @@ +Header header # timestamp + +float64 temperature # temperature in celsius +float64 humidity # humidity percentage + +int64 light_level # the level of illuminance of the ambience light, 1~20 diff --git a/switchbot_ros/msg/Meter.msg b/switchbot_ros/msg/Meter.msg new file mode 100644 index 000000000..df731f552 --- /dev/null +++ b/switchbot_ros/msg/Meter.msg @@ -0,0 +1,5 @@ +Header header # timestamp + +float64 temperature # temperature in celsius +float64 humidity # humidity percentage +float64 battery # the current battery level, 0-100 diff --git a/switchbot_ros/msg/PlugMini.msg b/switchbot_ros/msg/PlugMini.msg new file mode 100644 index 000000000..e74b320d0 --- /dev/null +++ b/switchbot_ros/msg/PlugMini.msg @@ -0,0 +1,7 @@ +Header header # timestamp + +float64 voltage # the voltage of the device, measured in Volt +float64 weight # the power consumed in a day, measured in Watts +float64 current # the current of the device at the moment, measured in Amp + +int32 minutes_day # he duration that the device has been used during a day, measured in minutes diff --git a/switchbot_ros/msg/StripLight.msg b/switchbot_ros/msg/StripLight.msg new file mode 100644 index 000000000..4164d184e --- /dev/null +++ b/switchbot_ros/msg/StripLight.msg @@ -0,0 +1,8 @@ +Header header # timestamp + +bool power # ON/OFF state True/False + +int64 brightness # the brightness value, range from 1 to 100 +int64 color_r # Red color value 0-255 +int64 color_g # Green color value 0-255 +int64 color_b # Blue color value 0-255 \ No newline at end of file diff --git a/switchbot_ros/package.xml b/switchbot_ros/package.xml index 9aa05e990..0414d05f2 100644 --- a/switchbot_ros/package.xml +++ b/switchbot_ros/package.xml @@ -11,15 +11,14 @@ BSD catkin - python-setuptools - python3-setuptools + python3-setuptools actionlib actionlib_msgs + catkin_virtualenv message_generation - python-requests - python3-requests + python3-requests actionlib actionlib_msgs message_runtime diff --git a/switchbot_ros/scripts/control_switchbot.py b/switchbot_ros/scripts/control_switchbot.py new file mode 100644 index 000000000..3dede636e --- /dev/null +++ b/switchbot_ros/scripts/control_switchbot.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +import rospy +from switchbot_ros.switchbot_ros_client import SwitchBotROSClient + +rospy.init_node('controler_node') +client = SwitchBotROSClient() + +devices = client.get_devices() +print(devices) + +client.control_device('pendant-light', 'turnOn', wait=True) + +client.control_device('bot74a', 'turnOn', wait=True) + diff --git a/switchbot_ros/scripts/switchbot_ros_server.py b/switchbot_ros/scripts/switchbot_ros_server.py old mode 100755 new mode 100644 index 2793cb892..b20262ac9 --- a/switchbot_ros/scripts/switchbot_ros_server.py +++ b/switchbot_ros/scripts/switchbot_ros_server.py @@ -26,9 +26,20 @@ def __init__(self): self.token = f.read().replace('\n', '') else: self.token = token + + # Switchbot API v1.1 needs secret key + secret = rospy.get_param('~secret', None ) + if secret is not None and os.path.exists(secret): + with open(secret, 'r', encoding='utf-8') as f: + self.secret = f.read().replace('\n', '') + else: + self.secret = secret + # Initialize switchbot client self.bots = self.get_switchbot_client() + self.print_apiversion() self.print_devices() + self.print_scenes() # Actionlib self._as = actionlib.SimpleActionServer( '~switch', SwitchBotCommandAction, @@ -37,10 +48,11 @@ def __init__(self): # Topic self.pub = rospy.Publisher('~devices', DeviceArray, queue_size=1, latch=True) self.published = False + rospy.loginfo('Ready.') def get_switchbot_client(self): try: - client = SwitchBotAPIClient(token=self.token) + client = SwitchBotAPIClient(token=self.token, secret=self.secret) rospy.loginfo('Switchbot API Client initialized.') return client except ConnectionError: # If the machine is not connected to the internet @@ -57,31 +69,84 @@ def spin(self): self.publish_devices() self.published = True + def print_apiversion(self): + if self.bots is None: + return + + apiversion_str = 'Using SwitchBot API '; + apiversion_str += self.bots.api_version; + rospy.loginfo(apiversion_str) + + def print_devices(self): if self.bots is None: return - device_list_str = 'Switchbot device list:\n' + + device_list_str = 'Switchbot Device List:\n' device_list = sorted( self.bots.device_list, - key=lambda device: str(device['deviceName'])) + key=lambda device: str(device.get('deviceName'))) + device_list_str += str(len(device_list)) + ' Item(s)\n' for device in device_list: - device_list_str += 'Name: ' + str(device['deviceName']) - device_list_str += ', Type: ' + str(device['deviceType']) + device_list_str += 'deviceName: ' + str(device.get('deviceName')) + device_list_str += ', deviceID: ' + str(device.get('deviceId')) + device_list_str += ', deviceType: ' + str(device.get('deviceType')) device_list_str += '\n' rospy.loginfo(device_list_str) + + remote_list_str = 'Switchbot Remote List:\n' + infrared_remote_list = sorted( + self.bots.infrared_remote_list, + key=lambda infrared_remote: str(infrared_remote.get('deviceName'))) + remote_list_str += str(len(infrared_remote_list)) + ' Item(s)\n' + for infrared_remote in infrared_remote_list: + remote_list_str += 'deviceName: ' + str(infrared_remote.get('deviceName')) + remote_list_str += ', deviceID: ' + str(infrared_remote.get('deviceId')) + remote_list_str += ', remoteType: ' + str(infrared_remote.get('remoteType')) + remote_list_str += '\n' + rospy.loginfo(remote_list_str) + + + def print_scenes(self): + if self.bots is None: + return + + scene_list_str = 'Switchbot Scene List:\n' + scene_list = sorted( + self.bots.scene_list, + key=lambda scene: str(scene.get('sceneName'))) + scene_list_str += str(len(scene_list)) + ' Item(s)\n' + for scene in scene_list: + scene_list_str += 'sceneName: ' + str(scene.get('sceneName')) + scene_list_str += ', sceneID: ' + str(scene.get('sceneId')) + scene_list_str += '\n' + rospy.loginfo(scene_list_str) + def publish_devices(self): if self.bots is None: return + msg = DeviceArray() + device_list = sorted( self.bots.device_list, - key=lambda device: str(device['deviceName'])) + key=lambda device: str(device.get('deviceName'))) for device in device_list: msg_device = Device() - msg_device.name = str(device['deviceName']) - msg_device.type = str(device['deviceType']) + msg_device.name = str(device.get('deviceName')) + msg_device.type = str(device.get('deviceType')) + msg.devices.append(msg_device) + + infrared_remote_list = sorted( + self.bots.infrared_remote_list, + key=lambda infrared_remote: str(infrared_remote.get('deviceName'))) + for infrared_remote in infrared_remote_list: + msg_device = Device() + msg_device.name = str(infrared_remote.get('deviceName')) + msg_device.type = str(infrared_remote.get('remoteType')) msg.devices.append(msg_device) + self.pub.publish(msg) def execute_cb(self, goal): @@ -97,7 +162,7 @@ def execute_cb(self, goal): command_type = 'command' try: if not self.bots: - self.bots = SwitchBotAPIClient(token=self.token) + self.bots = SwitchBotAPIClient(token=self.token, secret=self.secret) feedback.status = str( self.bots.control_device( command=goal.command, diff --git a/switchbot_ros/scripts/switchbot_status_publisher.py b/switchbot_ros/scripts/switchbot_status_publisher.py new file mode 100644 index 000000000..6ed571728 --- /dev/null +++ b/switchbot_ros/scripts/switchbot_status_publisher.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python + +import os.path +from requests import ConnectionError +import rospy +from switchbot_ros.switchbot import SwitchBotAPIClient +from switchbot_ros.switchbot import DeviceError, SwitchBotAPIError +from switchbot_ros.msg import Meter, PlugMini, Hub2, Bot, StripLight + + +class SwitchBotStatusPublisher: + """ + Publissh your switchbot status with ROS and SwitchBot API + """ + def __init__(self): + # SwitchBot configs + # '~token' can be file path or raw characters + token = rospy.get_param('~token') + if os.path.exists(token): + with open(token) as f: + self.token = f.read().replace('\n', '') + else: + self.token = token + + # Switchbot API v1.1 needs secret key + secret = rospy.get_param('~secret', None) + if secret is not None and os.path.exists(secret): + with open(secret, 'r', encoding='utf-8') as f: + self.secret = f.read().replace('\n', '') + else: + self.secret = secret + + # Initialize switchbot client + self.bots = self.get_switchbot_client() + self.print_apiversion() + + # Get parameters for publishing + self.rate = rospy.get_param('~rate', 0.1) + rospy.loginfo('Rate: ' + str(self.rate)) + + device_name = rospy.get_param('~device_name') + if device_name: + self.device_name = device_name + else: + rospy.logerr('No Device Name') + return + + self.device_type = None + self.device_list = sorted( + self.bots.device_list, + key=lambda device: str(device.get('deviceName'))) + for device in self.device_list: + device_name = str(device.get('deviceName')) + if self.device_name == device_name: + self.device_type = str(device.get('deviceType')) + + if self.device_type: + rospy.loginfo('deviceName: ' + self.device_name + ' / deviceType: ' + self.device_type) + else: + rospy.logerr('Invalid Device Name: ' + self.device_name) + return + + topic_name = '~' + self.device_name + topic_name = topic_name.replace('-', '_') + + # Publisher Message Class for each device type + if self.device_type == 'Remote': + rospy.logerr('Device Type: "' + self.device_type + '" has no status in specifications.') + return + else: + if self.device_type == 'Meter': + self.msg_class = Meter + elif self.device_type == 'MeterPlus': + self.msg_class = Meter + elif self.device_type == 'WoIOSensor': + self.msg_class = Meter + elif self.device_type == 'Hub 2': + self.msg_class = Hub2 + elif self.device_type == 'Plug Mini (JP)': + self.msg_class = PlugMini + elif self.device_type == 'Plug Mini (US)': + self.msg_class = PlugMini + elif self.device_type == 'Bot': + self.msg_class = Bot + elif self.device_type == 'Strip Light': + self.msg_class = StripLight + else: + rospy.logerr('No publisher process for "' + self.device_type + '" in switchbot_status_publisher.py') + return + + self.status_pub = rospy.Publisher(topic_name, self.msg_class, queue_size=1, latch=True) + + rospy.loginfo('Ready: SwitchBot Status Publisher for ' + self.device_name) + + + def get_switchbot_client(self): + try: + client = SwitchBotAPIClient(token=self.token, secret=self.secret) + rospy.loginfo('Switchbot API Client initialized.') + return client + except ConnectionError: # If the machine is not connected to the internet + rospy.logwarn_once('Failed to connect to the switchbot server. The client would try connecting to it when subscribes the ActionGoal topic.') + return None + + + def spin(self): + rate = rospy.Rate(self.rate) + while not rospy.is_shutdown(): + rate.sleep() + if self.bots is None: + self.bots = self.get_switchbot_client() + + if self.device_type == 'Remote': + return + else: + status = self.get_device_status(device_name=self.device_name) + + if status: + time = rospy.get_rostime() + if self.msg_class == Meter: + msg = Meter() + msg.header.stamp = time + msg.temperature = status['temperature'] + msg.humidity = status['humidity'] + msg.battery = status['battery'] + elif self.msg_class == Hub2: + msg = Hub2() + msg.header.stamp = time + msg.temperature = status['temperature'] + msg.humidity = status['humidity'] + msg.light_level = status['lightLevel'] + elif self.msg_class == PlugMini: + msg = PlugMini() + msg.header.stamp = time + msg.voltage = status['voltage'] + msg.weight = status['weight'] + msg.current = status['electricCurrent'] + msg.minutes_day = status['electricityOfDay'] + elif self.msg_class == Bot: + msg = Bot() + msg.header.stamp = time + msg.battery = status['battery'] + if status['power'] == 'on': + msg.power = True + else: + msg.power = False + msg.device_mode = status['deviceMode'] + elif self.msg_class == StripLight: + msg = StripLight() + msg.header.stamp = time + if status['power'] == 'on': + msg.power = True + else: + msg.power = False + msg.brightness = status['brightness'] + rgb_string = status['color'] + r, g, b = map(int, rgb_string.split(':')) + msg.color_r = r + msg.color_g = g + msg.color_b = b + else: + return + + if msg: + self.status_pub.publish(msg) + + + def get_device_status(self, device_name=None): + if self.bots is None: + return + elif device_name: + status = self.bots.device_status(device_name=device_name) + return status + else: + return + + + def print_apiversion(self): + if self.bots is None: + return + + apiversion_str = 'Using SwitchBot API '; + apiversion_str += self.bots.api_version; + rospy.loginfo(apiversion_str) + + +if __name__ == '__main__': + try: + rospy.init_node('switchbot_status_publisher') + ssp = SwitchBotStatusPublisher() + ssp.spin() + except rospy.ROSInterruptException: + pass diff --git a/switchbot_ros/src/switchbot_ros/switchbot.py b/switchbot_ros/src/switchbot_ros/switchbot.py index e7f75df21..7468774a2 100644 --- a/switchbot_ros/src/switchbot_ros/switchbot.py +++ b/switchbot_ros/src/switchbot_ros/switchbot.py @@ -4,15 +4,28 @@ import os.path import requests +import sys +import os +import time +import hashlib +import hmac +import base64 +import uuid + class SwitchBotAPIClient(object): """ For Using SwitchBot via official API. Please see https://github.com/OpenWonderLabs/SwitchBotAPI for details. """ - def __init__(self, token): - self._host_domain = "https://api.switch-bot.com/v1.0/" + def __init__(self, token, secret=""): + if not secret: + self.api_version = "v1.0" + else: + self.api_version = "v1.1" + self._host_domain = "https://api.switch-bot.com/" + self.api_version + "/" self.token = token + self.secret = secret # SwitchBot API v1.1 self.device_list = None self.infrared_remote_list = None self.scene_list = None @@ -21,6 +34,41 @@ def __init__(self, token): self.update_device_list() self.update_scene_list() + def make_sign(self, token, secret): + """ + Make Sign from token and secret + """ + nonce = uuid.uuid4() + t = int(round(time.time() * 1000)) + string_to_sign = '{}{}{}'.format(token, t, nonce) + + if sys.version_info[0] > 2: + string_to_sign = bytes(string_to_sign, 'utf-8') + secret = bytes(secret, 'utf-8') + else: + string_to_sign = bytes(string_to_sign) + secret = bytes(secret) + + sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest()) + + if sys.version_info[0] > 2: + sign = sign.decode('utf-8') + + return sign, str(t), nonce + + def make_request_header(self, token, secret): + """ + Make Request Header + """ + sign, t, nonce = self.make_sign(token, secret) + headers={ + "Authorization": token, + "sign": str(sign), + "t": str(t), + "nonce": str(nonce), + "Content-Type": "application/json; charset=utf8" + } + return headers def request(self, method='GET', devices_or_scenes='devices', service_id='', service='', json_body=None): """ @@ -30,20 +78,20 @@ def request(self, method='GET', devices_or_scenes='devices', service_id='', serv raise ValueError('Please set devices_or_scenes variable devices or scenes') url = os.path.join(self._host_domain, devices_or_scenes, service_id, service) + + headers = self.make_request_header(self.token, self.secret) if method == 'GET': response = requests.get( url, - headers={'Authorization': self.token} + headers=headers ) elif method == 'POST': response = requests.post( url, - json_body, - headers={ - 'Content-Type': 'application/json; charset=utf8', - 'Authorization': self.token - }) + json=json_body, + headers=headers + ) else: raise ValueError('Got unexpected http request method. Please use GET or POST.') @@ -87,8 +135,8 @@ def update_device_list(self): self.infrared_remote_list = res['body']['infraredRemoteList'] for device in self.device_list: self.device_name_id[device['deviceName']] = device['deviceId'] - for infrated_remote in self.infrared_remote_list: - self.device_name_id[device['deviceName']] = device['deviceId'] + for infrared_remote in self.infrared_remote_list: + self.device_name_id[infrared_remote['deviceName']] = infrared_remote['deviceId'] return self.device_list, self.infrared_remote_list @@ -99,7 +147,7 @@ def update_scene_list(self): """ self.scene_list = self.request(devices_or_scenes='scenes')['body'] for scene in self.scene_list: - self.scene_name_id[scene['sceneName']] = device['sceneId'] + self.scene_name_id[scene['sceneName']] = scene['sceneId'] return self.scene_list @@ -125,11 +173,11 @@ def control_device(self, command, parameter='default', command_type='command', d """ Send Command to the device. Please see https://github.com/OpenWonderLabs/SwitchBotAPI#send-device-control-commands for command options. """ - json_body = json.dumps({ - "command": command, - "parameter": parameter, - "commandType": command_type - }) + json_body = { + "command": str(command), + "parameter": str(parameter), + "commandType": str(command_type) + } if device_id: pass elif device_name: diff --git a/switchbot_ros/src/switchbot_ros/switchbot_ros_client.py b/switchbot_ros/src/switchbot_ros/switchbot_ros_client.py index cd1b8a8d8..df1a7b00a 100644 --- a/switchbot_ros/src/switchbot_ros/switchbot_ros_client.py +++ b/switchbot_ros/src/switchbot_ros/switchbot_ros_client.py @@ -9,7 +9,8 @@ class SwitchBotROSClient(object): def __init__(self, actionname='switchbot_ros/switch', - topicname='switchbot_ros/devices'): + topicname='switchbot_ros/devices', + timeout=5): self.actionname = actionname self.topicname = topicname @@ -17,6 +18,8 @@ def __init__(self, actionname, SwitchBotCommandAction ) + rospy.loginfo("Waiting for action server to start. (timeout: " + str(timeout) + "[sec])") + self.action_client.wait_for_server(timeout=rospy.Duration(timeout,0)) def get_devices(self, timeout=None):