Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TCP server implementation #46

Merged
merged 4 commits into from
Apr 10, 2021
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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,18 @@ jobs:
run: sudo apt-get install -y libboost-dev
- name: configure
run: mkdir build && cd build && cmake .. -DBUILDING_TESTS=1 -DINTEGRATION_TESTS=1
env:
CXXFLAGS: -g -O2 -fprofile-arcs -ftest-coverage
CFLAGS: -g -O2 -fprofile-arcs -ftest-coverage
LDFLAGS: -fprofile-arcs -ftest-coverage
- name: build
run: cmake --build build --config Debug
- name: test
run: cd build && make test
- name: install gcovr
run: sudo apt-get install -y gcovr
- name: gcovr
run: cd build && gcovr -r ..

check_links:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic")

add_library(urcl SHARED
src/comm/tcp_socket.cpp
src/comm/server.cpp
src/comm/tcp_server.cpp
src/primary/primary_package.cpp
src/primary/robot_message.cpp
src/primary/robot_state.cpp
Expand Down
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,9 @@ meant to send joint positions or velocities together with a mode that tells the
interpret those values (e.g. `SERVOJ`, `SPEEDJ`). Therefore, this interface can be used to do motion
command streaming to the robot.

Simultaneously this class offers a function to receive a keepalive signal from the robot. This
function expects to read a terminated string from the opened socket and returns the string that has
been read. If no string could be read, an empty string is returned instead.

In order to use this class in an application together with a robot, make sure that a corresponding
URScript is running on the robot that can interpret the commands sent and sends keepalive signals to
the interface. See [this example script](examples/resources/scriptfile.urscript) for reference.
URScript is running on the robot that can interpret the commands sent. See [this example
script](resources/external_control.urscript) for reference.

Also see the [ScriptSender](#scriptsender) for a way to define the corresponding URScript on the
control PC and sending it to the robot upon request.
Expand Down
74 changes: 45 additions & 29 deletions include/ur_client_library/comm/reverse_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
#ifndef UR_CLIENT_LIBRARY_REVERSE_INTERFACE_H_INCLUDED
#define UR_CLIENT_LIBRARY_REVERSE_INTERFACE_H_INCLUDED

#include "ur_client_library/comm/server.h"
#include "ur_client_library/comm/tcp_server.h"
#include "ur_client_library/types.h"
#include "ur_client_library/log.h"
#include <cstring>
#include <endian.h>
#include <condition_variable>

namespace urcl
{
Expand All @@ -58,27 +60,27 @@ class ReverseInterface
public:
ReverseInterface() = delete;
/*!
* \brief Creates a ReverseInterface object including a URServer.
* \brief Creates a ReverseInterface object including a TCPServer.
*
* \param port Port the Server is started on
* \param handle_program_state Function handle to a callback on program state changes.
*/
ReverseInterface(uint32_t port) : server_(port)
ReverseInterface(uint32_t port, std::function<void(bool)> handle_program_state)
: client_fd_(-1), server_(port), handle_program_state_(handle_program_state)
{
if (!server_.bind())
{
throw std::runtime_error("Could not bind to server");
}
if (!server_.accept())
{
throw std::runtime_error("Failed to accept robot connection");
}
handle_program_state_(false);
server_.setMessageCallback(
std::bind(&ReverseInterface::messageCallback, this, std::placeholders::_1, std::placeholders::_2));
server_.setConnectCallback(std::bind(&ReverseInterface::connectionCallback, this, std::placeholders::_1));
server_.setDisconnectCallback(std::bind(&ReverseInterface::disconnectionCallback, this, std::placeholders::_1));
server_.setMaxClientsAllowed(1);
server_.start();
}
/*!
* \brief Disconnects possible clients so the reverse interface object can be safely destroyed.
*/
~ReverseInterface()
{
server_.disconnectClient();
}

/*!
Expand All @@ -92,6 +94,10 @@ class ReverseInterface
*/
bool write(const vector6d_t* positions, const ControlMode control_mode = ControlMode::MODE_IDLE)
{
if (client_fd_ == -1)
{
return false;
}
uint8_t buffer[sizeof(int32_t) * 8];
uint8_t* b_pos = buffer;

Expand All @@ -118,33 +124,41 @@ class ReverseInterface

size_t written;

return server_.write(buffer, sizeof(buffer), written);
return server_.write(client_fd_, buffer, sizeof(buffer), written);
}

/*!
* \brief Reads a keepalive signal from the robot.
*
* \returns The received keepalive string or the empty string, if nothing was received
*/
std::string readKeepalive()
private:
void connectionCallback(const int filedescriptor)
{
const size_t buf_len = 16;
char buffer[buf_len];

bool read_successful = server_.readLine(buffer, buf_len);

if (read_successful)
if (client_fd_ < 0)
{
return std::string(buffer);
URCL_LOG_INFO("Robot connected to reverse interface. Ready to receive control commands.");
client_fd_ = filedescriptor;
handle_program_state_(true);
}
else
{
return std::string("");
URCL_LOG_ERROR("Connection request to ReverseInterface received while connection already established. Only one "
"connection is allowed at a time. Ignoring this request.");
}
}

private:
URServer server_;
void disconnectionCallback(const int filedescriptor)
{
URCL_LOG_INFO("Connection to reverse interface dropped.", filedescriptor);
client_fd_ = -1;
handle_program_state_(false);
}

void messageCallback(const int filedescriptor, char* buffer)
{
URCL_LOG_WARN("Messge on ReverseInterface received. The reverse interface currently does not support any message "
"handling. This message will be ignored.");
}

int client_fd_;
TCPServer server_;

static const int32_t MULT_JOINTSTATE = 1000000;

template <typename T>
Expand All @@ -154,6 +168,8 @@ class ReverseInterface
std::memcpy(buffer, &val, s);
return s;
}

std::function<void(bool)> handle_program_state_;
};

} // namespace comm
Expand Down
74 changes: 23 additions & 51 deletions include/ur_client_library/comm/script_sender.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,99 +29,71 @@
#ifndef UR_CLIENT_LIBRARY_SCRIPT_SENDER_H_INCLUDED
#define UR_CLIENT_LIBRARY_SCRIPT_SENDER_H_INCLUDED

