Commit 24de1dda authored by Giuseppe Carboni's avatar Giuseppe Carboni
Browse files

Fix #782, wrote a singleton socket class for SRT new minor servos (#783)

* Fix #782, wrote a singleton socket class for SRT new minor servos

This is the first implementation, connection checks for the socket should be added in order to avoid some disconnection errors

* Fix #782, improved SRT MS singleton socket and tests

The socket is now set to non-blocking, therefore some checks were added in order to accomodate to this change.
Fixed a wrong behavior regarding the singleton design pattern. Previously it was possible to call the getInstance(ip_address, port) method multiple times with multiple addresses, but only the first instance was returned, meaning that only the first instance was actually opened to the correct IP address and port. Now if the user tries to open a second socket on a different IP address and port an exception is raised.
parent 56330fd3
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -12,6 +12,8 @@
#include <map>
#include <variant>

#define CLOSER std::string("\r\n")

/**
 * SRT Minor Servo Command Library
 *
+8 −9
Original line number Diff line number Diff line
@@ -4,10 +4,9 @@
 * Giuseppe Carboni (giuseppe.carboni@inaf.it)
 */

#include "SRTMinorServoCommandLibrary.h"

#include <iostream>
#include <algorithm>
#include "SRTMinorServoCommandLibrary.h"

std::string SRTMinorServoCommandLibrary::status(std::string servo_id)
{
@@ -17,28 +16,28 @@ std::string SRTMinorServoCommandLibrary::status(std::string servo_id)
    {
        command << "=" << servo_id;
    }
    command << "\r\n";
    command << CLOSER;
    return command.str();
}

std::string SRTMinorServoCommandLibrary::setup(std::string configuration)
{
    std::stringstream command;
    command << "SETUP=" << configuration << "\r\n";
    command << "SETUP=" << configuration << CLOSER;
    return command.str();
}

std::string SRTMinorServoCommandLibrary::stow(std::string servo_id, unsigned int stow_position)
{
    std::stringstream command;
    command << "STOW=" << servo_id << "," << stow_position << "\r\n";
    command << "STOW=" << servo_id << "," << stow_position << CLOSER;
    return command.str();
}

std::string SRTMinorServoCommandLibrary::stop(std::string servo_id)
{
    std::stringstream command;
    command << "STOP=" << servo_id << "\r\n";
    command << "STOP=" << servo_id << CLOSER;
    return command.str();
}

@@ -50,7 +49,7 @@ std::string SRTMinorServoCommandLibrary::preset(std::string servo_id, std::vecto
    {
        command << "," << coordinates[coordinate];
    }
    command << "\r\n";
    command << CLOSER;
    return command.str();
}

@@ -72,7 +71,7 @@ std::string SRTMinorServoCommandLibrary::programTrack(std::string servo_id, long
    {
        command << "," << coordinates[coordinate];
    }
    command << "\r\n";
    command << CLOSER;
    return command.str();
}

@@ -84,7 +83,7 @@ std::string SRTMinorServoCommandLibrary::offset(std::string servo_id, std::vecto
    {
        command << "," << coordinates[coordinate];
    }
    command << "\r\n";
    command << CLOSER;
    return command.str();
}

+107 −0
Original line number Diff line number Diff line
#ifndef __SRTMINORSERVOSOCKET_H__
#define __SRTMINORSERVOSOCKET_H__

/**
 * SRTMinorServoSocketLibrary.h
 * 2023/02/23
 * Giuseppe Carboni (giuseppe.carboni@inaf.it)
 */

#include <mutex>
#include <IRA>
#include <ComponentErrors.h>
#include "SRTMinorServoCommandLibrary.h"

#define TIMEOUT 0.1

using namespace IRA;

// Definition of test class. It is re-defined in the unittest.cpp file.
class SRTMinorServoSocketTest;

class SRTMinorServoSocket: public IRA::CSocket
{
// Declare the SRTMinorServoSocketTest class as friend in order for it to have access to destroyInstance for testing purposes
friend class SRTMinorServoSocketTest;

public:
    /**
     * Calls the constructor and returns the singleton socket instance
     * @param ip_address the IP address to which the socket will connect
     * @param port the port to which the socket will connect
     * @return the singleton socket instance, connected to the given IP address and port, by reference
     * @throw ComponentErrors::SocketErrorExImpl when the user calls this method a second time with different IP address and port arguments
     */
    static SRTMinorServoSocket& getInstance(std::string ip_address, int port);

