Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions include/ur_client_library/ur/dashboard_client_implementation.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,27 @@ class DashboardClientImpl
*/
virtual void setReceiveTimeout([[maybe_unused]] const timeval& timeout) {};

/*!
* \brief Gets the configured send timeout. If send timeout is unconfigured "normal" socket
* timeout of 1 second will be returned.
*
* \returns configured send timeout
*/
virtual timeval getConfiguredSendTimeout() const
{
timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
return tv;
}

/*!
* \brief Sets the send timeout for the socket.
*
* \param timeout The timeout to be set
*/
virtual void setSendTimeout([[maybe_unused]] const timeval& timeout) {};

/*!
* \brief Sends command and verifies that a valid answer is received.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,10 @@ class DashboardClientImplX : public DashboardClientImpl
DashboardResponse commandUpdateProgram(const std::string& file_path) override;
DashboardResponse commandDownloadProgram(const std::string& program_name, const std::string& save_path) override;

void setReceiveTimeout([[maybe_unused]] const timeval& timeout) override
{
}
// Defined in the .cpp because httplib::Client is only forward-declared in this header.
void setReceiveTimeout(const timeval& timeout) override;
void setSendTimeout(const timeval& timeout) override;
timeval getConfiguredSendTimeout() const override;

protected:
DashboardResponse performProgramUpload(
Expand All @@ -185,6 +186,8 @@ class DashboardClientImplX : public DashboardClientImpl

std::unique_ptr<httplib::Client> cli_;
VersionInformation robot_api_version_;
timeval recv_timeout_ = { 10, 0 };
timeval send_timeout_ = { 10, 0 };
};

} // namespace urcl
73 changes: 67 additions & 6 deletions src/ur/dashboard_client_implementation_x.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#include <chrono>
#include <string>
#include <fstream>
#include <ios>
Expand All @@ -49,6 +50,38 @@ DashboardClientImplX::DashboardClientImplX(const std::string& host) : DashboardC
// Some targets have changed between versions redirecting to the correct endpoint. For this to
// work, we'll have to follow redirects, which is not the default for httplib.
cli_->set_follow_location(true);

// cpp-httplib's default read_timeout is 300 seconds. Applied unchanged, this makes any
// dashboard call hang for 5 minutes when the controller becomes unresponsive (e.g. network
// partition, paused container in tests).
//
// The 10 s default for read/write is chosen to cover blocking calls that legitimately take
// time on real hardware — brake_release, commandLoadProgram (read), commandUploadProgram
// (write), commandUpdateProgram (write), commandDownloadProgram (read) — without forcing
// each one to plumb its own timeout. commandPowerOn already accepts its own (longer)
// timeout parameter. Callers needing different limits can override via setReceiveTimeout.
Comment thread
cursor[bot] marked this conversation as resolved.
cli_->set_connection_timeout(std::chrono::seconds(5));
cli_->set_read_timeout(std::chrono::seconds(recv_timeout_.tv_sec) + std::chrono::microseconds(recv_timeout_.tv_usec));
cli_->set_write_timeout(std::chrono::seconds(send_timeout_.tv_sec) +
std::chrono::microseconds(send_timeout_.tv_usec));
}

void DashboardClientImplX::setReceiveTimeout(const timeval& timeout)
{
recv_timeout_ = timeout;
if (cli_)
{
cli_->set_read_timeout(std::chrono::seconds(timeout.tv_sec) + std::chrono::microseconds(timeout.tv_usec));
}
}

void DashboardClientImplX::setSendTimeout(const timeval& timeout)
{
send_timeout_ = timeout;
if (cli_)
{
cli_->set_write_timeout(std::chrono::seconds(timeout.tv_sec) + std::chrono::microseconds(timeout.tv_usec));
}
}

std::string DashboardClientImplX::sendAndReceive([[maybe_unused]] const std::string& text)
Expand Down Expand Up @@ -91,10 +124,12 @@ void DashboardClientImplX::disconnect()

timeval DashboardClientImplX::getConfiguredReceiveTimeout() const
{
timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
return tv;
return recv_timeout_;
}
Comment thread
urfeex marked this conversation as resolved.

timeval DashboardClientImplX::getConfiguredSendTimeout() const
{
return send_timeout_;
}

VersionInformation DashboardClientImplX::queryPolyScopeVersion()
Expand Down Expand Up @@ -145,9 +180,35 @@ DashboardResponse DashboardClientImplX::commandPowerOff()
return put("/robotstate/v1/state", R"({"action": "POWER_OFF"})");
}

DashboardResponse DashboardClientImplX::commandPowerOn([[maybe_unused]] const std::chrono::duration<double> timeout)
DashboardResponse DashboardClientImplX::commandPowerOn(const std::chrono::duration<double> timeout)
{
return put("/robotstate/v1/state", R"({"action": "POWER_ON"})");
// commandPowerOn can take significantly longer than steady-state dashboard calls (the robot
// boots, runs self-checks, etc.). Bump the read timeout to the caller-supplied value for the
// duration of the PUT, then restore — using the same save-bump-restore pattern as connect().
// The restore must run on every exit, including exceptions from inside put(), hence the
// catch(...) rethrow guard.
timeval configured_tv = getConfiguredReceiveTimeout();
// Preserve sub-second precision: duration_cast<seconds> truncates fractional values,
// so go via microseconds (the smallest unit timeval can represent) and split.
const auto pwron_us = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
timeval pwron_tv;
pwron_tv.tv_sec = static_cast<long>(pwron_us.count() / 1'000'000);
pwron_tv.tv_usec = static_cast<long>(pwron_us.count() % 1'000'000);
setReceiveTimeout(pwron_tv);
Comment thread
cursor[bot] marked this conversation as resolved.

DashboardResponse response;
try
{
response = put("/robotstate/v1/state", R"({"action": "POWER_ON"})");
}
catch (...)
{
setReceiveTimeout(configured_tv);
throw;
}

setReceiveTimeout(configured_tv);
return response;
Comment thread
urfeex marked this conversation as resolved.
}

DashboardResponse DashboardClientImplX::commandBrakeRelease()
Expand Down
52 changes: 52 additions & 0 deletions tests/test_dashboard_client_x.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,58 @@ TEST_F(DashboardClientTestX, download_program)
}
}

TEST_F(DashboardClientTestX, set_receive_timeout)
{
timeval expected_tv;
expected_tv.tv_sec = 30;
expected_tv.tv_usec = 0;
dashboard_client_->setReceiveTimeout(expected_tv);
EXPECT_TRUE(dashboard_client_->connect());

timeval actual_tv = dashboard_client_->getConfiguredReceiveTimeout();
EXPECT_EQ(expected_tv.tv_sec, actual_tv.tv_sec);
EXPECT_EQ(expected_tv.tv_usec, actual_tv.tv_usec);
}

TEST_F(DashboardClientTestX, set_send_timeout)
{
timeval expected_tv;
expected_tv.tv_sec = 30;
expected_tv.tv_usec = 0;
dashboard_client_->setSendTimeout(expected_tv);
EXPECT_TRUE(dashboard_client_->connect());

timeval actual_tv = dashboard_client_->getConfiguredSendTimeout();
EXPECT_EQ(expected_tv.tv_sec, actual_tv.tv_sec);
EXPECT_EQ(expected_tv.tv_usec, actual_tv.tv_usec);
}

TEST_F(DashboardClientTestX, timeouts_round_trip_subsecond)
{
timeval expected_tv;
expected_tv.tv_sec = 7;
expected_tv.tv_usec = 250000;
dashboard_client_->setReceiveTimeout(expected_tv);
dashboard_client_->setSendTimeout(expected_tv);

timeval recv_tv = dashboard_client_->getConfiguredReceiveTimeout();
EXPECT_EQ(expected_tv.tv_sec, recv_tv.tv_sec);
EXPECT_EQ(expected_tv.tv_usec, recv_tv.tv_usec);

timeval send_tv = dashboard_client_->getConfiguredSendTimeout();
EXPECT_EQ(expected_tv.tv_sec, send_tv.tv_sec);
EXPECT_EQ(expected_tv.tv_usec, send_tv.tv_usec);
}

TEST_F(DashboardClientTestX, microsecond_receive_timeout_makes_connect_fail)
{
timeval tiny_tv;
tiny_tv.tv_sec = 0;
tiny_tv.tv_usec = 1;
dashboard_client_->setReceiveTimeout(tiny_tv);
EXPECT_FALSE(dashboard_client_->connect());
}

int main(int argc, char* argv[])
{
::testing::InitGoogleTest(&argc, argv);
Expand Down
Loading