diff --git a/.gitignore b/.gitignore index 2f4074d..7aaac2b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ *.log examples/vs2015/bin/ examples/vs2015/build/ -.vs/ \ No newline at end of file +.vs/ +*.gch +build \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d245fef --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "examples/cmake/kissnet"] + path = examples/cmake/kissnet + url = https://github.com/andersc/kissnet.git diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0130191 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "g++-11 - Build and debug active file", + "type": "cppdbg", + "request": "launch", + "program": "${fileDirname}/${fileBasenameNoExtension}", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb", + // "preLaunchTask": "C/C++: g++-11 build active file" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7f33c17 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,84 @@ +{ + "files.associations": { + "functional": "cpp", + "iostream": "cpp", + "__bit_reference": "cpp", + "__bits": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__functional_base": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "exception": "cpp", + "initializer_list": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ostream": "cpp", + "queue": "cpp", + "random": "cpp", + "ratio": "cpp", + "semaphore": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "vector": "cpp", + "*.tcc": "cpp", + "memory_resource": "cpp", + "stop_token": "cpp", + "cinttypes": "cpp" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..238b42d --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: g++-11 build active file", + "command": "/usr/local/bin/g++-11", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Task generated by Debugger." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..24a3712 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.10) +project(PSN) + +add_subdirectory(examples/cmake) + +add_library(psnlib INTERFACE) +target_include_directories(psnlib INTERFACE ./include) diff --git a/README.md b/README.md index 1cd359b..bbeed67 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,41 @@ This package contains the documentation and a C++ implementation of the protocol The implementation only uses ".hpp" files. If you want to use this implementation in your project, you need to include the file "psn_lib.hpp" in one of your ".cpp" file. -The implementation is cross-platform so it should compile under Windows, Linux and Mac OS X. In revenge, the example projects will only work under a Windows operating system as they use a Windows specific implementation of a simple udp_socket class. If you want to run these demo on another platform, you will need to write your own udp_socket class. +The implementation is cross-platform so it should compile under Windows, Linux and Mac OS X. For any question about the protocol, please write at **info@posistage.net** + +## CMake compilation +You can compile the library and the examples using CMake (for Windows, MacOS and Linux) +- `git submodule update --init --recursive` (fetches the CppSockets library from https://github.com/simondlevy/CppSockets - only required for the examples to work; otherwise bring your own socket library) +- `mkdir build` +- `cd build` +- `cmake ..` +- `cmake --build .` + +Now you should be example to run examples as follows: +- `./examples/cmake/send_example` : encode and send PSN messages using UDP ("PSN Server") +- `./examples/cmake/receive_example` : receive PSN messages via UDP and decode ("PSN Client") + +## Installing this library using CMake +To install this library in your own CMake-based project, you could use a `CMakeList.txt` similar to the following, assuming you copied or cloned psn-cpp to a directory named `./libs/psn-cpp` relative to the project root: +``` +cmake_minimum_required(VERSION 3.19) + +project(my_project) + +# --- Optional: add CppSockets library if you need it +add_library(cppsockets INTERFACE) +set_property(TARGET cppsockets PROPERTY CXX_STANDARD 11) +target_include_directories(cppsockets INTERFACE ./libs/CppSockets) +# ---- + +add_executable(my_executable src/my_executable.cpp) +set_property(TARGET my_executable PROPERTY CXX_STANDARD 11) + +add_subdirectory(./libs/psn-cpp) + +target_include_directories(my_executable PUBLIC ./libs/tether/base_agent/cpp/src) + +target_link_libraries(my_executable PUBLIC psnlib cppsockets) +``` diff --git a/examples/cmake/.gitignore b/examples/cmake/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/examples/cmake/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/examples/cmake/CMakeLists.txt b/examples/cmake/CMakeLists.txt new file mode 100644 index 0000000..be1ae5f --- /dev/null +++ b/examples/cmake/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.19) +project (psn_cpp_examples) + +add_executable(send_example psn_server.cpp) +add_executable(receive_example psn_client.cpp) + +set_property(TARGET send_example PROPERTY CXX_STANDARD 17) +set_property(TARGET receive_example PROPERTY CXX_STANDARD 17) + +target_include_directories(send_example PUBLIC ../../include) +target_include_directories(send_example PUBLIC ./kissnet) + +target_include_directories(receive_example PUBLIC ../../include) +target_include_directories(receive_example PUBLIC ./kissnet) \ No newline at end of file diff --git a/examples/cmake/kissnet b/examples/cmake/kissnet new file mode 160000 index 0000000..8ff4671 --- /dev/null +++ b/examples/cmake/kissnet @@ -0,0 +1 @@ +Subproject commit 8ff467131e19ada27a552f52aa73d07e5d652a2e diff --git a/examples/cmake/psn_client.cpp b/examples/cmake/psn_client.cpp new file mode 100644 index 0000000..3d890a0 --- /dev/null +++ b/examples/cmake/psn_client.cpp @@ -0,0 +1,160 @@ +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +/** + * The MIT License (MIT) + * + * Copyright (c) 2014 VYV Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +**/ +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +#include +#include + +#include + +#include +#include +#include +#include + +const uint16_t PORT = 56565; +static const short BUFLEN = 1024; +static const uint32_t TIMEOUT_MSEC = 1000; + +namespace kn = kissnet; + +int main( void ) +{ + char char_buf[BUFLEN]; + + //==================================================== + // Init "client" (RECEIVE) + + auto mcast_listen_socket = kissnet::udp_socket(); + mcast_listen_socket.join(kissnet::endpoint("236.10.10.10", 56565)); + + kn::buffer<1024> recv_buff; + + ::psn::psn_decoder psn_decoder ; + uint8_t last_frame_id = 0 ; + int skip_cout = 0 ; + + //==================================================== + // Main loop + while ( 1 ) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1) ); + + printf("Waiting for data..."); + fflush(stdout); + + memset(char_buf, 0, BUFLEN); + + *char_buf = 0; + + auto [received_bytes, status] = mcast_listen_socket.recv(recv_buff); + const auto from = mcast_listen_socket.get_recv_endpoint(); + + std::cout << "Received " << received_bytes << " bytes"; + std::cout << " from: " << from.address << ':' << from.port << '\n'; + + if (received_bytes > 0) { + + std::memcpy(char_buf, recv_buff.data(), received_bytes); + + psn_decoder.decode( char_buf , BUFLEN ) ; + + // if ( psn_decoder.get_data().header.frame_id != last_frame_id ) + // { + last_frame_id = psn_decoder.get_data().header.frame_id ; + + const ::psn::tracker_map & recv_trackers = psn_decoder.get_data().trackers ; + + // if ( skip_cout++ % 20 == 0 ) + // { + ::std::cout << "--------------------PSN CLIENT-----------------" << ::std::endl ; + ::std::cout << "System Name: " << psn_decoder.get_info().system_name << ::std::endl ; + ::std::cout << "Frame Id: " << (int)last_frame_id << ::std::endl ; + ::std::cout << "Frame Timestamp: " << psn_decoder.get_data().header.timestamp_usec << ::std::endl ; + ::std::cout << "Tracker count: " << recv_trackers.size() << ::std::endl ; + + for ( auto it = recv_trackers.begin() ; it != recv_trackers.end() ; ++it ) + { + const ::psn::tracker & tracker = it->second ; + + ::std::cout << "Tracker - id: " << tracker.get_id() << " | name: " << tracker.get_name() << ::std::endl ; + + if ( tracker.is_pos_set() ) + ::std::cout << " pos: " << tracker.get_pos().x << ", " << + tracker.get_pos().y << ", " << + tracker.get_pos().z << std::endl ; + + if ( tracker.is_speed_set() ) + ::std::cout << " speed: " << tracker.get_speed().x << ", " << + tracker.get_speed().y << ", " << + tracker.get_speed().z << std::endl ; + + if ( tracker.is_ori_set() ) + ::std::cout << " ori: " << tracker.get_ori().x << ", " << + tracker.get_ori().y << ", " << + tracker.get_ori().z << std::endl ; + + if ( tracker.is_status_set() ) + ::std::cout << " status: " << tracker.get_status() << std::endl ; + + if ( tracker.is_accel_set() ) + ::std::cout << " accel: " << tracker.get_accel().x << ", " << + tracker.get_accel().y << ", " << + tracker.get_accel().z << std::endl ; + + if ( tracker.is_target_pos_set() ) + ::std::cout << " target pos: " << tracker.get_target_pos().x << ", " << + tracker.get_target_pos().y << ", " << + tracker.get_target_pos().z << std::endl ; + + if ( tracker.is_timestamp_set() ) + ::std::cout << " timestamp: " << tracker.get_timestamp() << std::endl ; + } + + ::std::cout << "-----------------------------------------------" << ::std::endl ; + //} // skip + + } + + else { + printf("\n"); + // sleep(1); + std::this_thread::sleep_for(std::chrono::milliseconds(1) ); + } + + + + // if ( socket_client.receive_message( msg , ::psn::MAX_UDP_PACKET_SIZE ) ) + // { + + + std::this_thread::sleep_for(std::chrono::milliseconds(1) ); + + } + + return 0; +} + + diff --git a/examples/cmake/psn_server.cpp b/examples/cmake/psn_server.cpp new file mode 100644 index 0000000..3921e86 --- /dev/null +++ b/examples/cmake/psn_server.cpp @@ -0,0 +1,156 @@ +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +/** + * The MIT License (MIT) + * + * Copyright (c) 2014 VYV Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +**/ +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +#include "psn_lib.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include + +const char * HOST = "127.0.0.1"; +const uint16_t PORT = 8888; + +namespace kn = kissnet; + +int main( void ) +{ + //==================================================== + // Init "server" (SENDs) + + kissnet::udp_socket mcast_send_socket(kissnet::endpoint("236.10.10.10", 56565)); + std::cout << "init socket OK" << std::endl; + + ::psn::psn_encoder psn_encoder( "Test PSN Server" ) ; + + ::psn::tracker_map trackers ; + int i = 0 ; + trackers[ i ] = ::psn::tracker( i++ , "Sun" ) ; + trackers[ i ] = ::psn::tracker( i++ , "Mercury" ) ; + trackers[ i ] = ::psn::tracker( i++ , "Venus" ) ; + trackers[ i ] = ::psn::tracker( i++ , "Earth" ) ; + trackers[ i ] = ::psn::tracker( i++ , "Mars" ) ; + trackers[ i ] = ::psn::tracker( i++ , "Jupiter" ) ; + trackers[ i ] = ::psn::tracker( i++ , "Saturn" ) ; + trackers[ i ] = ::psn::tracker( i++ , "Uranus" ) ; + trackers[ i ] = ::psn::tracker( i++ , "Neptune" ) ; + trackers[ i ] = ::psn::tracker( i++ , "Pluto" ) ; + + // Planets orbit in days + float orbits[ 10 ] = { 1.0f , 88.0f , 224.7f , 365.2f , 687.0f , 4332.0f , 10760.0f , 30700.0f , 60200.0f , 90600.0f } ; + // Planets distance from sun in million km x 100 + float dist_from_sun[ 10 ] = { 0.0f , 0.58f , 1.08f , 1.50f , 2.28f , 7.78f , 14.29f , 28.71f , 45.04f , 59.13f } ; + + uint64_t timestamp = 0 ; + + //==================================================== + // Main loop + while ( true ) + { + // Update trackers + for ( int i = 1 ; i < 10 ; ++i ) // do not update the sun + { + float a = 1.0f / orbits[ i ] ; + float b = dist_from_sun[ i ] ; + float x = (float)timestamp ; + float cb = ::std::cosf( a * x ) * b ; + float sb = ::std::sinf( a * x ) * b ; + trackers[ i ].set_pos( ::psn::float3( sb , 0 , cb ) ) ; + trackers[ i ].set_speed( ::psn::float3( a * cb , 0 , -a * sb ) ) ; + trackers[ i ].set_ori( ::psn::float3( 0 , x / 1000.0f , 0 ) ) ; + trackers[ i ].set_accel( ::psn::float3( ::psn::float3( -a * a * sb , 0 , -a * a * cb ) ) ) ; + trackers[ i ].set_target_pos( ::psn::float3( 3 , 14 , 16 ) ) ; + trackers[ i ].set_status( i / 10.0f ) ; + trackers[ i ].set_timestamp( timestamp ) ; + } + + // Send data + if ( timestamp % 16 == 0 ) // transmit data at 60 Hz approx. + { + std::cout << "timestamp = " << timestamp << std::endl; + + ::std::list< ::std::string > data_packets = psn_encoder.encode_data( trackers , timestamp ) ; + + ::std::cout << "--------------------PSN SERVER-----------------" << ::std::endl ; + ::std::cout << "Sending PSN_DATA_PACKET : " + << "Frame Id = " << (int)psn_encoder.get_last_data_frame_id() << std::endl + << "Tracker count = " << (int)trackers.size() + << " , Packet Count = " << data_packets.size() << ::std::endl ; + + for ( auto it = data_packets.begin() ; it != data_packets.end() ; ++it ) + { + std::cout << "Send: PSN data of length " << it->length() << std::endl; + + kn::buffer<1024> buff; + std::memcpy(buff.data(), it->c_str(), it->size()); + + + // std::cout << "Size to send: " << buff.size() << "/" << sizeof(buff.data()) << "/" << it->size() << "\n"; + auto[sent_bytes, status] = mcast_send_socket.send( + buff.data(), + it->length() // do not use sizeof(buff.data()), because null-term chars will confuse matters! + ); + std::cout <<"Sent " << sent_bytes << ", status=" << status << "\n"; + + + } + + ::std::cout << "-----------------------------------------------" << ::std::endl ; + } + + // Send Info + if ( timestamp % 1000 == 0 ) // transmit info at 1 Hz approx. + { + ::std::list< ::std::string > info_packets = psn_encoder.encode_info( trackers , timestamp ) ; + + ::std::cout << "--------------------PSN SERVER-----------------" << ::std::endl ; + ::std::cout << "Sending PSN_INFO_PACKET : " + << "Frame Id = " << (int)psn_encoder.get_last_info_frame_id() + << " , Packet Count = " << info_packets.size() << ::std::endl ; + + for ( auto it = info_packets.begin() ; it != info_packets.end() ; ++it ) { + kn::buffer<1024> buff; + std::memcpy(buff.data(), it->c_str(), it->size()); + auto[sent_bytes, status] = mcast_send_socket.send( + buff.data(), + it->length() // do not use sizeof(buff.data()), because null-term chars will confuse matters! + ); + } + + + ::std::cout << "-----------------------------------------------" << ::std::endl ; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1) ); + timestamp++ ; + } + + return 0; +} \ No newline at end of file