    /**
     * Returns the previously generated singleton socket instance
     * @return the singleton socket instance, by reference
     * @throw ComponentErrors::SocketErrorExImpl when the user calls this method when the instance has not been generated yet
     */
    static SRTMinorServoSocket& getInstance();

    /**
     * Sends a command on the socket and returns the received answer, if any
     * @param command the command to be sent over the socket
     * @return the received answer to the given command
     */
    std::string sendCommand(std::string command);

    /**
     * Copy constructor operator disabled by default
     */
    SRTMinorServoSocket(SRTMinorServoSocket const&) = delete;

    /**
     * Copy assignment operator disabled by default
     */
    void operator=(SRTMinorServoSocket const&) = delete;

protected:
    /*
     * Force the destruction of the singleton socket object. Only used in tests
     */
    static void destroyInstance();

private:
    /*
     * Constructor method. Generates the singleton socket instance
     * @param ip_address the IP address to which the socket will connect
     * @param port the port to which the socket will connect
     */
    SRTMinorServoSocket(std::string ip_address, int port);

    /*
     * Destructor method. Closes the socket upon destruction
     */
    ~SRTMinorServoSocket();

    /*
     * Instance of the socket. By default it gets initialized to a null pointer
     */
    inline static SRTMinorServoSocket* m_instance = nullptr;

    /*
     * IP address and port of the socket. Being object members their values only exist when a singleton socket object is created correctly
     */
    std::string m_address;
    int m_port;

    /*
     * Mutex object used to syncronize communications and prevent collisions between multiple threads
     */
    std::mutex m_mutex;

    /*
     * Socket status enumerator
     */
    enum socket_status { READY, NOTRDY, BROKEN, TOUT, BUSY } m_socket_status;

    /*
     * Socket error variable. This stores an error condition in case it arises
     */
    IRA::CError m_error;
};

#endif
+61 −0
Original line number Diff line number Diff line
#********************************************
#--------------------------------------------
# Giuseppe Carboni <giuseppe.carboni@inaf.it>
#--------------------------------------------
#********************************************
#
# C programs (public and local)
# -----------------------------
EXECUTABLES     =
EXECUTABLES_L   =

# On-Line Database Files
# ----------------------
CDB_SCHEMAS =

# ----------------------------
# Libraries (public and local)
# ----------------------------
LIBRARIES = SRTMinorServoSocket

SRTMinorServoSocket_OBJECTS = SRTMinorServoSocket
SRTMinorServoSocket_CFLAGS = -std=c++17
SRTMinorServoSocket_LIBS = IRALibrary ComponentErrors SRTMinorServoCommandLibrary


# ----------------------------------------------------------------------
# List of all possible C-sources (used to create automatic dependencies)
# ----------------------------------------------------------------------
CSOURCENAMES = \
	$(foreach exe, $(EXECUTABLES) $(EXECUTABLES_L), $($(exe)_OBJECTS)) \
	$(foreach lib, $(LIBRARIES) $(LIBRARIES_L), $($(lib)_OBJECTS))


# -----------------
# Include Standards
# -----------------

