2025-10-11 16:44:12 +08:00
|
|
|
#include "rclcpp/rclcpp.hpp"
|
|
|
|
|
#include <orbbec_camera/ob_camera_node_driver.h>
|
|
|
|
|
#include <orbbec_camera/utils.h>
|
2026-04-03 16:35:54 +08:00
|
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
|
#include <cstring>
|
2026-03-24 15:31:57 +08:00
|
|
|
#include <iostream>
|
2025-10-11 16:44:12 +08:00
|
|
|
#include <sstream>
|
|
|
|
|
#include <string>
|
2026-03-25 14:05:31 +08:00
|
|
|
#include <thread>
|
2025-10-11 16:44:12 +08:00
|
|
|
|
|
|
|
|
using namespace ob;
|
|
|
|
|
|
2026-04-03 16:35:54 +08:00
|
|
|
struct CliArgs {
|
|
|
|
|
enum class Operation {
|
|
|
|
|
NONE,
|
2026-04-03 21:45:42 +08:00
|
|
|
DHCP,
|
2026-04-03 16:35:54 +08:00
|
|
|
SET_IP,
|
|
|
|
|
FORCE_IP,
|
2026-04-24 11:39:35 +08:00
|
|
|
SET_DHCP_TIMEOUT,
|
2026-04-03 16:35:54 +08:00
|
|
|
};
|
|
|
|
|
bool help = false;
|
|
|
|
|
Operation operation = Operation::NONE;
|
|
|
|
|
bool dhcp = false;
|
2026-04-03 21:45:42 +08:00
|
|
|
bool dhcp_option_set = false;
|
2026-04-24 11:39:35 +08:00
|
|
|
int dhcp_assign_ip_timeout = -1;
|
|
|
|
|
bool dhcp_assign_ip_timeout_set = false;
|
2026-04-03 16:35:54 +08:00
|
|
|
std::string force_ip_mac;
|
2026-04-21 10:08:35 +08:00
|
|
|
std::string current_ip = "192.168.1.10";
|
2026-04-03 16:35:54 +08:00
|
|
|
int port = 8090;
|
|
|
|
|
std::string new_ip = "192.168.1.200";
|
|
|
|
|
std::string mask = "255.255.255.0";
|
|
|
|
|
std::string gateway = "192.168.1.1";
|
2026-05-07 17:57:28 +08:00
|
|
|
std::string sdk_log_level = "off";
|
2026-04-03 16:35:54 +08:00
|
|
|
};
|
|
|
|
|
|
2025-10-11 16:44:12 +08:00
|
|
|
bool parseIpString(const std::string &ip_str, uint8_t ip[4]) {
|
|
|
|
|
std::stringstream ss(ip_str);
|
|
|
|
|
std::string item;
|
|
|
|
|
int i = 0;
|
|
|
|
|
while (std::getline(ss, item, '.')) {
|
|
|
|
|
if (i >= 4) return false;
|
|
|
|
|
try {
|
|
|
|
|
int num = std::stoi(item);
|
|
|
|
|
if (num < 0 || num > 255) return false;
|
|
|
|
|
ip[i++] = static_cast<uint8_t>(num);
|
|
|
|
|
} catch (...) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return i == 4;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 16:35:54 +08:00
|
|
|
bool parseBool(const std::string &value, bool &out) {
|
|
|
|
|
if (value == "true" || value == "1") {
|
|
|
|
|
out = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (value == "false" || value == "0") {
|
|
|
|
|
out = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool parseInt(const std::string &value, int &out) {
|
|
|
|
|
try {
|
|
|
|
|
size_t consumed = 0;
|
|
|
|
|
const int parsed = std::stoi(value, &consumed);
|
|
|
|
|
if (consumed != value.size()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
out = parsed;
|
|
|
|
|
return true;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-24 15:31:57 +08:00
|
|
|
void printHelp() {
|
2026-03-25 14:05:31 +08:00
|
|
|
std::cout
|
|
|
|
|
<< "Usage:\n"
|
2026-04-03 16:35:54 +08:00
|
|
|
<< " ros2 run orbbec_camera ip_config_tool --\\\n"
|
2026-04-24 11:39:35 +08:00
|
|
|
<< " <dhcp|set_ip|force_ip|set_dhcp_timeout> [options]\n"
|
2026-04-03 21:45:42 +08:00
|
|
|
<< " ros2 run orbbec_camera ip_config_tool -- dhcp --enable_dhcp <true|false>\\\n"
|
2026-04-21 10:08:35 +08:00
|
|
|
<< " [--current_ip <ip>] [--port <port>]\n"
|
2026-04-03 21:45:42 +08:00
|
|
|
<< " ros2 run orbbec_camera ip_config_tool -- set_ip\\\n"
|
2026-04-24 11:39:35 +08:00
|
|
|
<< " [--current_ip <ip>] [--port <port>] [--new_ip <ip>] [--mask <ip>] [--gateway "
|
|
|
|
|
"<ip>]\n"
|
2026-04-03 16:35:54 +08:00
|
|
|
<< " ros2 run orbbec_camera ip_config_tool -- force_ip --force_ip_mac <mac>\\\n"
|
|
|
|
|
<< " [--enable_dhcp <true|false>] [--new_ip <ip>] [--mask <ip>] [--gateway <ip>]\n"
|
2026-04-24 11:39:35 +08:00
|
|
|
<< " ros2 run orbbec_camera ip_config_tool -- set_dhcp_timeout\\\n"
|
|
|
|
|
<< " [--current_ip <ip>] [--port <port>] --timeout <seconds>\n"
|
2026-05-07 17:57:28 +08:00
|
|
|
<< " [--enable_sdk_log] [--sdk_log_level debug]\n"
|
2026-03-25 14:05:31 +08:00
|
|
|
<< " (legacy alias: set_device_ip)\n\n"
|
2026-04-03 16:35:54 +08:00
|
|
|
<< "Subcommands:\n"
|
2026-04-03 21:45:42 +08:00
|
|
|
<< " dhcp Configure DHCP on device by current device address.\n"
|
|
|
|
|
<< " set_ip Configure static IP on device by current device address.\n"
|
2026-04-24 11:39:35 +08:00
|
|
|
<< " force_ip Force IP by MAC address.\n"
|
|
|
|
|
<< " set_dhcp_timeout Configure DHCP address assignment timeout.\n\n"
|
2026-03-25 14:05:31 +08:00
|
|
|
<< "Parameters:\n"
|
2026-04-03 21:45:42 +08:00
|
|
|
<< " --enable_dhcp <bool> DHCP flag for dhcp/force-ip (default: false).\n"
|
2026-04-24 11:39:35 +08:00
|
|
|
<< " --current_ip <ip> Current device IP for dhcp/set-ip/set_dhcp_timeout "
|
|
|
|
|
"(default: 192.168.1.10).\n"
|
|
|
|
|
<< " --port <port> Device port for dhcp/set-ip/set_dhcp_timeout "
|
|
|
|
|
"(default: 8090).\n"
|
2026-04-03 16:35:54 +08:00
|
|
|
<< " --new_ip <ip> Static IP for set-ip/force-ip (default: 192.168.1.200).\n"
|
|
|
|
|
<< " --mask <ip> Subnet mask for set-ip/force-ip (default: 255.255.255.0).\n"
|
|
|
|
|
<< " --gateway <ip> Gateway for set-ip/force-ip (default: 192.168.1.1).\n"
|
|
|
|
|
<< " --force_ip_mac <mac> Target MAC for force-ip (required, e.g. "
|
2026-04-24 11:39:35 +08:00
|
|
|
"54:14:FD:06:07:DA).\n"
|
|
|
|
|
<< " --timeout <sec> DHCP timeout in seconds for set_dhcp_timeout.\n"
|
|
|
|
|
<< " --dhcp_assign_ip_timeout <sec>\n"
|
|
|
|
|
<< " Alias of --timeout.\n\n"
|
2026-05-07 17:57:28 +08:00
|
|
|
<< " --enable_sdk_log Enable SDK file log at debug level under ~/.ros/Log.\n"
|
|
|
|
|
<< " --sdk_log_level LEVEL SDK file log level: debug/info/warn/error/fatal/off "
|
|
|
|
|
"(default: off).\n\n"
|
2026-03-25 14:05:31 +08:00
|
|
|
<< "Examples:\n"
|
|
|
|
|
<< "\n"
|
2026-04-03 21:45:42 +08:00
|
|
|
<< " [DHCP]\n"
|
|
|
|
|
<< " Enable: ros2 run orbbec_camera ip_config_tool -- \\\n"
|
|
|
|
|
<< " dhcp \\\n"
|
2026-04-21 10:08:35 +08:00
|
|
|
<< " --current_ip 192.168.1.10 \\\n"
|
2026-04-03 16:35:54 +08:00
|
|
|
<< " --enable_dhcp true\n"
|
2026-04-03 21:45:42 +08:00
|
|
|
<< " [Set IP]\n"
|
2026-04-03 16:35:54 +08:00
|
|
|
<< " Static: ros2 run orbbec_camera ip_config_tool -- \\\n"
|
|
|
|
|
<< " set_ip \\\n"
|
2026-04-21 10:08:35 +08:00
|
|
|
<< " --current_ip 192.168.1.10 \\\n"
|
2026-04-03 16:35:54 +08:00
|
|
|
<< " --new_ip 192.168.1.200 \\\n"
|
|
|
|
|
<< " --mask 255.255.255.0 \\\n"
|
|
|
|
|
<< " --gateway 192.168.1.1\n"
|
2026-03-25 14:05:31 +08:00
|
|
|
<< "\n"
|
|
|
|
|
<< " [Force IP]\n"
|
2026-04-03 16:35:54 +08:00
|
|
|
<< " by MAC: ros2 run orbbec_camera ip_config_tool -- \\\n"
|
|
|
|
|
<< " force_ip \\\n"
|
|
|
|
|
<< " --force_ip_mac 54:14:FD:06:07:DA \\\n"
|
|
|
|
|
<< " --new_ip 192.168.1.200 \\\n"
|
|
|
|
|
<< " --mask 255.255.255.0 \\\n"
|
2026-04-24 11:39:35 +08:00
|
|
|
<< " --gateway 192.168.1.1\n"
|
|
|
|
|
<< "\n"
|
|
|
|
|
<< " [Set DHCP Timeout]\n"
|
|
|
|
|
<< " Timeout: ros2 run orbbec_camera ip_config_tool -- \\\n"
|
|
|
|
|
<< " set_dhcp_timeout \\\n"
|
|
|
|
|
<< " --current_ip 192.168.1.10 \\\n"
|
2026-05-07 17:57:28 +08:00
|
|
|
<< " --timeout 10\n"
|
|
|
|
|
<< " [SDK Log]\n"
|
|
|
|
|
<< " Debug: ros2 run orbbec_camera ip_config_tool -- \\\n"
|
|
|
|
|
<< " dhcp --current_ip 192.168.1.10 --enable_dhcp true \\\n"
|
|
|
|
|
<< " --enable_sdk_log --sdk_log_level debug\n";
|
2026-04-03 16:35:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool parseArgs(int argc, char **argv, CliArgs &args, std::string &error) {
|
|
|
|
|
for (int i = 1; i < argc; ++i) {
|
|
|
|
|
const std::string current = argv[i];
|
|
|
|
|
|
|
|
|
|
if (current == "-h" || current == "--help") {
|
|
|
|
|
args.help = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-04-03 21:45:42 +08:00
|
|
|
if (current == "dhcp") {
|
|
|
|
|
if (args.operation != CliArgs::Operation::NONE) {
|
2026-04-24 11:39:35 +08:00
|
|
|
error = "Only one subcommand is allowed";
|
2026-04-03 21:45:42 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.operation = CliArgs::Operation::DHCP;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-04-03 16:35:54 +08:00
|
|
|
if (current == "set_ip" || current == "set-ip") {
|
|
|
|
|
if (args.operation != CliArgs::Operation::NONE) {
|
2026-04-24 11:39:35 +08:00
|
|
|
error = "Only one subcommand is allowed";
|
2026-04-03 16:35:54 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.operation = CliArgs::Operation::SET_IP;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (current == "force_ip" || current == "force-ip") {
|
|
|
|
|
if (args.operation != CliArgs::Operation::NONE) {
|
2026-04-24 11:39:35 +08:00
|
|
|
error = "Only one subcommand is allowed";
|
2026-04-03 16:35:54 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.operation = CliArgs::Operation::FORCE_IP;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-04-24 11:39:35 +08:00
|
|
|
if (current == "set_dhcp_timeout" || current == "set-dhcp-timeout") {
|
|
|
|
|
if (args.operation != CliArgs::Operation::NONE) {
|
|
|
|
|
error = "Only one subcommand is allowed";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.operation = CliArgs::Operation::SET_DHCP_TIMEOUT;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-04-03 16:35:54 +08:00
|
|
|
|
|
|
|
|
if (current.rfind("--enable_dhcp=", 0) == 0) {
|
|
|
|
|
if (!parseBool(current.substr(std::strlen("--enable_dhcp=")), args.dhcp)) {
|
|
|
|
|
error = "--enable_dhcp expects true/false";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-04-03 21:45:42 +08:00
|
|
|
args.dhcp_option_set = true;
|
2026-04-03 16:35:54 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (current == "--enable_dhcp") {
|
|
|
|
|
if (++i >= argc || !parseBool(argv[i], args.dhcp)) {
|
|
|
|
|
error = "--enable_dhcp expects true/false";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-04-03 21:45:42 +08:00
|
|
|
args.dhcp_option_set = true;
|
2026-04-03 16:35:54 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 10:08:35 +08:00
|
|
|
if (current.rfind("--current_ip=", 0) == 0) {
|
|
|
|
|
args.current_ip = current.substr(std::strlen("--current_ip="));
|
2026-04-03 16:35:54 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
2026-04-21 10:08:35 +08:00
|
|
|
if (current == "--current_ip") {
|
2026-04-03 16:35:54 +08:00
|
|
|
if (++i >= argc) {
|
2026-04-21 10:08:35 +08:00
|
|
|
error = "--current_ip requires a value";
|
2026-04-03 16:35:54 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-04-21 10:08:35 +08:00
|
|
|
args.current_ip = argv[i];
|
2026-04-03 16:35:54 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (current.rfind("--port=", 0) == 0) {
|
|
|
|
|
if (!parseInt(current.substr(std::strlen("--port=")), args.port)) {
|
|
|
|
|
error = "--port expects an integer";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (current == "--port") {
|
|
|
|
|
if (++i >= argc || !parseInt(argv[i], args.port)) {
|
|
|
|
|
error = "--port expects an integer";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (current.rfind("--new_ip=", 0) == 0) {
|
|
|
|
|
args.new_ip = current.substr(std::strlen("--new_ip="));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (current == "--new_ip") {
|
|
|
|
|
if (++i >= argc) {
|
|
|
|
|
error = "--new_ip requires a value";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.new_ip = argv[i];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (current.rfind("--mask=", 0) == 0) {
|
|
|
|
|
args.mask = current.substr(std::strlen("--mask="));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (current == "--mask") {
|
|
|
|
|
if (++i >= argc) {
|
|
|
|
|
error = "--mask requires a value";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.mask = argv[i];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (current.rfind("--gateway=", 0) == 0) {
|
|
|
|
|
args.gateway = current.substr(std::strlen("--gateway="));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (current == "--gateway") {
|
|
|
|
|
if (++i >= argc) {
|
|
|
|
|
error = "--gateway requires a value";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.gateway = argv[i];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (current.rfind("--force_ip_mac=", 0) == 0) {
|
|
|
|
|
args.force_ip_mac = current.substr(std::strlen("--force_ip_mac="));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (current == "--force_ip_mac") {
|
|
|
|
|
if (++i >= argc) {
|
|
|
|
|
error = "--force_ip_mac requires a value";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.force_ip_mac = argv[i];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 11:39:35 +08:00
|
|
|
if (current.rfind("--timeout=", 0) == 0) {
|
|
|
|
|
if (!parseInt(current.substr(std::strlen("--timeout=")), args.dhcp_assign_ip_timeout)) {
|
|
|
|
|
error = "--timeout expects an integer";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.dhcp_assign_ip_timeout_set = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (current == "--timeout") {
|
|
|
|
|
if (++i >= argc || !parseInt(argv[i], args.dhcp_assign_ip_timeout)) {
|
|
|
|
|
error = "--timeout expects an integer";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.dhcp_assign_ip_timeout_set = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (current.rfind("--dhcp_assign_ip_timeout=", 0) == 0) {
|
|
|
|
|
if (!parseInt(current.substr(std::strlen("--dhcp_assign_ip_timeout=")),
|
|
|
|
|
args.dhcp_assign_ip_timeout)) {
|
|
|
|
|
error = "--dhcp_assign_ip_timeout expects an integer";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.dhcp_assign_ip_timeout_set = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (current == "--dhcp_assign_ip_timeout") {
|
|
|
|
|
if (++i >= argc || !parseInt(argv[i], args.dhcp_assign_ip_timeout)) {
|
|
|
|
|
error = "--dhcp_assign_ip_timeout expects an integer";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.dhcp_assign_ip_timeout_set = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 17:57:28 +08:00
|
|
|
if (current == "--enable_sdk_log") {
|
|
|
|
|
args.sdk_log_level = "debug";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (current.rfind("--sdk_log_level=", 0) == 0) {
|
|
|
|
|
args.sdk_log_level = current.substr(std::strlen("--sdk_log_level="));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (current == "--sdk_log_level") {
|
|
|
|
|
if (++i >= argc) {
|
|
|
|
|
error = "--sdk_log_level requires a value";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
args.sdk_log_level = argv[i];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 16:35:54 +08:00
|
|
|
error = "Unknown argument: " + current;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (args.operation == CliArgs::Operation::NONE) {
|
2026-04-24 11:39:35 +08:00
|
|
|
error = "Missing subcommand. Use one of: dhcp, set_ip, force_ip, set_dhcp_timeout";
|
2026-04-03 21:45:42 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (args.operation == CliArgs::Operation::DHCP && !args.dhcp_option_set) {
|
|
|
|
|
error = "dhcp requires --enable_dhcp <true|false>";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (args.operation == CliArgs::Operation::SET_IP && args.dhcp_option_set) {
|
|
|
|
|
error = "set_ip only supports static IP now. Use subcommand 'dhcp' for --enable_dhcp";
|
2026-04-03 16:35:54 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (args.operation == CliArgs::Operation::FORCE_IP && args.force_ip_mac.empty()) {
|
|
|
|
|
error = "force_ip requires --force_ip_mac <mac>";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-04-24 11:39:35 +08:00
|
|
|
if (args.operation == CliArgs::Operation::SET_DHCP_TIMEOUT && !args.dhcp_assign_ip_timeout_set) {
|
|
|
|
|
error = "set_dhcp_timeout requires --timeout <seconds>";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-05-07 17:57:28 +08:00
|
|
|
const auto log_severity = orbbec_camera::obLogSeverityFromString(args.sdk_log_level);
|
|
|
|
|
if (log_severity == OBLogSeverity::OB_LOG_SEVERITY_OFF && args.sdk_log_level != "off" &&
|
|
|
|
|
args.sdk_log_level != "none") {
|
|
|
|
|
error = "--sdk_log_level expects one of: debug, info, warn, error, fatal, off";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-04-03 16:35:54 +08:00
|
|
|
|
|
|
|
|
return true;
|
2026-03-24 15:31:57 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-11 16:44:12 +08:00
|
|
|
int main(int argc, char **argv) {
|
2026-04-03 16:35:54 +08:00
|
|
|
CliArgs args;
|
|
|
|
|
std::string parse_error;
|
|
|
|
|
if (!parseArgs(argc, argv, args, parse_error)) {
|
|
|
|
|
std::cerr << "Argument error: " << parse_error << std::endl;
|
|
|
|
|
printHelp();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (args.help) {
|
|
|
|
|
printHelp();
|
|
|
|
|
return 0;
|
2026-03-24 15:31:57 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-11 16:44:12 +08:00
|
|
|
rclcpp::init(argc, argv);
|
2026-04-03 16:35:54 +08:00
|
|
|
auto logger = rclcpp::get_logger("ip_config_tool");
|
2026-03-25 11:00:06 +08:00
|
|
|
|
2025-10-11 16:44:12 +08:00
|
|
|
try {
|
2026-05-07 17:57:28 +08:00
|
|
|
const auto sdk_log_path =
|
|
|
|
|
orbbec_camera::configureObSdkLoggerForTool("ip_config_tool", args.sdk_log_level);
|
|
|
|
|
if (!sdk_log_path.empty()) {
|
|
|
|
|
RCLCPP_INFO(logger, "SDK file log enabled: %s", sdk_log_path.c_str());
|
|
|
|
|
}
|
2025-10-11 16:44:12 +08:00
|
|
|
auto context = std::make_shared<ob::Context>();
|
|
|
|
|
|
2026-04-03 21:45:42 +08:00
|
|
|
if (args.operation == CliArgs::Operation::DHCP) {
|
2026-04-21 10:08:35 +08:00
|
|
|
RCLCPP_INFO(logger, "Connecting to device %s:%d ...", args.current_ip.c_str(), args.port);
|
|
|
|
|
auto device = context->createNetDevice(args.current_ip.c_str(), args.port);
|
2026-04-03 21:45:42 +08:00
|
|
|
|
2026-04-21 10:08:35 +08:00
|
|
|
uint8_t current_ip_bytes[4] = {0};
|
|
|
|
|
if (args.dhcp && !parseIpString(args.current_ip, current_ip_bytes)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Invalid current_ip format: %s", args.current_ip.c_str());
|
2026-04-03 22:03:05 +08:00
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 21:45:42 +08:00
|
|
|
const bool v2_supported =
|
|
|
|
|
device->isPropertySupported(OB_STRUCT_DEVICE_IP_ADDR_CONFIG_V2, OB_PERMISSION_READ_WRITE);
|
|
|
|
|
if (v2_supported) {
|
|
|
|
|
OBNetIpConfigV2 ip_config_v2{};
|
2026-04-04 00:48:00 +08:00
|
|
|
uint32_t data_size = sizeof(ip_config_v2);
|
|
|
|
|
device->getStructuredData(OB_STRUCT_DEVICE_IP_ADDR_CONFIG_V2,
|
|
|
|
|
reinterpret_cast<uint8_t *>(&ip_config_v2), &data_size);
|
|
|
|
|
|
2026-04-03 21:45:42 +08:00
|
|
|
if (args.dhcp) {
|
2026-04-04 00:48:00 +08:00
|
|
|
// Enable DHCP, keep current IP settings payload.
|
|
|
|
|
ip_config_v2.flags = static_cast<uint16_t>(
|
|
|
|
|
(ip_config_v2.flags & ~OB_NET_IP_FLAG_PERSISTENT) | OB_NET_IP_FLAG_DHCP);
|
2026-04-03 21:45:42 +08:00
|
|
|
} else {
|
2026-04-04 00:48:00 +08:00
|
|
|
// Disable DHCP and enable persistent mode, keep existing static IP settings.
|
2026-04-04 00:54:33 +08:00
|
|
|
ip_config_v2.flags = static_cast<uint16_t>((ip_config_v2.flags & ~OB_NET_IP_FLAG_DHCP) |
|
|
|
|
|
OB_NET_IP_FLAG_PERSISTENT);
|
2026-04-03 21:45:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCLCPP_INFO(logger, "Applying dhcp configuration with V2 property (1088)...");
|
|
|
|
|
device->setStructuredData(OB_STRUCT_DEVICE_IP_ADDR_CONFIG_V2,
|
|
|
|
|
reinterpret_cast<const uint8_t *>(&ip_config_v2),
|
|
|
|
|
sizeof(ip_config_v2));
|
|
|
|
|
RCLCPP_INFO(logger, "DHCP configuration applied successfully (V2).");
|
|
|
|
|
} else {
|
|
|
|
|
OBNetIpConfig ip_config{};
|
|
|
|
|
ip_config.dhcp = args.dhcp ? 1 : 0;
|
2026-04-03 22:03:05 +08:00
|
|
|
if (args.dhcp) {
|
|
|
|
|
// Some devices reject all-zero IP payload when enabling DHCP.
|
2026-04-21 10:08:35 +08:00
|
|
|
std::memcpy(ip_config.address, current_ip_bytes, sizeof(current_ip_bytes));
|
2026-04-03 22:03:05 +08:00
|
|
|
}
|
2026-04-03 21:45:42 +08:00
|
|
|
|
2026-04-04 00:54:33 +08:00
|
|
|
RCLCPP_WARN(
|
|
|
|
|
logger,
|
|
|
|
|
"Device does not support IP config V2 (1088), fallback to legacy property (1041).");
|
2026-04-03 21:45:42 +08:00
|
|
|
RCLCPP_INFO(logger, "Applying dhcp configuration...");
|
|
|
|
|
device->setStructuredData(OB_STRUCT_DEVICE_IP_ADDR_CONFIG,
|
|
|
|
|
reinterpret_cast<const uint8_t *>(&ip_config), sizeof(ip_config));
|
|
|
|
|
RCLCPP_INFO(logger, "DHCP configuration applied successfully.");
|
|
|
|
|
}
|
|
|
|
|
RCLCPP_INFO(logger, "DHCP target state: %s", args.dhcp ? "enabled" : "disabled");
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 16:35:54 +08:00
|
|
|
if (args.operation == CliArgs::Operation::SET_IP) {
|
2026-04-21 10:08:35 +08:00
|
|
|
RCLCPP_INFO(logger, "Connecting to device %s:%d ...", args.current_ip.c_str(), args.port);
|
|
|
|
|
auto device = context->createNetDevice(args.current_ip.c_str(), args.port);
|
2026-03-25 11:00:06 +08:00
|
|
|
|
2026-04-03 16:54:33 +08:00
|
|
|
const bool v2_supported =
|
|
|
|
|
device->isPropertySupported(OB_STRUCT_DEVICE_IP_ADDR_CONFIG_V2, OB_PERMISSION_READ_WRITE);
|
2026-03-25 11:00:06 +08:00
|
|
|
|
2026-04-03 16:54:33 +08:00
|
|
|
if (v2_supported) {
|
|
|
|
|
// V2 supports enabling DHCP and persistent(static) independently.
|
|
|
|
|
uint8_t address[4] = {0};
|
|
|
|
|
uint8_t mask[4] = {0};
|
|
|
|
|
uint8_t gateway[4] = {0};
|
|
|
|
|
if (!parseIpString(args.new_ip, address)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Invalid new_ip format: %s", args.new_ip.c_str());
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (!parseIpString(args.mask, mask)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Invalid mask format: %s", args.mask.c_str());
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (!parseIpString(args.gateway, gateway)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Invalid gateway format: %s", args.gateway.c_str());
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OBNetIpConfigV2 ip_config_v2{};
|
2026-04-04 00:54:33 +08:00
|
|
|
// Preserve current addressing mode flags (DHCP/PERSISTENT/LLA), only update static IP
|
|
|
|
|
// values.
|
2026-04-04 00:48:00 +08:00
|
|
|
uint32_t data_size = sizeof(ip_config_v2);
|
|
|
|
|
device->getStructuredData(OB_STRUCT_DEVICE_IP_ADDR_CONFIG_V2,
|
|
|
|
|
reinterpret_cast<uint8_t *>(&ip_config_v2), &data_size);
|
|
|
|
|
// Ensure persistent profile is enabled so updated static fields can be used,
|
|
|
|
|
// while keeping DHCP bit unchanged.
|
|
|
|
|
ip_config_v2.flags = static_cast<uint16_t>(ip_config_v2.flags | OB_NET_IP_FLAG_PERSISTENT);
|
2026-04-03 16:54:33 +08:00
|
|
|
std::memcpy(ip_config_v2.address, address, sizeof(address));
|
|
|
|
|
std::memcpy(ip_config_v2.mask, mask, sizeof(mask));
|
|
|
|
|
std::memcpy(ip_config_v2.gateway, gateway, sizeof(gateway));
|
|
|
|
|
|
|
|
|
|
RCLCPP_INFO(logger, "Applying set-ip configuration with V2 property (1088)...");
|
|
|
|
|
device->setStructuredData(OB_STRUCT_DEVICE_IP_ADDR_CONFIG_V2,
|
|
|
|
|
reinterpret_cast<const uint8_t *>(&ip_config_v2),
|
|
|
|
|
sizeof(ip_config_v2));
|
|
|
|
|
|
|
|
|
|
RCLCPP_INFO(logger, "Set-ip configuration applied successfully (V2).");
|
2026-04-04 00:48:00 +08:00
|
|
|
RCLCPP_INFO(logger, "Set-ip target mode: unchanged (flags=%u).", ip_config_v2.flags);
|
2026-04-03 16:54:33 +08:00
|
|
|
RCLCPP_INFO(logger, "Set-ip target static IP: %d.%d.%d.%d", ip_config_v2.address[0],
|
|
|
|
|
ip_config_v2.address[1], ip_config_v2.address[2], ip_config_v2.address[3]);
|
|
|
|
|
RCLCPP_INFO(logger, "Set-ip target mask: %d.%d.%d.%d", ip_config_v2.mask[0],
|
|
|
|
|
ip_config_v2.mask[1], ip_config_v2.mask[2], ip_config_v2.mask[3]);
|
|
|
|
|
RCLCPP_INFO(logger, "Set-ip target gateway: %d.%d.%d.%d", ip_config_v2.gateway[0],
|
|
|
|
|
ip_config_v2.gateway[1], ip_config_v2.gateway[2], ip_config_v2.gateway[3]);
|
2026-03-25 11:00:06 +08:00
|
|
|
} else {
|
2026-04-03 16:54:33 +08:00
|
|
|
OBNetIpConfig ip_config{};
|
2026-04-04 00:48:00 +08:00
|
|
|
// Preserve current DHCP mode on legacy property as well.
|
|
|
|
|
uint32_t data_size = sizeof(ip_config);
|
2026-04-04 00:54:33 +08:00
|
|
|
device->getStructuredData(OB_STRUCT_DEVICE_IP_ADDR_CONFIG,
|
|
|
|
|
reinterpret_cast<uint8_t *>(&ip_config), &data_size);
|
2026-04-03 21:45:42 +08:00
|
|
|
uint8_t address[4] = {0};
|
|
|
|
|
uint8_t mask[4] = {0};
|
|
|
|
|
uint8_t gateway[4] = {0};
|
|
|
|
|
if (!parseIpString(args.new_ip, address)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Invalid new_ip format: %s", args.new_ip.c_str());
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
2026-04-03 16:54:33 +08:00
|
|
|
}
|
2026-04-03 21:45:42 +08:00
|
|
|
if (!parseIpString(args.mask, mask)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Invalid mask format: %s", args.mask.c_str());
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (!parseIpString(args.gateway, gateway)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Invalid gateway format: %s", args.gateway.c_str());
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
std::memcpy(ip_config.address, address, sizeof(address));
|
|
|
|
|
std::memcpy(ip_config.mask, mask, sizeof(mask));
|
|
|
|
|
std::memcpy(ip_config.gateway, gateway, sizeof(gateway));
|
2026-04-03 16:54:33 +08:00
|
|
|
|
2026-04-04 00:54:33 +08:00
|
|
|
RCLCPP_WARN(
|
|
|
|
|
logger,
|
|
|
|
|
"Device does not support IP config V2 (1088), fallback to legacy property (1041).");
|
2026-04-03 16:54:33 +08:00
|
|
|
|
|
|
|
|
RCLCPP_INFO(logger, "Applying set-ip configuration...");
|
|
|
|
|
device->setStructuredData(OB_STRUCT_DEVICE_IP_ADDR_CONFIG,
|
|
|
|
|
reinterpret_cast<const uint8_t *>(&ip_config), sizeof(ip_config));
|
|
|
|
|
|
|
|
|
|
RCLCPP_INFO(logger, "Set-ip configuration applied successfully.");
|
2026-04-04 00:48:00 +08:00
|
|
|
RCLCPP_INFO(logger, "Set-ip target mode: unchanged (dhcp=%u).", ip_config.dhcp);
|
2026-04-03 21:45:42 +08:00
|
|
|
RCLCPP_INFO(logger, "Set-ip target static IP: %d.%d.%d.%d", ip_config.address[0],
|
|
|
|
|
ip_config.address[1], ip_config.address[2], ip_config.address[3]);
|
|
|
|
|
RCLCPP_INFO(logger, "Set-ip target mask: %d.%d.%d.%d", ip_config.mask[0], ip_config.mask[1],
|
|
|
|
|
ip_config.mask[2], ip_config.mask[3]);
|
|
|
|
|
RCLCPP_INFO(logger, "Set-ip target gateway: %d.%d.%d.%d", ip_config.gateway[0],
|
|
|
|
|
ip_config.gateway[1], ip_config.gateway[2], ip_config.gateway[3]);
|
2026-03-25 11:00:06 +08:00
|
|
|
}
|
2026-03-24 15:31:57 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 16:35:54 +08:00
|
|
|
if (args.operation == CliArgs::Operation::FORCE_IP) {
|
2026-04-03 16:54:33 +08:00
|
|
|
OBNetIpConfig ip_config{};
|
|
|
|
|
ip_config.dhcp = args.dhcp ? 1 : 0;
|
|
|
|
|
|
|
|
|
|
if (!args.dhcp) {
|
|
|
|
|
if (!parseIpString(args.new_ip, ip_config.address)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Invalid new_ip format: %s", args.new_ip.c_str());
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (!parseIpString(args.mask, ip_config.mask)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Invalid mask format: %s", args.mask.c_str());
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (!parseIpString(args.gateway, ip_config.gateway)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Invalid gateway format: %s", args.gateway.c_str());
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 16:35:54 +08:00
|
|
|
RCLCPP_INFO(logger, "Applying force-ip to MAC %s ...", args.force_ip_mac.c_str());
|
|
|
|
|
if (context->forceIp(args.force_ip_mac.c_str(), ip_config)) {
|
2026-03-25 11:00:06 +08:00
|
|
|
RCLCPP_INFO(logger, "Force-ip operation applied successfully.");
|
2026-03-25 14:05:31 +08:00
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
2026-04-03 16:35:54 +08:00
|
|
|
if (args.dhcp) {
|
2026-03-25 11:00:06 +08:00
|
|
|
RCLCPP_INFO(logger, "Force-ip target mode: DHCP.");
|
|
|
|
|
} else {
|
2026-04-03 16:35:54 +08:00
|
|
|
RCLCPP_INFO(logger, "Force-ip target static IP: %s", args.new_ip.c_str());
|
|
|
|
|
RCLCPP_INFO(logger, "Force-ip target mask: %s", args.mask.c_str());
|
|
|
|
|
RCLCPP_INFO(logger, "Force-ip target gateway: %s", args.gateway.c_str());
|
2026-03-25 11:00:06 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
RCLCPP_ERROR(logger, "Force-ip failed (SDK returned false).");
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2025-10-11 16:44:12 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-24 11:39:35 +08:00
|
|
|
if (args.operation == CliArgs::Operation::SET_DHCP_TIMEOUT) {
|
|
|
|
|
RCLCPP_INFO(logger, "Connecting to device %s:%d ...", args.current_ip.c_str(), args.port);
|
|
|
|
|
auto device = context->createNetDevice(args.current_ip.c_str(), args.port);
|
|
|
|
|
|
|
|
|
|
if (!device->isPropertySupported(OB_PROP_DHCP_ASSIGN_IP_TIMEOUT_INT, OB_PERMISSION_WRITE)) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Current device or firmware does not support DHCP assign IP timeout");
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto range = device->getIntPropertyRange(OB_PROP_DHCP_ASSIGN_IP_TIMEOUT_INT);
|
|
|
|
|
if (args.dhcp_assign_ip_timeout < range.min || args.dhcp_assign_ip_timeout > range.max) {
|
|
|
|
|
RCLCPP_ERROR(logger, "Timeout %d is out of range [%d, %d]", args.dhcp_assign_ip_timeout,
|
|
|
|
|
range.min, range.max);
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
device->setIntProperty(OB_PROP_DHCP_ASSIGN_IP_TIMEOUT_INT, args.dhcp_assign_ip_timeout);
|
|
|
|
|
const int current_timeout = device->getIntProperty(OB_PROP_DHCP_ASSIGN_IP_TIMEOUT_INT);
|
|
|
|
|
RCLCPP_INFO(logger, "DHCP assign IP timeout applied successfully: %d second(s)",
|
|
|
|
|
current_timeout);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-11 16:44:12 +08:00
|
|
|
} catch (ob::Error &e) {
|
2026-04-01 20:52:46 +08:00
|
|
|
RCLCPP_ERROR(logger, "ip_config_tool: %s", orbbec_camera::formatObErrorWithStatus(e).c_str());
|
2026-03-25 11:00:06 +08:00
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
2025-10-11 16:44:12 +08:00
|
|
|
} catch (const std::exception &e) {
|
2026-03-25 11:39:55 +08:00
|
|
|
RCLCPP_ERROR(logger, "ip_config_tool: %s", e.what());
|
2026-03-25 11:00:06 +08:00
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
2025-10-11 16:44:12 +08:00
|
|
|
} catch (...) {
|
2026-03-25 11:39:55 +08:00
|
|
|
RCLCPP_ERROR(logger, "ip_config_tool: unknown error");
|
2026-03-25 11:00:06 +08:00
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 1;
|
2025-10-11 16:44:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rclcpp::shutdown();
|
|
|
|
|
return 0;
|
2026-03-24 15:31:57 +08:00
|
|
|
}
|