Linux :
sudo apt-get install vrpn
Windows :
Compile the library by yourself using this zip file : https://github.com/vrpn/vrpn/releases/download/v07.33/vrpn_07_33.zip
MacOS :
brew install vrpn
Although, the vrpn installed through brew should work for most cases but if it does not, it can always be build manually.
Compile the library by yourself using this zip file : https://github.com/vrpn/vrpn/releases/download/v07.33/vrpn_07_33.zip
Please ensure oscpack version is >= 1.1.X. Do not use the default packages provided by ubuntu repository (1.0.X version). You can find a fully working version here : http://ftp.debian.org/debian/pool/main/o/oscpack/?C=M;O=D
wget http://ftp.debian.org/debian/pool/main/o/oscpack/liboscpack1_1.1.0-2_amd64.deb
sudo dpkg -i liboscpack1_1.1.0-2_amd64.deb
wget http://ftp.debian.org/debian/pool/main/o/oscpack/liboscpack-dev_1.1.0-2_amd64.deb
sudo dpkg -i liboscpack-dev_1.1.0-2_amd64.deb
Compile the library by yourself using this zip file https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/oscpack/oscpack_1_1_0_RC2.zip
Compile the library by yourself using this zip file https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/oscpack/oscpack_1_1_0_RC2.zip
Depending of your distribution, the package name can be different.
Ubuntu :
sudo apt-get install libzmq3-dev
Fedora :
sudo dnf install libzmq-devel
Compile the library by yourself using this zip file : https://github.com/zeromq/libzmq/releases/download/v4.2.3/zeromq-4.2.3.zip
Compile the library by yourself using this zip file : https://github.com/zeromq/libzmq/releases/download/v4.2.3/zeromq-4.2.3.zip
Note :
zmq.hpp
is not created by default while compiling the library. While, building sofa if error occurs likezmq.hpp not found
, manually paste the file in this location/usr/local/include
.
To learn how to create a SOFA scene, please refer to the tutorial provided by the SOFA Modeler or this documentation.
Here is an example of how you can use the Communication component. In this example we want to send or receive sofa's data
ServerCommunication is an abstract class allowing users to create asynchronous communication class . Actually, there is three implementations of it. These three implementations use -
- ZMQ
- OSC
- VRPN
ServerCommunication provides default DataFields :
- job -> "receiver" or "sender". Depends if you want to receive or send datas. Default value is "receiver"
- refreshRate -> an int. This is only related to the send part. For exemple if you set 2 a message will be sent every 2hz (aka 500ms). Default value is 30
- address -> an int. Define on which network address you want to send data. Default value is "127.0.0.1"
- port -> an int. Define on which network port you want to send data. Default value is 6000
OSC and ZMQ implementations have specifics DataFields :
- OSC
- packetSize -> an int. Define size of OSC packets. Default value is 1024
- ZMQ
- pattern -> "publish/subscribe" or "request/reply". It describe how zmq will works. Default value "publish/subscribe".
Subscriber is a class needed to ServerCommunication. This will allow user to define which kind of message he want to send/receive and which sofa´s data he want to bind to.
Subscriber DataFields explanation :
- subject -> a string. ServerCommunication will receive and send only subscribed subjects. Default value is ""
- communication -> a ServerCommunication link. A subscriber is attached to a serverCommunication
- target -> a BaseObject link. This object will be use to read/write data in it
- datas -> a string. A list of variables name. Existing or not inside the target
A serverCommunication should contains at least one subscriber.
<ServerCommunicationVRPN name="vrpn1" job="receiver" address="localhost"/>
<CommunicationSubscriber name="sub1" communication="@vrpn1" subject="testing" target="@light1" datas="aNewStringValue"/>
<ServerCommunicationVRPN name="vrpn1" job="sender" address="localhost"/>
<CommunicationSubscriber name="sub1" communication="@vrpn1" subject="testing" target="@light1" datas="position"/>
<ServerCommunicationOSC name="aServerCommunicationOSC" job="receiver" port="6000"/>
<CommunicationSubscriber name="subscriberOSC" communication="@aServerCommunicationOSC" subject="/test" target="@aServerCommunicationOSC" datas="x y"/>
<ServerCommunicationOSC name="aServerCommunicationOSC" job="receiver" port="6000" refreshRate="2"/>
<CommunicationSubscriber name="subscriberOSC" communication="@aServerCommunicationOSC" subject="/test" target="@aServerCommunicationOSC" datas="x y"/>
<ServerCommunicationZMQ name="aServerCommunicationZMQ" job="receiver" pattern="request/reply" port="6000"/>
<CommunicationSubscriber name="subscriberZMQ" communication="@aServerCommunicationZMQ" subject="/test" target="@aServerCommunicationZMQ" datas="x y"/>
<ServerCommunicationZMQ name="aServerCommunicationZMQ" job="sender" pattern="request/reply" port="6000" refreshRate="2"/>
<CommunicationSubscriber name="subscriberZMQ" communication="@aServerCommunicationZMQ" subject="/test" target="@aServerCommunicationZMQ" datas="x y"/>
A set of examples are availables in example plugin directory : Examples
Implementing a new protocol is quite easy. Simply extend from ServerCommunication. Then you will have to implement some virtual methods such as :
- getFactoryInstance
- initTypeFactory
- sendData
- receiveData
- getArgumentType
- getArgumentValue
- defaultDataType
If you try to fetch a non existing data the ServerCommunication will create it by asking a factory. Allowing the user to create sofa data from received data using factory is an elegant way to add flexibility and reusability. Two virtual function are related to the factory let's see how it has been implemented for OSC.
Header file :
//////////////////////////////// Factory OSC type /////////////////////////////////
typedef CommunicationDataFactory OSCDataFactory;
OSCDataFactory* getFactoryInstance() override;
virtual void initTypeFactory() override;
/////////////////////////////////////////////////////////////////////////////////
Cpp file :
ServerCommunicationOSC::OSCDataFactory* ServerCommunicationOSC::getFactoryInstance(){
static OSCDataFactory* s_localfactory = nullptr ;
if(s_localfactory==nullptr)
s_localfactory = new ServerCommunicationOSC::OSCDataFactory() ;
return s_localfactory ;
}
void ServerCommunicationOSC::initTypeFactory()
{
getFactoryInstance()->registerCreator("f", new DataCreator<float>());
getFactoryInstance()->registerCreator("d", new DataCreator<double>());
getFactoryInstance()->registerCreator("i", new DataCreator<int>());
getFactoryInstance()->registerCreator("s", new DataCreator<std::string>());
getFactoryInstance()->registerCreator("matrixf", new DataCreator<vector<float>>());
getFactoryInstance()->registerCreator("matrixi", new DataCreator<vector<int>>());
getFactoryInstance()->registerCreator("matrixd", new DataCreator<vector<double>>());
}
The getFactoryInstance function si responsible to return a DataFactory instance. In this case, a singleton. The initTypeFactory is the place where we will do the binding between receveived data type and sofa's type. For example, using OSC, if we received a float his tag type will be "f". The equivalent in sofa is the primitive type float. Then we bind "f" to float. In case of non existing data with type "f" the serverCommunication will create a sofa float data.
virtual void sendData() override;
virtual void receiveData() override;
Those functions have to be implemented by the new protocols. Both are runs by the mother class inside a thread. This should be the place where you loop for sending or receiving datas. You can find examples in ZMQ, OSC and VRPN implementations.
For receiving and sending datas you will need to fetch them using the mother class function named fetchDataFromSenderBuffer and saveDatasToReceivedBuffer.
Example for sending datas :
std::string ServerCommunicationZMQ::createZMQMessage(CommunicationSubscriber* subscriber, std::string argument)
{
std::stringstream messageStr;
BaseData* data = fetchDataFromSenderBuffer(subscriber, argument);
if (!data)
throw std::invalid_argument("data is null");
const AbstractTypeInfo *typeinfo = data->getValueTypeInfo();
const void* valueVoidPtr = data->getValueVoidPtr();
In this example we retrieve the data from a buffer using fetchDataFromSenderBuffer. The argument named argument is the argument name we want to fetch. You can find more details in ZMQ, OSC and VRPN inl files.
Example for receiving datas :