├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug.md │ └── feature.md └── workflows │ └── ci.yml ├── .gitignore ├── wireless_msgs ├── CHANGELOG.rst ├── CMakeLists.txt ├── msg │ ├── Connection.msg │ ├── Network.msg │ ├── Quality.msg │ └── Scan.msg └── package.xml └── wireless_watcher ├── CHANGELOG.rst ├── CMakeLists.txt ├── include └── wireless_watcher │ └── wireless_watcher.hpp ├── launch └── watcher.launch.py ├── package.xml └── src └── wireless_watcher.cpp /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Default all changes will request review from: 2 | * @clearpathrobotics/clearpath-platform-team 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Provide a report for that the issue is 4 | title: '' 5 | labels: bug 6 | assignees: clearpathrobotics/clearpath-platform-team 7 | 8 | --- 9 | 10 | **Please provide the following information:** 11 | - OS: (e.g. Ubuntu 24.04) 12 | - ROS 2 Distro: (e.g. Jazzy) 13 | - Built from source or installed: 14 | - Package version: (if from repository, give version from `sudo dpkg -s ros-$ROS_DISTRO-wireless-watcher`, if from source, give commit hash) 15 | 16 | 17 | **Expected behaviour** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Actual behaviour** 21 | A clear and concise description of what you encountered. 22 | 23 | **To Reproduce** 24 | Provide the steps to reproduce: 25 | 1. run something 26 | 2. launch something else 27 | 3. see the error 28 | 29 | 30 | **Other notes** 31 | Add anything else you think is important. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Provide context for the feature you are requesting 4 | title: '' 5 | labels: enhancement 6 | assignees: clearpathrobotics/clearpath-platform-team 7 | 8 | --- 9 | 10 | **Describe the the feature you would like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Other notes** 14 | Add anything else you think is important. 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: wireless_watcher_ci 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" # every day at midnight 8 | 9 | jobs: 10 | wireless_watcher_osrf_industrial_ci_humble: 11 | name: Humble OSRF Industrial 12 | strategy: 13 | matrix: 14 | env: 15 | - {ROS_REPO: testing, ROS_DISTRO: humble} 16 | - {ROS_REPO: main, ROS_DISTRO: humble} 17 | fail-fast: false 18 | runs-on: ubuntu-22.04 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: 'ros-industrial/industrial_ci@master' 22 | env: ${{matrix.env}} 23 | wireless_watcher_cpr_ci_humble: 24 | name: Humble Clearpath Release 25 | runs-on: ubuntu-22.04 26 | steps: 27 | - uses: actions/checkout@v3 28 | - uses: ros-tooling/setup-ros@v0.7 29 | with: 30 | required-ros-distributions: humble 31 | - name: clearpath-package-server 32 | run: | 33 | sudo apt install wget 34 | wget https://packages.clearpathrobotics.com/public.key -O - | sudo apt-key add - 35 | sudo sh -c 'echo "deb https://packages.clearpathrobotics.com/stable/ubuntu $(lsb_release -cs) main" > /etc/apt/sources.list.d/clearpath-latest.list' 36 | sudo apt-get update 37 | - uses: ros-tooling/action-ros-ci@v0.3 38 | id: action_ros_ci_step 39 | with: 40 | target-ros2-distro: humble 41 | package-name: | 42 | wireless_watcher 43 | wireless_watcher_src_ci_humble: 44 | name: Humble Clearpath Source 45 | runs-on: ubuntu-22.04 46 | steps: 47 | - uses: actions/checkout@v3 48 | - uses: ros-tooling/setup-ros@v0.7 49 | with: 50 | required-ros-distributions: humble 51 | - uses: ros-tooling/action-ros-ci@v0.3 52 | id: action_ros_ci_step 53 | with: 54 | target-ros2-distro: humble 55 | package-name: | 56 | wireless_watcher 57 | wireless_watcher_osrf_industrial_ci_jazzy: 58 | name: Jazzy OSRF Industrial 59 | strategy: 60 | matrix: 61 | env: 62 | - {ROS_REPO: testing, ROS_DISTRO: jazzy} 63 | - {ROS_REPO: main, ROS_DISTRO: jazzy} 64 | fail-fast: false 65 | runs-on: ubuntu-24.04 66 | steps: 67 | - uses: actions/checkout@v3 68 | - uses: 'ros-industrial/industrial_ci@master' 69 | env: ${{matrix.env}} 70 | wireless_watcher_cpr_ci_jazzy: 71 | name: Jazzy Clearpath Release 72 | runs-on: ubuntu-24.04 73 | steps: 74 | - uses: actions/checkout@v3 75 | - uses: ros-tooling/setup-ros@v0.7 76 | with: 77 | required-ros-distributions: jazzy 78 | - name: clearpath-package-server 79 | run: | 80 | sudo apt install wget 81 | wget https://packages.clearpathrobotics.com/public.key -O - | sudo apt-key add - 82 | sudo sh -c 'echo "deb https://packages.clearpathrobotics.com/stable/ubuntu $(lsb_release -cs) main" > /etc/apt/sources.list.d/clearpath-latest.list' 83 | sudo apt-get update 84 | - uses: ros-tooling/action-ros-ci@v0.3 85 | id: action_ros_ci_step 86 | with: 87 | target-ros2-distro: jazzy 88 | package-name: | 89 | wireless_watcher 90 | wireless_watcher_src_ci_jazzy: 91 | name: Jazzy Clearpath Source 92 | runs-on: ubuntu-24.04 93 | steps: 94 | - uses: actions/checkout@v3 95 | - uses: ros-tooling/setup-ros@v0.7 96 | with: 97 | required-ros-distributions: jazzy 98 | - uses: ros-tooling/action-ros-ci@v0.3 99 | id: action_ros_ci_step 100 | with: 101 | target-ros2-distro: jazzy 102 | package-name: | 103 | wireless_watcher 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | msg_gen 3 | wireless_msgs/src/wireless_msgs 4 | wireless_quality/src/wireless_quality 5 | *.py[co] 6 | *.swp 7 | bin 8 | *.cfgc 9 | wireless_quality/cfg/wireless_quality 10 | docs 11 | -------------------------------------------------------------------------------- /wireless_msgs/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package wireless_msgs 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 1.1.5 (2025-04-30) 6 | ------------------ 7 | * Added Github codeowners, issue template, CI. Also, fixed linting. (`#24 `_) 8 | * Contributors: Tony Baltovski 9 | 10 | 1.1.4 (2025-04-21) 11 | ------------------ 12 | 13 | 1.1.3 (2025-03-03) 14 | ------------------ 15 | 16 | 1.1.2 (2024-12-11) 17 | ------------------ 18 | 19 | 1.1.1 (2024-08-29) 20 | ------------------ 21 | 22 | 1.1.0 (2024-03-06) 23 | ------------------ 24 | 25 | 1.0.1 (2023-05-01) 26 | ------------------ 27 | 28 | 1.0.0 (2022-10-16) 29 | ------------------ 30 | * Minor fixes 31 | * Updated wireless_msgs for ROS2. 32 | Updated wireless_watcher for ROS2. 33 | * Contributors: Roni Kreinin 34 | 35 | 0.1.2 (2021-11-30) 36 | ------------------ 37 | 38 | 0.1.1 (2020-05-01) 39 | ------------------ 40 | 41 | 0.1.0 (2019-10-10) 42 | ------------------ 43 | 44 | 0.0.9 (2018-11-27) 45 | ------------------ 46 | 47 | 0.0.8 (2018-11-07) 48 | ------------------ 49 | * Package format 2. 50 | * Contributors: Mike Purvis 51 | 52 | 0.0.7 (2015-09-09) 53 | ------------------ 54 | * Added frequency to watcher node 55 | * Contributors: Alex Bencz 56 | 57 | 0.0.6 (2015-09-02) 58 | ------------------ 59 | 60 | 0.0.5 (2015-08-25) 61 | ------------------ 62 | 63 | 0.0.4 (2015-06-25) 64 | ------------------ 65 | 66 | 0.0.3 (2015-06-17) 67 | ------------------ 68 | * Added ESSID and BSSID fields, use floats for bitrate 69 | * Contributors: Alex Bencz 70 | 71 | 0.0.2 (2013-10-24) 72 | ------------------ 73 | 74 | 0.0.1 (2013-10-17) 75 | ------------------ 76 | * Catkinize wireless_msgs 77 | -------------------------------------------------------------------------------- /wireless_msgs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(wireless_msgs) 3 | 4 | # Default to C++14 5 | if(NOT CMAKE_CXX_STANDARD) 6 | set(CMAKE_CXX_STANDARD 14) 7 | endif() 8 | 9 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 10 | add_compile_options(-Wall -Wextra -Wpedantic) 11 | endif() 12 | 13 | find_package(ament_cmake REQUIRED) 14 | find_package(std_msgs REQUIRED) 15 | find_package(builtin_interfaces REQUIRED) 16 | find_package(rosidl_default_generators REQUIRED) 17 | 18 | rosidl_generate_interfaces(${PROJECT_NAME} 19 | msg/Connection.msg 20 | msg/Network.msg 21 | msg/Quality.msg 22 | msg/Scan.msg 23 | DEPENDENCIES std_msgs builtin_interfaces 24 | ) 25 | 26 | ament_export_dependencies(rosidl_default_runtime) 27 | 28 | ament_package() 29 | -------------------------------------------------------------------------------- /wireless_msgs/msg/Connection.msg: -------------------------------------------------------------------------------- 1 | float32 bitrate # Mb/s 2 | int16 txpower # dBm 3 | 4 | # Fractional link quality number preserved in raw 5 | # field, resolved to decimal for link_quality field. 6 | string link_quality_raw 7 | float32 link_quality 8 | 9 | int16 signal_level # dBm 10 | int16 noise_level # dBm 11 | 12 | string essid 13 | string bssid 14 | float64 frequency 15 | -------------------------------------------------------------------------------- /wireless_msgs/msg/Network.msg: -------------------------------------------------------------------------------- 1 | string type 2 | string essid 3 | string mac 4 | string mode 5 | string frequency 6 | bool encryption 7 | -------------------------------------------------------------------------------- /wireless_msgs/msg/Quality.msg: -------------------------------------------------------------------------------- 1 | std_msgs/Header header 2 | 3 | uint16 messages_received 4 | uint16 messages_missed 5 | 6 | uint32 total_length 7 | uint32[] message_lengths 8 | 9 | float32 latency_avg 10 | float32[] latency_measurements 11 | -------------------------------------------------------------------------------- /wireless_msgs/msg/Scan.msg: -------------------------------------------------------------------------------- 1 | Network[] networks 2 | -------------------------------------------------------------------------------- /wireless_msgs/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | wireless_msgs 5 | 1.1.5 6 | Messages for describing a wireless network such as bitrate, essid, and link quality. 7 | 8 | Roni Kreinin 9 | Tony Baltovski 10 | 11 | BSD 12 | 13 | Mike Purvis 14 | 15 | ament_cmake 16 | 17 | std_msgs 18 | 19 | builtin_interfaces 20 | rosidl_default_generators 21 | 22 | builtin_interfaces 23 | rosidl_default_runtime 24 | 25 | rosidl_interface_packages 26 | 27 | 28 | ament_cmake 29 | 30 | 31 | -------------------------------------------------------------------------------- /wireless_watcher/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package wireless_watcher 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 1.1.5 (2025-04-30) 6 | ------------------ 7 | * Added Github codeowners, issue template, CI. Also, fixed linting. (`#24 `_) 8 | * Contributors: Tony Baltovski 9 | 10 | 1.1.4 (2025-04-21) 11 | ------------------ 12 | * Add IP address info in diagnostic (`#23 `_) 13 | * Contributors: Hilary Luo 14 | 15 | 1.1.3 (2025-03-03) 16 | ------------------ 17 | * demark private variables with underscore 18 | * Run loop using a timer callback 19 | * Add Wi-Fi diagnostics 20 | * Contributors: Hilary Luo 21 | 22 | 1.1.2 (2024-12-11) 23 | ------------------ 24 | * Define static type in template (`#21 `_) 25 | * Contributors: luis-camero 26 | 27 | 1.1.1 (2024-08-29) 28 | ------------------ 29 | * Set NaN when empty BitRate/Frequency 30 | * Add try-catch to BitRate and Frequency field 31 | * Contributors: Luis Camero 32 | 33 | 1.1.0 (2024-03-06) 34 | ------------------ 35 | * License 36 | * Reimplemented in C++ 37 | * Use time.sleep instead of rate.sleep to properly exit loop on external shutdown 38 | Default 'dev' parameter to empty string rather than None 39 | * Contributors: Roni Kreinin 40 | 41 | 1.0.1 (2023-05-01) 42 | ------------------ 43 | * [wireless_watcher] Switched to underscores to get rid of usage of dash-separated warning. 44 | * Contributors: Tony Baltovski 45 | 46 | 1.0.0 (2022-10-16) 47 | ------------------ 48 | * Minor fixes 49 | * Added previous changelogs 50 | * Updated wireless_msgs for ROS2. 51 | Updated wireless_watcher for ROS2. 52 | * Contributors: Roni Kreinin 53 | 54 | 0.1.2 (2021-11-30) 55 | ------------------ 56 | * Add wireless-tools as a dependency (`#14 `_) 57 | * Contributors: Chris I-B 58 | 59 | 0.1.1 (2020-05-01) 60 | ------------------ 61 | * Improve connected detection logic so topic doesn't stop when interface is down 62 | * Contributors: Nikesh Bernardshaw 63 | 64 | 0.1.0 (2019-10-10) 65 | ------------------ 66 | * Python 3 fixes for watcher_node. (`#10 `_) 67 | * Contributors: Mike Purvis 68 | 69 | 0.0.9 (2018-11-27) 70 | ------------------ 71 | * Accept 'wifi' as device prefix for auto-detection. 72 | * Contributors: Mike Purvis 73 | 74 | 0.0.8 (2018-11-07) 75 | ------------------ 76 | * Package format 2. 77 | * Further pep8 fixups. 78 | * Auto-detect wl* device if not passed explicitly. 79 | * Contributors: Mike Purvis 80 | 81 | 0.0.7 (2015-09-09) 82 | ------------------ 83 | * Added frequency to watcher node 84 | * Contributors: Alex Bencz 85 | 86 | 0.0.6 (2015-09-02) 87 | ------------------ 88 | * Added queue_size parameter to publishers 89 | * Contributors: Mustafa Safri 90 | 91 | 0.0.5 (2015-08-25) 92 | ------------------ 93 | * Added checks for missing fields 94 | * Contributors: Mustafa Safri 95 | 96 | 0.0.4 (2015-06-25) 97 | ------------------ 98 | * Add install rule for launch dir 99 | * Contributors: Paul Bovbel 100 | 101 | 0.0.3 (2015-06-17) 102 | ------------------ 103 | * Added ESSID and BSSID fields, use floats for bitrate 104 | * Contributors: Alex Bencz 105 | 106 | 0.0.2 (2013-10-24) 107 | ------------------ 108 | * Workaround to suppress DummyThread error spew. 109 | * Add simple boolean to publish ethernet-has-ip status. 110 | 111 | 0.0.1 (2013-10-17) 112 | ------------------ 113 | * Catkinize wireless_watcher 114 | -------------------------------------------------------------------------------- /wireless_watcher/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(wireless_watcher) 3 | 4 | # Find dependencies 5 | find_package(ament_cmake REQUIRED) 6 | find_package(diagnostic_updater REQUIRED) 7 | find_package(rclcpp REQUIRED) 8 | find_package(std_msgs REQUIRED) 9 | find_package(wireless_msgs REQUIRED) 10 | 11 | # Add executable 12 | add_executable(wireless_watcher src/wireless_watcher.cpp) 13 | 14 | target_include_directories( 15 | wireless_watcher 16 | PRIVATE 17 | include 18 | ) 19 | 20 | # Link against ROS 2 libraries 21 | ament_target_dependencies(wireless_watcher 22 | diagnostic_updater 23 | rclcpp 24 | std_msgs 25 | wireless_msgs 26 | ) 27 | 28 | # Install executable 29 | install(TARGETS 30 | wireless_watcher 31 | DESTINATION lib/${PROJECT_NAME} 32 | ) 33 | 34 | # Install launch files 35 | install(DIRECTORY 36 | launch 37 | DESTINATION share/${PROJECT_NAME} 38 | ) 39 | 40 | # Export dependencies 41 | ament_export_dependencies(rclcpp std_msgs wireless_msgs) 42 | 43 | # Install package.xml 44 | install(FILES package.xml 45 | DESTINATION share/${PROJECT_NAME} 46 | ) 47 | 48 | if(BUILD_TESTING) 49 | find_package(ament_lint_auto REQUIRED) 50 | list(APPEND AMENT_LINT_AUTO_EXCLUDE 51 | ament_cmake_copyright 52 | ament_cmake_cpplint 53 | ament_cmake_uncrustify 54 | ) 55 | ament_lint_auto_find_test_dependencies() 56 | endif() 57 | 58 | # Specify install targets 59 | ament_package() 60 | -------------------------------------------------------------------------------- /wireless_watcher/include/wireless_watcher/wireless_watcher.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * \file 4 | * \brief Wireless Watcher Node 5 | * \author Mike Purvis 6 | * \author Roni Kreinin 7 | * \author Tony Baltovski 8 | * \copyright Copyright (c) 2023, Clearpath Robotics, Inc. 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * * Neither the name of Clearpath Robotics, Inc. nor the 18 | * names of its contributors may be used to endorse or promote products 19 | * derived from this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL CLEARPATH ROBOTICS, INC. BE LIABLE FOR ANY 25 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * Please send comments, questions, or patches to code@clearpathrobotics.com 33 | * 34 | */ 35 | 36 | #ifndef WIRELESS_WATCHER__WIRELESS_WATCHER_HPP_ 37 | #define WIRELESS_WATCHER__WIRELESS_WATCHER_HPP_ 38 | 39 | #include 40 | #include 41 | 42 | #include "diagnostic_updater/diagnostic_updater.hpp" 43 | #include "rclcpp/rclcpp.hpp" 44 | #include "std_msgs/msg/bool.hpp" 45 | #include "wireless_msgs/msg/connection.hpp" 46 | #include "wireless_msgs/msg/network.hpp" 47 | 48 | #define SYS_NET_PATH "/sys/class/net" 49 | #define SIGNAL_STRENGTH_WEAK -67 50 | #define SIGNAL_STRENGTH_VERY_WEAK -75 51 | 52 | class WirelessWatcher : public rclcpp::Node 53 | { 54 | public: 55 | WirelessWatcher(); 56 | 57 | private: 58 | // Parameters 59 | double hz; 60 | std::string dev; 61 | std::string connected_topic; 62 | std::string connection_topic; 63 | 64 | // Other Variables 65 | rclcpp::TimerBase::SharedPtr timer_; 66 | rclcpp::Publisher::SharedPtr connected_pub_; 67 | rclcpp::Publisher::SharedPtr connection_pub_; 68 | std_msgs::msg::Bool connected_msg_; 69 | wireless_msgs::msg::Connection connection_msg_; 70 | diagnostic_updater::Updater updater_; 71 | 72 | // Methods 73 | void timer_callback(); 74 | std::string exec_cmd(const std::string& cmd); 75 | std::vector split(const std::string& s, const std::string& delimiter); 76 | void diagnostic(diagnostic_updater::DiagnosticStatusWrapper & stat); 77 | void ip_address_diag(std::string dev, diagnostic_updater::DiagnosticStatusWrapper & stat); 78 | }; 79 | 80 | #endif // WIRELESS_WATCHER__WIRELESS_WATCHER_HPP_ 81 | -------------------------------------------------------------------------------- /wireless_watcher/launch/watcher.launch.py: -------------------------------------------------------------------------------- 1 | # Software License Agreement (BSD) 2 | # 3 | # @author Roni Kreinin 4 | # @copyright (c) 2022, Clearpath Robotics, Inc., All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # * Redistributions of source code must retain the above copyright notice, 9 | # this list of conditions and the following disclaimer. 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # * Neither the name of Clearpath Robotics nor the names of its contributors 14 | # may be used to endorse or promote products derived from this software 15 | # without specific prior written permission. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | # POSSIBILITY OF SUCH DAMAGE. 28 | 29 | 30 | from launch import LaunchDescription 31 | from launch.actions import DeclareLaunchArgument 32 | from launch.substitutions import LaunchConfiguration 33 | 34 | from launch_ros.actions import Node 35 | 36 | ARGUMENTS = [ 37 | DeclareLaunchArgument('hz', default_value='1.0', description='Update frequency'), 38 | DeclareLaunchArgument('dev', default_value='', description='Wireless device'), 39 | DeclareLaunchArgument( 40 | 'connected_topic', default_value='connected', description='Connected status topic' 41 | ), 42 | DeclareLaunchArgument( 43 | 'connection_topic', default_value='connection', description='Connection information topic' 44 | ), 45 | DeclareLaunchArgument('namespace', default_value='', description='Namespace'), 46 | ] 47 | 48 | 49 | def generate_launch_description(): 50 | 51 | watcher = Node( 52 | package='wireless_watcher', 53 | executable='wireless_watcher', 54 | name='wireless_watcher', 55 | namespace=LaunchConfiguration('namespace'), 56 | output='screen', 57 | parameters=[ 58 | { 59 | 'hz': LaunchConfiguration('hz'), 60 | 'dev': LaunchConfiguration('dev'), 61 | 'connected_topic': LaunchConfiguration('connected_topic'), 62 | 'connection_topic': LaunchConfiguration('connection_topic'), 63 | } 64 | ], 65 | ) 66 | 67 | ld = LaunchDescription(ARGUMENTS) 68 | ld.add_action(watcher) 69 | return ld 70 | -------------------------------------------------------------------------------- /wireless_watcher/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | wireless_watcher 5 | 1.1.5 6 | A node which publishes connection information about a linux wireless interface. 7 | 8 | Roni Kreinin 9 | Tony Baltovski 10 | 11 | BSD 12 | 13 | Mike Purvis 14 | 15 | diagnostic_updater 16 | rclcpp 17 | wireless_msgs 18 | wireless-tools 19 | 20 | ament_lint_auto 21 | ament_lint_common 22 | 23 | 24 | ament_cmake 25 | 26 | 27 | -------------------------------------------------------------------------------- /wireless_watcher/src/wireless_watcher.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * \file 4 | * \brief Wireless Watcher Node 5 | * \author Mike Purvis 6 | * \author Roni Kreinin 7 | * \author Tony Baltovski 8 | * \copyright Copyright (c) 2023, Clearpath Robotics, Inc. 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * * Neither the name of Clearpath Robotics, Inc. nor the 18 | * names of its contributors may be used to endorse or promote products 19 | * derived from this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL CLEARPATH ROBOTICS, INC. BE LIABLE FOR ANY 25 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * Please send comments, questions, or patches to code@clearpathrobotics.com 33 | * 34 | */ 35 | 36 | #include "wireless_watcher/wireless_watcher.hpp" 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #include "diagnostic_updater/diagnostic_updater.hpp" 54 | 55 | using namespace std::chrono_literals; 56 | 57 | 58 | WirelessWatcher::WirelessWatcher() : rclcpp::Node("wireless_watcher"), updater_(this) 59 | { 60 | this->declare_parameter("hz", 1.0); 61 | this->declare_parameter("dev", ""); 62 | this->declare_parameter("connected_topic", "connected"); 63 | this->declare_parameter("connection_topic", "connection"); 64 | 65 | hz = this->get_parameter("hz").as_double(); 66 | dev = this->get_parameter("dev").as_string(); 67 | connected_topic = this->get_parameter("connected_topic").as_string(); 68 | connection_topic = this->get_parameter("connection_topic").as_string(); 69 | 70 | if (dev.empty()) 71 | { 72 | std::vector wldevs; 73 | DIR *dir; 74 | struct dirent *ent; 75 | if ((dir = opendir(SYS_NET_PATH)) != NULL) 76 | { 77 | while ((ent = readdir(dir)) != NULL) 78 | { 79 | std::string dev_name = ent->d_name; 80 | if (dev_name.compare(0, 2, "wl") == 0 || dev_name.compare(0, 4, "wifi") == 0) 81 | { 82 | wldevs.push_back(dev_name); 83 | } 84 | } 85 | closedir(dir); 86 | } 87 | else 88 | { 89 | RCLCPP_FATAL(this->get_logger(), "Failed to open directory: %s", SYS_NET_PATH); 90 | return; 91 | } 92 | if (!wldevs.empty()) 93 | { 94 | dev = wldevs[0]; 95 | } 96 | else 97 | { 98 | RCLCPP_FATAL(this->get_logger(), "No wireless device found to monitor."); 99 | return; 100 | } 101 | } 102 | 103 | RCLCPP_INFO(this->get_logger(), "Monitoring %s", dev.c_str()); 104 | 105 | connected_pub_ = this->create_publisher(connected_topic, rclcpp::SensorDataQoS()); 106 | connection_pub_ = this->create_publisher(connection_topic, rclcpp::SensorDataQoS()); 107 | 108 | timer_ = this->create_wall_timer(std::chrono::milliseconds(static_cast(1000.0 / hz)), std::bind(&WirelessWatcher::timer_callback, this)); 109 | 110 | updater_.setHardwareID(dev); 111 | updater_.add("Wi-Fi Monitor", this, &WirelessWatcher::diagnostic); 112 | } 113 | 114 | void WirelessWatcher::timer_callback() 115 | { 116 | try 117 | { 118 | std::string operstate_filepath = std::string(SYS_NET_PATH); 119 | operstate_filepath += "/"; 120 | operstate_filepath += dev; 121 | operstate_filepath += "/operstate"; 122 | std::ifstream operstate_file(operstate_filepath.c_str()); 123 | std::string operstate; 124 | operstate_file >> operstate; 125 | connected_msg_.data = operstate == "up"; 126 | } 127 | catch (const std::exception& e) 128 | { 129 | connected_msg_.data = false; 130 | } 131 | connected_pub_->publish(connected_msg_); 132 | 133 | if (!connected_msg_.data) 134 | { 135 | return; 136 | } 137 | 138 | std::string iwconfig_output = exec_cmd("iwconfig " + dev); 139 | std::vector fields_str = split(iwconfig_output, "\\s\\s+"); 140 | 141 | std::unordered_map fields_dict; 142 | fields_dict["dev"] = fields_str[0]; 143 | fields_dict["type"] = fields_str[1]; 144 | fields_dict["Access Point"] = split(fields_str[5], " ").back(); 145 | 146 | for (size_t i = 2; i < fields_str.size(); ++i) 147 | { 148 | std::vector field = split(fields_str[i], "[:=]"); 149 | if (field.size() == 2) 150 | { 151 | fields_dict[field[0]] = field[1]; 152 | } 153 | } 154 | 155 | if (fields_dict["Access Point"].find("Not-Associated") == std::string::npos) 156 | { 157 | try 158 | { 159 | connection_msg_.bitrate = std::stof(split(fields_dict["Bit Rate"], " ")[0]); 160 | } 161 | catch (std::invalid_argument) 162 | { 163 | connection_msg_.bitrate = std::numeric_limits::quiet_NaN(); 164 | } 165 | 166 | connection_msg_.txpower = std::stoi(split(fields_dict["Tx-Power"], " ")[0]); 167 | connection_msg_.signal_level = std::stoi(split(fields_dict["Signal level"], " ")[0]); 168 | 169 | // Strip quotations from ESSID 170 | std::string essid = fields_dict["ESSID"]; 171 | essid.erase(std::remove(essid.begin(), essid.end(), '\"'), essid.end()); 172 | connection_msg_.essid = essid; 173 | 174 | try 175 | { 176 | connection_msg_.frequency = std::stof(split(fields_dict["Frequency"], " ")[0]); 177 | } 178 | catch (std::invalid_argument) 179 | { 180 | connection_msg_.frequency = std::numeric_limits::quiet_NaN(); 181 | } 182 | 183 | connection_msg_.bssid = fields_dict["Access Point"]; 184 | 185 | // Calculate link_quality from Link Quality 186 | std::string link_quality_str = fields_dict["Link Quality"]; 187 | connection_msg_.link_quality_raw = link_quality_str; 188 | size_t delimiter_pos = link_quality_str.find("/"); 189 | if (delimiter_pos != std::string::npos) 190 | { 191 | int num = std::stoi(link_quality_str.substr(0, delimiter_pos)); 192 | int den = std::stoi(link_quality_str.substr(delimiter_pos + 1)); 193 | connection_msg_.link_quality = static_cast(num) / den; 194 | } 195 | 196 | connection_pub_->publish(connection_msg_); 197 | } 198 | } 199 | 200 | std::string WirelessWatcher::exec_cmd(const std::string& cmd) 201 | { 202 | std::array buffer; 203 | std::string result; 204 | std::unique_ptr pipe(popen(cmd.c_str(), "r"), pclose); 205 | if (!pipe) 206 | { 207 | throw std::runtime_error("popen() failed!"); 208 | } 209 | while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) 210 | { 211 | result += buffer.data(); 212 | } 213 | return result; 214 | } 215 | 216 | std::vector WirelessWatcher::split(const std::string& s, const std::string& delimiter) 217 | { 218 | std::regex regex(delimiter); 219 | std::sregex_token_iterator it(s.begin(), s.end(), regex, -1); 220 | std::vector tokens{it, {}}; 221 | return tokens; 222 | } 223 | 224 | 225 | void WirelessWatcher::diagnostic(diagnostic_updater::DiagnosticStatusWrapper & stat) 226 | { 227 | stat.add("Wireless Network Interface", dev); 228 | stat.add("Wi-Fi Connected", connected_msg_.data ? "True" : "False"); 229 | 230 | if (!connected_msg_.data) 231 | { 232 | stat.summaryf(diagnostic_updater::DiagnosticStatusWrapper::WARN, "%s Disconnected", dev.c_str()); 233 | return; 234 | } 235 | stat.summary(diagnostic_updater::DiagnosticStatusWrapper::OK, "OK"); 236 | 237 | ip_address_diag(dev, stat); 238 | stat.add("Frequency (GHz)", connection_msg_.frequency); 239 | stat.add("ESSID", connection_msg_.essid); 240 | stat.add("BSSID", connection_msg_.bssid); 241 | stat.add("Transmit Power (dBm)", connection_msg_.txpower); 242 | stat.add("Theoretical Max Bitrate (Mbps)", connection_msg_.bitrate); 243 | stat.add("Link Quality Raw", connection_msg_.link_quality_raw); 244 | stat.addf("Link Quality (%)", "%.1f", connection_msg_.link_quality * 100); 245 | stat.add("Signal Strength (dBm)", connection_msg_.signal_level); 246 | 247 | if (connection_msg_.signal_level < SIGNAL_STRENGTH_VERY_WEAK) 248 | { 249 | stat.mergeSummaryf(diagnostic_updater::DiagnosticStatusWrapper::WARN, 250 | "Very Poor Signal Strength (%d dBm)", connection_msg_.signal_level); 251 | } 252 | else if (connection_msg_.signal_level < SIGNAL_STRENGTH_WEAK) 253 | { 254 | stat.mergeSummaryf(diagnostic_updater::DiagnosticStatusWrapper::WARN, 255 | "Poor Signal Strength (%d dBm)", connection_msg_.signal_level); 256 | } 257 | } 258 | 259 | void WirelessWatcher::ip_address_diag( 260 | std::string dev, diagnostic_updater::DiagnosticStatusWrapper & stat) 261 | { 262 | 263 | struct ifaddrs *ptr_ifaddrs = nullptr, *entry; 264 | 265 | if (getifaddrs(&ptr_ifaddrs) == 0) 266 | { 267 | for (entry = ptr_ifaddrs; entry != nullptr; entry = entry->ifa_next) 268 | { 269 | // Find the requested interface and ensure it has an address 270 | if (std::string(entry->ifa_name) != dev || entry->ifa_addr == nullptr) 271 | { 272 | continue; 273 | } 274 | 275 | sa_family_t address_family = entry->ifa_addr->sa_family; 276 | // Skip if the address is not IPv4 277 | if (address_family != AF_INET) 278 | { 279 | continue; 280 | } 281 | char buffer[INET_ADDRSTRLEN] = {}; 282 | inet_ntop( 283 | address_family, 284 | &((struct sockaddr_in*)(entry->ifa_addr))->sin_addr, 285 | buffer, 286 | INET_ADDRSTRLEN 287 | ); 288 | 289 | stat.add("IP Address", std::string(buffer)); 290 | 291 | if (entry->ifa_netmask != nullptr) 292 | { 293 | char buffer[INET_ADDRSTRLEN] = {0, }; 294 | inet_ntop( 295 | address_family, 296 | &((struct sockaddr_in*)(entry->ifa_netmask))->sin_addr, 297 | buffer, 298 | INET_ADDRSTRLEN 299 | ); 300 | 301 | stat.add("Netmask", std::string(buffer)); 302 | } 303 | else 304 | { 305 | stat.add("Netmask", "Not found"); 306 | } 307 | 308 | freeifaddrs(ptr_ifaddrs); 309 | return; 310 | } 311 | } 312 | stat.mergeSummaryf(diagnostic_updater::DiagnosticStatusWrapper::WARN, 313 | "Failed to get IP addresses for %s", dev.c_str()); 314 | stat.add("IP Address", "Not found"); 315 | stat.add("Netmask", "Not found"); 316 | 317 | if (ptr_ifaddrs != nullptr) 318 | { 319 | freeifaddrs(ptr_ifaddrs); 320 | } 321 | return; 322 | } 323 | 324 | int main(int argc, char * argv[]) 325 | { 326 | rclcpp::init(argc, argv); 327 | rclcpp::spin(std::make_shared()); 328 | rclcpp::shutdown(); 329 | return 0; 330 | } 331 | --------------------------------------------------------------------------------