MAKEDIRTMP := $(shell searchFile include/acsMakefile)
ifneq ($(MAKEDIRTMP),\#error\#)
   MAKEDIR := $(MAKEDIRTMP)/include
   include $(MAKEDIR)/acsMakefile
endif


# TARGETS
all:	do_all
	@echo " . . . 'all' done" 

clean : clean_all 
	@echo " . . . clean done"

clean_dist : clean clean_dist_all 
	@echo " . . . clean_dist done"

man   : do_man 
	@echo " . . . man page(s) done"

install : install_all
	@echo " . . . installation done"

#___oOo___
+130 −0
Original line number Diff line number Diff line
#include "SRTMinorServoSocket.h"

SRTMinorServoSocket& SRTMinorServoSocket::getInstance(std::string ip_address, int port)
{
    if(m_instance != nullptr)
    {
        if(m_instance->m_address != ip_address && m_instance->m_port != port)
        {
            ComponentErrors::SocketErrorExImpl exImpl(__FILE__, __LINE__, "SRTMinorServoSocket::getInstance(std::string, int)");
            exImpl.addData("Reason", "Socket already open on '" + m_instance->m_address + ":" + std::to_string(m_instance->m_port) + "' . Use getInstance() (no arguments) to retrieve the object.");
            throw exImpl;
        }
    }
    else
    {
        m_instance = new SRTMinorServoSocket(ip_address, port);
    }
    return *m_instance;
}

SRTMinorServoSocket& SRTMinorServoSocket::getInstance()
{
    if(m_instance == nullptr)
    {
        ComponentErrors::SocketErrorExImpl exImpl(__FILE__, __LINE__, "SRTMinorServoSocket::getInstance()");
        exImpl.addData("Reason", "Socket not yet initialized. Use getInstance(std::string ip_address, int port) to initialize it and retrieve the object.");
        throw exImpl;
    }
    return *m_instance;
}

void SRTMinorServoSocket::destroyInstance()
{
    if(m_instance != nullptr)
    {
        delete m_instance;
        m_instance = nullptr;
    }
}

SRTMinorServoSocket::SRTMinorServoSocket(std::string ip_address, int port)
{
    if(Create(m_error, STREAM) == FAIL)
    {
        Close(m_error);
        ComponentErrors::SocketErrorExImpl exImpl(__FILE__, __LINE__, "SRTMinorServoSocket::SRTMinorServoSocket()");
        exImpl.addData("Reason", "Cannot create the socket.");
        throw exImpl;
    }

    if(Connect(m_error, ip_address.c_str(), port) == FAIL)
    {
        m_socket_status = TOUT;
        Close(m_error);
        ComponentErrors::SocketErrorExImpl exImpl(__FILE__, __LINE__, "SRTMinorServoSocket::SRTMinorServoSocket()");
        exImpl.addData("Reason", "Cannot connect the socket.");
        throw exImpl;
    }

    if(setSockMode(m_error, NONBLOCKING) != SUCCESS)
    {
        m_socket_status = NOTRDY;
        Close(m_error);
        ComponentErrors::SocketErrorExImpl exImpl(__FILE__, __LINE__, "SRTMinorServoSocket::SRTMinorServoSocket()");
        exImpl.addData("Reason", "Cannot set the socket to non-blocking.");
        throw exImpl;
    }

    m_address = ip_address;
    m_port = port;
}

SRTMinorServoSocket::~SRTMinorServoSocket()
{
    std::lock_guard<std::mutex> guard(m_mutex);
    Close(m_error);
}

std::string SRTMinorServoSocket::sendCommand(std::string command)
{
    std::lock_guard<std::mutex> guard(m_mutex);

    double start_time = CIRATools::getUNIXEpoch();
    size_t sent_bytes = 0;

    while(sent_bytes < command.size())
    {
        size_t sent_now = Send(m_error, command.substr(sent_bytes, command.size() - sent_bytes).c_str(), command.size() - sent_bytes);
        sent_bytes += sent_now;

        if(sent_now > 0)
        {
            // Reset the timer
            start_time = CIRATools::getUNIXEpoch();
        }
        else if(CIRATools::getUNIXEpoch() - start_time >= TIMEOUT)
        {
            m_socket_status = TOUT;
            Close(m_error);
            ComponentErrors::SocketErrorExImpl exImpl(__FILE__, __LINE__, "SRTMinorServoSocket::sendCommand()");
            exImpl.addData("Reason", "Timeout when sending command.");
            throw exImpl;
        }
    }

    start_time = CIRATools::getUNIXEpoch();
    std::string answer;

    while(answer.size() < 2 || !(answer.rfind(CLOSER) == answer.size() - CLOSER.size()))
    {
        char buf;
        if(Receive(m_error, &buf, 1) == 1)
        {
            answer += buf;

            // Reset the timer
            start_time = CIRATools::getUNIXEpoch();
        }
        else if(CIRATools::getUNIXEpoch() - start_time >= TIMEOUT)
        {
            m_socket_status = TOUT;
            Close(m_error);
            ComponentErrors::SocketErrorExImpl exImpl(__FILE__, __LINE__, "SRTMinorServoSocket::sendCommand()");
            exImpl.addData("Reason", "Timeout when receiving answer.");
            throw exImpl;
        }
    }

    return answer;
}
Loading