#include "ur_client_library/comm/server.h"
#include <thread>

#include "ur_client_library/comm/tcp_server.h"
#include "ur_client_library/log.h"

namespace urcl
{
namespace comm
{
/*!
* \brief The ScriptSender class starts a URServer for a robot to connect to and waits for a
* \brief The ScriptSender class starts a TCPServer for a robot to connect to and waits for a
* request to receive a program. This program is then delivered to the requesting robot.
*/
class ScriptSender
{
public:
ScriptSender() = delete;
/*!
* \brief Creates a ScriptSender object, including a new URServer and not yet started thread.
* \brief Creates a ScriptSender object, including a new TCPServer
*
* \param port Port to start the server on
* \param program Program to send to the robot upon request
*/
ScriptSender(uint32_t port, const std::string& program) : server_(port), script_thread_(), program_(program)
{
if (!server_.bind())
{
throw std::runtime_error("Could not bind to server");
}
}

/*!
* \brief Starts the thread that handles program requests by a robot.
*/
void start()
{
script_thread_ = std::thread(&ScriptSender::runScriptSender, this);
server_.setMessageCallback(
std::bind(&ScriptSender::messageCallback, this, std::placeholders::_1, std::placeholders::_2));
server_.setConnectCallback(std::bind(&ScriptSender::connectionCallback, this, std::placeholders::_1));
server_.setDisconnectCallback(std::bind(&ScriptSender::disconnectionCallback, this, std::placeholders::_1));
server_.start();
}

private:
URServer server_;
TCPServer server_;
std::thread script_thread_;
std::string program_;

const std::string PROGRAM_REQUEST_ = std::string("request_program\n");

void runScriptSender()
void connectionCallback(const int filedescriptor)
{
while (true)
{
if (!server_.accept())
{
throw std::runtime_error("Failed to accept robot connection");
}
if (requestRead())
{
URCL_LOG_INFO("Robot requested program");
sendProgram();
}
server_.disconnectClient();
}
URCL_LOG_DEBUG("New client connected at FD %d.", filedescriptor);
}

bool requestRead()
void disconnectionCallback(const int filedescriptor)
{
const size_t buf_len = 1024;
char buffer[buf_len];

bool read_successful = server_.readLine(buffer, buf_len);
URCL_LOG_DEBUG("Client at FD %d disconnected.", filedescriptor);
}

if (read_successful)
{
if (std::string(buffer) == PROGRAM_REQUEST_)
{
return true;
}
else
{
URCL_LOG_WARN("Received unexpected message on script request port ");
}
}
else
void messageCallback(const int filedescriptor, char* buffer)
{
if (std::string(buffer) == PROGRAM_REQUEST_)
{
URCL_LOG_WARN("Could not read on script request port");
URCL_LOG_INFO("Robot requested program");
sendProgram(filedescriptor);
}
return false;
}

void sendProgram()
void sendProgram(const int filedescriptor)
{
size_t len = program_.size();
const uint8_t* data = reinterpret_cast<const uint8_t*>(program_.c_str());
size_t written;

if (server_.write(data, len, written))
if (server_.write(filedescriptor, data, len, written))
{
URCL_LOG_INFO("Sent program to robot");
}
Expand Down
Loading