From 0fcc044cc4c434de6895e7dbb8bee61a1b196bdb Mon Sep 17 00:00:00 2001 From: abernste Date: Wed, 12 Sep 2018 15:50:10 +0300 Subject: [PATCH 01/25] Enable BUILD_WITH_TM2 CMAKE flag --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf94978227..5ce38481a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,7 +121,7 @@ elseif(UNIX) endif() -option(BUILD_WITH_TM2 "Build with support for Intel TM2 tracking device" OFF) +option(BUILD_WITH_TM2 "Build with support for Intel TM2 tracking device" ON) option(BUILD_EASYLOGGINGPP "Build EasyLogging++ as a part of the build" ON) From baa997ccc91f5995e7c9d71eb88d6d8cdccc6934 Mon Sep 17 00:00:00 2001 From: belkinirena Date: Wed, 12 Sep 2018 15:57:16 +0300 Subject: [PATCH 02/25] Initial commit --- CMake/Findlibtm.cmake | 5 ++++- CMakeLists.txt | 3 ++- src/tm2/tm-context.cpp | 8 ++++---- src/tm2/tm-context.h | 4 ++-- src/tm2/tm-device.cpp | 18 +++++++++--------- src/tm2/tm-device.h | 4 ++++ 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/CMake/Findlibtm.cmake b/CMake/Findlibtm.cmake index 604c3aee09..b7c1f039fd 100644 --- a/CMake/Findlibtm.cmake +++ b/CMake/Findlibtm.cmake @@ -55,7 +55,10 @@ find_library(INFRA_LIBRARY_DEBUG HINTS "${LIBTM_DIR}/bin/Debug" ) - +find_library(LIBUSB_LIBRARY + NAMES + libusb-1.0.lib + ) # handle the LIBTM and REQUIRED arguments and set LIBTM_FOUND to TRUE if # all listed variables are TRUE diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ce38481a6..61fd292f13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -869,7 +869,8 @@ if (BUILD_WITH_TM2) ) include_directories(${LIBTM_INCLUDE_DIR}) - set(TRACKING_DEVICE_LIBS libtm) + link_directories(${LIBTM_LIBRARY} ${INFRA_LIBRARY} ${LIBUSB_LIBRARY}) + set(TRACKING_DEVICE_LIBS tm infra libusb-1.0) install(TARGETS libtm EXPORT realsense2Targets diff --git a/src/tm2/tm-context.cpp b/src/tm2/tm-context.cpp index f1904ea238..047e859731 100644 --- a/src/tm2/tm-context.cpp +++ b/src/tm2/tm-context.cpp @@ -19,7 +19,7 @@ namespace librealsense tm2_context::tm2_context(context* ctx) : _is_disposed(false), _t(&tm2_context::thread_proc, this), _ctx(ctx) { - _manager = std::shared_ptr(perc::TrackingManager::CreateInstance(this), + _manager = std::shared_ptr(perc::TrackingManager::CreateInstance(this), [](perc::TrackingManager* ptr) { perc::TrackingManager::ReleaseInstance(ptr); }); if (_manager == nullptr) { @@ -48,7 +48,7 @@ namespace librealsense } - void tm2_context::onStateChanged(TrackingManager::EventType state, TrackingDevice* dev) + void tm2_context::onStateChanged(TrackingManager::EventType state, TrackingDevice* dev, TrackingData::DeviceInfo devInfo) { std::shared_ptr added; std::shared_ptr removed; @@ -73,9 +73,9 @@ namespace librealsense on_device_changed(removed, added); } - void tm2_context::onError(TrackingManager::Error error, TrackingDevice* dev) + void tm2_context::onError(Status error, TrackingDevice* dev) { - LOG_ERROR("Error occured while connecting device:" << dev << " Error: 0x" << std::hex << error); + LOG_ERROR("Error occured while connecting device:" << dev << " Error: 0x" << std::hex << static_cast(error)); } void tm2_context::thread_proc() diff --git a/src/tm2/tm-context.h b/src/tm2/tm-context.h index 04b58e594e..716f39809f 100644 --- a/src/tm2/tm-context.h +++ b/src/tm2/tm-context.h @@ -21,8 +21,8 @@ namespace librealsense std::vector query_devices() const; signal, std::shared_ptr> on_device_changed; // TrackingManager::Listener - void onStateChanged(perc::TrackingManager::EventType state, perc::TrackingDevice*) override; - void onError(perc::TrackingManager::Error error, perc::TrackingDevice*) override; + void onStateChanged(perc::TrackingManager::EventType state, perc::TrackingDevice* device, perc::TrackingData::DeviceInfo deviceInfo) override; + void onError(perc::Status error, perc::TrackingDevice*) override; private: void thread_proc(); friend class connect_disconnect_listener; diff --git a/src/tm2/tm-device.cpp b/src/tm2/tm-device.cpp index eddde9376d..29c7c994c8 100644 --- a/src/tm2/tm-device.cpp +++ b/src/tm2/tm-device.cpp @@ -164,7 +164,7 @@ namespace librealsense profile->set_unique_id(environment::get_instance().generate_stream_id()); if (tm_profile.sensorIndex == 0) { - profile->make_default(); + profile->tag_profile(profile_tag::PROFILE_TAG_DEFAULT | profile_tag::PROFILE_TAG_SUPERSET); } stream_profile sp = { stream, profile->get_stream_index(), p.width, p.height, p.fps, profile->get_format() }; auto intrinsics = get_intrinsics(sp); @@ -192,7 +192,7 @@ namespace librealsense profile->set_unique_id(environment::get_instance().generate_stream_id()); if (tm_profile.sensorIndex == 0) { - profile->make_default(); + profile->tag_profile(profile_tag::PROFILE_TAG_DEFAULT | profile_tag::PROFILE_TAG_SUPERSET); } auto intrinsics = get_motion_intrinsics(*profile); profile->set_intrinsics([intrinsics]() { return intrinsics; }); @@ -212,7 +212,7 @@ namespace librealsense profile->set_unique_id(environment::get_instance().generate_stream_id()); if (tm_profile.sensorIndex == 0) { - profile->make_default(); + profile->tag_profile(profile_tag::PROFILE_TAG_DEFAULT | profile_tag::PROFILE_TAG_SUPERSET); } auto intrinsics = get_motion_intrinsics(*profile); profile->set_intrinsics([intrinsics]() { return intrinsics; }); @@ -239,7 +239,7 @@ namespace librealsense profile->set_unique_id(environment::get_instance().generate_stream_id()); if (tm_profile.profileType == SixDofProfile0) { - profile->make_default(); + profile->tag_profile(profile_tag::PROFILE_TAG_DEFAULT | profile_tag::PROFILE_TAG_SUPERSET); } results.push_back(profile); @@ -630,7 +630,7 @@ namespace librealsense video_md.arrival_ts = tm_frame.arrivalTimeStamp; video_md.exposure_time = tm_frame.exposuretime; - frame_additional_data additional_data(ts_ms.count(), tm_frame.frameId, system_ts_ms.count(), sizeof(video_md), (uint8_t*)&video_md, tm_frame.arrivalTimeStamp); + frame_additional_data additional_data(ts_ms.count(), tm_frame.frameId, system_ts_ms.count(), sizeof(video_md), (uint8_t*)&video_md, tm_frame.arrivalTimeStamp, 0 ,0); // Find the frame stream profile std::shared_ptr profile = nullptr; @@ -714,7 +714,7 @@ namespace librealsense pose_frame_metadata frame_md = { 0 }; frame_md.arrival_ts = tm_frame.arrivalTimeStamp; - frame_additional_data additional_data(ts_ms.count(), frame_num++, system_ts_ms.count(), sizeof(frame_md), (uint8_t*)&frame_md, tm_frame.arrivalTimeStamp); + frame_additional_data additional_data(ts_ms.count(), frame_num++, system_ts_ms.count(), sizeof(frame_md), (uint8_t*)&frame_md, tm_frame.arrivalTimeStamp, 0, 0); // Find the frame stream profile std::shared_ptr profile = nullptr; @@ -822,7 +822,7 @@ namespace librealsense motion_md.arrival_ts = tm_frame_ts.arrivalTimeStamp; motion_md.temperature = temperature; - frame_additional_data additional_data(ts_ms.count(), frame_number, system_ts_ms.count(), sizeof(motion_md), (uint8_t*)&motion_md, tm_frame_ts.arrivalTimeStamp); + frame_additional_data additional_data(ts_ms.count(), frame_number, system_ts_ms.count(), sizeof(motion_md), (uint8_t*)&motion_md, tm_frame_ts.arrivalTimeStamp, 0, 0); // Find the frame stream profile std::shared_ptr profile = nullptr; @@ -931,7 +931,7 @@ namespace librealsense } register_info(RS2_CAMERA_INFO_NAME, tm2_device_name()); register_info(RS2_CAMERA_INFO_SERIAL_NUMBER, to_string() << info.serialNumber); - register_info(RS2_CAMERA_INFO_FIRMWARE_VERSION, to_string() << info.fw.major << "." << info.fw.minor << "." << info.fw.patch << "." << info.fw.build); + register_info(RS2_CAMERA_INFO_FIRMWARE_VERSION, to_string() << info.version.fw.major << "." << info.version.fw.minor << "." << info.version.fw.patch << "." << info.version.fw.build); register_info(RS2_CAMERA_INFO_PRODUCT_ID, to_string() << info.usbDescriptor.idProduct); std::string device_path = std::string("vid_") + std::to_string(info.usbDescriptor.idVendor) + @@ -947,7 +947,7 @@ namespace librealsense tm2_device::~tm2_device() { - for (auto&& d : get_context()->query_devices()) + for (auto&& d : get_context()->query_devices(RS2_PRODUCT_LINE_ANY_INTEL)) { for (auto&& tmd : d->get_device_data().tm2_devices) { diff --git a/src/tm2/tm-device.h b/src/tm2/tm-device.h index b5796f32ff..7fd5fa00ac 100644 --- a/src/tm2/tm-device.h +++ b/src/tm2/tm-device.h @@ -28,6 +28,10 @@ namespace librealsense bool is_enabled() const override; void connect_controller(const std::array& mac_address) override; void disconnect_controller(int id) override; + std::vector get_profiles_tags() const override + { + return std::vector(); + }; private: static const char* tm2_device_name() { From 996f25da3b6f40596ebcbffde873f0aaa8dad846 Mon Sep 17 00:00:00 2001 From: belkinirena Date: Thu, 13 Sep 2018 13:24:49 +0300 Subject: [PATCH 03/25] Add libtm source code to librealsense solution (#27) --- CMakeLists.txt | 30 +- third-party/libtm/.gitignore | 26 + third-party/libtm/CMakeLists.txt | 25 + third-party/libtm/cmake/linux.cmake | 8 + third-party/libtm/cmake/os.cmake | 5 + third-party/libtm/cmake/windows.cmake | 30 + third-party/libtm/libtm/CMakeLists.txt | 37 + .../libtm/libtm/include/TrackingCommon.h | 144 + .../libtm/libtm/include/TrackingData.h | 875 ++++ .../libtm/libtm/include/TrackingDevice.h | 607 +++ .../libtm/libtm/include/TrackingManager.h | 52 + .../libtm/libtm/include/TrackingSerializer.h | 47 + third-party/libtm/libtm/infra/Android.mk | 56 + third-party/libtm/libtm/infra/CMakeLists.txt | 38 + third-party/libtm/libtm/infra/Dispatcher.cpp | 411 ++ third-party/libtm/libtm/infra/Dispatcher.h | 253 + third-party/libtm/libtm/infra/EmbeddedList.h | 187 + third-party/libtm/libtm/infra/Event.h | 32 + third-party/libtm/libtm/infra/EventHandler.h | 62 + third-party/libtm/libtm/infra/Event_lin.cpp | 35 + third-party/libtm/libtm/infra/Event_win.cpp | 37 + third-party/libtm/libtm/infra/Fence.h | 39 + third-party/libtm/libtm/infra/Fsm.cpp | 394 ++ third-party/libtm/libtm/infra/Fsm.h | 334 ++ third-party/libtm/libtm/infra/Log.cpp | 351 ++ third-party/libtm/libtm/infra/Log.h | 134 + third-party/libtm/libtm/infra/Poller.h | 62 + third-party/libtm/libtm/infra/Poller_lin.cpp | 112 + third-party/libtm/libtm/infra/Poller_win.cpp | 154 + third-party/libtm/libtm/infra/Semaphore.h | 40 + .../libtm/libtm/infra/Semaphore_lin.cpp | 49 + .../libtm/libtm/infra/Semaphore_win.cpp | 63 + third-party/libtm/libtm/infra/Utils.cpp | 163 + third-party/libtm/libtm/infra/Utils.h | 86 + third-party/libtm/libtm/src/.bdsignore | 1 + third-party/libtm/libtm/src/CMakeLists.txt | 67 + third-party/libtm/libtm/src/Common.cpp | 379 ++ third-party/libtm/libtm/src/Common.h | 47 + third-party/libtm/libtm/src/CompleteTask.h | 730 +++ third-party/libtm/libtm/src/Device.cpp | 4221 +++++++++++++++++ third-party/libtm/libtm/src/Device.h | 416 ++ third-party/libtm/libtm/src/Loader.cpp | 134 + third-party/libtm/libtm/src/Loader.h | 52 + third-party/libtm/libtm/src/Main.cpp | 655 +++ third-party/libtm/libtm/src/Manager.cpp | 587 +++ third-party/libtm/libtm/src/Manager.h | 157 + third-party/libtm/libtm/src/Message.cpp | 500 ++ third-party/libtm/libtm/src/Message.h | 1876 ++++++++ third-party/libtm/libtm/src/Protocol.cpp | 363 ++ third-party/libtm/libtm/src/Protocol.h | 31 + .../libtm/libtm/src/RcSerializer/Packet.cpp | 373 ++ .../libtm/libtm/src/RcSerializer/Packet.h | 185 + .../libtm/libtm/src/RcSerializer/Player.cpp | 390 ++ .../libtm/libtm/src/RcSerializer/Player.h | 50 + .../libtm/libtm/src/RcSerializer/Recorder.cpp | 249 + .../libtm/libtm/src/RcSerializer/Recorder.h | 68 + .../libtm/src/RcSerializer/concurrency.h | 310 ++ .../libtm/src/RcSerializer/latency_queue.h | 62 + .../libtm/libtm/src/RcSerializer/rc_packet.h | 355 ++ .../libtm/libtm/src/UsbPlugListener.cpp | 149 + third-party/libtm/libtm/src/UsbPlugListener.h | 111 + third-party/libtm/libtm/src/Version.h.in | 33 + third-party/libtm/libtm/src/version.rc.in | 93 + third-party/libtm/resources/CMakeLists.txt | 320 ++ third-party/libtm/versions.cmake | 91 + third-party/win/libusb/include/libusb.h | 2008 ++++++++ 66 files changed, 20016 insertions(+), 25 deletions(-) create mode 100644 third-party/libtm/.gitignore create mode 100644 third-party/libtm/CMakeLists.txt create mode 100644 third-party/libtm/cmake/linux.cmake create mode 100644 third-party/libtm/cmake/os.cmake create mode 100644 third-party/libtm/cmake/windows.cmake create mode 100644 third-party/libtm/libtm/CMakeLists.txt create mode 100644 third-party/libtm/libtm/include/TrackingCommon.h create mode 100644 third-party/libtm/libtm/include/TrackingData.h create mode 100644 third-party/libtm/libtm/include/TrackingDevice.h create mode 100644 third-party/libtm/libtm/include/TrackingManager.h create mode 100644 third-party/libtm/libtm/include/TrackingSerializer.h create mode 100644 third-party/libtm/libtm/infra/Android.mk create mode 100644 third-party/libtm/libtm/infra/CMakeLists.txt create mode 100644 third-party/libtm/libtm/infra/Dispatcher.cpp create mode 100644 third-party/libtm/libtm/infra/Dispatcher.h create mode 100644 third-party/libtm/libtm/infra/EmbeddedList.h create mode 100644 third-party/libtm/libtm/infra/Event.h create mode 100644 third-party/libtm/libtm/infra/EventHandler.h create mode 100644 third-party/libtm/libtm/infra/Event_lin.cpp create mode 100644 third-party/libtm/libtm/infra/Event_win.cpp create mode 100644 third-party/libtm/libtm/infra/Fence.h create mode 100644 third-party/libtm/libtm/infra/Fsm.cpp create mode 100644 third-party/libtm/libtm/infra/Fsm.h create mode 100644 third-party/libtm/libtm/infra/Log.cpp create mode 100644 third-party/libtm/libtm/infra/Log.h create mode 100644 third-party/libtm/libtm/infra/Poller.h create mode 100644 third-party/libtm/libtm/infra/Poller_lin.cpp create mode 100644 third-party/libtm/libtm/infra/Poller_win.cpp create mode 100644 third-party/libtm/libtm/infra/Semaphore.h create mode 100644 third-party/libtm/libtm/infra/Semaphore_lin.cpp create mode 100644 third-party/libtm/libtm/infra/Semaphore_win.cpp create mode 100644 third-party/libtm/libtm/infra/Utils.cpp create mode 100644 third-party/libtm/libtm/infra/Utils.h create mode 100644 third-party/libtm/libtm/src/.bdsignore create mode 100644 third-party/libtm/libtm/src/CMakeLists.txt create mode 100644 third-party/libtm/libtm/src/Common.cpp create mode 100644 third-party/libtm/libtm/src/Common.h create mode 100644 third-party/libtm/libtm/src/CompleteTask.h create mode 100644 third-party/libtm/libtm/src/Device.cpp create mode 100644 third-party/libtm/libtm/src/Device.h create mode 100644 third-party/libtm/libtm/src/Loader.cpp create mode 100644 third-party/libtm/libtm/src/Loader.h create mode 100644 third-party/libtm/libtm/src/Main.cpp create mode 100644 third-party/libtm/libtm/src/Manager.cpp create mode 100644 third-party/libtm/libtm/src/Manager.h create mode 100644 third-party/libtm/libtm/src/Message.cpp create mode 100644 third-party/libtm/libtm/src/Message.h create mode 100644 third-party/libtm/libtm/src/Protocol.cpp create mode 100644 third-party/libtm/libtm/src/Protocol.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/Packet.cpp create mode 100644 third-party/libtm/libtm/src/RcSerializer/Packet.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/Player.cpp create mode 100644 third-party/libtm/libtm/src/RcSerializer/Player.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/Recorder.cpp create mode 100644 third-party/libtm/libtm/src/RcSerializer/Recorder.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/concurrency.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/latency_queue.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/rc_packet.h create mode 100644 third-party/libtm/libtm/src/UsbPlugListener.cpp create mode 100644 third-party/libtm/libtm/src/UsbPlugListener.h create mode 100644 third-party/libtm/libtm/src/Version.h.in create mode 100644 third-party/libtm/libtm/src/version.rc.in create mode 100644 third-party/libtm/resources/CMakeLists.txt create mode 100644 third-party/libtm/versions.cmake create mode 100644 third-party/win/libusb/include/libusb.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 61fd292f13..28f089ead0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -822,7 +822,9 @@ add_subdirectory(third-party/realsense-file) if (BUILD_WITH_TM2) message(STATUS "Building with TM2") - + + add_subdirectory(third-party/libtm) + if(WIN32) source_group("Source Files\\Devices\\Tracking" FILES src/tm2/tm-context.cpp @@ -840,19 +842,6 @@ if (BUILD_WITH_TM2) endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMake) - find_package(libtm REQUIRED) - - if(BUILD_WITH_STATIC_CRT) - foreach(flag_var - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO - CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif(${flag_var} MATCHES "/MD") - endforeach(flag_var) - endif() list(APPEND REALSENSE_HPP src/tm2/tm-context.h @@ -867,17 +856,6 @@ if (BUILD_WITH_TM2) src/tm2/tm-device.cpp src/tm2/tm-info.cpp ) - - include_directories(${LIBTM_INCLUDE_DIR}) - link_directories(${LIBTM_LIBRARY} ${INFRA_LIBRARY} ${LIBUSB_LIBRARY}) - set(TRACKING_DEVICE_LIBS tm infra libusb-1.0) - - install(TARGETS libtm - EXPORT realsense2Targets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) endif() if(LRS_TRY_USE_AVX) @@ -905,6 +883,8 @@ endif() if(BUILD_WITH_TM2) target_compile_definitions(realsense2 PRIVATE WITH_TRACKING=1 BUILD_STATIC=1) + target_link_libraries(realsense2 PRIVATE tm ${CMAKE_THREAD_LIBS_INIT} ${TRACKING_DEVICE_LIBS}) + target_include_directories(realsense2 PRIVATE third-party/libtm/libtm/include) endif() set_target_properties(realsense2 PROPERTIES VERSION ${REALSENSE_VERSION_STRING} SOVERSION ${REALSENSE_VERSION_MAJOR}) diff --git a/third-party/libtm/.gitignore b/third-party/libtm/.gitignore new file mode 100644 index 0000000000..75b2b2760b --- /dev/null +++ b/third-party/libtm/.gitignore @@ -0,0 +1,26 @@ +output_build +*.user +.settings +*.jar +*.apk +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +*-build/ +/captures +.externalNativeBuild +Version.h +/.vs +/versions.log +/resources/target.mvcmd +/resources/central_bl.bin +/resources/central_app.bin +/resources/remote_versions.log +/libtm/src/fw.h +/libtm/src/CentralBlFw.h +/libtm/src/CentralAppFw.h +/libtm/src/version.rc \ No newline at end of file diff --git a/third-party/libtm/CMakeLists.txt b/third-party/libtm/CMakeLists.txt new file mode 100644 index 0000000000..349585d45e --- /dev/null +++ b/third-party/libtm/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 2.8) +project(libtm) + +include(cmake/os.cmake) + +set(LIBTM_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") +set(LIBTM_INCLUDE_DIR "${LIBTM_ROOT}/libtm/include") +set(LIBTM_INFRA_DIR "${LIBTM_ROOT}/libtm/infra") +set(LIBTM_SRC_DIR "${LIBTM_ROOT}/libtm/src") +set(LIBTM_RESOURCES_DIR "${LIBTM_ROOT}/resources") + +message("${LIBTM_INCLUDE_DIR}") +include(versions.cmake) + +# Build resources (FW, Central, Controller binaries) +add_subdirectory(resources) + +set(LIB_TYPE "STATIC") +add_definitions(-DBUILD_STATIC) + +add_subdirectory(libtm) + +message("----------------------------------------------------------------------------") +message("CMake Done") +message("----------------------------------------------------------------------------") \ No newline at end of file diff --git a/third-party/libtm/cmake/linux.cmake b/third-party/libtm/cmake/linux.cmake new file mode 100644 index 0000000000..32d1ffbfde --- /dev/null +++ b/third-party/libtm/cmake/linux.cmake @@ -0,0 +1,8 @@ +set(OS "lin") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + +include_directories("/usr/include/libusb-1.0") +set(OS_SPECIFIC_LIBS "pthread") +set(LIBUSB_LIB "usb-1.0") diff --git a/third-party/libtm/cmake/os.cmake b/third-party/libtm/cmake/os.cmake new file mode 100644 index 0000000000..72965fc1d0 --- /dev/null +++ b/third-party/libtm/cmake/os.cmake @@ -0,0 +1,5 @@ +IF(WIN32) + include(cmake/windows.cmake) +ELSE() + include(cmake/linux.cmake) +ENDIF(WIN32) diff --git a/third-party/libtm/cmake/windows.cmake b/third-party/libtm/cmake/windows.cmake new file mode 100644 index 0000000000..4eba3b62c4 --- /dev/null +++ b/third-party/libtm/cmake/windows.cmake @@ -0,0 +1,30 @@ +set(OS "win") + +set(WINDOWS_LIBUSB_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third-party/win/libusb/include") +set(WINDOWS_BRAINSTEM2_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third-party/win/BrainStem2/include") +set(WINDOWS_BRAINSTEM2_SRC_DIR "${CMAKE_SOURCE_DIR}/third-party/win/BrainStem2/src") + + +message("Platform is set to ${PLATFORM}") + +IF(PLATFORM STREQUAL "x64") + set(WINDOWS_LIBUSB_LIB_DIR "${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/x64") + set(WINDOWS_BRAINSTEM2_LIB_DIR "${CMAKE_SOURCE_DIR}/third-party/win/BrainStem2/bin/x64") +else() + set(WINDOWS_LIBUSB_LIB_DIR "${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/win32") + set(WINDOWS_BRAINSTEM2_LIB_DIR "${CMAKE_SOURCE_DIR}/third-party/win/BrainStem2/bin/w32") +endif() + +include_directories("${WINDOWS_LIBUSB_INCLUDE_DIR}") +link_directories("${WINDOWS_LIBUSB_LIB_DIR}") + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT /Zi") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") +set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG") + +set(LIBUSB_LIB "libusb-1.0") +set(BRAINSTEM2_LIB "BrainStem2") \ No newline at end of file diff --git a/third-party/libtm/libtm/CMakeLists.txt b/third-party/libtm/libtm/CMakeLists.txt new file mode 100644 index 0000000000..4380ab709c --- /dev/null +++ b/third-party/libtm/libtm/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 2.8) +project(libtm) + +STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+\\.[0-9]+" "\\1" LIBTM_VERSION_MAJOR "${HOST_VERSION}") +STRING(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" LIBTM_VERSION_MINOR "${HOST_VERSION}") +STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" LIBTM_VERSION_PATCH "${HOST_VERSION}") +STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" LIBTM_VERSION_BUILD "${HOST_VERSION}") + +set (LIBTM_API_VERSION_MAJOR 10) # Major part of the device supported interface API version, updated upon an incompatible API change +set (LIBTM_API_VERSION_MINOR 0) # Minor part of the device supported interface API version, updated upon a backwards-compatible change + +set(LIBVERSION ${LIBTM_VERSION_MAJOR}.${LIBTM_VERSION_MINOR}.${LIBTM_VERSION_PATCH}) + +# Retrieve Git branch name +execute_process(COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE) + +MESSAGE("--------------------------------------------------------------------------------------------------------------------------------------------------------------") +MESSAGE("Building ${PROJECT_NAME} project on ${OS}, LIBTM version [${LIBTM_VERSION_MAJOR}.${LIBTM_VERSION_MINOR}.${LIBTM_VERSION_PATCH}.${LIBTM_VERSION_BUILD}], API version [${LIBTM_API_VERSION_MAJOR}.${LIBTM_API_VERSION_MINOR}], branch [${GIT_BRANCH}], FW [${FW_VERSION}], Central APP [${CENTRAL_APP_VERSION}], Central BL [${CENTRAL_BL_VERSION}]") + +# Configure version file according to libtm version definitions and branch name above +MESSAGE("Creating version file ${PROJECT_SOURCE_DIR}/src/Version.h") +configure_file("${PROJECT_SOURCE_DIR}/src/Version.h.in" "${PROJECT_SOURCE_DIR}/src/Version.h") +configure_file("${PROJECT_SOURCE_DIR}/src/version.rc.in" "${PROJECT_SOURCE_DIR}/src/version.rc") + +# Add include directories to the search path +include_directories(${LIBTM_INCLUDE_DIR}) +SET(INSTALL_PATH ${CMAKE_INSTALL_PREFIX}) +install(DIRECTORY ${LIBTM_INCLUDE_DIR} DESTINATION ${INSTALL_PATH}) + +include_directories("infra") + +# Add subdirectory to the build +add_subdirectory(infra) +add_subdirectory(src) diff --git a/third-party/libtm/libtm/include/TrackingCommon.h b/third-party/libtm/libtm/include/TrackingCommon.h new file mode 100644 index 0000000000..aab5e37300 --- /dev/null +++ b/third-party/libtm/libtm/include/TrackingCommon.h @@ -0,0 +1,144 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include + +#ifdef _WIN32 +#include +#ifdef tm_EXPORTS +#define DLL_EXPORT __declspec(dllexport) +#else +#ifdef BUILD_STATIC +#define DLL_EXPORT +#else +#define DLL_EXPORT __declspec(dllimport) +#endif +#endif /* tm_EXPORTS */ +#else /* defined (WIN32) */ +#define DLL_EXPORT +#endif + + +#define IN +#define OUT + +#ifdef __unix +#include +#include +#define fopen_s(pFile,filename,mode) ((*(pFile))=fopen((filename), (mode)))==NULL +#endif + +namespace perc { + +#ifdef _WIN32 + typedef HANDLE Handle; +#define ILLEGAL_HANDLE NULL +#else + typedef int Handle; +#define ILLEGAL_HANDLE (-1) + +#endif + + #define MAC_ADDRESS_SIZE 6 + #define CONTROLLER_SENSOR_DATA_SIZE 6 + + typedef uint8_t SensorId; + + /** + * @brief Defines all sensors types (bSensorID/bCameraID/bMotionID) + */ + enum SensorType + { + Color = 0, + Depth = 1, + IR = 2, + Fisheye = 3, + Gyro = 4, + Accelerometer = 5, + Controller = 6, + Rssi = 7, + Velocimeter = 8, + Max + }; + + enum class Status + { + SUCCESS = 0, /**< No issues found */ + COMMON_ERROR = 1, /**< FW unknown message error */ + FEATURE_UNSUPPORTED = 2, /**< Feature is partly implemented and currently unsupported by host */ + ERROR_PARAMETER_INVALID = 3, /**< Invalid parameter */ + INIT_FAILED = 4, /**< Init flow failed (Loading FW, Identifying device) */ + ALLOC_FAILED = 5, /**< Allocation error */ + ERROR_USB_TRANSFER = 6, /**< USB Error while transmitting message to FW */ + ERROR_EEPROM_VERIFY_FAIL = 7, /**< EEPROM after write verification failed */ + ERROR_FW_INTERNAL = 8, /**< Internal FW error */ + BUFFER_TOO_SMALL = 9, /**< Buffer is too small for this operation */ + NOT_SUPPORTED_BY_FW = 10, /**< Feature is currently unsupported by FW */ + DEVICE_BUSY = 11, /**< Indicates that this command is not supported in the current device state, e.g.trying to change configuration after START */ + TIMEOUT = 12, /**< Controller connection failed due to timeout */ + TABLE_NOT_EXIST = 13, /**< The requested configuration table does not exists on the EEPROM */ + TABLE_LOCKED = 14, /**< The configuration table is locked for writing or permanently locked from unlocking */ + DEVICE_STOPPED = 15, /**< The last message over interrupt/stream endpoint after a DEV_STOP command */ + TEMPERATURE_WARNING = 16, /**< The device temperature reached 10 % from its threshold */ + TEMPERATURE_STOP = 17, /**< The device temperature reached its threshold, and the device stopped tracking */ + CRC_ERROR = 18, /**< CRC Error in firmware update */ + INCOMPATIBLE = 19, /**< Controller version is incompatible with TM2 version */ + SLAM_NO_DICTIONARY = 20, /**< No relocalization dictionary was loaded */ + NO_CALIBRATION_DATA = 21, /**< No calibration data is available */ + SLAM_LOCALIZATION_DATA_SET_SUCCESS = 22, /**< Reading all localization data chunks completed successfully */ + SLAM_ERROR_CODE_VISION = 23, /**< No visual features were detected in the most recent image. This is normal in some circumstances, such as quick motion or if the device temporarily looks at a blank wall */ + SLAM_ERROR_CODE_SPEED = 24, /**< The device moved more rapidly than expected for typical handheld motion. This may indicate that rc_Tracker has failed and is providing invalid data */ + SLAM_ERROR_CODE_OTHER = 25, /**< A fatal SLAM internal error has occurred */ + CONTROLLER_CALIBRATION_VALIDATION_FAILURE = 26, /**< Controller Calibration validation error */ + CONTROLLER_CALIBRATION_FLASH_ACCESS_FAILURE = 27, /**< Controller Flash access failure */ + CONTROLLER_CALIBRATION_IMU_FAILURE = 28, /**< Controller Calibration IMU failure */ + CONTROLLER_CALIBRATION_INTERNAL_FAILURE = 29, /**< Controller Calibration internal failure */ + AUTH_ERROR = 30, /**< Authentication error in firmware update or error in image signature */ + LIST_TOO_BIG = 31, /**< Image size is too big */ + DEVICE_RESET = 32, /**< A device reset has occurred. The user may read the FW log for additional details */ + NO_BLUETOOTH = 33, /**< The device doesn't have bluetooth, so the command failed */ + }; + + enum PixelFormat + { + ANY = 0, /**< Any pixel format */ + Z16 = 1, /**< 16-bit per pixel - linear depth values. The depth is meters is equal to depth scale * pixel value */ + DISPARITY16 = 2, /**< 16-bit per pixel - linear disparity values. The depth in meters is equal to depth scale / pixel value */ + XYZ32F = 3, /**< 96-bit per pixel - 32 bit floating point 3D coordinates. */ + YUYV = 4, /**< 16-bit per pixel - Standard YUV pixel format as described in https://en.wikipedia.org/wiki/YUV */ + RGB8 = 5, /**< 24-bit per pixel - 8-bit Red, Green and Blue channels */ + BGR8 = 6, /**< 24-bit per pixel - 8-bit Blue, Green and Red channels, suitable for OpenCV */ + RGBA8 = 7, /**< 32-bit per pixel - 8-bit Red, Green, Blue channels + constant alpha channel equal to FF */ + BGRA8 = 8, /**< 32-bit per pixel - 8-bit Blue, Green, Red channels + constant alpha channel equal to FF */ + Y8 = 9, /**< 8-bit per pixel - grayscale image */ + Y16 = 10, /**< 16-bit per-pixel - grayscale image */ + RAW8 = 11, /**< 8-bit per pixel - raw image */ + RAW10 = 12, /**< 10-bit per pixel - Four 10-bit luminance values encoded into a 5-byte macropixel */ + RAW16 = 13 /**< 16-bit per pixel - raw image */ + }; + + /** + * @brief Defines all 6dof interrupt rates + */ + typedef enum { + SIXDOF_INTERRUPT_RATE_NONE = 0X0000, /* No interrupts*/ + SIXDOF_INTERRUPT_RATE_FISHEYE = 0x0001, /* Interrupts on every fisheye camera update (30 interrupts per second for TM2) */ + SIXDOF_INTERRUPT_RATE_IMU = 0x0002, /* Interrupts on every IMU update (400-500 interrupts per second for TM2) - default value*/ + SIXDOF_INTERRUPT_RATE_MAX + } SIXDOF_INTERRUPT_RATE; + + /** + * @brief Defines all 6dof modes + */ + typedef enum { + SIXDOF_MODE_NORMAL = 0X0000, + SIXDOF_MODE_FAST_PLAYBACK = 0x0001, + SIXDOF_MODE_ENABLE_MAPPING = 0x0002, + SIXDOF_MODE_ENABLE_RELOCALIZATION = 0x0004, + SIXDOF_MODE_MAX + } SIXDOF_MODE; + +} diff --git a/third-party/libtm/libtm/include/TrackingData.h b/third-party/libtm/libtm/include/TrackingData.h new file mode 100644 index 0000000000..5d5fda651a --- /dev/null +++ b/third-party/libtm/libtm/include/TrackingData.h @@ -0,0 +1,875 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "TrackingCommon.h" +#include +#include + +namespace perc +{ + #define SENSOR_TYPE_POS (0) + #define SENSOR_TYPE_MASK (0x001F << SENSOR_TYPE_POS) /**< Bits 0-4 */ + #define SENSOR_INDEX_POS (5) + #define SENSOR_INDEX_MASK (0x0007 << SENSOR_INDEX_POS) /**< Bits 5-7 */ + #define SET_SENSOR_ID(_type, _index) (((_type << SENSOR_TYPE_POS) & SENSOR_TYPE_MASK) | ((_index << SENSOR_INDEX_POS) & SENSOR_INDEX_MASK)) + #define GET_SENSOR_INDEX(_sensorID) ((_sensorID & SENSOR_INDEX_MASK) >> SENSOR_INDEX_POS) + #define GET_SENSOR_TYPE(_sensorID) ((_sensorID & SENSOR_TYPE_MASK) >> SENSOR_TYPE_POS) + #define MAX_VIDEO_STREAMS 8 + #define MAX_SUPPORTED_STREAMS 32 + #define MAX_USB_TREE_DEPTH 64 + #define MAX_LOCALIZATION_DATA_CHUNK_SIZE 1024 + #define MAX_CONFIGURATION_SIZE 1016 + #define MAX_VERSION 0xFFFF + #define MAX_LOG_BUFFER_ENTRIES 1024 + #define MAX_LOG_BUFFER_ENTRY_SIZE (256) + #define MAX_LOG_BUFFER_MODULE_SIZE (32) + + enum ProfileType + { + HMD = 0, + Controller1 = 1, + Controller2 = 2, + ProfileTypeMax = 3, + }; + + enum VideoProfileType + { + VideoProfile0 = 0, /* Sensor index 0 - HMD High exposure left camera */ + VideoProfile1 = 1, /* Sensor index 1 - HMD High exposure right camera */ + VideoProfile2 = 2, /* Sensor index 2 - HMD Low exposure left camera for controller 1 tracking */ + VideoProfile3 = 3, /* Sensor index 3 - HMD Low exposure right camera for controller 2 tracking */ + VideoProfileMax = 4, + }; + + enum AccelerometerProfileType + { + AccelerometerProfile0 = 0, /* Sensor index 0 - HMD Accelerometer */ + AccelerometerProfile1 = 1, /* Sensor index 1 - Controller 1 Accelerometer */ + AccelerometerProfile2 = 2, /* Sensor index 2 - Controller 2 Accelerometer */ + AccelerometerProfileMax = 3, + }; + + enum GyroProfileType + { + GyroProfile0 = 0, /* Sensor index 0 - HMD Gyro */ + GyroProfile1 = 1, /* Sensor index 1 - Controller 1 Gyro */ + GyroProfile2 = 2, /* Sensor index 2 - Controller 2 Gyro */ + GyroProfileMax = 3, + }; + + enum VelocimeterProfileType + { + VelocimeterProfile0 = 0, /* External Sensor index 0 - HMD Velocimeter */ + VelocimeterProfile1 = 1, /* External Sensor index 1 - HMD Velocimeter */ + VelocimeterProfileMax = 2, + }; + + enum SixDofProfileType + { + SixDofProfile0 = 0, /* Source index 0 - HMD 6dof */ + SixDofProfile1 = 1, /* Source index 1 - Controller 1 6dof */ + SixDofProfile2 = 2, /* Source index 2 - Controller 2 6dof */ + SixDofProfileMax = 3, + }; + + enum LogVerbosityLevel { + None = 0x0000, /**< Logging is disabled */ + Error = 0x0001, /**< Error conditions */ + Info = 0x0002, /**< High importance informational entries. this verbosity level exists to allow logging of general information on production images. */ + Warning = 0x0003, /**< Warning messages */ + Debug = 0x0004, /**< Debug informational entries */ + Verbose = 0x0005, /**< Even more informational entries */ + Trace = 0x0006 /**< On entry and exit from every function call. also shall be used for profiling */ + }; + + enum LogOutputMode { + LogOutputModeScreen = 0x0000, /**< All logs print to screen output */ + LogOutputModeBuffer = 0x0001, /**< All logs saved to buffer */ + LogOutputModeMax = 0x0002, + }; + + enum TemperatureSensorType { + TemperatureSensorVPU = 0x0000, /**< Temperature Sensor located in the Vision Processing Unit */ + TemperatureSensorIMU = 0x0001, /**< Temperature Sensor located in the Inertial Measurement Unit */ + TemperatureSensorBLE = 0x0002, /**< Temperature Sensor located in the Bluetooth Low Energy Unit */ + TemperatureSensorMax = 0x0003, + }; + + enum LockType { + HardwareLock = 0x0000, /**< The locking is done in hardware by locking the upper quarter of the EEPROM memory */ + SoftwareLock = 0x0001, /**< The locking is done in software by the firmware managing a lock bits in each configuration table metadata */ + MaxLockType = 0x0002, + }; + + enum EepromLockState { + LockStateWriteable = 0x0000, /**< The EEPROM is fully writeable */ + LockStateLocked = 0x0001, /**< The upper quarter of the EEPROM memory is write-protected */ + LockStatePermanentLocked = 0x0002, /**< The upper quarter of the EEPROM memory is permanently write-protected */ + LockStateMax = 0x0003, + }; + + enum AddressType { + AddressTypePublic = 0x0000, /**< Public Address - Static IEEE assigned Mac Address build from manufacturer assigned ID (24 bits) and unique device ID (24 bits) */ + AddressTypeRandom = 0x0001, /**< Random Address - Randomized temporary Address used for a certain amount of time (48 bits) */ + AddressTypeMax = 0x0002, /**< Unspecified Address */ + }; + + class TrackingData + { + public: + class Axis { + public: + Axis(float _x = 0.0, float _y = 0.0, float _z = 0.0) : x(_x), y(_y), z(_z) {} + void set(float _x, float _y, float _z) + { + x = _x; + y = _y; + z = _z; + } + float x; /**< X value, used for translation/acceleration/velocity */ + float y; /**< Y value, used for translation/acceleration/velocity */ + float z; /**< Z value, used for translation/acceleration/velocity */ + }; + + class EulerAngles { + public: + EulerAngles(float _x = 0.0, float _y = 0.0, float _z = 0.0) : x(_x), y(_y), z(_z) {} + void set(float _x, float _y, float _z) + { + x = _x; + y = _y; + z = _z; + } + float x; /**< X value, used for angular velocity/angular acceleration */ + float y; /**< Y value, used for angular velocity/angular acceleration */ + float z; /**< Z value, used for angular velocity/angular acceleration */ + }; + + class Quaternion { + public: + Quaternion(float _i = 0.0, float _j = 0.0, float _k = 0.0, float _r = 0.0) : i(_i), j(_j), k(_k), r(_r) {} + void set(float _i, float _j, float _k, float _r) + { + i = _i; + j = _j; + k = _k; + r = _r; + } + float i; /**< Qi components of rotation as represented in quaternion rotation */ + float j; /**< Qj components of rotation as represented in quaternion rotation */ + float k; /**< Qk components of rotation as represented in quaternion rotation */ + float r; /**< Qr components of rotation as represented in quaternion rotation */ + }; + + class Version { + public: + Version(uint32_t _major = MAX_VERSION, uint32_t _minor = MAX_VERSION, uint32_t _patch = MAX_VERSION, uint32_t _build = MAX_VERSION) { + major = _major; + minor = _minor; + patch = _patch; + build = _build; + } + + void set(uint32_t _major = MAX_VERSION, uint32_t _minor = MAX_VERSION, uint32_t _patch = MAX_VERSION, uint32_t _build = MAX_VERSION) { + major = _major; + minor = _minor; + patch = _patch; + build = _build; + } + + uint32_t major; /**< Major part of version */ + uint32_t minor; /**< Minor part of version */ + uint32_t patch; /**< Patch part of version */ + uint32_t build; /**< Build part of version */ + }; + + class TimestampedData + { + public: + TimestampedData() : timestamp(0), arrivalTimeStamp(0), systemTimestamp(0) {}; + int64_t timestamp; /**< Frame integration timestamp, as measured in nanoseconds since device initialization */ + int64_t arrivalTimeStamp; /**< Frame arrival timestamp, as measured in nanoseconds since device initialization */ + int64_t systemTimestamp; /**< Host correlated time stamp in nanoseconds */ + }; + + class PoseFrame : public TimestampedData { + public: + PoseFrame() : sourceIndex(0), trackerConfidence(0), mapperConfidence(0) {}; + uint8_t sourceIndex; /**< Index of HMD or controller - 0x0 = HMD, 0x1 - controller 1, 0x2 - controller 2 */ + Axis translation; /**< X, Y, Z values of translation, in meters (relative to initial position) */ + Axis velocity; /**< X, Y, Z values of velocity, in meter/sec */ + Axis acceleration; /**< X, Y, Z values of acceleration, in meter/sec^2 */ + Quaternion rotation; /**< Qi, Qj, Qk, Qr components of rotation as represented in quaternion rotation (relative to initial position) */ + EulerAngles angularVelocity; /**< X, Y, Z values of angular velocity, in radians/sec */ + EulerAngles angularAcceleration; /**< X, Y, Z values of angular acceleration, in radians/sec^2 */ + uint32_t trackerConfidence; /**< pose data confidence 0x0 - Failed, 0x1 - Low, 0x2 - Medium, 0x3 - High */ + uint32_t mapperConfidence; /**< pose data confidence 0x0 - Failed, 0x1 - Low, 0x2 - Medium, 0x3 - High */ + }; + + class GyroFrame : public TimestampedData { + public: + GyroFrame() : temperature(0), sensorIndex(0), frameId(0) {}; + uint8_t sensorIndex; /**< Zero based index of sensor with the same type within device */ + uint32_t frameId; /**< A running index of frames from every unique sensor, starting from 0 */ + Axis angularVelocity; /**< X, Y, Z values of gyro, in radians/sec */ + float temperature; /**< Gyro temperature */ + }; + + class VelocimeterFrame : public TimestampedData { + public: + VelocimeterFrame() : temperature(0), sensorIndex(0), frameId(0) {}; + uint8_t sensorIndex; /**< Zero based index of sensor with the same type within device */ + uint32_t frameId; /**< A running index of frames from every unique sensor, starting from 0 */ + Axis angularVelocity; /**< X, Y, Z values of velocimeter, in radians/sec */ + float temperature; /**< Velocimeter temperature */ + }; + + class AccelerometerFrame : public TimestampedData { + public: + AccelerometerFrame() : temperature(0), sensorIndex(0), frameId(0) {}; + uint8_t sensorIndex; /**< Zero based index of sensor with the same type within device */ + uint32_t frameId; /**< A running index of frames from every unique sensor, starting from 0 */ + Axis acceleration; /**< X, Y, Z values of acceleration, in meter/sec^2 */ + float temperature; /**< Accelerometer temperature */ + }; + + class ControllerFrame : public TimestampedData { + public: + ControllerFrame() : sensorIndex(0), frameId(0), eventId(0), instanceId(0), sensorData{0} {}; + uint8_t sensorIndex; /**< Zero based index of sensor with the same type within device */ + uint32_t frameId; /**< A running index of frames from every unique sensor, starting from 0 */ + uint8_t eventId; /**< Event ID - button, trackpad or battery (vendor specific), supported values 0-63 */ + uint8_t instanceId; /**< Instance of the sensor in case of multiple sensors */ + uint8_t sensorData[CONTROLLER_SENSOR_DATA_SIZE]; /**< Sensor data that is pass-through from the controller firmware */ + }; + + class RssiFrame : public TimestampedData { + public: + RssiFrame() : sensorIndex(0), frameId(0), signalStrength(0) {}; + uint8_t sensorIndex; /**< Zero based index of sensor with the same type within device */ + uint32_t frameId; /**< A running index of frames from every unique sensor, starting from 0 */ + float_t signalStrength; /**< Sampled signal strength (dB), a value closer to 0 indicates a stronger signal */ + }; + + class ControllerDiscoveryEventFrame : public TimestampedData { + public: + ControllerDiscoveryEventFrame() : macAddress{0}, addressType(AddressTypeRandom), manufacturerId(0), vendorData(0), protocol(), app(), softDevice(), bootLoader() {}; + uint8_t macAddress[MAC_ADDRESS_SIZE]; /**< Discovered device byte array of MAC address */ + AddressType addressType; /**< Discovered device address type: Public or Random */ + uint16_t manufacturerId; /**< Identifier of the controller manufacturer */ + uint16_t vendorData; /**< vendor specific data copied from the controller advertisement */ + Version protocol; /**< Controller BLE protocol version - only major part is active */ + Version app; /**< Controller App version - only major, minor, build parts are active */ + Version softDevice; /**< Controller Soft Device version - only major part is active */ + Version bootLoader; /**< Controller Boot Loader version - only major, minor, build parts are active */ + }; + + class ControllerConnectedEventFrame : public TimestampedData { + public: + ControllerConnectedEventFrame() : status(Status::SUCCESS), controllerId(0), manufacturerId(0), protocol(), app(), softDevice(), bootLoader() {}; + Status status; /**< Connection status: SUCCESS – connection succeeded */ + /**< TIMEOUT – connection timed out */ + /**< INCOMPATIBLE – connection succeeded but controller version is incompatible with TM2 version */ + uint8_t controllerId; /**< Disconnected controller identifier (1 or 2) */ + uint16_t manufacturerId; /**< Identifier of the controller manufacturer */ + Version protocol; /**< Controller BLE protocol version - only major part is active */ + Version app; /**< Controller App version - only major, minor, build parts are active */ + Version softDevice; /**< Controller Soft Device version - only major part is active */ + Version bootLoader; /**< Controller Boot Loader version - only major, minor, build parts are active */ + }; + + class ControllerDisconnectedEventFrame : public TimestampedData { + public: + ControllerDisconnectedEventFrame() : controllerId(0) {}; + uint8_t controllerId; /**< Disconnected controller identifier (1 or 2) */ + }; + + class ControllerCalibrationEventFrame : public TimestampedData { + public: + ControllerCalibrationEventFrame() : controllerId(0), status(Status::SUCCESS) {}; + uint8_t controllerId; /**< Calibrated controller identifier (1 or 2) */ + Status status; /**< Calibration status: SUCCESS - Controller was calibrated successfully */ + /**< CONTROLLER_CALIBRATION_VALIDATION_FAILURE - validation failure */ + /**< CONTROLLER_CALIBRATION_FLASH_ACCESS_FAILURE - flash access failure */ + /**< CONTROLLER_CALIBRATION_IMU_FAILURE - IMU failure */ + /**< CONTROLLER_CALIBRATION_INTERNAL_FAILURE - Internal failure */ + }; + + class ControllerFWEventFrame : public TimestampedData { + public: + ControllerFWEventFrame() : status(Status::SUCCESS), macAddress{0}, progress(0) {}; + Status status; /**< Progress status: SUCCESS - update progresses successfully */ + /**< CRC_ERROR - error in image integrity */ + /**< AUTH_ERROR - error in image signature */ + /**< LIST_TOO_BIG - image size too big */ + /**< TIMEOUT - operation did not complete due to device disconnect or timeout */ + uint8_t macAddress[MAC_ADDRESS_SIZE]; /**< MAC address of the updated device. All zeros for central FW update */ + uint8_t progress; /**< Progress counter (percentage of update complete) */ + }; + + class StatusEventFrame : public TimestampedData { + public: + StatusEventFrame() : status(Status::SUCCESS) {}; + Status status; /**< Status code */ + }; + + class ControllerLedEventFrame : public TimestampedData { + public: + uint8_t controllerId; /**< Controller identifier (1 or 2) */ + uint8_t ledId; /**< Led identifier (1 or 2) */ + uint32_t intensity; /**< Led intensity [0-100] */ + }; + + class RawProfile + { + public: + RawProfile() : width(0), height(0), stride(0), pixelFormat(PixelFormat::ANY) {} + void set(uint16_t _width, uint16_t _height, uint16_t _stride, PixelFormat _format) { + width = _width; + height = _height; + stride = _stride; + pixelFormat = _format; + } + uint16_t stride; /**< Length in bytes of each line in the image (including padding). 0 for non-camera streams. */ + uint16_t width; /**< Supported width (in pixels) of first stream, 0 for non-camera streams */ + uint16_t height; /**< Supported height (in pixels) or first stream, 0 for non-camera streams */ + PixelFormat pixelFormat; /**< Pixel format of the stream, according to enum PixelFormat */ + }; + + class VideoFrame : public TimestampedData { + public: + uint8_t sensorIndex; /**< Zero based index of sensor with the same type within device */ + uint32_t frameId; /**< Running index of frames from every unique sensor. Starting from 0. */ + RawProfile profile; /**< Frame format profile - includes width, height, stride, pixelFormat */ + uint32_t exposuretime; /**< Exposure time of this frame in microseconds */ + float_t gain; /**< Gain multiplier of this frame */ + uint32_t frameLength; /**< Length of frame below, in bytes, shall be equal to Stride X Height X BPP */ + const uint8_t* data; /**< Frame data pointer */ + }; + + class EnableConfig + { + public: + EnableConfig() : enabled(false), outputEnabled(false), fps(0), sensorIndex(0) {}; + void set(bool _enabled, bool _outputEnabled, uint16_t _fps, uint8_t _sensorIndex) { + enabled = _enabled; + outputEnabled = _outputEnabled; + fps = _fps; + sensorIndex = _sensorIndex; + } + bool enabled; /**< true if this profile is enabled */ + bool outputEnabled; /**< 0x0 - Send sensor outputs to the internal middlewares only, 0x1 - Send this sensor outputs also to the host over the USB interface. */ + uint16_t fps; /**< Supported frame per second for this profile */ + uint8_t sensorIndex; /**< Zero based index of sensor with the same type within device */ + }; + + class GyroProfile : public EnableConfig + { + + }; + + class VelocimeterProfile : public EnableConfig + { + + }; + + class AccelerometerProfile : public EnableConfig + { + + }; + + class SixDofProfile + { + public: + SixDofProfile() : enabled(false), mode(SIXDOF_MODE_ENABLE_MAPPING | SIXDOF_MODE_ENABLE_RELOCALIZATION), interruptRate(SIXDOF_INTERRUPT_RATE::SIXDOF_INTERRUPT_RATE_IMU), profileType(SixDofProfileMax) {}; + SixDofProfile(bool _enabled, uint8_t _mode, SIXDOF_INTERRUPT_RATE _interruptRate, SixDofProfileType _profileType) : + enabled(_enabled), mode(_mode), interruptRate(_interruptRate), profileType(_profileType) {}; + void set(bool _enabled, uint8_t _mode, SIXDOF_INTERRUPT_RATE _interruptRate, SixDofProfileType _profileType) + { + enabled = _enabled; + mode = _mode; + interruptRate = _interruptRate; + profileType = _profileType; + } + + bool enabled; /**< true if this profile is enabled */ + uint8_t mode; /**< 0x00 - Normal Mode, 0x01 - Fast Playback, 0x02 - Enable Mapping */ + SixDofProfileType profileType; /**< Type of this 6dof profile - HMD, Controller 1, Controller 2 */ + SIXDOF_INTERRUPT_RATE interruptRate; /**< Rate of 6DoF interrupts. The following values are supported: */ + /**< 0x0 - no interrupts */ + /**< 0x1 - interrupts on every fisheye camera update (30 interrupts per second for TM2) */ + /**< 0x2 - interrupts on every IMU update (400-500 interrupts per second for TM2) - default value */ + }; + + class VideoProfile : public EnableConfig + { + public: + RawProfile profile; /**< Raw video profile */ + }; + + class Profile + { + public: + + Profile() : playbackEnabled(false) { + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + video[i].outputEnabled = false; + video[i].enabled = false; + video[i].fps = 0; + video[i].sensorIndex = VideoProfileMax; + video[i].profile.set(0, 0, 0, ANY); + } + + for (uint8_t i = 0; i < GyroProfileMax; i++) + { + gyro[i].enabled = false; + gyro[i].outputEnabled = false; + gyro[i].fps = 0; + gyro[i].sensorIndex = GyroProfileMax; + } + + for (uint8_t i = 0; i < VelocimeterProfileMax; i++) + { + velocimeter[i].enabled = false; + velocimeter[i].outputEnabled = false; + velocimeter[i].fps = 0; + velocimeter[i].sensorIndex = VelocimeterProfileMax; + } + + for (uint8_t i = 0; i < AccelerometerProfileMax; i++) + { + accelerometer[i].enabled = false; + accelerometer[i].outputEnabled = false; + accelerometer[i].fps = 0; + accelerometer[i].sensorIndex = AccelerometerProfileMax; + } + + for (uint8_t i = 0; i < SixDofProfileMax; i++) + { + sixDof[i].enabled = false; + sixDof[i].mode = (SIXDOF_MODE_ENABLE_MAPPING | SIXDOF_MODE_ENABLE_RELOCALIZATION); + sixDof[i].interruptRate = SIXDOF_INTERRUPT_RATE_MAX; + sixDof[i].profileType = SixDofProfileMax; + } + }; + + void reset(void) { + + playbackEnabled = false; + + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + video[i].outputEnabled = false; + video[i].enabled = false; + video[i].fps = 0; + video[i].sensorIndex = VideoProfileMax; + video[i].profile.set(0, 0, 0, ANY); + } + + for (uint8_t i = 0; i < GyroProfileMax; i++) + { + gyro[i].enabled = false; + gyro[i].outputEnabled = false; + gyro[i].fps = 0; + gyro[i].sensorIndex = GyroProfileMax; + } + + for (uint8_t i = 0; i < VelocimeterProfileMax; i++) + { + velocimeter[i].enabled = false; + velocimeter[i].outputEnabled = false; + velocimeter[i].fps = 0; + velocimeter[i].sensorIndex = VelocimeterProfileMax; + } + + for (uint8_t i = 0; i < AccelerometerProfileMax; i++) + { + accelerometer[i].enabled = false; + accelerometer[i].outputEnabled = false; + accelerometer[i].fps = 0; + accelerometer[i].sensorIndex = AccelerometerProfileMax; + } + + for (uint8_t i = 0; i < SixDofProfileMax; i++) + { + sixDof[i].enabled = false; + sixDof[i].mode = (SIXDOF_MODE_ENABLE_MAPPING | SIXDOF_MODE_ENABLE_RELOCALIZATION); + sixDof[i].interruptRate = SIXDOF_INTERRUPT_RATE_MAX; + sixDof[i].profileType = SixDofProfileMax; + } + }; + + void set(VideoProfile _video, bool _enabled, bool _outputEnabled) { + if (_video.sensorIndex < VideoProfileMax) + { + video[_video.sensorIndex] = _video; + video[_video.sensorIndex].enabled = _enabled; + video[_video.sensorIndex].outputEnabled = _outputEnabled; + } + } + + void set(GyroProfile _gyro, bool _enabled, bool _outputEnabled) { + if (_gyro.sensorIndex < GyroProfileMax) + { + gyro[_gyro.sensorIndex] = _gyro; + gyro[_gyro.sensorIndex].enabled = _enabled; + gyro[_gyro.sensorIndex].outputEnabled = _outputEnabled; + } + } + + void set(VelocimeterProfile _velocimeter, bool _enabled, bool _outputEnabled) { + if (_velocimeter.sensorIndex < VelocimeterProfileMax) + { + velocimeter[_velocimeter.sensorIndex] = _velocimeter; + velocimeter[_velocimeter.sensorIndex].enabled = _enabled; + velocimeter[_velocimeter.sensorIndex].outputEnabled = _outputEnabled; + } + } + + void set(AccelerometerProfile _accelerometer, bool _enabled, bool _outputEnabled) { + if (_accelerometer.sensorIndex < AccelerometerProfileMax) + { + accelerometer[_accelerometer.sensorIndex] = _accelerometer; + accelerometer[_accelerometer.sensorIndex].enabled = _enabled; + accelerometer[_accelerometer.sensorIndex].outputEnabled = _outputEnabled; + } + } + + void set(SixDofProfile _sixDof, bool _enabled) { + if (_sixDof.profileType < SixDofProfileMax) + { + sixDof[(uint8_t)_sixDof.profileType] = _sixDof; + sixDof[(uint8_t)_sixDof.profileType].enabled = _enabled; + } + } + + VideoProfile video[VideoProfileMax]; /**< All supported Video profiles according to sensor index */ + GyroProfile gyro[GyroProfileMax]; /**< All supported Gyro profiles according to sensor index */ + AccelerometerProfile accelerometer[AccelerometerProfileMax]; /**< All supported Accelerometer profiles according to sensor index */ + SixDofProfile sixDof[SixDofProfileMax]; /**< All supported SixDof profiles according to sensor profile (HMD, Controller1, Controlelr2) */ + VelocimeterProfile velocimeter[VelocimeterProfileMax]; /**< All supported Velocimeter profiles */ + bool playbackEnabled; /**< Enable video playback mode */ + }; + + class DeviceInfo + { + public: + class UsbConnectionDescriptor { + public: + UsbConnectionDescriptor() : idVendor(0), idProduct(0), bcdUSB(0), port(0), bus(0), portChainDepth(0), portChain{0} {} + uint16_t idVendor; /**< USB Vendor ID: DFU Device = 0x03E7, Device = 0x8087 */ + uint16_t idProduct; /**< USB Product ID: DFU Device = 0x2150, Device = 0x0AF3 */ + uint16_t bcdUSB; /**< USB specification release number: 0x100 = USB 1.0, 0x110 = USB 1.1, 0x200 = USB 2.0, 0x300 = USB 3.0 */ + uint8_t port; /**< Number of the port that the device is connected to */ + uint8_t bus; /**< Number of the bus that the device is connected to */ + uint8_t portChainDepth; /**< Number of ports in the port tree of this device */ + uint8_t portChain[MAX_USB_TREE_DEPTH]; /**< List of all port numbers from root for this device */ + }; + + class DeviceStatus { + public: + DeviceStatus() : hw(Status::SUCCESS), host(Status::SUCCESS) {} + Status host; /**< Host status */ + Status hw; /**< HW status */ + }; + + class DeviceVersion { + public: + DeviceVersion() {} + Version deviceInterface; /**< Device supported interface API version */ + Version host; /**< libtm Host version */ + Version fw; /**< Myriad firmware version */ + Version hw; /**< ASIC Board version: 0x00 = ES0, 0x01 = ES1, 0x02 = ES2, 0x03 = ES3, 0xFF = Unknown - only major part is active */ + Version centralApp; /**< Central firmware version */ + Version centralProtocol; /**< Central BLE protocol version - only major part is active */ + Version centralBootLoader; /**< Central BLE protocol version - only major, minor, patch parts are active */ + Version centralSoftDevice; /**< Central BLE protocol version - only major part is active */ + Version eeprom; /**< EEPROM data version - only major and minor parts are active */ + Version rom; /**< Myriad ROM version - only major part is active */ + }; + + DeviceInfo() : deviceType(0), serialNumber(0), numGyroProfile(0), numVelocimeterProfile(0), numAccelerometerProfiles(0), numVideoProfiles(0), eepromLockState(LockStateMax) {}; + UsbConnectionDescriptor usbDescriptor; /**< USB Connection Descriptor includes USB info and physical location */ + DeviceStatus status; /**< HW and Host status */ + DeviceVersion version; /**< HW, FW, Host, Central, EEPROM, ROM, interface versions */ + EepromLockState eepromLockState; /**< EEPROM Lock state */ + uint8_t deviceType; /**< Device identifier: 0x1 = T250 */ + uint64_t serialNumber; /**< Device serial number */ + uint8_t numGyroProfile; /**< Number of Gyro Supported Profiles returned by Supported RAW Streams */ + uint8_t numVelocimeterProfile; /**< Number of Velocimeter Supported Profiles returned by Supported RAW Streams */ + uint8_t numAccelerometerProfiles; /**< Number of Accelerometer Supported Profiles returned by Supported RAW Streams */ + uint8_t numVideoProfiles; /**< Number of Video Supported Profiles returned by Supported RAW Streams */ + }; + + class LogControl { + public: + LogControl() : verbosity(None), outputMode(LogOutputModeMax), rolloverMode(false) {}; + LogControl(LogVerbosityLevel _verbosity, LogOutputMode _OutputMode, bool _RolloverMode) { + verbosity = _verbosity; + outputMode = _OutputMode; + rolloverMode = _RolloverMode; + } + + LogVerbosityLevel verbosity; /**< Log Verbosity level */ + LogOutputMode outputMode; /**< Output mode - screen or buffer */ + bool rolloverMode; /**< Rollover mode - False: no rollover (logging will be paused after log is filled, Until cleared, only first logs will be stored), True: Last logs will be stored */ + }; + + class Log { + public: + class LocalTime { + public: + LocalTime() : year(0), month(0), dayOfWeek(0), day(0), hour(0), minute(0), second(0), milliseconds(0) {} + uint16_t year; /**< Year */ + uint16_t month; /**< Month */ + uint16_t dayOfWeek; /**< Day of week */ + uint16_t day; /**< Day */ + uint16_t hour; /**< Hour */ + uint16_t minute; /**< Minute */ + uint16_t second; /**< Second */ + uint16_t milliseconds; /**< Milliseconds */ + }; + + class LogEntry { + public: + + LogEntry() : timeStamp(0), localTimeStamp(), verbosity(None), deviceID(0), threadID(0), moduleID{0}, functionSymbol(0), lineNumber(0), payloadSize(0), payload{ 0 } {} + int64_t timeStamp; /**< Host log - Current timestamp relative to host initialization (nanosec) */ + /**< FW log - Current timestamp relative to device initialization (nanosec) */ + LocalTime localTimeStamp; /**< Host log - Current time of log */ + /**< FW log - Capture time of FW log */ + LogVerbosityLevel verbosity; /**< Verbosity level */ + uint64_t deviceID; /**< Device ID */ + uint32_t threadID; /**< Thread ID */ + char moduleID[MAX_LOG_BUFFER_MODULE_SIZE]; /**< Module ID */ + uint32_t functionSymbol; /**< Function symbol */ + uint32_t lineNumber; /**< Line number */ + uint32_t payloadSize; /**< payload size */ + char payload[MAX_LOG_BUFFER_ENTRY_SIZE]; /**< payload */ + }; + + Log() : entries(0), maxEntries(0), entry{} {} + LogEntry entry[MAX_LOG_BUFFER_ENTRIES]; /**< Log entry info and payload */ + uint32_t entries; /**< Active log entries in the buffer */ + uint32_t maxEntries; /**< Max entries (for GetFWLog call rate calculations) */ + }; + + class CameraIntrinsics { + public: + CameraIntrinsics() : width(0), height(0), ppx(0), ppy(0), fx(0), fy(0), distortionModel(0){ + memset(coeffs, 0, sizeof(coeffs)); + }; + + uint32_t width; /**< Width of the image in pixels */ + uint32_t height; /**< Height of the image in pixels */ + float_t ppx; /**< Horizontal coordinate of the principal point of the image, as a pixel offset from the left edge */ + float_t ppy; /**< Vertical coordinate of the principal point of the image, as a pixel offset from the top edge */ + float_t fx; /**< Focal length of the image plane, as a multiple of pixel width */ + float_t fy; /**< Focal length of the image plane, as a multiple of pixel Height */ + uint32_t distortionModel; /**< Distortion model of the image: NONE = 0, MODIFIED_BROWN_CONRADY = 1, INVERSE_BROWN_CONRADY = 2, FTHETA = 3, KANNALA_BRANDT4 = 4 */ + float_t coeffs[5]; /**< Distortion coefficients */ + }; + + class MotionIntrinsics { + public: + MotionIntrinsics() { + memset(data, 0, sizeof(data)); + memset(noiseVariances, 0, sizeof(noiseVariances)); + memset(biasVariances, 0, sizeof(biasVariances)); + }; + + float_t data[3][4]; /**< Scale matrix */ + float_t noiseVariances[3]; /**< Noise variances */ + float_t biasVariances[3]; /**< Bias variances */ + }; + + class SensorExtrinsics { + public: + SensorExtrinsics() : referenceSensorId(0) { + memset(rotation, 0, sizeof(rotation)); + memset(translation, 0, sizeof(translation)); + }; + + float_t rotation[9]; /**< Column-major 3x3 rotation matrix */ + float_t translation[3]; /**< 3 element translation vector, in meters */ + SensorId referenceSensorId; /**< Reference sensor uses for extrinsics calculation */ + }; + + class ExposureModeControl { + public: + ExposureModeControl() : antiFlickerMode(0) + { + memset(videoStreamAutoExposure, 0, sizeof(videoStreamAutoExposure)); + }; + + ExposureModeControl(uint8_t _bVideoStreamsAutoExposureMask, uint8_t _bAntiFlickerMode) + { + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + videoStreamAutoExposure[i] = (bool)((_bVideoStreamsAutoExposureMask >> i) & 1); + } + + antiFlickerMode = _bAntiFlickerMode; + } + + bool videoStreamAutoExposure[VideoProfileMax]; /**< Video streams to apply the auto exposure configuration */ + uint8_t antiFlickerMode; /**< Anti Flicker Mode: 0 - disable (e.g. for outside use), 1 - 50Hz, 2 - 60Hz, 3 - Auto (currently not supported) */ + }; + + class Exposure { + public: + + class StreamExposure { + public: + StreamExposure() : cameraId(0), integrationTime(0), gain(0) {}; + StreamExposure(uint8_t _cameraId, uint32_t _integrationTime, float_t _gain) : cameraId(_cameraId), integrationTime(_integrationTime), gain(_gain) {}; + uint8_t cameraId; /**< Bits 0-4: Type of requested camera, supported values are: Color = 0, Depth = 1, IR = 2, Fisheye = 3 */ + /**< Bits 5-7: Camera index - Zero based index of camera with the same type within device */ + uint32_t integrationTime; /**< Integration time in micro-seconds (Max 16000 uSec) */ + float_t gain; /**< Digital gain */ + }; + + Exposure() : numOfVideoStreams(0) { memset(stream, 0, sizeof(stream)); }; + uint8_t numOfVideoStreams; /**< Number of video streams to configure */ + StreamExposure stream[MAX_VIDEO_STREAMS]; /**< Stream exposure data variable sized array, according to wNumberOfStreams */ + }; + + class Temperature { + public: + class SensorTemperature { + public: + SensorTemperature() : index(TemperatureSensorMax), temperature(0.0), threshold(0.0) {}; + SensorTemperature(TemperatureSensorType _index, float_t _temperature, float_t _threshold) : index(_index), temperature(_temperature), threshold(_threshold) {}; + TemperatureSensorType index; /**< Temperature sensor index: 0x0 - VPU, 0x1 - IMU, 0x2 - BLE */ + float_t temperature; /**< Sensor temperature (Celsius) */ + float_t threshold; /**< Sensor temperature threshold (Celsius) */ + }; + + Temperature() : numOfSensors(0) { memset(sensor, 0, sizeof(sensor)); }; + uint32_t numOfSensors; /**< Number of temperature sensors */ + SensorTemperature sensor[TemperatureSensorMax]; /**< Stream exposure data variable sized array, according to wNumberOfStreams */ + }; + + class LocalizationDataFrame { + public: + LocalizationDataFrame() : moreData(false), chunkIndex(0), length(0), buffer(NULL) { }; + bool moreData; /**< moreData indicates there are more data to send. The last chunk (possibly even a zero-size chunk) shall be marked moreData = false */ + Status status; /**< Set localization status */ + uint32_t chunkIndex; /**< A running counter starting in 0 identifying the chunk in a single data stream */ + uint32_t length; /**< The length in bytes of the localization buffer */ + uint8_t* buffer; /**< Localization data buffer pointer of max size MAX_CONFIGURATION_SIZE bytes, Data format is algorithm specific and opaque to the USB and host stack */ + }; + + class RelativePose { + public: + RelativePose() {}; + Axis translation; /**< X, Y, Z values of translation, in meters (in the coordinate system of the tracker relative to the current position) */ + Quaternion rotation; /**< Qi, Qj, Qk, Qr components of rotation as represented in quaternion rotation (in the coordinate system of the tracker relative to the current position) */ + }; + + class GeoLocalization { + public: + GeoLocalization() : latitude(0), longitude(0), altitude(0) { }; + GeoLocalization(double_t _latitude, double_t _longitude, double_t _altitude) : latitude(_latitude), longitude(_longitude), altitude(_altitude) {}; + void set(double_t _latitude, double_t _longitude, double_t _altitude) + { + latitude = _latitude; + longitude = _longitude; + altitude = _altitude; + }; + double_t latitude; /**< Latitude in degrees */ + double_t longitude; /**< Longitude in degrees */ + double_t altitude; /**< Altitude in meters above the WGS 84 reference ellipsoid */ + }; + + class ControllerAssociatedDevices { + public: + ControllerAssociatedDevices() : macAddress1{0}, macAddress2{0}, addressType1(AddressTypeRandom), addressType2(AddressTypeRandom) {}; + ControllerAssociatedDevices(uint8_t* _macAddress1, uint8_t* _macAddress2 = nullptr, AddressType _addressType1 = AddressTypeRandom, AddressType _addressType2 = AddressTypeRandom) + { + set(_macAddress1, _macAddress2, _addressType1, _addressType2); + } + + void set(uint8_t* _macAddress1, uint8_t* _macAddress2 = nullptr, AddressType _addressType1 = AddressTypeRandom, AddressType _addressType2 = AddressTypeRandom) + { + copy(macAddress1, _macAddress1); + copy(macAddress2, _macAddress2); + addressType1 = _addressType1; + addressType2 = _addressType2; + } + + uint8_t macAddress1[MAC_ADDRESS_SIZE]; /**< MAC address of controller 1 */ + uint8_t macAddress2[MAC_ADDRESS_SIZE]; /**< MAC address of controller 2, set to all zeros if controller is not setup */ + AddressType addressType1; /**< Controller 1 Mac Address Type: Public or Random */ + AddressType addressType2; /**< Controller 2 Mac Address Type: Public or Random */ + + private: + /**< Copy source address to destination address if source is not NULL, else zero destination address */ + void copy(uint8_t* dstAddress, uint8_t* srcAddress) + { + for (uint8_t i = 0; i < MAC_ADDRESS_SIZE; i++) + { + dstAddress[i] = ((srcAddress == nullptr) ? 0 : srcAddress[i]); + } + } + }; + + class ControllerDeviceConnect { + public: + ControllerDeviceConnect() : macAddress{0}, timeout(0), addressType(AddressTypeRandom){}; + ControllerDeviceConnect(uint8_t* _macAddress, uint16_t _timeout, AddressType _addressType = AddressTypeRandom) : addressType(_addressType), timeout(_timeout) + { + for (uint8_t i = 0; i < MAC_ADDRESS_SIZE; i++) + { + macAddress[i] = _macAddress[i]; + } + } + + uint8_t macAddress[MAC_ADDRESS_SIZE]; /**< MAC address of controller */ + AddressType addressType; /**< Address Type: Public or Random */ + uint16_t timeout; /**< Controller connect timeout (msec) */ + }; + + class ControllerData { + public: + ControllerData() : controllerId (0), commandId(0), buffer(NULL), bufferSize(0){}; + ControllerData(uint8_t _controllerId, uint8_t _commandId, uint16_t _bufferSize, uint8_t* _buffer) : controllerId(_controllerId), commandId(_commandId), bufferSize(_bufferSize), buffer(_buffer) {}; + void set(uint8_t _controllerId, uint8_t _commandId, uint16_t _bufferSize, uint8_t* _buffer) + { + controllerId = _controllerId; + commandId = _commandId; + bufferSize = _bufferSize; + buffer = _buffer; + } + + uint8_t controllerId; /**< Controller identifier (1 or 2) */ + uint8_t commandId; /**< Command to be sent to the controller (vendor specific) - values 0-63 supported */ + uint16_t bufferSize; /**< Size of data buffer */ + uint8_t* buffer; /**< Controller data to be sent. Data format is vendor specific */ + }; + + class ControllerFW { + public: + ControllerFW() : macAddress{0}, image(NULL), imageSize(0), addressType(AddressTypeRandom) {}; + ControllerFW(uint8_t* _macAddress, uint16_t _imageSize, uint8_t* _image, AddressType _addressType = AddressTypeRandom) : addressType(_addressType), imageSize(_imageSize), image(_image) + { + if (_macAddress != NULL) + { + for (uint8_t i = 0; i < MAC_ADDRESS_SIZE; i++) + { + macAddress[i] = _macAddress[i]; + } + } + } + + uint8_t macAddress[MAC_ADDRESS_SIZE]; /**< MAC address of controller to be updated */ + AddressType addressType; /**< Discovered device address type: Public or Random */ + uint32_t imageSize; /**< Size of Controller FW image */ + uint8_t* image; /**< New controller FW image to be updated */ + }; + }; +} \ No newline at end of file diff --git a/third-party/libtm/libtm/include/TrackingDevice.h b/third-party/libtm/libtm/include/TrackingDevice.h new file mode 100644 index 0000000000..ed8eedfdac --- /dev/null +++ b/third-party/libtm/libtm/include/TrackingDevice.h @@ -0,0 +1,607 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "TrackingCommon.h" +#include "TrackingData.h" + +#define IN +#define OUT + +namespace perc +{ + class DLL_EXPORT TrackingDevice + { + public: + class DLL_EXPORT Listener + { + public: + + /** + * @brief onPoseFrame + * The function will be called once TrackingDevice has a new pose data + * + * @param pose - Pose object containing the pose data + */ + virtual void onPoseFrame(OUT TrackingData::PoseFrame& pose) {} + + /** + * @brief onVideoFrame + * The function will be called once TrackingDevice has a new video (color / depth / fisheye) frame + * + * @param frame - VideoFrame object containing frame + */ + virtual void onVideoFrame(OUT TrackingData::VideoFrame& frame) {} + + /** + * @brief onAccelerometerFrame + * The function will be called once TrackingDevice has a new accelerometer data + * + * @param frame - Accelerometer object containing frame + */ + virtual void onAccelerometerFrame(OUT TrackingData::AccelerometerFrame& frame) {} + + /** + * @brief onGyroFrame + * The function will be called once TrackingDevice has a new gyro data + * + * @param frame - Gyro object containing frame + */ + virtual void onGyroFrame(OUT TrackingData::GyroFrame& frame) {} + + /** + * @brief onVelocimeterFrame + * The function will be called once TrackingDevice has a new velocimeter data + * + * @param frame - Velocimeter object containing frame + */ + virtual void onVelocimeterFrame(OUT TrackingData::VelocimeterFrame& frame) {} + + /** + * @brief onControllerDiscoveryEventFrame + * The function will be called once TrackingDevice has a new controller discovery event + * + * @param frame - Controller object containing frame + */ + virtual void onControllerDiscoveryEventFrame(OUT TrackingData::ControllerDiscoveryEventFrame& frame) {} + + /** + * @brief onControllerFrame + * The function will be called once TrackingDevice has a new controller frame + * + * @param frame - Controller object containing frame + */ + virtual void onControllerFrame(OUT TrackingData::ControllerFrame& frame) {} + + /** + * @brief onControllerConnectedEventFrame + * The function will be called once TrackingDevice has a new controller connected event + * + * @param frame - Controller object containing frame + */ + virtual void onControllerConnectedEventFrame(OUT TrackingData::ControllerConnectedEventFrame& frame) {} + + /** + * @brief onControllerDisconnectedEventFrame + * The function will be called once TrackingDevice has a new controller disconnected event + * + * @param frame - Controller object containing frame + */ + virtual void onControllerDisconnectedEventFrame(OUT TrackingData::ControllerDisconnectedEventFrame& frame) {} + + /** + * @brief onControllerCalibrationEventFrame + * The function will be called once the controller calibration process is finished + * + * @param frame - Controller object containing frame + */ + virtual void onControllerCalibrationEventFrame(OUT TrackingData::ControllerCalibrationEventFrame& frame) {} + + /** + * @brief onRssiFrame + * The function will be called once TrackingDevice has a new RSSI frame + * + * @param frame - Rssi object containing frame + */ + virtual void onRssiFrame(OUT TrackingData::RssiFrame& frame) {} + + /** + * @brief onLocalizationDataEventFrame + * The function will be called once TrackingDevice has a new localization data event + * + * @param frame - Localization Data object containing frame for Set or Get localization + * Get localization data - relevant fields: status, mapIndex, moreData, chunkIndex, length, buffer + * Set localization data - relevant fields: status, mapIndex + */ + virtual void onLocalizationDataEventFrame(OUT TrackingData::LocalizationDataFrame& frame) {} + + /** + * @brief onFWUpdateEvent + * The host interface shall support firmware update progress events + * + * @param frame - Firmware update event containing frame indicates the progress status + */ + virtual void onFWUpdateEvent(OUT TrackingData::ControllerFWEventFrame& frame) {} + + /** + * @brief onStatusEvent + * The host interface shall support general status event + * + * @param frame - Status event frame indicates the current status + */ + virtual void onStatusEvent(OUT TrackingData::StatusEventFrame& frame) {} + + /** + * @brief onControllerLedEvent + * + * @param frame - Controller Led event frame indicates the controller led intensity [0-100] + */ + virtual void onControllerLedEvent(OUT TrackingData::ControllerLedEventFrame& frame) {} + }; + + + public: + virtual ~TrackingDevice() {} + + /** + * @brief GetSupportedProfile + * Initialize basic profile according to all supported streams - adding only the first profile on every profile index + * Gyro: sensorIndex = all supported sensorIndex, fps = first supported gyro stream with this sensorIndex, enabled = false, outputEnabled = false + * Accelerometer: sensorIndex = all supported sensorIndex, fps = first supported accelerometer stream with this sensorIndex, enabled = false, outputEnabled = false + * Video: sensorIndex = all supported sensorIndex, fps/RawProfile = first supported video stream with this sensorIndex, enabled = false, outputEnabled = false + * SixDof: profileType = all profileTypes, rate = SIXDOF_INTERRUPT_RATE_IMU, enabled = false + * + * @return Status + */ + virtual Status GetSupportedProfile(OUT TrackingData::Profile& profile) = 0; + + /** + * @brief start + * Start streaming of all stream that were previously configured. + * @param Data listener + * @param includes all required configuration according to stream session. + * User shouldn't change running configuration during streaming + * TODO: review set/get functions + * @return Status + */ + virtual Status Start(IN Listener*, IN TrackingData::Profile* = NULL) = 0; + + /** + * @brief stop + * Stop streaming of all stream that were previously configured, all stream configuration parameters will be cleared. + * @return Status + */ + virtual Status Stop(void) = 0; + + /** + * @brief GetDeviceInfo + * Retrieve information on the TM2 device + * @param message - Device info message buffer + * @return Status + */ + virtual Status GetDeviceInfo(OUT TrackingData::DeviceInfo& info) = 0; + + /** + * @brief GetSupportedRawStreams + * Get all FW supported raw streams: Video, Gyro, Accelerometer. Velocimeter. + * @param videoProfiles - Video profile buffer containing all supported video streams (Buffer size should be according to DeviceInfo.numVideoProfiles) + * @param gyroProfiles - Gyro profile buffer containing all supported gyro streams (Buffer size should be according to DeviceInfo.numGyroProfiles) + * @param accelerometerProfiles - Accelerometer profile buffer containing all supported accelerometer streams (Buffer size should be according to DeviceInfo.numAccelerometerProfiles) + * @param velocimeterProfiles - Velocimeter profile buffer containing all supported velocimeter streams (Buffer size should be according to DeviceInfo.numVelocimeterProfile) + * @return Status + */ + virtual Status GetSupportedRawStreams(OUT TrackingData::VideoProfile* videoProfiles, OUT TrackingData::GyroProfile* gyroProfiles, OUT TrackingData::AccelerometerProfile* accelerometerProfiles, OUT TrackingData::VelocimeterProfile* velocimeterProfiles = nullptr) = 0; + + /** + * @brief SetFWLogControl + * Controls the FW logging parameters, such as verbosity and log mode. + * @param logControl - logControl object to fill with verbosity and log mode + * @return Status + */ + virtual Status SetFWLogControl(IN const TrackingData::LogControl& logControl) = 0; + + /** + * @brief GetFWLog + * Get FW log entries - every call to this function will get and clean all FW log entries + * Need to enable FW logs (SetFWLogControl) before calling GetFWLog + * Use entries and maxEntries fields to calculate the GetFWLog call rate to get max FW logs with min GetFWLog requests, + * For example: In case log entries is equal to log maxEntries, FW log buffer is completely filled so several FW logs are dumped, + * Increase the GetFWLog call rate to decrease the dumped logs amount. + * @param log - log object to fill with log entries + * @return Status + */ + virtual Status GetFWLog(OUT TrackingData::Log& log) = 0; + + /** + * @brief GetCameraIntrinsics + * Retrieves the intrinsic parameters of an individual camera in the device + * @param id - Sensor Id (Sensor type + sensor index) + * @param intrinsics - intrinsic object to fill + * @return Status + */ + virtual Status GetCameraIntrinsics(IN SensorId id, OUT TrackingData::CameraIntrinsics& intrinsics) = 0; + + /** + * @brief SetCameraIntrinsics + * Set the intrinsic parameters of an individual camera in the device + * @param id - Sensor Id (Sensor type + sensor index) + * @param message - intrinsic parameters to set + * @return Status + */ + virtual Status SetCameraIntrinsics(IN SensorId id, IN const TrackingData::CameraIntrinsics& intrinsics) = 0; + + /** + * @brief GetMotionModuleIntrinsics + * Retrieves the intrinsic parameters of an individual motion module in the device + * @param id - Sensor Id (Sensor type + sensor index) + * @param message - Buffer of intrinsic parameters + * @return Status + */ + virtual Status GetMotionModuleIntrinsics(IN SensorId id, OUT TrackingData::MotionIntrinsics& intrinsics) = 0; + + /** + * @brief SetMotionModuleIntrinsics + * Set the intrinsic parameters of an individual motion module in the device + * @param id - Sensor Id (Sensor type + sensor index) + * @param message - Buffer of intrinsic parameters + * @return Status + */ + virtual Status SetMotionModuleIntrinsics(IN SensorId id, IN const TrackingData::MotionIntrinsics& intrinsics) = 0; + + /** + * @brief GetExtrinsics + * Retrieves extrinsic pose of on individual sensor in the device relative to another one + * @param id - Sensor Id (Sensor type + sensor index) + * @param extrinsics - Output container for extrinsic parameters + * @return Status + */ + virtual Status GetExtrinsics(IN SensorId id, OUT TrackingData::SensorExtrinsics& extrinsics) = 0; + + /** + * @brief SetOccupancyMapControl + * Enables/disables occupancy map calculation. Occupancy map calculation is based on 6DoF calculation, + * So it cannot be enabled when 6DoF is disabled, and an UNSUPPORTED error will be returned + * If no SetOccupancyMapControl command was called before the host calls to Start, the default value used shall be disabled. + * @param enable - Enable/Disable occupancy map calculation + * @return Status + */ + /* Currently not supported at FW side */ + //virtual Status SetOccupancyMapControl(uint8_t enable) = 0; + + /** + * @brief GetPose + * Retrieves the latest pose of the camera relative to its initial position + * @param pose - pose frame container + * @param sourceIndex - 0x0 = HMD, 0x1 - controller 1, 0x2 - controller 2 + * @return Status + */ + /* Currently not supported at FW side */ + //virtual Status GetPose(TrackingData::PoseFrame& pose, uint8_t sourceIndex) = 0; + + /** + * @brief SetExposureModeControl + * Enable/disable the auto-exposure management of the different video streams, and configure the powerline frequency rate. + * Calling this message is only supported before the streams are started. + * The default values for video streams 0 and 1 shall be auto-exposure enable with 50Hz flicker rate. + * The firmware only supports the following: + * 1. Disabling auto-exposure for all streams (bVideoStreamsMask==0) + * 2. Enabling auto-exposure for both video stream 0 and 1 together (bVideoStreamsMask ==0x3). + * @param mode - VideoStreamsMask and AntiFlickerMode + * @return Status + */ + virtual Status SetExposureModeControl(IN const TrackingData::ExposureModeControl& mode) = 0; + + /** + * @brief SetExposure + * Sets manual values for the video streams integration time and gain. + * @param exposure - Exposure data for all video streams + * @return Status + */ + virtual Status SetExposure(IN const TrackingData::Exposure& exposure) = 0; + + /** + * @brief GetTemperature + * Returns temperature and temperature threshold from all temperature sensors (VPU, IMU, BLE) + * @param temperature - Sensor temperature container + * @return Status + */ + virtual Status GetTemperature(OUT TrackingData::Temperature& temperature) = 0; + + /** + * @brief SetTemperatureThreshold + * Set temperature threshold to requested sensors (VPU, IMU, BLE) + * The firmware shall actively monitor the temperature of the underlying sensors. + * When a component temperature is 10 % close to its defined threshold, the firmware shall send a DEV_ERROR message with a TEMPERATURE_WARNING status to the host, + * When the temperature reach the threshold, the firmware shall stop all running algorithms and sensors (as if DEV_STOP was called), and send a TEMPERATURE_SHUTDOWN status to the user. + * @param temperature - Temperature threshold to set per temperature sensors (relevant fields - index, threshold) + * @param token - Secured token to override max temperature threshold + * @return Status + */ + virtual Status SetTemperatureThreshold(IN const TrackingData::Temperature& temperature, IN uint32_t token) = 0; + + /** + * @brief LockConfiguration + * Write-protect the manufacturing configuration tables from future changes. + * This can be done in hardware by locking the upper quarter of the EEPROM memory, or by software by the firmware managing a lock bits in each configuration table metadata. + * @param type - Hardware or software. + * @param lock - False: unlock the configuration table. + * True: lock the configuration table. + * @return Status + */ + virtual Status LockConfiguration(IN LockType type, IN bool lock) = 0; + + /** + * @brief PermanentLockConfiguration + * Permanent Write-protect the manufacturing configuration tables from future changes. + * This can be done in hardware by locking the upper quarter of the EEPROM memory, or by software by the firmware managing a lock bits in each configuration table metadata. + * Warning - This is an irreversible action. + * @param type - Hardware or software. + * @param token - Secured token + * @return Status + */ + virtual Status PermanentLockConfiguration(IN LockType type, IN uint32_t token) = 0; + + /** + * @brief ReadConfiguration + * Reads a configuration table from the device memory. + * The host shall return UNSUPPORTED if it does not recognize the requested TableType. + * The host shall return TABLE_NOT_EXIST if it recognize the table type but no such table exists yet in the EEPROM. + * The host shall return BUFFER_TOO_SMALL if input buffer is too small for needed table type. + * @param configurationId - The ID of the requested configuration table. + * @param size - EEPROM read configuration buffer size (max value = MAX_CONFIGURATION_SIZE). + * @param buffer - EEPROM read configuration buffer pointer. + * @param actualSize - Actual EEPROM read configuration buffer size. + * @return Status + */ + virtual Status ReadConfiguration(IN uint16_t configurationId, IN uint16_t size, OUT uint8_t* buffer, OUT uint16_t* actualSize = nullptr) = 0; + + /** + * @brief WriteConfiguration + * Writes a configuration table to the device's EEPROM memory. + * This command shall only be supported while the device is stopped, otherwise it shall return DEVICE_BUSY. + * The host shall return UNSUPPORTED if it does not recognize the requested TableType. + * The host shall return TABLE_LOCKED if the configuration table is write protected and cannot be overridden. + * The new data shall be available immediately after completion without requiring a device reset, both to any firmware code or to an external client through a "read configuration" command. + * All internal data object in the firmware memory that were already initialized from some EEPROM data or previous "write configuration" command shall be invalidated and refreshed to the new written data. + * @param configurationId - The ID of the requested configuration table. + * @param size - EEPROM read configuration buffer size (max value = MAX_CONFIGURATION_SIZE). + * @param buffer - EEPROM read configuration buffer pointer. + * @return Status + */ + virtual Status WriteConfiguration(IN uint16_t configurationId, IN uint16_t size, IN uint8_t* buffer) = 0; + + /** + * @brief DeleteConfiguration + * Delete a configuration table from the internal EEPROM storage. + * @param configurationId - The ID of the requested configuration table. + * @return Status + */ + virtual Status DeleteConfiguration(IN uint16_t configurationId) = 0; + + /** + * @brief GetLocalizationData + * Returns the localization data as created during a 6DoF session. + * The response to this message is generated and streamed by the underlying firmware algorithm in run-time, so the total size of the data cannot be known in advance. + * The entire data will be streams in "chunks" using callback onLocalizationDataEventFrame + * @param listener - Data listener + * @return Status + */ + virtual Status GetLocalizationData(IN Listener* listener) = 0; + + /** + * @brief SetLocalizationData + * Sets the localization data to be used to localize the 6DoF reports. + * Caution - this call may take several seconds. + * @param listener - Data listener + * @param length - The length in bytes of the localization buffer + * @param buffer - Localization data buffer (Max size MAX_CONFIGURATION_SIZE) + * @return Status + */ + virtual Status SetLocalizationData(IN Listener* listener, IN uint32_t length, IN const uint8_t* buffer) = 0; + + /** + * @brief ResetLocalizationData + * Resets the localization data + * @param flag - 0 - Reset all localization data tables, 1 - reset only the map by its mapIndex + * @return Status + */ + virtual Status ResetLocalizationData(IN uint8_t flag) = 0; + + /** + * @brief SetStaticNode + * Set a relative position of a static node + * @param guid - Unique name (Null-terminated C-string) for the static node, max length is 127 bytes plus one byte for the terminating null character + * Using guid that already exist in the map overrides the previous node + * @param relativePose - Static node relative pose data including translation and rotation + * @return Status + */ + virtual Status SetStaticNode(IN const char* guid, IN const TrackingData::RelativePose& relativePose) = 0; + + /** + * @brief GetStaticNode + * Get relative position of a static node + * @param guid - Unique name (Null-terminated C-string) for the static node, max length 127 bytes plus one byte for the terminating null character + * @param relativePose - Static node relative pose data including translation and rotation + * @return Status + */ + virtual Status GetStaticNode(IN const char* guid, OUT TrackingData::RelativePose& relativePose) = 0; + + /** + * @brief SetGeoLocation + * Sets the geographical location (e.g. GPS data). This data can be later used by the algorithms to correct IMU readings. + * @param geoLocation - latitude, longitude, altitude + * @return Status + */ + virtual Status SetGeoLocation(IN const TrackingData::GeoLocalization& geoLocation) = 0; + + /** + * @brief EepromRead + * Reads Eeprom from device + * @param offset - Offset from start of Eeprom + * @param size - Size of memory to read + * @param buffer - where to put the read data. Buffer should be at least of 'size' bytes + * @param actual - The actual number of bytes read from EEPROM. + * + * @return Status + */ + virtual Status EepromRead(IN uint16_t offset, IN uint16_t size, OUT uint8_t* buffer, OUT uint16_t& actual) = 0; + + /** + * @brief EepromWrite + * Writes Eeprom from device + * @param offset - Offset from start of Eeprom + * @param size - Size of memory to write + * @param buffer - A buffer to be written to the eeprom. + * @param actual - The actual number of bytes written to EEPROM. The fucntions returns this value to user. + * @param verify - Should the written data be verified afterwards using EepromRead calls (will take more time) + * + * @return Status + */ + virtual Status EepromWrite(IN uint16_t offset, IN uint16_t size, IN uint8_t* buffer, OUT uint16_t& actual, IN bool verify = false) = 0; + + /** + * @brief Reset + * Resets the device and loads FW + * Caution - this function is non blocking, need to sleep at least 2 seconds afterwards to let the FW load again + * + * @return Status + */ + virtual Status Reset(void) = 0; + + /** + * @brief SendFrame + * Sends video frame to the device + * @param frame - Video frame + * + * @return Status + */ + virtual Status SendFrame(IN const TrackingData::VideoFrame& frame) = 0; + + /** + * @brief SendFrame + * Sends Gyro frame to the device + * @param frame - Gyro frame + * + * @return Status + */ + virtual Status SendFrame(IN const TrackingData::GyroFrame& frame) = 0; + + /** + * @brief SendFrame + * Sends Velocimeter frame to the device + * @param frame - Velocimeter frame + * + * @return Status + */ + virtual Status SendFrame(IN const TrackingData::VelocimeterFrame& frame) = 0; + + /** + * @brief SendFrame + * Sends Accelerometer frame to the device + * @param frame - Accelerometer frame + * + * @return Status + */ + virtual Status SendFrame(IN const TrackingData::AccelerometerFrame& frame) = 0; + + /** + * @brief ControllerConnect + * Connect Controller to the device, + * If the device is already connected to the same MAC address, or if there are no controllers left to connect, libtm shall return a Status::DEVICE_BUSY + * Otherwise, libtm shall reply immediately, and the reply contains an assigned controller ID. + * This is not an indication that the connect succeeded, rather only that the connect started. + * libtm will later send a status of the connection on onControllerConnectedEventFrame. + * @param device - macAddress (Input) - MAC address of the controller to be connected + * timeout (Input) - Connect timeout (msec) + * @param controllerId - controllerId (Output) - controllerId (1 or 2) assigned by the device + * + * @return Status + */ + virtual Status ControllerConnect(IN const TrackingData::ControllerDeviceConnect& device, OUT uint8_t& controllerId) = 0; + + /** + * @brief ControllerDisconnect + * Disconnect Controller from the device + * Disconnection completion indication will be done upon onControllerDisconnectedEventFrame + * @param controllerId - Controller identifier (1 or 2) + * + * @return Status + */ + virtual Status ControllerDisconnect(IN uint8_t controllerId) = 0; + + /** + * @brief ControllerStartCalibration + * Start the controller calibration process, + * When the controller receives the indication to start calibration, it starts reading its gyroscope output for 30 seconds. + * The controller averages the readings over the first 25 seconds and uses this value as the gyro bias. + * The controller then averages the readings over the next 5 seconds and compares the value to the gyro bias from the first 25 seconds. + * If the values are the same (up to TBD noise value), then the gyro bias is stored to flash and the controller sends the onControllerCalibrationEventFrame with success. + * Otherwise, the controller reports a failure. + * @param controllerId - controllerId (Output) - controllerId (1 or 2) + * + * @return Status + */ + virtual Status ControllerStartCalibration(IN uint8_t controllerId) = 0; + + /** + * @brief GetAssociatedDevices + * Get Controller associated devices from the EEPROM + * @param devices - buffer to fill associated devices + * + * @return Status + */ + virtual Status GetAssociatedDevices(OUT TrackingData::ControllerAssociatedDevices& devices) = 0; + + /** + * @brief SetAssociatedDevices + * Set Controller associated devices to the EEPROM + * @param devices - buffers to take associated devices + * + * @return Status + */ + virtual Status SetAssociatedDevices(IN const TrackingData::ControllerAssociatedDevices& devices) = 0; + + /** + * @brief ControllerSendData + * Sends opaque data to the controller. The vendor's controller code is responsible for interpreting the data. + * Example data could be a command to set a LED on the controller or set a vibration pattern on a haptic device. + * Controller must be connected before sending data. + * @param controllerData - data to send to the controller + * + * @return Status + */ + virtual Status ControllerSendData(IN const TrackingData::ControllerData& controllerData) = 0; + + /** + * @brief ControllerRssiTestControl + * Start or Stop the RSSI test + * @param controllerId - Controller identifier (1 or 2) + * @param testControl - True = start the test, False = stop the test + * + * @return Status + */ + virtual Status ControllerRssiTestControl(IN uint8_t controllerId, IN bool testControl) = 0; + + /** + * @brief SetGpioControl + * Enable manufacturing tools to directly control the following GPIOs - 74, 75, 76 & 77. + * @param gpioControl - Setting each bit to 0 will toggle the corresponding GPIO low, Setting to 1 will toggle it to high. + * Bit 0: GPIO 74, Bit 1: GPIO 75, Bit 2: GPIO 76, Bit 3: GPIO 77, Bits 4-7: Reserved + * + * @return Status + */ + virtual Status SetGpioControl(IN uint8_t gpioControl) = 0; + + /** + * @brief ControllerFWUpdate + + * Updates the FW image on a connected controller device. + * @param ControllerFW - Controller FW image to update + * + * @return Status + */ + virtual Status ControllerFWUpdate(const TrackingData::ControllerFW& fw) = 0; + }; +} diff --git a/third-party/libtm/libtm/include/TrackingManager.h b/third-party/libtm/libtm/include/TrackingManager.h new file mode 100644 index 0000000000..0a3dba5ce1 --- /dev/null +++ b/third-party/libtm/libtm/include/TrackingManager.h @@ -0,0 +1,52 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "TrackingCommon.h" +#include "TrackingDevice.h" +#include "TrackingData.h" + +namespace perc +{ + class DLL_EXPORT TrackingManager + { + public: + enum EventType + { + ATTACH = 0, + DETACH + }; + + class Listener + { + public: + // state : enum ATTACH, DETACH.... + virtual void onStateChanged(EventType state, TrackingDevice* device, TrackingData::DeviceInfo deviceInfo) = 0; + virtual void onError(Status, TrackingDevice* device)= 0; + }; + + public: + // factory + static TrackingManager* CreateInstance(Listener*, void* param = 0); + // release existing manager and clean the pointer + static void ReleaseInstance(TrackingManager*& manager); + // interface + virtual Handle getHandle() = 0; + virtual Status handleEvents(bool blocking = true) = 0; + virtual size_t getDeviceList(TrackingDevice** list, unsigned int maxListSize) = 0; + virtual Status setHostLogControl(IN const TrackingData::LogControl& logControl) = 0; + virtual Status getHostLog(OUT TrackingData::Log* log) = 0; + + /** + * libtm version + * MAJOR = 0xFF00000000000000 + * MINOR = 0x00FF000000000000 + * PATCH = 0x0000FFFF00000000 + * Build = 0x00000000FFFFFFFF + */ + virtual uint64_t version() = 0; + virtual ~TrackingManager() {} + }; +} diff --git a/third-party/libtm/libtm/include/TrackingSerializer.h b/third-party/libtm/libtm/include/TrackingSerializer.h new file mode 100644 index 0000000000..6b539eb79d --- /dev/null +++ b/third-party/libtm/libtm/include/TrackingSerializer.h @@ -0,0 +1,47 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "TrackingDevice.h" + +namespace perc +{ + class DLL_EXPORT TrackingRecorder : public TrackingDevice::Listener + { + public: + virtual ~TrackingRecorder() = default; + static TrackingRecorder* CreateInstance(TrackingDevice* device, TrackingDevice::Listener* listener, const char* file, bool stereoMode = false); + }; + + class DLL_EXPORT TrackingPlayer + { + public: + virtual ~TrackingPlayer() = default; + /** + * @brief start + * start sending packets from the file to the listener, starts from the beginning of the file. + */ + virtual void start(bool start_after_calibration = true) = 0; + + /** + * @brief device_configure + * configure and the player to send packets to the device, and to start device streaming + */ + virtual bool device_configure(TrackingDevice* device, TrackingDevice::Listener* listener, TrackingData::Profile* profile = nullptr) = 0; + + /** + * @brief isStreaming + * Return if the player is in the middle of streaming from the file + */ + virtual bool isStreaming() = 0; + /** + * @brief stop + * Stop streaming and close the file. + */ + virtual void stop() = 0; + + static TrackingPlayer* CreateInstance(TrackingDevice::Listener* listener, const char* file); + }; +} diff --git a/third-party/libtm/libtm/infra/Android.mk b/third-party/libtm/libtm/infra/Android.mk new file mode 100644 index 0000000000..7fb6e2fac8 --- /dev/null +++ b/third-party/libtm/libtm/infra/Android.mk @@ -0,0 +1,56 @@ +SAVED_LOCAL_PATH := $(LOCAL_PATH) +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +#------------------------------------------------------------------------------ +# CFLAGS +LOCAL_CFLAGS += -DANDROID -w -fstack-protector -fPIE -fPIC -pie -Wformat -Wformat-security +LOCAL_CPPFLAGS += -DANDROID -w -fstack-protector -fPIE -fPIC -pie -Wformat -Wformat-security + +ifneq ($(filter userdebug eng tests, $(TARGET_BUILD_VARIANT)),) +LOCAL_CFLAGS += -g -O0 +LOCAL_CPPFLAGS += -g -O0 +endif + +# adding c++11 support +LOCAL_CPPFLAGS += -std=c++11 -fexceptions +include external/libcxx/libcxx.mk + +#------------------------------------------------------------------------------ +# INCLUDES +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/../include \ + $(SYSTEM_INCLUDE_PATHS) \ + $(FRAMEWORK_INCLUDE_PATHS) \ + +#------------------------------------------------------------------------------ +# SRC +LOCAL_SRC_FILES := \ + Log.c \ + Trace.c \ + Dispatcher.cpp \ + Fsm.cpp \ + FormatConverter.cpp + +#------------------------------------------------------------------------------ +# LINKER +LOCAL_LDFLAGS += \ + -z noexecstack \ + +LOCAL_STATIC_LIBRARIES += \ + +LOCAL_SHARED_LIBRARIES += \ + libcutils \ + libutils \ + +LOCAL_LDLIBS += \ + -llog \ + +#------------------------------------------------------------------------------ +# BUILD client API +LOCAL_MODULE_TAGS := optional +LOCAL_MULTILIB := 64 +LOCAL_MODULE := libmminfra +include $(BUILD_SHARED_LIBRARY) + +LOCAL_PATH := $(SAVED_LOCAL_PATH) diff --git a/third-party/libtm/libtm/infra/CMakeLists.txt b/third-party/libtm/libtm/infra/CMakeLists.txt new file mode 100644 index 0000000000..afd9fa63c4 --- /dev/null +++ b/third-party/libtm/libtm/infra/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 2.8) +project(infra) + +#Source Files +set(SOURCE_FILES + Log.h + Log.cpp + Event.h + Fence.h + EventHandler.h + Poller.h + Poller_${OS}.cpp + Dispatcher.h + Dispatcher.cpp + Fsm.h + Fsm.cpp + Utils.h + Utils.cpp + Semaphore.h + Semaphore_${OS}.cpp + Event_${OS}.cpp +) + + +#Building Library +set(SDK_LIB_TYPE "STATIC") +MESSAGE("Building project ${PROJECT_NAME} as ${SDK_LIB_TYPE} library") +add_library(${PROJECT_NAME} ${SDK_LIB_TYPE} ${SOURCE_FILES}) + +set(CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/../cmake/realsense2") +set_target_properties (${PROJECT_NAME} PROPERTIES FOLDER Library) + +install(TARGETS ${PROJECT_NAME} + EXPORT realsense2Targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) diff --git a/third-party/libtm/libtm/infra/Dispatcher.cpp b/third-party/libtm/libtm/infra/Dispatcher.cpp new file mode 100644 index 0000000000..6bd083fefa --- /dev/null +++ b/third-party/libtm/libtm/infra/Dispatcher.cpp @@ -0,0 +1,411 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#define LOG_TAG "infra/Dispatcher" +#define LOG_NDEBUG 1 // controls LOGV only +#include "Log.h" +#include "Dispatcher.h" + + +namespace perc { +// ---------------------------------------------------------------------------- + Dispatcher::Dispatcher() : mThreadId(), m_Handlers(), m_HandlersGuard(), m_MessagesGuard(), m_Timers(), m_TimersGuard(), m_HoldersGuard(), m_Holders() +{ + bool ret = mPoller.add(Poller::event(mEvent.handle(), Poller::READ_MASK, this)); + if (ret != true) + { + ASSERT(ret); + } +} + +// ---------------------------------------------------------------------------- +Dispatcher::~Dispatcher() +{ + processExit(); + { + std::lock_guard guard(m_MessagesGuard); + for (int i = 0; i < PRIORITY_MAX; i++) { + Holder *holder = (Holder *)m_Messages[i].GetHead(); + while (holder) { + do { + m_Messages[i].RemoveHead(); + delete holder; + } while ((holder = (Holder *)m_Messages[i].GetHead()) != 0); + } + } + } + { + std::lock_guard guard(m_HandlersGuard); + m_Handlers.clear(); + } + { + std::lock_guard guard(m_TimersGuard); + Holder *holder = (Holder *)m_Timers.GetHead(); + while (holder) { + do { + m_Timers.RemoveHead(); + delete holder; + } while ((holder = (Holder *)m_Timers.GetHead()) != 0); + } + } + mPoller.remove(mEvent.handle()); +} + +// ---------------------------------------------------------------------------- +// DISPATCHER LOOP +// +// Returns: +// >0 - the total number of timers, I/Oevents and messages that were dispatched in single call +// 0 - if the elapsed without dispatching any handlers +// -1 - if an error occurs +int Dispatcher::handleEvents(nsecs_t timeout) +{ + if (mExitPending) { + LOGV("handleEvents(): processExit"); + processExit(); + return -1; + } + int ret = 0; + mThreadId = std::this_thread::get_id(); + LOGV("handleEvents(): Poller::poll()"); + Poller::event event; + int n = mPoller.poll(event, calculatePollTimeout(timeout)); + LOGV("handleEvents(): Poller::poll() ret %d", n); + if (n > 0) { + // process message queues: complete number of messages from message list that was signaled by event + if (event.handle == mEvent.handle()) { + LOGV("handleEvents(): processMessages"); + ret += processMessages(); + } + else { + // process file descriptor + LOGV("handleEvents(): processEvent fd %d, revents %x", event.handle, event.mask); + ret += processEvents(event); + } + } + else if (n == -1) { + LOGE("handleEvents(): Poller::poll() ret %d", n); + return -1; + } + // process timers + ret += processTimers(); + return ret; +} + +// ---------------------------------------------------------------------------- +void Dispatcher::endEventsLoop() +{ +// TRACE(""); + mExitPending = true; + wakeup(); +} + +// ---------------------------------------------------------------------------- +int Dispatcher::registerHandler(EventHandler *handler, Handle fd, unsigned long mask, void *act) +{ + if (mExitPending) return -1; + ASSERT(handler); + ASSERT(mThreadId == std::this_thread::get_id()); + int res = -1; + HandlerHolder holder(handler, fd, mask, act); + if (mPoller.add(holder.Event)) { + std::lock_guard guard(m_HandlersGuard); + if (m_Handlers.count(fd) == 0) { + // adding new one + m_Handlers.emplace(fd, holder); + } + else { + // modifying old one + m_Handlers[fd] = holder; + } + res = 0; + } + return res; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::registerHandler(EventHandler *handler) +{ + if (mExitPending) return -1; + ASSERT(handler); + std::lock_guard guard(m_HoldersGuard); + EmbeddedList::Iterator it(m_Holders); + Holder *h = (Holder *)it.Current(); + // check if handler exists in a list + while(h) { + if (h->Handler == handler) + return -1; + h = (Holder *)it.Next(); + } + Holder *holder = new Holder(handler); + if (!holder) return -1; + m_Holders.AddTail(holder); + return 0; +} + +// ---------------------------------------------------------------------------- +void Dispatcher::removeHandle(Handle fd) +{ + ASSERT(mThreadId == std::this_thread::get_id()); + mPoller.remove(fd); + std::lock_guard guard(m_HandlersGuard); + if (m_Handlers.count(fd) > 0) { + m_Handlers.erase(fd); + } +} + +// ---------------------------------------------------------------------------- +void Dispatcher::cancelTimer (uintptr_t timerId) +{ + ASSERT(timerId); + ASSERT(mThreadId == std::this_thread::get_id()); + std::lock_guard guard(m_TimersGuard); + HolderTimer *holder = (HolderTimer *)timerId; + // check if trying to cancel timer from callback + if (holder->Uptime) { + m_Timers.Remove(holder); + delete holder; + } +} + +// ---------------------------------------------------------------------------- +int Dispatcher::removeHandler(EventHandler *handler, unsigned int mask) +{ + ASSERT(handler); + ASSERT(mThreadId == std::this_thread::get_id()); + int removedHandlers = 0; + + if (mask & MESSAGES_MASK) { + std::lock_guard guard(m_MessagesGuard); + for (int i = 0; i < PRIORITY_MAX; i++) { + EmbeddedList::Iterator it(m_Messages[i]); + Holder *holder = (Holder *)it.Current(); + while (holder) { + Holder *curr = holder; + holder = (Holder *)it.Next(); + if (curr->Handler == handler) { + m_Messages[i].Remove(curr); + delete curr; + removedHandlers++; + } + } + } + } + if (mask & HANDLES_MASK) { + std::lock_guard guard(m_HandlersGuard); + for (auto pair : m_Handlers) { + const HandlerHolder &holder = pair.second; + if (holder.Handler == handler) { + Handle fd = pair.first; + mPoller.remove(fd); + m_Handlers.erase(fd); + removedHandlers++; + } + } + } + if (mask & TIMERS_MASK) { + std::lock_guard guard(m_TimersGuard); + EmbeddedList::Iterator it(m_Timers); + Holder *holder = (Holder *)it.Current(); + while (holder) { + Holder *curr = holder; + holder = (Holder *)it.Next(); + if (curr->Handler == handler) { + m_Timers.Remove(curr); + delete curr; + removedHandlers++; + } + } + } + if (mask & EXIT_MASK) { + std::lock_guard guard(m_HoldersGuard); + EmbeddedList::Iterator it(m_Holders); + Holder *holder = (Holder *)it.Current(); + while (holder) { + Holder *curr = holder; + holder = (Holder *)it.Next(); + if (curr->Handler == handler) { + m_Holders.Remove(curr); + delete curr; + removedHandlers++; + break; + } + } + } + + return removedHandlers; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::putMessage(Holder *holder, int priority) +{ + if (mExitPending) return -1; + ASSERT(holder); + if (priority >= PRIORITY_MAX) priority = PRIORITY_MAX - 1; + if (priority < 0) priority = 0; + std::lock_guard guard(m_MessagesGuard); + m_Messages[priority].AddTail(holder); + // wake up running loop + if (!wakeup()) { + m_Messages[priority].Remove(holder); + delete holder; + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +uintptr_t Dispatcher::putTimer(EventHandler *handler, nsecs_t delay, Message *msg, int priority) +{ + // sanity check + if ((mExitPending == true) || (msg == NULL)) + { + return 0; + } + + ASSERT(handler && delay != 0); + // TODO: optimization: implement free list with local allocator + HolderTimer *holder = new HolderTimer(handler, msg, delay); + if (!holder) + { + delete msg; + return 0; + } + std::lock_guard guard(m_TimersGuard); + EmbeddedList::Iterator it(m_Timers); + HolderTimer *curr = (HolderTimer *)it.Current(); + if (!curr) { + m_Timers.AddHead(holder); + } + else { + do { + if (holder->Uptime < curr->Uptime) { + m_Timers.AddBefore(curr, holder); + break; + } + } while ((curr = (HolderTimer *)it.Next()) != 0); + if (!curr) { + m_Timers.AddTail(holder); + } + } + // reschedule timers by wake up running loop if was performed from another context + if (mThreadId != std::this_thread::get_id()) + wakeup(); + return (uintptr_t)holder; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::processMessages() +{ + mEvent.reset(); + // normalize wake-up events counter and real messages number before callback + int cnt = 0; + for (int i = 0; i < PRIORITY_MAX; i++) { + cnt += m_Messages[i].Size(); + } + int cntMsgs = cnt; + while (cnt) { + int priority = 0; + for (int i = (PRIORITY_MAX - 1); i >= 0; i--) { + if (m_Messages[i].Size()) { + priority = i; + break; + } + } + m_MessagesGuard.lock(); + Holder *holder = (Holder *)m_Messages[priority].RemoveHead(); + m_MessagesGuard.unlock(); + if (holder) { + holder->complete(); + delete holder; + } + else { + break; + } + cnt--; + } + return cntMsgs; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::processEvents(Poller::event &event) +{ + m_HandlersGuard.lock(); + auto it = m_Handlers.find(event.handle); + if (it != m_Handlers.end()) { + const HandlerHolder &holder = it->second; + m_HandlersGuard.unlock(); + holder.Handler->onEvent(holder.Event.handle, event.mask, holder.Event.act); + return 1; + } + else { + mPoller.remove(event.handle); + m_HandlersGuard.unlock(); + } + return 0; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::processTimers() +{ + int cnt = 0; + HolderTimer* timer; + m_TimersGuard.lock(); + timer = (HolderTimer *)m_Timers.GetHead(); + if (timer) { + // work with head only, user can add new timers in a callback + do { + nsecs_t now = systemTime(); + if (timer->Uptime <= now) { + m_Timers.RemoveHead (); + m_TimersGuard.unlock(); + timer->complete(); + delete timer; + m_TimersGuard.lock(); + cnt++; + } + else { + break; + } + } while ((timer = (HolderTimer *)m_Timers.GetHead()) != 0); + } + m_TimersGuard.unlock(); + return cnt; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::processExit() +{ + m_HoldersGuard.lock(); + Holder *holder; + while (holder = (Holder *)m_Holders.RemoveHead()) { + m_HoldersGuard.unlock(); + holder->Handler->onExit(); + delete holder; + m_HoldersGuard.lock(); + } + m_HoldersGuard.unlock(); + return 0; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::calculatePollTimeout(nsecs_t timeout) +{ + // calculate next wake-up time according to timers queue or user timeout + std::lock_guard guard(m_TimersGuard); + HolderTimer *timer = (HolderTimer *)m_Timers.GetHead(); + if (timer) { + int t1 = toMillisecondTimeoutDelay(systemTime(), timer->Uptime); + int t2 = ns2ms(timeout); + return ((unsigned long)t2 == INFINITE)? t1 : (t1 > t2 ? t2 : t1); + } + + // No timer in timer queue, returning timeout input in msec + return ns2ms(timeout); +} + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Dispatcher.h b/third-party/libtm/libtm/infra/Dispatcher.h new file mode 100644 index 0000000000..4446e3c803 --- /dev/null +++ b/third-party/libtm/libtm/infra/Dispatcher.h @@ -0,0 +1,253 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include "Utils.h" +#include "Event.h" +#include "Fence.h" +#include "EmbeddedList.h" +#include "EventHandler.h" +#include "Poller.h" + +namespace perc { +// ---------------------------------------------------------------------------- +/// +/// @class Dispatcher +/// +/// @brief Provides a event demultiplexing mechanism for messages, timers +/// and file descriptors events. +/// +// ---------------------------------------------------------------------------- +class Dispatcher +{ +public: + Dispatcher(); + ~Dispatcher(); + + // Message notification mechanism + enum + { + PRIORITY_IDLE = 0, // Lowest priority + PRIORITY_NORMAL, + PRIORITY_HIGH, + PRIORITY_MAX, + }; + + // = Post/Send message + // + // User can call to this function from any running context. + // Call to these functions will trigger callback. + // Post message to via the Dispatcher's notification mechanism, don't wait for execution. + // This method will internally copy message (using copy constructor) and free after dispatching. + template + int postMessage(EventHandler *, const T&, int priority = Dispatcher::PRIORITY_IDLE); + + // Send message to via the Dispatcher's notification mechanism and blocking wait for execution. + // User SHOULD call to this function from different running context not one. + // This function waits to the message processing by client and returns after returns + // from callback. + // parameter is transferred by reference to callback - user may indicate return parameters if needed. + // Don't call to this function in the same running context. + template + int sendMessage(EventHandler *, const T&, int priority = Dispatcher::PRIORITY_IDLE); + + // = Register/remove handler for I/O and OS events. + // + // A handler can be associated with multiple handles. + // A handle cannot be associated with multiple handlers. + // User can call to this function from any running context. + // Call to these functions will trigger callback, + // according to parameter + int registerHandler(EventHandler *, Handle fd, unsigned long mask = Poller::READ_MASK, void *act = 0); + + // Register handler for Dispatcher exit processing, callback should be called + // during Disptcher deactivation. + int registerHandler(EventHandler *); + + // Remove handle from dispatcher and it's associated handler. + // User SHOULD call these functions from dispatcher context only (from callbacks)!!! + void removeHandle(Handle fd); + + // Remove all messages (and release them), handles and timers (cancel them) associated with this handler + // according to mask parameters. + // + // User SHOULD call these functions from dispatcher context only (from callbacks)!!! + enum + { + MESSAGES_MASK = (1<<0), + HANDLES_MASK = (1<<1), + TIMERS_MASK = (1<<2), + EXIT_MASK = (1<<3), + ALL_MASK = MESSAGES_MASK | HANDLES_MASK | TIMERS_MASK | EXIT_MASK, + }; + int removeHandler(EventHandler *, unsigned int mask = Dispatcher::ALL_MASK); + + // = Timers. + // + // @params: delayMs Time interval in nanoseconds after which the timer will expire. + // @return or 0 in case of error. + template + uintptr_t scheduleTimer (EventHandler *, nsecs_t delay, const T&, int priority = Dispatcher::PRIORITY_IDLE); + + // Cancel timer associated with this . + // User SHOULD call these functions from dispatcher context only (from callbacks)!!! + void cancelTimer (uintptr_t timerId); + + // Main dispatch function + // + // Returns: + // >0 - the total number of timers, I/Oevents and messages that were dispatched in single call + // 0 - if the elapsed without dispatching any handlers + // -1 - if an error occurs + int handleEvents(nsecs_t timeout = INFINITE); + void endEventsLoop(); + +protected: + + // holder of messages base class + class Holder : public EmbeddedListElement + { + public: + Holder(EventHandler *h) : Handler(h) {} + virtual ~Holder() {} + virtual void complete() {} + EventHandler *Handler; + }; + +private: + std::thread::id mThreadId; + Event mEvent; + Poller mPoller; + bool mExitPending = false; + + int putMessage(Holder *, int priority); + uintptr_t putTimer (EventHandler *, nsecs_t delay, Message *, int priority); + int processMessages(); + int processEvents(Poller::event &); + int processTimers(); + int processExit(); + + /** + * @brief This function calculates the next wake-up time according to timers queue or user timeout + * + * @param[in] timeout - poll timeout in nsec. + * @return Next wake up time in msec. + */ + int calculatePollTimeout(nsecs_t); + bool wakeup() { return mEvent.signal(); } + + class HolderPost : public Holder + { + public: + HolderPost(EventHandler *h, Message *m) : Holder(h), Msg(m) {} + virtual ~HolderPost() { delete Msg; } + virtual void complete() { Handler->onMessage(*Msg); } + Message *Msg; + + /* Disallow these operations */ + HolderPost &operator=(const HolderPost &); + HolderPost(const HolderPost &); + }; + + class HolderSend : public Holder + { + public: + HolderSend(EventHandler *h, const Message &m, Fence &w) : + Holder(h), Msg(m), Waiter(w) {} + virtual ~HolderSend() { Waiter.notify(); } + virtual void complete() { Handler->onMessage(Msg); } + const Message &Msg; + Fence &Waiter; // sync wait lock handle + }; + EmbeddedList m_Messages[PRIORITY_MAX]; + std::mutex m_MessagesGuard; + + // I/O HANDLERS + struct HandlerHolder + { + EventHandler *Handler; + Poller::event Event; + HandlerHolder() : Handler(NULL) {} + HandlerHolder(EventHandler *handler, Handle fd, unsigned long mask, void *act) : + Handler(handler), Event(fd, mask, act) {} + + }; + std::unordered_map m_Handlers; + std::mutex m_HandlersGuard; + + // TIMERS + class HolderTimer : public HolderPost + { + public: + HolderTimer(EventHandler *h, Message *m, nsecs_t d) : HolderPost(h, m) + { Uptime = systemTime() + d; } + virtual void complete() { Uptime = 0; Handler->onTimeout((uintptr_t)this, *Msg); } + nsecs_t Uptime; + }; + EmbeddedList m_Timers; + std::mutex m_TimersGuard; + + // HANDLERS + EmbeddedList m_Holders; + std::mutex m_HoldersGuard; + + // Disallow these operations. + Dispatcher &operator=(const Dispatcher &); + Dispatcher(const Dispatcher &); +}; + + +// ---------------------------------------------------------------------------- +// Template functions MUST be implemented in header file with minimal code size +template +int Dispatcher::postMessage(EventHandler *handler, const T &msg, int priority) +{ + T::IS_DERIVED_FROM_Message; + if (!handler) return -1; + + T *m = new T(msg); // use copy constructor to create message clone + if (!m) return -ENOMEM; + + Holder *holder = new HolderPost(handler, m); + if (!holder) + { + delete m; + return -ENOMEM; + } + return putMessage(holder, priority); +} + +// ---------------------------------------------------------------------------- +template +int Dispatcher::sendMessage(EventHandler *handler, const T &msg, int priority) +{ + T::IS_DERIVED_FROM_Message; + ASSERT(mThreadId != std::this_thread::get_id()); + // use message reference + Fence f; + Holder *holder = new HolderSend(handler, msg, f); + if (!holder ) + return -ENOMEM; + + if (putMessage(holder, priority) < 0) + return -1; + + // wake for message completion or reset by dispatcher + f.wait(); + return 0; +} + +// ---------------------------------------------------------------------------- +template +uintptr_t Dispatcher::scheduleTimer (EventHandler *handler, nsecs_t delay, const T &msg, int priority) +{ + T::IS_DERIVED_FROM_Message; + return putTimer(handler, delay, new T(msg), priority); +} + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/infra/EmbeddedList.h b/third-party/libtm/libtm/infra/EmbeddedList.h new file mode 100644 index 0000000000..5e18777097 --- /dev/null +++ b/third-party/libtm/libtm/infra/EmbeddedList.h @@ -0,0 +1,187 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + + +namespace perc { +// ---------------------------------------------------------------------------- +/// +/// @class EmbeddedList +/// +/// @brief Doubly linked list. +/// +/// @note This implementation of a doubly linked list does not require +/// use of dynamically allocated memory. Instead, each class +/// that is a potential list element must inherite from a class +/// . All of the list functions operate on these +/// embedded element. +/// +// ---------------------------------------------------------------------------- +class EmbeddedListElement +{ +public: + EmbeddedListElement *Next () const {return m_EmbeddedListElementNext; } + void Next (EmbeddedListElement *Element) { m_EmbeddedListElementNext = Element; } + EmbeddedListElement *Prev () const {return m_EmbeddedListElementPrev; } + void Prev (EmbeddedListElement *Element) { m_EmbeddedListElementPrev = Element; } +private: + EmbeddedListElement *m_EmbeddedListElementNext; + EmbeddedListElement *m_EmbeddedListElementPrev; +}; + +class EmbeddedList +{ +public: + + EmbeddedList () : Head (0), Tail (0), m_Size(0) {} + + // Check Head + EmbeddedListElement *GetHead () + { + return Head; + } + + // Inserts an element onto the top of the list + void AddHead (EmbeddedListElement *Element) + { + //ASSERT (Element); + Element->Prev (0); + Element->Next (Head); + + // if first element, the tail also points to this element + if (!Head) + Tail = Element; + // more than one + else + // poin to the new head + Head->Prev (Element); + Head = Element; + m_Size++; + } + + // Takes a element off from the top of the list + EmbeddedListElement *RemoveHead () + { + EmbeddedListElement *element = Head; + if (element) + { + // if only one element + if (Tail == Head) + Tail = 0; + else + element->Next ()->Prev (0); + Head = element->Next (); + m_Size--; + } + return element; + } + + // Inserts a element onto the end of the list + void AddTail (EmbeddedListElement *Element) + { + //ASSERT (Element); + Element->Prev (Tail); + Element->Next (0); + + // if first element, the tail also points to this element + if (!Tail) + Head = Element; + // more than one + else + // poin to the new tail + Tail->Next (Element); + Tail = Element; + m_Size++; + } + + // Takes a element off from the end of the list + EmbeddedListElement *RemoveTail () + { + EmbeddedListElement *element = Tail; + if (element) + { + // if only one element + if (Tail == Head) + Head = 0; + else + element->Prev ()->Next (0); + Tail = element->Prev (); + m_Size--; + } + return element; + } + + // Inserts a element onto the end of the list + void AddBefore (EmbeddedListElement *ElementCurr, EmbeddedListElement *ElementNew) + { + //ASSERT (ElementCurr && ElementNew); + if (ElementCurr == Head) + AddHead (ElementNew); + else + { + ElementNew->Prev (ElementCurr->Prev ()); + ElementCurr->Prev (ElementNew); + ElementNew->Next (ElementCurr); + ElementNew->Prev ()->Next (ElementNew); + m_Size++; + } + } + + // Takes a element off from the list + int Remove (EmbeddedListElement *Element) + { + //ASSERT (Element); + if (Element == Head) + RemoveHead (); + else if (Element == Tail) + RemoveTail (); + else + { + Element->Next ()->Prev (Element->Prev ()); + Element->Prev ()->Next (Element->Next ()); + m_Size--; + } + return 0; + } + + /// @class Iterator + class Iterator + { + const EmbeddedList &m_List; + EmbeddedListElement *m_CurrentNode; + public: + Iterator (const EmbeddedList &List) : m_List (List) { Reset (); } + + // Reset the Iterator so that GetNext will give you the first list element + void Reset () + { + m_CurrentNode = m_List.Head; + } + + // Return the next list element (i.e. advance iterator and return next list element) + EmbeddedListElement *Next () + { + // move the iterator one step forward + if (m_CurrentNode) + m_CurrentNode = m_CurrentNode->Next (); + return m_CurrentNode; + } + + // Return the current list element + EmbeddedListElement *Current () const { return m_CurrentNode; } + }; + + int Size() const { return m_Size; } + +private: + EmbeddedListElement *Head; + EmbeddedListElement *Tail; + int m_Size; +}; + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Event.h b/third-party/libtm/libtm/infra/Event.h new file mode 100644 index 0000000000..e033457900 --- /dev/null +++ b/third-party/libtm/libtm/infra/Event.h @@ -0,0 +1,32 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "TrackingCommon.h" +#include "Log.h" + +namespace perc +{ + class Event + { + public: + Event(); + + inline Handle handle() { return mEvent; } + + int signal(); + + int reset(); + + ~Event(); + + private: + Handle mEvent; + + void operator= (const Event &) = delete; + Event(const Event &) = delete; + }; + +} // namespace perc diff --git a/third-party/libtm/libtm/infra/EventHandler.h b/third-party/libtm/libtm/infra/EventHandler.h new file mode 100644 index 0000000000..096cccb607 --- /dev/null +++ b/third-party/libtm/libtm/infra/EventHandler.h @@ -0,0 +1,62 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "TrackingCommon.h" + +namespace perc { +// ---------------------------------------------------------------------------- +/// +/// @class Message +/// +/// @brief Declare a base class for all messages that can be posted to Dispatcher. +/// User MUST manage allocation policy if needed by defining copy constructor +// and appropriate destructor. and its derives will be copied internally and +/// SHOULD be freed after dispatching callback by Dispatcher. +/// +// ---------------------------------------------------------------------------- +class Message +{ +public: + Message(int type, int param = 0) : Type(type), Param(param), Result(-1) {} + virtual ~Message() {} + int Type; + int Param; + mutable int Result; + + // replace mechanism that generate compilation error + // if derived class was not inherited from class + enum { IS_DERIVED_FROM_Message = true }; +}; + +// ---------------------------------------------------------------------------- +/// +/// @class EventHandler +/// +/// @brief This base class defines the interface for receiving the +/// results of sync and asynchronous operations. +/// Subclasses of this class will fill in appropriate methods. +/// @note +/// +// ---------------------------------------------------------------------------- +class EventHandler +{ +public: + virtual ~EventHandler() {} + + // = Completion callbacks + virtual void onMessage(const Message &) {} + virtual void onEvent(Handle fd, unsigned long mask, void *act) {} + virtual void onTimeout(uintptr_t timerId, const Message &) {} + virtual void onExit() {} + +protected: + // Hide the constructor + EventHandler() {} +}; + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Event_lin.cpp b/third-party/libtm/libtm/infra/Event_lin.cpp new file mode 100644 index 0000000000..30071e4394 --- /dev/null +++ b/third-party/libtm/libtm/infra/Event_lin.cpp @@ -0,0 +1,35 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include "Event.h" +#include +# include + +perc::Event::Event() +{ + // LINUX: NONBLOCK + mEvent = ::eventfd(0, EFD_NONBLOCK); + + ASSERT(mEvent != ILLEGAL_HANDLE); +} + +perc::Event::~Event() +{ + ::close(mEvent); +} + +int perc::Event::reset() +{ + ASSERT(mEvent != ILLEGAL_HANDLE); + + eventfd_t cnt; + return !::eventfd_read(mEvent, &cnt); +} + +int perc::Event::signal() { + ASSERT(mEvent != ILLEGAL_HANDLE); + + return !::eventfd_write(mEvent, 1); +} \ No newline at end of file diff --git a/third-party/libtm/libtm/infra/Event_win.cpp b/third-party/libtm/libtm/infra/Event_win.cpp new file mode 100644 index 0000000000..b9f832751c --- /dev/null +++ b/third-party/libtm/libtm/infra/Event_win.cpp @@ -0,0 +1,37 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include "Event.h" + +perc::Event::Event() +{ + // WINDOWS: manual reset event + mEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + NULL // Unnamed event + ); + + ASSERT(mEvent != ILLEGAL_HANDLE); +} + +perc::Event::~Event() +{ + CloseHandle(mEvent); +} + +int perc::Event::reset() +{ + ASSERT(mEvent != ILLEGAL_HANDLE); + return ResetEvent(mEvent); +} + +int perc::Event::signal() +{ + ASSERT(mEvent != ILLEGAL_HANDLE); + + return SetEvent(mEvent); +} \ No newline at end of file diff --git a/third-party/libtm/libtm/infra/Fence.h b/third-party/libtm/libtm/infra/Fence.h new file mode 100644 index 0000000000..06401a9ab8 --- /dev/null +++ b/third-party/libtm/libtm/infra/Fence.h @@ -0,0 +1,39 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include +#include + + +namespace perc { + +class Fence +{ +public: + Fence(){} + + inline void notify() { + std::unique_lock l(m); + fired = true; + cv.notify_one(); + } + + inline void wait() { + std::unique_lock l(m); + cv.wait(l, [this]{ return fired; }); + fired = false; + } + +private: + std::mutex m; + std::condition_variable cv; + bool fired = false; + // Prevent assignment and initialization. + void operator= (const Fence &); + Fence (const Fence &); +}; + +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Fsm.cpp b/third-party/libtm/libtm/infra/Fsm.cpp new file mode 100644 index 0000000000..4bd281cca6 --- /dev/null +++ b/third-party/libtm/libtm/infra/Fsm.cpp @@ -0,0 +1,394 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ +#define LOG_TAG "Fsm" + +#include "Fsm.h" + +namespace perc { +// -[statics]------------------------------------------------------------------ +// Default NONE transition definition. +const FsmTransition s_FsmTransitionNone = +{ + FSM_TRANSITION_NONE_NAME, + FSM_TRANSITION_NONE, + FSM_EVENT_NONE, + FSM_STATE_FINAL, + 0, + 0, + FSM_TRANSITION_AFTER_MAX_TIMEOUT +}; + +// Default FINAL state definition. +const FsmState s_FsmStateFinal = +{ + FSM_STATE_FINAL_NAME, + FSM_STATE_FINAL, + 0, + 0, + (FsmTransition *)&s_FsmTransitionNone +}; + + +// ---------------------------------------------------------------------------- +Fsm::Fsm() : + m_pFsm(0), + m_CurrStateId(FSM_STATE_FINAL), + m_Owner(NULL), + m_Dispatcher(NULL), + m_Name(""), + m_SelfEvent(NULL) +{ +} + +// ---------------------------------------------------------------------------- +Fsm::~Fsm() +{ + done(); +} + +// ---------------------------------------------------------------------------- +int +Fsm::init (const FsmState * const *pFsm, + void *Owner, + Dispatcher *pDispatcher, + const char *Name) +{ + // User MUST define FSM definition!!! + ASSERT (pFsm); + + // Save context parameters + m_pFsm = pFsm; + m_Owner = Owner; + m_Dispatcher = pDispatcher; + m_Name = Name; + + // Check if Dispatcher was set + if (!dispatcher ()) + { + LOGW("engine not found, can't schedule after transitions!"); + } + + // Init Initial state and call to initial state entry function. + // Initial state always first into the states list! + resetSelfEvent(); + int ret_code = InitNewState (m_pFsm[0]->Type); + if (FSM_CONTEXT_STATUS_OK != ret_code) + { + ASSERT(!m_SelfEvent); + logRetCode(ret_code, m_pFsm[m_CurrStateId], Message(FSM_EVENT_NONE)); + return ret_code; + } + // Process self event if exist + return processSelfEvent(); +} + +// ---------------------------------------------------------------------------- +int +Fsm::done () +{ + if (!m_pFsm) return FSM_CONTEXT_STATUS_ERROR; + + CancelAfterTransitions (); + + // Reset FSM context parameters. + m_pFsm = 0; + m_Owner = 0; + m_Dispatcher = 0; + + return FSM_CONTEXT_STATUS_OK; +} + +// ---------------------------------------------------------------------------- +int +Fsm::fireEvent (const Message &pEvent) +{ + ASSERT (m_pFsm); + + resetSelfEvent(); + + // Find transition that wait for this event + const FsmState *state = m_pFsm[m_CurrStateId]; + int transition_id = FSM_TRANSITION_NONE; + int ret_code = FSM_CONTEXT_STATUS_ERROR; + const FsmTransition *transition = 0; + + if ((ret_code = FindTransition (&transition_id, pEvent)) != FSM_CONTEXT_STATUS_OK) + goto exit_; + else + transition = &state->TransitionList[transition_id]; + + ASSERT (transition); + + // Check if transition internal - in this case only call to transition action and return + if (transition->NewState == FSM_STATE_SAME) + { + CallTransitionAction (transition, pEvent); + ret_code = FSM_CONTEXT_STATUS_OK; + goto exit_; + } + + // Transition is not internal - we must to do some work... + // Call to state exit function. + DoneCurrState (); + + // Call to transition action. + CallTransitionAction (transition, pEvent); + + // Go to new state, that may be final... + ret_code = InitNewState (transition->NewState); + +exit_: + if (FSM_CONTEXT_STATUS_OK != ret_code) + { + ASSERT(!m_SelfEvent); + logRetCode(ret_code, state, pEvent); + return ret_code; + } + // Process self event if exist + return processSelfEvent(); +} + +// -[Private functions]-------------------------------------------------------- +int +Fsm::InitNewState (int StateType) +{ + // Check if new state - final state + if (StateType == FSM_STATE_FINAL) + return FSM_CONTEXT_STATUS_STATE_FINAL; + + // Find new state definition + for (int state_id = 0; m_pFsm[state_id]->Type != FSM_STATE_FINAL; state_id++) + { + if (m_pFsm[state_id]->Type == StateType) + { + // Remember new state index. + m_CurrStateId = state_id; + + CallStateEntry (); + + // Schedule After transitions timers via engine object. + ScheduleAfterTransitions (); + + return FSM_CONTEXT_STATUS_OK; + } + } + return FSM_CONTEXT_STATUS_STATE_NOT_FOUND; +} + +// ---------------------------------------------------------------------------- +int +Fsm::DoneCurrState () +{ + // Cancel after transitions timers + CancelAfterTransitions (); + + // Call to old state exit function + CallStateExit (); + + return FSM_CONTEXT_STATUS_OK; +} + +// ---------------------------------------------------------------------------- +int +Fsm::FindTransition (int *pTransitionId, const Message &pEvent) +{ + const FsmTransition *transition_list = m_pFsm[m_CurrStateId]->TransitionList; + int ret_code = FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND; + + // = Find transition that wait for this event type + // + if (pEvent.Type == FSM_EVENT_TIMEOUT) + { + int tid = pEvent.Param; + + ASSERT (transition_list[tid].Type == FSM_TRANSITION_AFTER || + transition_list[tid].Type == FSM_TRANSITION_INTERNAL_AFTER); + + // Check transition Guard function + if (CallTransitionGuard (&transition_list[tid], pEvent)) + { + // Save found transition + *pTransitionId = tid; + ret_code = FSM_CONTEXT_STATUS_OK; + } + else + ret_code = FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED; + } + else + { + for (int i = 0; transition_list[i].Type != FSM_TRANSITION_NONE; i++) + { + // Check if this transition wait for current event. + if (transition_list[i].EventType == pEvent.Type) + { + ret_code = FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED; + + if (CallTransitionGuard (&transition_list[i], pEvent)) + { + // Save found transition + *pTransitionId = i; + return FSM_CONTEXT_STATUS_OK; + } + } + } + } + return ret_code; +} + +// ---------------------------------------------------------------------------- +int +Fsm::ScheduleAfterTransitions () +{ + if (!dispatcher ()) return -1; + + const FsmState *state = m_pFsm[m_CurrStateId]; + const FsmTransition *transition_list = state->TransitionList; + + // Find after transitions and schedule timers for them. + for (int tid = 0; transition_list[tid].Type != FSM_TRANSITION_NONE; tid++) + { + // Check if this after transition + if (transition_list[tid].TimeOut != FSM_TRANSITION_AFTER_MAX_TIMEOUT) + { + uintptr_t timerId = dispatcher ()->scheduleTimer(this, ms2ns(transition_list[tid].TimeOut), Message(FSM_EVENT_TIMEOUT, tid)); + if (!timerId) + { + LOGE("[%s]:invalid timer id, can't schedule more!", state->Name); + } + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +int Fsm::CancelAfterTransitions() +{ + if (!dispatcher()) return -1; + + // Cancel timer for all after transitions that was scheduled. + dispatcher()->removeHandler(this, Dispatcher::TIMERS_MASK); + return 0; +} + +// ---------------------------------------------------------------------------- +bool Fsm::CallTransitionGuard(const FsmTransition *pTransition, const Message &pEvent) +{ + bool ret = true; + + if (pTransition->Guard) + ret = pTransition->Guard(this, pEvent); + + LOGV("[%s]:%s[%s]:guard%s %d", + m_pFsm[m_CurrStateId]->Name, + TransitionType (pTransition->Type), + pTransition->Name, + pTransition->Guard ? "()": "(none)", + ret); + return ret; +} + +// ---------------------------------------------------------------------------- +void Fsm::CallTransitionAction(const FsmTransition *pTransition, const Message &pEvent) +{ + LOGV("[%s]:%s[%s]:action%s", + m_pFsm[m_CurrStateId]->Name, + TransitionType (pTransition->Type), + pTransition->Name, + pTransition->Action ? "()": "(none)"); + if (pTransition->Action) + pTransition->Action(this, pEvent); +} + +// ---------------------------------------------------------------------------- +void Fsm::CallStateEntry() +{ + const FsmState *state = m_pFsm[m_CurrStateId]; + LOGV("[%s]:entry%s", + state->Name, + state->Entry ? "()": "(none)"); + if (state->Entry) + state->Entry(this); +} + +// ---------------------------------------------------------------------------- +void Fsm::CallStateExit() +{ + const FsmState *state = m_pFsm[m_CurrStateId]; + LOGV("[%s]:exit%s", + state->Name, + state->Exit ? "()": "(none)"); + if (state->Exit) + state->Exit(this); +} + +// ---------------------------------------------------------------------------- +// A utility static method that translates FSM status to a string. +const char* +Fsm::statusName (int Status) +{ + switch (Status) + { + case FSM_CONTEXT_STATUS_ERROR: return "Error"; + case FSM_CONTEXT_STATUS_OK: return "OK"; + case FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND: return "Transition not found"; + case FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED: return "Event not handled"; + case FSM_CONTEXT_STATUS_STATE_FINAL: return "State final"; + case FSM_CONTEXT_STATUS_STATE_NOT_FOUND: return "State not found"; + } + return "Unknown status"; +} + +// ---------------------------------------------------------------------------- +// A utility static method that translates FSM transition to a string. +const char* +Fsm::TransitionType (int Transition) +{ + switch (Transition) + { + case FSM_TRANSITION_NONE: return "TN"; + case FSM_TRANSITION: return "T"; + case FSM_TRANSITION_AFTER: return "TA"; + case FSM_TRANSITION_INTERNAL: return "TI"; + case FSM_TRANSITION_INTERNAL_AFTER: return "TIA"; + } + return "T?"; +} + +// ---------------------------------------------------------------------------- +// Debug print of FSM status +void +Fsm::logRetCode (int retCode, const FsmState *state, const Message &pEvent) +{ + if (FSM_CONTEXT_STATUS_STATE_FINAL == retCode) + { + LOGD("final state reached"); + } + else + { + if (FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED == retCode) + { + LOGW("[%s]:event[%d] not handled", state->Name, pEvent.Type); + } + else if (FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND == retCode) + { + LOGW("[%s]:no appropriate transition for this event[%d]", + state->Name, pEvent.Type); + } + else if (FSM_CONTEXT_STATUS_STATE_NOT_FOUND == retCode) + { + LOGW("[%s]:no appropriate state found for this event[%d]", + state->Name, pEvent.Type); + } + else + { + LOGE("[%s]:undefined status error - %d, event[%d]", + state->Name, retCode, pEvent.Type); + } + } +} + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Fsm.h b/third-party/libtm/libtm/infra/Fsm.h new file mode 100644 index 0000000000..3ea0c6b43f --- /dev/null +++ b/third-party/libtm/libtm/infra/Fsm.h @@ -0,0 +1,334 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "Log.h" +#include "Dispatcher.h" + + +namespace perc { +// -[Defines]------------------------------------------------------------------ +/// C++ helpers macros that transform static FSM functions interface to class methods. +// Put these macros into header file (protected/private) section. +#define ACTION(Class, State, Event) Class::sAction_s ## State ## _e ## Event +#define GUARD(Class, State, Event) Class::sGuard_s ## State ## _e ## Event +#define ENTRY(Class, State) Class::sEntry_s ## State +#define EXIT(Class, State) Class::sExit_s ## State + +#define DECLARE_FSM_ACTION(State, Event) \ + static void sAction_s##State##_e##Event(Fsm *, const Message &); \ + void Action_s##State##_e##Event(const Message &) + +#define DECLARE_FSM_GUARD(State, Event) \ + static bool sGuard_s##State##_e##Event(Fsm *, const Message &); \ + bool Guard_s##State##_e##Event(const Message &) + +#define DECLARE_FSM_STATE_ENTRY(State) \ + static void sEntry_s##State(Fsm *); \ + void Entry_s##State() + +#define DECLARE_FSM_STATE_EXIT(State) \ + static void sExit_s##State(Fsm *); \ + void Exit_s##State() + +// Put these macros into source file and define body. +#define DEFINE_FSM_ACTION(Class, State, Event, Msg) \ + void ACTION(Class, State, Event)(Fsm *pContext, const Message &Msg) \ + { Class* pThis = reinterpret_cast (pContext->owner()); pThis->Action_s ## State ## _e ## Event(Msg); } \ + void Class::Action_s ## State ## _e ## Event(const Message &Msg) + +#define DEFINE_FSM_GUARD(Class, State, Event, Msg) \ + bool GUARD(Class, State, Event)(Fsm *pContext, const Message &Msg) \ + { Class* pThis = reinterpret_cast (pContext->owner()); return pThis->Guard_s##State##_e##Event(Msg); } \ + bool Class::Guard_s##State##_e##Event(const Message &Msg) + +#define DEFINE_FSM_STATE_ENTRY(Class, State) \ + void ENTRY(Class, State)(Fsm *pContext) \ + { Class* pThis = reinterpret_cast (pContext->owner()); pThis->Entry_s##State(); } \ + void Class::Entry_s##State() + +#define DEFINE_FSM_STATE_EXIT(Class, State) \ + void EXIT(Class, State)(Fsm *pContext) \ + { Class* pThis = reinterpret_cast (pContext->owner()); pThis->Exit_s##State(); } \ + void Class::Exit_s##State() + + +// -[FSM Event]---------------------------------------------------------------- +/// +/// Event types definitions. +/// +/// @note: event types - this is a user defined type exclude timeout event type. +/// User must define application specific events like this (offset from USER_DEFINED): +/// - #define EVENT_TYPE_0 (0 + FSM_EVENT_USER_DEFINED) +/// - #define EVENT_TYPE_1 (1 + FSM_EVENT_USER_DEFINED) +/// - ... +/// +#define FSM_EVENT_NONE ((char)-1) +#define FSM_EVENT_TIMEOUT 0 +#define FSM_EVENT_USER_DEFINED 1 + + +// -[FSM Transition]----------------------------------------------------------- +/// +/// Internal FSM transition type definitions. +/// +/// @note: Only for Infra::Fsm internal use. +/// +#define FSM_TRANSITION_NONE_NAME "NONE" +#define FSM_TRANSITION_NONE ((char)-1) +#define FSM_TRANSITION 0 +#define FSM_TRANSITION_AFTER 1 +#define FSM_TRANSITION_INTERNAL 2 +#define FSM_TRANSITION_INTERNAL_AFTER 3 +#define FSM_TRANSITION_UNCONDITIONAL 4 + +// Define max timeout of after transition. +#define FSM_TRANSITION_AFTER_MAX_TIMEOUT ((unsigned long)~0) + +// Guard and Action function prototypes. +class Fsm; +class FsmEvent; +typedef bool (*FsmTransitionGuard) (Fsm *, const Message &); +typedef void (*FsmTransitionAction)(Fsm *, const Message &); + + +// -[Infra:FSM State]---------------------------------------------------------- +/// +/// FSM state type definitions. +/// +/// @note: !!! WARNING - Forbidden defining state with typeid - (-1) +/// User must define application specific states like this(offset from USER_DEFINED): +/// - #define STATE_0 (0 + FSM_STATE_USER_DEFINED) +/// - #define STATE_1 (1 + FSM_STATE_USER_DEFINED) +/// - ... +/// +#define FSM_STATE_FINAL_NAME "FINAL" +#define FSM_STATE_FINAL ((char)-1) ///< !!! Don't use this define for user defined states +#define FSM_STATE_SAME 0 +#define FSM_STATE_USER_DEFINED 1 + +// State entry/exit functions prototypes. +typedef void (*FsmStateEntry)(Fsm *pFsmContext); +typedef void (*FsmStateExit) (Fsm *pFsmContext); + + +// -[FSM Definition]----------------------------------------------------------- +/// Transition definition +typedef struct FsmTransition_T +{ + const char *Name; + char Type; + char EventType; + char NewState; + FsmTransitionGuard Guard; + FsmTransitionAction Action; + unsigned long TimeOut; +} FsmTransition; + +/// Default NONE transition definition. +extern const FsmTransition s_FsmTransitionNone; + + +/// State definition +typedef struct FsmState_T +{ + const char *Name; + char Type; + FsmStateEntry Entry; + FsmStateExit Exit; + FsmTransition *TransitionList; +} FsmState; + +/// Final state default definitions +extern const FsmState s_FsmStateFinal; + + +/// Internal use: FSM loggings definition macros +# define FSM_NAME(_name) _name, + +/// Transition list definition macros +#define TRANSITION_INTERNAL(_event, _guard, _action) \ + { FSM_NAME (#_event) FSM_TRANSITION_INTERNAL, _event, FSM_STATE_SAME, _guard, _action, FSM_TRANSITION_AFTER_MAX_TIMEOUT }, + +#define TRANSITION_INTERNAL_AFTER(_guard, _action, _timeout) \ + { FSM_NAME ("INTERNAL_AFTER") FSM_TRANSITION_INTERNAL_AFTER, FSM_EVENT_TIMEOUT, FSM_STATE_SAME, _guard, _action, _timeout }, + +#define TRANSITION(_event, _guard, _action, _new_state_type) \ + { FSM_NAME (#_event) FSM_TRANSITION, _event, _new_state_type, _guard, _action, FSM_TRANSITION_AFTER_MAX_TIMEOUT }, + +#define TRANSITION_AFTER(_guard, _action, _new_state_type, _timeout) \ + { FSM_NAME ("AFTER") FSM_TRANSITION_AFTER, FSM_EVENT_TIMEOUT, _new_state_type, _guard, _action, _timeout }, + + +/// State definition macros +#define DECLARE_FSM_STATE(_state) \ + static const FsmState s_Fsm##_s##_state; \ + static const FsmTransition s_Fsm##_s##_state##_##TransitionList[] + +#define DEFINE_FSM_STATE_BEGIN(Class, _state) \ + const FsmTransition Class::s_Fsm##_s##_state##_##TransitionList[] = { + +#define DEFINE_FSM_STATE_END(Class, _state, _entry, _exit) \ + { FSM_NAME (FSM_TRANSITION_NONE_NAME) FSM_TRANSITION_NONE, FSM_EVENT_NONE, FSM_STATE_FINAL, 0, 0, FSM_TRANSITION_AFTER_MAX_TIMEOUT } }; \ + const FsmState Class::s_Fsm##_s##_state = \ + { FSM_NAME (#_state) _state, _entry, _exit, (FsmTransition *)Class::s_Fsm##_s##_state##_##TransitionList }; + + +/// Macros for defining FSM. +#define FSM(_fsm) s_Fsm##_##_fsm + +#define DEFINE_FSM_BEGIN(Class, _fsm) \ + const FsmState * const Class::FSM(_fsm)[] = { + +#define STATE(Class, _state) \ + &Class::s_Fsm##_s##_state, + +#define DEFINE_FSM_END() \ + &s_FsmStateFinal \ + }; + +#define DECLARE_FSM(_fsm) \ + static const FsmState * const FSM(_fsm)[]; + + +// ---------------------------------------------------------------------------- +/// +/// @class Fsm +/// +/// @brief FSM context class. +/// +// ---------------------------------------------------------------------------- +/// FSM engine handle event return codes. +#define FSM_CONTEXT_STATUS_ERROR (-1) +#define FSM_CONTEXT_STATUS_OK 0 +#define FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND 1 +#define FSM_CONTEXT_STATUS_UNCOND_TRANSITION_NOT_FOUND 2 +#define FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED 3 +#define FSM_CONTEXT_STATUS_STATE_FINAL 4 +#define FSM_CONTEXT_STATUS_STATE_NOT_FOUND 5 + +#define FSM_CONTEXT_DEFAULT_NAME "Fsm" +#define FSM_CONTEXT_TIMER_LIST_LEN 8 + +class Fsm : public EventHandler +{ +public: + + Fsm (); + virtual ~Fsm (); + + /// Explicit init function. + int init (const FsmState * const *pFsm, + void *Owner = 0, + Dispatcher *pDispatcher = 0, + const char *Name = FSM_CONTEXT_DEFAULT_NAME); + + /// + /// FSM context main function - handle user events and run defined state machine. + /// Before calling this function user MUST call Init() function. + /// User MUST call this function from well defined (single) context. + /// + /// @param pEvent Event to proceed. + /// + /// @return See Infra FSM engine handle event return codes. + /// In case of successful event handling function returns - FSM_CONTEXT_STATUS_OK. + /// In another case function returns error code that user MUST check and process. + /// + /// @note May enter recursively !!! + /// + int fireEvent(const Message &); + + int done(); + + // = Get/Set methods + // + /// Get User param method. + void *owner() const { return m_Owner; } + void owner(void *pOwner) { m_Owner = pOwner; } + + // = Self event mechanism + // + // Call to function will enable recursive message processing without + // main dispatcher loop. User SHOULD call once to this function in function, + // and transitions only. + void selfEvent(const Message &selfEvent) + { + ASSERT(!m_SelfEvent); + m_SelfEvent = new Message(selfEvent); + ASSERT(m_SelfEvent); + } + + /// Utility + int getCurrentState (void) const + { + if (m_pFsm) + return m_pFsm[m_CurrStateId]->Type; + return FSM_STATE_FINAL; + } + + static const char *statusName (int Status); + +protected: + + // = Interface: EventHandler + // + void onMessage(const Message &msg) { fireEvent (msg); } + void onTimeout(uintptr_t timerId, const Message &msg) { fireEvent (msg); } + + // Get the dispatcher associated with this handler + Dispatcher *dispatcher() const { return m_Dispatcher; } + +private: + /// FSM context parameters. + const FsmState * const *m_pFsm; + void *m_Owner; + Dispatcher *m_Dispatcher; + int m_CurrStateId; + const char *m_Name; + + /// Self event + Message *m_SelfEvent; + void resetSelfEvent() { m_SelfEvent = 0; } + int processSelfEvent() + { + int ret = FSM_CONTEXT_STATUS_OK; + if (m_SelfEvent) + { + Message *savedSelfEvent = m_SelfEvent; + ret = fireEvent (*m_SelfEvent); + delete savedSelfEvent; + } + return ret; + } + + // = Internal functions + // + int InitNewState(int StateType); + int DoneCurrState(); + static const char *TransitionType(int Transition); + void logRetCode (int retCode, const FsmState *state, const Message &); + + /// + /// This function returns id of transition that wait for this event type in OUT parameter. + /// Checks guard function of transition. In case that we reached last state's defined transition + /// this function returns error - INFRA_FSM_TRANSITION_NONE. + /// + int FindTransition(int /*OUT*/ *, const Message &); + + /// Init after transition list: schedule timer via engine object + int ScheduleAfterTransitions(); + + /// Cancel all timers and reset after transition list + int CancelAfterTransitions(); + + bool CallTransitionGuard(const FsmTransition *, const Message &); + void CallTransitionAction(const FsmTransition *, const Message &); + void CallStateEntry(); + void CallStateExit(); +}; + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Log.cpp b/third-party/libtm/libtm/infra/Log.cpp new file mode 100644 index 0000000000..47113020e7 --- /dev/null +++ b/third-party/libtm/libtm/infra/Log.cpp @@ -0,0 +1,351 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include "Log.h" +#include "Utils.h" +#include +#include +#include +#include +#include "../include/TrackingData.h" + +#ifdef _WIN32 +#include +#include +#include +#include +#endif + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#include +#define gettid() syscall(SYS_gettid) +#endif + +using namespace perc; + +/* Define TIME_OF_DAY_LOG_HEADER to enable time of day (HH:MM:SS.mmm) log */ +/* Undefine TIME_OF_DAY_LOG_HEADER to enable epoch time log */ +#define TIME_OF_DAY_LOG_HEADER + +#define MAX_LOG_CONTAINERS 2 + +// Default priority mask - ALL enabled +static int s_PriorityMask = /*LOG_PRI2MASK(LOG_VERBOSE) | */ +LOG_PRI2MASK(LOG_DEBUG) | +LOG_PRI2MASK(LOG_TRACE) | +LOG_PRI2MASK(LOG_INFO) | +LOG_PRI2MASK(LOG_WARN) | +LOG_PRI2MASK(LOG_ERROR); + +class AdvancedLog : public TrackingData::Log { +public: + AdvancedLog() : rolledOver(0) {}; + + uint8_t rolledOver; +}; + + +class LogConfiguration { +public: + uint32_t outputMode; + uint8_t verbosityMask; + uint8_t rolloverMode; +} ; + +class logManager { +public: + logManager() : activeContainer(0) + { + loadTimestamp = systemTime(); + configuration[LogSourceHost].verbosityMask = s_PriorityMask; + configuration[LogSourceHost].rolloverMode = true; + configuration[LogSourceHost].outputMode = LogOutputModeScreen; + }; + + nsecs_t loadTimestamp; + + std::mutex configurationMutex; + LogConfiguration configuration[LogSourceMax]; + + std::atomic activeContainer; + std::mutex logContainerMutex[MAX_LOG_CONTAINERS]; + AdvancedLog logContainer[MAX_LOG_CONTAINERS]; +}; + +logManager gLogManager; + +FILE *gStream = NULL; + +const char* logPrioritySign[] = { "U" ,/* LOG_UNKNOWN */ +"T" ,/* LOG_DEFAULT */ +"V" ,/* LOG_VERBOSE */ +"D" ,/* LOG_DEBUG */ +"T" ,/* LOG_TRACE */ +"I" ,/* LOG_INFO */ +"W" ,/* LOG_WARN */ +"E" ,/* LOG_ERROR */ +"F" ,/* LOG_FATAL */ +"S" ,/* LOG_SILENT */ }; + +const LogVerbosityLevel prio2verbosity[] = {None, None, Verbose, Debug, Trace, Info, Warning, Error, Error, None}; + +// LOG_FATAL is always enabled by default +#define isPriorityEnabled(prio) (LOG_FATAL == prio || (gLogManager.configuration[LogSourceHost].verbosityMask & LOG_PRI2MASK(prio))) + +void __perc_Log_write(int prio, const char *tag, const char *text) +{ + if (!isPriorityEnabled(prio)) + return; + + fprintf(stdout, "%s", text); + +} + + void __perc_Log_Save(void* deviceId, int prio, const char *tag, int line, int payloadLen, char* payload) +{ + std::lock_guard guardLogContainer(gLogManager.logContainerMutex[gLogManager.activeContainer]); + AdvancedLog* logContainer = &gLogManager.logContainer[gLogManager.activeContainer]; + uint32_t entries = logContainer->entries; + + if ((gLogManager.configuration[LogSourceHost].rolloverMode == 0) && (logContainer->rolledOver == 1)) + { + printf("rolled over - stopped saving prints on container %d, entries = %d...\n", (uint32_t)gLogManager.activeContainer, entries); + return; + } + + logContainer->entry[entries].timeStamp = systemTime() - gLogManager.loadTimestamp; + + HostLocalTime localTime = getLocalTime(); + + logContainer->entry[entries].localTimeStamp.year = localTime.year; + logContainer->entry[entries].localTimeStamp.month = localTime.month; + logContainer->entry[entries].localTimeStamp.dayOfWeek = localTime.dayOfWeek; + logContainer->entry[entries].localTimeStamp.day = localTime.day; + logContainer->entry[entries].localTimeStamp.hour = localTime.hour; + logContainer->entry[entries].localTimeStamp.minute = localTime.minute; + logContainer->entry[entries].localTimeStamp.second = localTime.second; + logContainer->entry[entries].localTimeStamp.milliseconds = localTime.milliseconds; + + logContainer->entry[entries].lineNumber = line; + + if (tag != NULL) + { + perc::copy(&logContainer->entry[entries].moduleID, tag, MAX_LOG_BUFFER_MODULE_SIZE); + } + + logContainer->entry[entries].verbosity = prio2verbosity[prio]; + logContainer->entry[entries].deviceID = (uint64_t)deviceId; + logContainer->entry[entries].threadID = getThreadId(); + + snprintf(logContainer->entry[entries].payload, payloadLen + 1, "%s", payload); + logContainer->entry[entries].payloadSize = payloadLen; + + if (entries == (MAX_LOG_BUFFER_ENTRIES-1)) + { + printf("entries %d, setting rollover = 1...\n", logContainer->entries); + logContainer->rolledOver = 1; + } + + logContainer->entries = (entries + 1) % MAX_LOG_BUFFER_ENTRIES; + +} + + +#ifdef __linux__ +int __perc_Log_print_header(char * buf, int bufSize, int prio, const char *tag, const char* deviceId) +{ + struct timeval tv; + int headerLen; + static struct timeval loadTime = { 0 }; + + gettimeofday(&tv, NULL); + +#ifdef TIME_OF_DAY_LOG_HEADER + + // Round to nearest millisec + int millisec = lrint(tv.tv_usec / 1000.0); + if (millisec >= 1000) + { + millisec -= 1000; + tv.tv_sec++; + } + + struct tm* tm_info = localtime(&tv.tv_sec); // Transform time to ASCII + int timeLen = 8; // Length of adding HH:MM:SS + strftime(buf, timeLen + 2, "%H:%M:%S", tm_info); // Adding HH:MM:SS/0 to buffer + headerLen = snprintf(buf + timeLen, bufSize - timeLen, ".%03d [%lu] [%s] %s%s: ", millisec, gettid(), logPrioritySign[prio], tag, deviceId) + timeLen; + +#else + if (loadTime.tv_sec == 0) + { + loadTime = tv; + } + + unsigned long currentTime = ((tv.tv_sec * 1000000) + tv.tv_usec) - ((loadTime.tv_sec * 1000000) + loadTime.tv_usec); + headerLen = snprintf(buf, bufSize, "%014lu [%lu] [%s] %s: ", currentTime, gettid(), logPrioritySign[prio], tag); + +#endif //TIME_OF_DAY_LOG_HEADER + + return headerLen; +} +#elif _WIN32 +int __perc_Log_print_header(char * buf, int bufSize, int prio, const char *tag, const char* deviceId) +{ + int headerLen = 0; + +#ifdef TIME_OF_DAY_LOG_HEADER + + SYSTEMTIME lt; + GetLocalTime(<); + + headerLen = snprintf(buf, bufSize, "%02d:%02d:%02d:%03d [%06lu] [%s] %s%s: ", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds, GetCurrentThreadId(), logPrioritySign[prio], tag, deviceId); + +#else + + static unsigned long long loadTime = 0; + FILETIME currentFileTime; + GetSystemTimeAsFileTime(¤tFileTime); + unsigned long long currentTime = (((ULONGLONG)currentFileTime.dwHighDateTime << 32) | (ULONGLONG)currentFileTime.dwLowDateTime) / 10000; + + if (loadTime == 0) + { + loadTime = currentTime; + } + + headerLen = snprintf(buf, bufSize, "%011llu [%06lu] [%s] %s: ", currentTime - loadTime, GetCurrentThreadId(), logPrioritySign[prio], tag); + +#endif // TIME_OF_DAY_LOG_HEADER + + return headerLen; + +} +#endif + +void __perc_Log_print(void* deviceId, int prio, const char *tag, int line, const char *fmt, ...) +{ + if (isPriorityEnabled(prio)) { + uint8_t outputMode; + uint8_t verbosity; + uint8_t rolloverMode; + int headerLen = 0; + int payloadLen = 0; + + va_list ap; + va_start(ap, fmt); + + __perc_Log_Get_Configuration(LogSourceHost, &outputMode, &verbosity, &rolloverMode); + + char buf[BUFSIZ * 4]; + + if (outputMode == LogOutputModeScreen) + { + char deviceBuf[30] = { 0 }; + if (deviceId != NULL) + { + short device = ((uintptr_t)deviceId & 0xFFFF); + snprintf(deviceBuf, sizeof(deviceBuf), "-%04hX", device); + } + + headerLen = __perc_Log_print_header(buf, sizeof(buf), prio, tag, deviceBuf); + + ASSERT((size_t)headerLen < sizeof(buf)); + payloadLen = vsnprintf(buf + headerLen, sizeof(buf) - headerLen, fmt, ap); + fprintf(stdout, "%s\n", buf); + } + else + { + payloadLen = vsnprintf(buf, sizeof(buf), fmt, ap); + __perc_Log_Save(deviceId, prio, tag, line, payloadLen, buf); + } + + va_end(ap); + } +} + +void __perc_Log_Set_Configuration(uint8_t source, uint8_t outputMode, uint8_t verbosity, uint8_t rolloverMode) +{ + std::lock_guard guardLogConfiguration(gLogManager.configurationMutex); + + gLogManager.configuration[source].outputMode = outputMode; + gLogManager.configuration[source].rolloverMode = rolloverMode; + + uint8_t verbosityMask = 0; + switch (verbosity) + { + case Trace: + verbosityMask |= LOG_PRI2MASK(LOG_TRACE); + case Verbose: + verbosityMask |= LOG_PRI2MASK(LOG_VERBOSE); + case Debug: + verbosityMask |= LOG_PRI2MASK(LOG_DEBUG); + case Warning: + verbosityMask |= LOG_PRI2MASK(LOG_WARN); + case Info: + verbosityMask |= LOG_PRI2MASK(LOG_INFO); + case Error: + verbosityMask |= LOG_PRI2MASK(LOG_ERROR); + case None: + verbosityMask |= 0; + } + + gLogManager.configuration[source].verbosityMask = verbosityMask; +} + +void __perc_Log_Get_Configuration(uint8_t source, uint8_t* outputMode, uint8_t* verbosity, uint8_t* rolloverMode) +{ + std::lock_guard guardLogConfiguration(gLogManager.configurationMutex); + + *outputMode = gLogManager.configuration[source].outputMode; + *verbosity = gLogManager.configuration[source].verbosityMask; + *rolloverMode = gLogManager.configuration[source].rolloverMode; +} + + +void __perc_Log_Get_Log(void* log) +{ + TrackingData::Log* logOutput = (TrackingData::Log*)log; + uint32_t entryIndex = 0; + + uint8_t activeContainer = gLogManager.activeContainer; + + gLogManager.activeContainer ^= 1; + + { + std::lock_guard guardLogContainer(gLogManager.logContainerMutex[activeContainer]); + AdvancedLog* logContainer = &gLogManager.logContainer[activeContainer]; + + if (logContainer->rolledOver == 1) + { + for (uint32_t i = logContainer->entries; i < MAX_LOG_BUFFER_ENTRIES; i++, entryIndex++) + { + logOutput->entry[entryIndex] = logContainer->entry[i]; + } + + logOutput->entries = MAX_LOG_BUFFER_ENTRIES; + } + else + { + logOutput->entries = logContainer->entries; + } + + for (uint32_t i = 0; i < logContainer->entries; i++, entryIndex++) + { + logOutput->entry[entryIndex] = logContainer->entry[i]; + } + + logOutput->maxEntries = MAX_LOG_BUFFER_ENTRIES; + + logContainer->entries = 0; + logContainer->rolledOver = 0; + + //printf("got log on container %d - %d entries (new container %d)\n", activeContainer, logOutput->entries, (uint32_t)gLogManager.activeContainer); + } +} diff --git a/third-party/libtm/libtm/infra/Log.h b/third-party/libtm/libtm/infra/Log.h new file mode 100644 index 0000000000..cde77a2042 --- /dev/null +++ b/third-party/libtm/libtm/infra/Log.h @@ -0,0 +1,134 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include +#include +#include +#include + +// Log priority values, in ascending priority order. +// Note: This definition is using the same priority order as android log subsystem. +typedef enum LogPriority { + LOG_UNKNOWN = 0, + LOG_DEFAULT, + LOG_VERBOSE, + LOG_DEBUG, + LOG_TRACE, + LOG_INFO, + LOG_WARN, + LOG_ERROR, + LOG_FATAL, + LOG_SILENT, +#define LOG_PRI2MASK(_pri) (1 << _pri) +} LogPriority; + +enum LogSource { + LogSourceHost = 0x0000, /**< Host Log Configuration */ + LogSourceFW = 0x0001, /**< FW Log Configuration */ + LogSourceMax = 0x0002, +}; + +// Normally we strip LOGV (VERBOSE messages) from release builds. +// You can modify this (for example with "#define LOG_NDEBUG 0" at the top of your source file) to change that behavior. +#ifndef NDEBUG +#define NDEBUG +#endif + +#ifndef LOG_NDEBUG +#ifdef NDEBUG +#define LOG_NDEBUG 1 +#else +#define LOG_NDEBUG 0 +#endif +#endif + +#ifndef LOG_TAG +#define LOG_TAG NULL +#endif + +// Macro to send a verbose log message using the current LOG_TAG. +#ifndef LOGV +#if LOG_NDEBUG +#define LOGV(...) ((void)0) +#else +#define LOGV(...) ((void)LOG(NULL, LOG_VERBOSE, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif +#endif + +#ifndef DEVICELOGV +#if LOG_NDEBUG +#define DEVICELOGV(...) ((void)0) +#else +#define DEVICELOGV(...) ((void)LOG(this, LOG_VERBOSE, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif +#endif + +// Macros to send a priority log message using the current LOG_TAG. +#ifndef LOGD +#define LOGD(...) ((void)LOG(NULL, LOG_DEBUG, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOGT +#define LOGT(...) ((void)LOG(NULL, LOG_TRACE, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOGI +#define LOGI(...) ((void)LOG(NULL, LOG_INFO, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOGW +#define LOGW(...) ((void)LOG(NULL, LOG_WARN, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOGE +#define LOGE(...) ((void)LOG(NULL, LOG_ERROR, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOGF +#define LOGF(...) ((void)LOG(NULL, LOG_FATAL, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGD +#define DEVICELOGD(...) ((void)LOG(this, LOG_DEBUG, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGT +#define DEVICELOGT(...) ((void)LOG(this, LOG_TRACE, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGI +#define DEVICELOGI(...) ((void)LOG(this, LOG_INFO, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGW +#define DEVICELOGW(...) ((void)LOG(this, LOG_WARN, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGE +#define DEVICELOGE(...) ((void)LOG(this, LOG_ERROR, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGF +#define DEVICELOGF(...) ((void)LOG(this, LOG_FATAL, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOG +#define LOG(_deviceId, _prio, _tag, _line, ...) \ + __perc_Log_print(_deviceId, _prio, _tag, _line, __VA_ARGS__) +#endif + +#define ASSERT assert +#define UNUSED(_var) (void)(_var) + +// platform depended print declaration +void __perc_Log_write(int prio, const char *tag, const char *text); +void __perc_Log_print(void* deviceId, int prio, const char *tag, int line, const char *fmt, ...); +int __perc_Log_print_header(char * buf, int bufSize, int prio, const char *tag, const char* deviceId); +void __perc_Log_Save(void* deviceId, int prio, int payloadLen, char* payload); +void __perc_Log_Set_Configuration(uint8_t source, uint8_t outputMode, uint8_t verbosity, uint8_t rolloverMode); +void __perc_Log_Get_Configuration(uint8_t source, uint8_t* outputMode, uint8_t* verbosityMask, uint8_t* rolloverMode); +void __perc_Log_Get_Log(void* log); + diff --git a/third-party/libtm/libtm/infra/Poller.h b/third-party/libtm/libtm/infra/Poller.h new file mode 100644 index 0000000000..7190c35952 --- /dev/null +++ b/third-party/libtm/libtm/infra/Poller.h @@ -0,0 +1,62 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include +#include +#include +#include +#include "TrackingCommon.h" + +namespace perc { + + class Poller + { + public: + + struct event + { + Handle handle; + unsigned long mask; + void *act; + event() : + handle(ILLEGAL_HANDLE), mask(Poller::NULL_MASK), act(0) {} + event(Handle h, unsigned long m, void *a) : + handle(h), mask(m), act(a) {} + }; + + Poller(); + ~Poller(); + bool add(const Poller::event &); + bool remove(Handle); + + // POLLING LOOP + // + // Returns: + // >0 - the total number of events + // 0 - if the elapsed without dispatching any handle + // -1 - if an error occurs + int poll(Poller::event &, unsigned long timeoutMs); + + public: + + static const int NULL_MASK; + static const int READ_MASK; + static const int WRITE_MASK; + static const int EXCEPT_MASK; + static const int ALL_MASK; + + + private: + + struct CheshireCat; + std::unique_ptr mData; + + // Prevent assignment and initialization. + void operator= (const Poller &) = delete; + Poller(const Poller &) = delete; + }; + +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Poller_lin.cpp b/third-party/libtm/libtm/infra/Poller_lin.cpp new file mode 100644 index 0000000000..90050cf34e --- /dev/null +++ b/third-party/libtm/libtm/infra/Poller_lin.cpp @@ -0,0 +1,112 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#define LOG_TAG "infra/Poller" +#define LOG_NDEBUG 1 // controls LOGV only +#include "Log.h" +#include "Utils.h" +#include "Poller.h" +#include "TrackingCommon.h" +#include +#include + +namespace perc +{ + + struct Poller::CheshireCat + { + std::unordered_map mEvents; + std::mutex mEventsGuard; + int mEpoll; + }; + + const int Poller::READ_MASK = EPOLLIN | EPOLLRDNORM; + const int Poller::NULL_MASK = 0; + const int Poller::WRITE_MASK = EPOLLOUT | EPOLLWRNORM; + const int Poller::EXCEPT_MASK = EPOLLPRI | EPOLLERR | EPOLLHUP | EPOLLRDHUP; + const int Poller::ALL_MASK = READ_MASK | WRITE_MASK | EXCEPT_MASK; + + Poller::Poller() : mData(new CheshireCat()) { + mData->mEpoll = ::epoll_create(1); + ASSERT(mData->mEpoll != -1); + } + + Poller::~Poller() + { + ::close(mData->mEpoll); + } + + bool Poller::add(const Poller::event &evt) + { + int res = -1; + if (evt.handle != -1) { + struct epoll_event e; + e.events = evt.mask; + e.data.fd = evt.handle; + // synchronized section + std::lock_guard guard(mData->mEventsGuard); + if (mData->mEvents.count(evt.handle) == 0) { + // adding new one + res = ::epoll_ctl(mData->mEpoll, EPOLL_CTL_ADD, evt.handle, &e); + if (!res) + mData->mEvents.emplace(evt.handle, evt); + } + else { + // modifying old one + res = ::epoll_ctl(mData->mEpoll, EPOLL_CTL_MOD, evt.handle, &e); + if (!res) + mData->mEvents[evt.handle] = evt; + } + } + return res == 0; + } + + bool Poller::remove(Handle handle) + { + if (handle != -1) { + //ASSERT(::pthread_equal(tid, ::pthread_self())); + std::lock_guard guard(mData->mEventsGuard); + if (mData->mEvents.count(handle) > 0) { + int res = ::epoll_ctl(mData->mEpoll, EPOLL_CTL_DEL, handle, 0); + mData->mEvents.erase(handle); + return res == 0; + } + } + return false; + } + + int Poller::poll(Poller::event &evt, unsigned long timeoutMs) + { + struct epoll_event e; + int timeoutAbs = (int)timeoutMs == INFINITE ? INFINITE : ns2ms(systemTime()) + (int)timeoutMs; + again: + int n = ::epoll_wait(mData->mEpoll, &e, 1, (int)timeoutMs); + if (n > 0) { + std::lock_guard guard(mData->mEventsGuard); + LOGV("poll: e.data.fd %d", e.data.fd); + if (mData->mEvents.count(e.data.fd) > 0) { + evt = mData->mEvents[e.data.fd]; + } + else { + ::epoll_ctl(mData->mEpoll, EPOLL_CTL_DEL, e.data.fd, 0); + int cur = ns2ms(systemTime()); + if ((int)timeoutMs != INFINITE) { + if (cur < (int)timeoutAbs) + timeoutMs = timeoutAbs - cur; + else + return 0; + } + goto again; + } + } + else if (n == -1) { + // TODO: add logs or clear errors + LOGE("poll: epoll_wait error %d", errno); + } + // timeout + return n; + } + +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Poller_win.cpp b/third-party/libtm/libtm/infra/Poller_win.cpp new file mode 100644 index 0000000000..e15d5505e7 --- /dev/null +++ b/third-party/libtm/libtm/infra/Poller_win.cpp @@ -0,0 +1,154 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#define LOG_TAG "infra/Poller" +#define LOG_NDEBUG 1 // controls LOGV only +#include "Log.h" +#include "Utils.h" +#include "Poller.h" +#include "TrackingCommon.h" + + +namespace perc +{ + + struct Poller::CheshireCat + { + std::unordered_map mEvents; + std::mutex mEventsGuard; + + std::mutex mAddRemoveMutex; + std::vector mHandles; + HANDLE mAddRemoveDoneEvent; + }; + + const int Poller::READ_MASK = 0; + const int Poller::NULL_MASK = 0; + const int Poller::WRITE_MASK = 0; + const int Poller::EXCEPT_MASK = 0; + const int Poller::ALL_MASK = 0; + + + Poller::Poller() : mData(new CheshireCat()) + { + mData->mAddRemoveDoneEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + NULL // Unnamed event + ); + ASSERT(mData->mAddRemoveDoneEvent != ILLEGAL_HANDLE); + + mData->mHandles.push_back(mData->mAddRemoveDoneEvent); + } + + Poller::~Poller() + { + CloseHandle(mData->mAddRemoveDoneEvent); + } + + bool Poller::add(const Poller::event &evt) + { + // Take Add remove/mutex + std::lock_guard guardAddRemove(mData->mAddRemoveMutex); + + // Notify poll thread to exit and release mEventsGuard + SetEvent(mData->mAddRemoveDoneEvent); + + // Take mEventsGuard - wait for poll thread to release the mutex + std::lock_guard guardEvents(mData->mEventsGuard); + + ResetEvent(mData->mAddRemoveDoneEvent); + + //check if this handle already exists + auto it = std::find(mData->mHandles.begin(), mData->mHandles.end(), evt.handle); + + // we are already at maximum events + if (mData->mHandles.size() >= MAXIMUM_WAIT_OBJECTS && it == mData->mHandles.end()) + return false; + + if (it == mData->mHandles.end()) + mData->mHandles.push_back(evt.handle); + + // Add or modify event in events map + mData->mEvents[evt.handle] = evt; + + return true; + } + + bool Poller::remove(Handle handle) + { + // Take Add remove/mutex + std::lock_guard guardAddRemove(mData->mAddRemoveMutex); + + // Notify poll thread to exit and release mEventsGuard + SetEvent(mData->mAddRemoveDoneEvent); + + // Take mEventsGuard - wait for poll thread to release the mutex + std::lock_guard guardEvents(mData->mEventsGuard); + + ResetEvent(mData->mAddRemoveDoneEvent); + + //check if this handle already exists + auto it = std::find(mData->mHandles.begin(), mData->mHandles.end(), handle); + + if (it == mData->mHandles.end()) + return false; + + // Remove the handle from vector + mData->mHandles.erase(it); + + // remove the evt from events map + mData->mEvents.erase(handle); + + return true; + } + + int Poller::poll(Poller::event &evt, unsigned long timeoutMs) + { + int timeoutAbs = (timeoutMs == INFINITE)? INFINITE : ns2ms(systemTime()) + (int)timeoutMs; + + while (true) + { + int exitReason; + { + // Take events guard, to wait safely on vector of handles + std::lock_guard guardEvents(mData->mEventsGuard); + + exitReason = WaitForMultipleObjects((DWORD)mData->mHandles.size(), + mData->mHandles.data(), + FALSE, + timeoutMs); + + int eventIndex = exitReason - WAIT_OBJECT_0; + if ((exitReason < 0) || ((unsigned int)eventIndex >= mData->mHandles.size())) + { + return 0; + } + else if (eventIndex != 0) // zero is special case - it is our private event + { + evt = mData->mEvents[mData->mHandles[eventIndex]]; + return 1; + } + } // here we release the events guard, so Add/remove function may continue + + // WAIT_OBJECT_0 means add/remove function called, lets wait for it completion by just waiting for the lock + { + std::lock_guard guardAddRemove(mData->mAddRemoveMutex); + + // we are going to a second iteration - update timeout + if (timeoutMs != INFINITE) + { + auto nowMs = ns2ms(systemTime()); + if (nowMs >= timeoutAbs) + timeoutMs = 0; + else + timeoutMs = timeoutAbs - nowMs; + } + } + } + return 0; + } +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Semaphore.h b/third-party/libtm/libtm/infra/Semaphore.h new file mode 100644 index 0000000000..371b352527 --- /dev/null +++ b/third-party/libtm/libtm/infra/Semaphore.h @@ -0,0 +1,40 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "Utils.h" +#include +/* +* Wrapper class for pthread semaphore implementation. +*/ + + +namespace perc +{ + + class Semaphore + { + public: + Semaphore(unsigned int initValue = 0); + + int get(nsecs_t timeoutNs = -1); + int put(); + + ~Semaphore(); + + private: + + struct CheshireCat; + + std::unique_ptr mData; + + // = Prevent assignment and initialization. + void operator= (const Semaphore &) = delete; + Semaphore(const Semaphore &) = delete; + + }; + + +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Semaphore_lin.cpp b/third-party/libtm/libtm/infra/Semaphore_lin.cpp new file mode 100644 index 0000000000..7fdfc94353 --- /dev/null +++ b/third-party/libtm/libtm/infra/Semaphore_lin.cpp @@ -0,0 +1,49 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include "Semaphore.h" +#include "Utils.h" +#include + +namespace perc +{ + + struct Semaphore::CheshireCat + { + ::sem_t m_Semaphore; + }; + + int Semaphore::get(nsecs_t timeoutNs) + { + if (timeoutNs == -1) + return ::sem_wait(&mData->m_Semaphore); + if (timeoutNs == 0) + return ::sem_trywait(&mData->m_Semaphore); + nsecs_t nsecsTime = systemTime() + timeoutNs; + struct timespec absTimeout; + absTimeout.tv_sec = ns2s(nsecsTime); + absTimeout.tv_nsec = nsecsTime % 1000000000; + return ::sem_timedwait(&mData->m_Semaphore, &absTimeout); + } + + + Semaphore::Semaphore(unsigned int initValue) : mData(new CheshireCat()) + { + ::sem_init(&mData->m_Semaphore, 0, initValue); + } + + Semaphore::~Semaphore() + { + ::sem_destroy(&mData->m_Semaphore); + } + + int Semaphore::put() + { + return ::sem_post(&mData->m_Semaphore); + } + + + +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Semaphore_win.cpp b/third-party/libtm/libtm/infra/Semaphore_win.cpp new file mode 100644 index 0000000000..5fac93e5a5 --- /dev/null +++ b/third-party/libtm/libtm/infra/Semaphore_win.cpp @@ -0,0 +1,63 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include "Semaphore.h" +#include "Utils.h" +#include +#include + +#define MAX_SEMAPHORE_COUNT 128 + +namespace perc { + + struct Semaphore::CheshireCat + { + HANDLE m_Semaphore; + }; + + int Semaphore::get(nsecs_t timeoutNs) + { + DWORD timeoutMs; + + timeoutMs = ns2ms(timeoutNs); + + auto ret = WaitForSingleObject(mData->m_Semaphore, timeoutMs); + + if (ret == WAIT_OBJECT_0) // well this is useless , since WAIT_OBJECT_0 is equal to 0, but at least this is clear; + return 0; + + return -1; + } + + + Semaphore::Semaphore(unsigned int initValue) : mData(new CheshireCat()) + { + mData->m_Semaphore = CreateSemaphore( + NULL, // default security attributes + initValue, // initial count + MAX_SEMAPHORE_COUNT, // maximum count + NULL); + + assert(mData->m_Semaphore != NULL); + } + + Semaphore::~Semaphore() + { + CloseHandle(mData->m_Semaphore); + } + + int Semaphore::put() + { + int ret = ReleaseSemaphore( + mData->m_Semaphore, // handle to semaphore + 1, // increase count by one + NULL); // not interested in previous count + + return !ret; // return zero on success; same as in linux + } + + + +} // namespace perc diff --git a/third-party/libtm/libtm/infra/Utils.cpp b/third-party/libtm/libtm/infra/Utils.cpp new file mode 100644 index 0000000000..0713f5c5a0 --- /dev/null +++ b/third-party/libtm/libtm/infra/Utils.cpp @@ -0,0 +1,163 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ +#define LOG_TAG "Utils" +#include "Utils.h" +#include +#include + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#define gettid() syscall(SYS_gettid) +#else +#include +#include +#endif + +nsecs_t systemTime() +{ +#ifdef _WIN32 + /* + auto start = std::chrono::high_resolution_clock::now(); + std::chrono::time_point time_point_ns(start); + return time_point_ns.time_since_epoch().count();*/ + LARGE_INTEGER StartingTime; + LARGE_INTEGER Frequency; + + QueryPerformanceFrequency(&Frequency); + QueryPerformanceCounter(&StartingTime); + + StartingTime.QuadPart *= 1000000LL; // convert to microseconds + StartingTime.QuadPart /= Frequency.QuadPart; + StartingTime.QuadPart *= 1000; // Convert to nano seconds + + return StartingTime.QuadPart; + +#else + struct timespec ts; + ts.tv_sec = ts.tv_nsec = 0; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ((nsecs_t)(ts.tv_sec)) * 1000000000LL + ts.tv_nsec; +#endif +} + + +HostLocalTime getLocalTime() +{ + HostLocalTime localTime; + +#ifdef _WIN32 + SYSTEMTIME lt; + GetLocalTime(<); + + localTime.year = lt.wYear; + localTime.month = lt.wMonth; + localTime.dayOfWeek = lt.wDayOfWeek; + localTime.day = lt.wDay; + localTime.hour = lt.wHour; + localTime.minute = lt.wMinute; + localTime.second = lt.wSecond; + localTime.milliseconds = lt.wMilliseconds; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + + // Round to nearest millisec + int millisec = lrint(tv.tv_usec / 1000.0); + if (millisec >= 1000) + { + millisec -= 1000; + tv.tv_sec++; + } + + struct tm* tm_info = localtime(&tv.tv_sec); // Transform time to ASCII + + localTime.year = tm_info->tm_year; + localTime.month = tm_info->tm_mon; + localTime.dayOfWeek = tm_info->tm_wday; + localTime.day = tm_info->tm_mday; + localTime.hour = tm_info->tm_hour; + localTime.minute = tm_info->tm_min; + localTime.second = tm_info->tm_sec; + localTime.milliseconds = millisec; +#endif + + return localTime; +} + + +uint64_t bytesSwap(uint64_t val) +{ +#ifdef _WIN32 + return _byteswap_uint64(val); +#else + return htobe64(val); +#endif +} + +uint32_t getThreadId(void) +{ +#ifdef _WIN32 + return GetCurrentThreadId(); +#else + return gettid(); +#endif +} + + +void perc::copy(void* dst, void const* src, size_t size) +{ +#ifdef _WIN32 + memcpy_s(dst, size, src, size); +#else + auto from = reinterpret_cast(src); + std::copy(from, from + size, reinterpret_cast(dst)); +#endif +} + + +size_t perc::stringLength(const char* string, size_t maxSize) +{ +#ifdef _WIN32 + return strnlen_s(string, maxSize); +#else + return strnlen(string, maxSize); +#endif +} + +bool setProcessPriorityToRealtime() +{ +#ifdef _WIN32 + HANDLE pid = GetCurrentProcess(); + if (!SetPriorityClass(pid, REALTIME_PRIORITY_CLASS)) + { + LOGE("Error: Failed to set process priority to runtime"); + return false; + } + // Display priority class + DWORD dwPriClass = GetPriorityClass(pid); + LOGD("Setting process priority to 0x%X", dwPriClass); +#else + const int MAX_NICENESS = -20; + + id_t pid = getpid(); + int ret = setpriority(PRIO_PROCESS, pid, MAX_NICENESS); + if (ret == -1) + { + LOGE("Error: Failed to set process priority - to set priority rerun with sudo\n"); + return true; + } + // Display priority class + ret = getpriority(PRIO_PROCESS, pid); + LOGD("Setting process priority to 0x%X", ret); +#endif + + return true; +} + diff --git a/third-party/libtm/libtm/infra/Utils.h b/third-party/libtm/libtm/infra/Utils.h new file mode 100644 index 0000000000..94039f2e39 --- /dev/null +++ b/third-party/libtm/libtm/infra/Utils.h @@ -0,0 +1,86 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include +#include "Log.h" + +#ifdef _WIN32 +#include +#else +#include +#define INFINITE (-1) +#endif + +class HostLocalTime { +public: + HostLocalTime() : year(0), month(0), dayOfWeek(0), day(0), hour(0), minute(0), second(0), milliseconds(0) {} + uint16_t year; + uint16_t month; + uint16_t dayOfWeek; + uint16_t day; + uint16_t hour; + uint16_t minute; + uint16_t second; + uint16_t milliseconds; +}; + +HostLocalTime getLocalTime(); + +#ifdef __cplusplus +extern "C" { +#endif + typedef int64_t nsecs_t; + + inline nsecs_t s2ns(nsecs_t sec) + { + return (sec < 0) ? INFINITE : sec * 1000000000; + } + + inline nsecs_t ms2ns(nsecs_t ms) + { + return (ms < 0) ? INFINITE : ms * 1000000; + } + + inline int ns2s(nsecs_t ns) + { + return (ns < 0) ? INFINITE : (int)(ns / 1000000000); + } + + inline int ns2ms(nsecs_t ns) + { + return (ns < 0) ? INFINITE : (int)(ns / 1000000); + } + + nsecs_t systemTime(); + + uint64_t bytesSwap(uint64_t val); + + uint32_t getThreadId(void); + + inline int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime) + { + int timeoutDelayMsec = 0; + + if (timeoutTime > referenceTime) + { + timeoutDelayMsec = (int)(((uint64_t)(timeoutTime - referenceTime) + 999999LL) / 1000000LL); + } + + return timeoutDelayMsec; + } + + bool setProcessPriorityToRealtime(); + + namespace perc + { + void copy(void* dst, void const* src, size_t size); + size_t stringLength(const char* string, size_t maxSize); + } + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/third-party/libtm/libtm/src/.bdsignore b/third-party/libtm/libtm/src/.bdsignore new file mode 100644 index 0000000000..d17d0eea09 --- /dev/null +++ b/third-party/libtm/libtm/src/.bdsignore @@ -0,0 +1 @@ +fw.h \ No newline at end of file diff --git a/third-party/libtm/libtm/src/CMakeLists.txt b/third-party/libtm/libtm/src/CMakeLists.txt new file mode 100644 index 0000000000..8b84af0c92 --- /dev/null +++ b/third-party/libtm/libtm/src/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 2.8) +cmake_policy(SET CMP0015 NEW) + +project(tm) + +include_directories(${LIBTM_INCLUDE_DIR}) +include_directories(${LIBTM_INFRA_DIR}) + +#Source Files +set(SOURCE_FILES + ${LIBTM_INCLUDE_DIR}/TrackingManager.h + ${LIBTM_INCLUDE_DIR}/TrackingDevice.h + ${LIBTM_INCLUDE_DIR}/TrackingCommon.h + ${LIBTM_INCLUDE_DIR}/TrackingData.h + ${LIBTM_INCLUDE_DIR}/TrackingSerializer.h + Manager.h + Manager.cpp + Device.h + Device.cpp + Message.h + UsbPlugListener.h + UsbPlugListener.cpp + CompleteTask.h + Common.h + Common.cpp + + RcSerializer/Recorder.h + RcSerializer/Recorder.cpp + RcSerializer/Packet.h + RcSerializer/Packet.cpp + RcSerializer/latency_queue.h + RcSerializer/rc_packet.h + RcSerializer/concurrency.h + RcSerializer/Player.h + RcSerializer/Player.cpp +) + +#Add versioning to DLL +IF(WIN32) +SET(SOURCE_FILES "${SOURCE_FILES};version.rc") +ENDIF(WIN32) + +#Building Library +MESSAGE("Building project ${PROJECT_NAME} as ${LIB_TYPE} library") +add_library(${PROJECT_NAME} ${LIB_TYPE} ${SOURCE_FILES}) + +#LINK_LIBRARIES +target_link_libraries(${PROJECT_NAME} +infra +${OS_SPECIFIC_LIBS} +${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/x64/libusb-1.0.lib +) + +set_target_properties(${PROJECT_NAME} PROPERTIES VERSION "${LIBVERSION}" SOVERSION "${LIBTM_VERSION_MAJOR}") +set_target_properties (${PROJECT_NAME} PROPERTIES FOLDER Library) + +#install(TARGETS ${PROJECT_NAME} DESTINATION lib) + +set(CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/../cmake/realsense2") + +install(TARGETS ${PROJECT_NAME} + EXPORT realsense2Targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + diff --git a/third-party/libtm/libtm/src/Common.cpp b/third-party/libtm/libtm/src/Common.cpp new file mode 100644 index 0000000000..e6d6e499ae --- /dev/null +++ b/third-party/libtm/libtm/src/Common.cpp @@ -0,0 +1,379 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include +#include +#include "Common.h" +#include "Message.h" +#include "TrackingData.h" +#include "Utils.h" + +using namespace perc; + +/* Description: This function prints data in hexadecimal format */ +/* Parameters: IN data - data to print */ +/* IN data_len - data length */ +/* Returns: Void */ +void print_data(IN unsigned char * data, IN unsigned int data_len) +{ + unsigned int i; + for (i = 0; i < data_len; i++) + { + printf("0x%X ", data[i]); + if (i % 16 == 15) + { + printf("\n"); + } + } + printf("\n\n"); +} + +perc::Status fwToHostStatus(perc::MESSAGE_STATUS status) +{ + switch (status) + { + case MESSAGE_STATUS::INTERNAL_ERROR: return perc::Status::ERROR_FW_INTERNAL; + case MESSAGE_STATUS::SUCCESS: return perc::Status::SUCCESS; + case MESSAGE_STATUS::MORE_DATA_AVAILABLE: return perc::Status::SUCCESS; + case MESSAGE_STATUS::UNKNOWN_MESSAGE_ID: return Status::NOT_SUPPORTED_BY_FW; + case MESSAGE_STATUS::INVALID_PARAMETER: return Status::ERROR_PARAMETER_INVALID; + case MESSAGE_STATUS::DEVICE_BUSY: return Status::DEVICE_BUSY; + case MESSAGE_STATUS::TIMEOUT: return Status::TIMEOUT; + case MESSAGE_STATUS::TABLE_NOT_EXIST: return Status::TABLE_NOT_EXIST; + case MESSAGE_STATUS::TABLE_LOCKED: return Status::TABLE_LOCKED; + case MESSAGE_STATUS::DEVICE_STOPPED: return Status::DEVICE_STOPPED; + case MESSAGE_STATUS::TEMPERATURE_WARNING: return Status::TEMPERATURE_WARNING; + case MESSAGE_STATUS::TEMPERATURE_STOP: return Status::TEMPERATURE_STOP; + case MESSAGE_STATUS::CRC_ERROR: return Status::CRC_ERROR; + case MESSAGE_STATUS::INCOMPATIBLE: return Status::INCOMPATIBLE; + case MESSAGE_STATUS::SLAM_NO_DICTIONARY: return Status::SLAM_NO_DICTIONARY; + case MESSAGE_STATUS::AUTH_ERROR: return Status::AUTH_ERROR; + case MESSAGE_STATUS::DEVICE_RESET: return Status::DEVICE_RESET; + case MESSAGE_STATUS::LIST_TOO_BIG: return Status::LIST_TOO_BIG; + case MESSAGE_STATUS::NO_BLUETOOTH: return Status::NO_BLUETOOTH; + default: + return perc::Status::COMMON_ERROR; + } +} + + +perc::Status slamToHostStatus(perc::SLAM_STATUS_CODE status) +{ + switch (status) + { + case SLAM_STATUS_CODE::SLAM_STATUS_CODE_SUCCESS: return perc::Status::SUCCESS; + case SLAM_STATUS_CODE::SLAM_STATUS_CODE_LOCALIZATION_DATA_SET_SUCCESS: return perc::Status::SLAM_LOCALIZATION_DATA_SET_SUCCESS; + default: + return perc::Status::COMMON_ERROR; + } +} + +perc::Status slamErrorToHostStatus(perc::SLAM_ERROR_CODE error) +{ + switch (error) + { + case SLAM_ERROR_CODE::SLAM_ERROR_CODE_NONE: return perc::Status::SUCCESS; + case SLAM_ERROR_CODE::SLAM_ERROR_CODE_VISION: return perc::Status::SLAM_ERROR_CODE_VISION; + case SLAM_ERROR_CODE::SLAM_ERROR_CODE_SPEED: return perc::Status::SLAM_ERROR_CODE_SPEED; + case SLAM_ERROR_CODE::SLAM_ERROR_CODE_OTHER: return perc::Status::SLAM_ERROR_CODE_OTHER; + default: + return perc::Status::COMMON_ERROR; + } +} + +perc::Status controllerCalibrationToHostStatus(perc::CONTROLLER_CALIBRATION_STATUS_CODE status) +{ + switch (status) + { + case CONTROLLER_CALIBRATION_STATUS_CODE::CONTROLLER_CALIBRATION_STATUS_SUCCEEDED: return perc::Status::SUCCESS; + case CONTROLLER_CALIBRATION_STATUS_CODE::CONTROLLER_CALIBRATION_STATUS_VALIDATION_FAILURE: return perc::Status::CONTROLLER_CALIBRATION_VALIDATION_FAILURE; + case CONTROLLER_CALIBRATION_STATUS_CODE::CONTROLLER_CALIBRATION_STATUS_FLASH_ACCESS_FAILURE: return perc::Status::CONTROLLER_CALIBRATION_FLASH_ACCESS_FAILURE; + case CONTROLLER_CALIBRATION_STATUS_CODE::CONTROLLER_CALIBRATION_STATUS_IMU_FAILURE: return perc::Status::CONTROLLER_CALIBRATION_IMU_FAILURE; + case CONTROLLER_CALIBRATION_STATUS_CODE::CONTROLLER_CALIBRATION_STATUS_INTERNAL_FAILURE: return perc::Status::CONTROLLER_CALIBRATION_INTERNAL_FAILURE; + default: return perc::Status::CONTROLLER_CALIBRATION_INTERNAL_FAILURE; + } +} + +TrackingData::PoseFrame poseMessageToClass(perc::pose_data msg, uint8_t sourceIndex, nsecs_t systemTime) +{ + TrackingData::PoseFrame pose; + + /* Make sure systemTime doesn't go backward */ + static nsecs_t prevSystemTime = 0; + if (systemTime < prevSystemTime) + { + systemTime = prevSystemTime; + } + else + { + prevSystemTime = systemTime; + } + + pose.sourceIndex = sourceIndex; + pose.acceleration.set(msg.flAx, msg.flAy, msg.flAz); + pose.angularAcceleration.set(msg.flAAX, msg.flAAY, msg.flAAZ); + pose.angularVelocity.set(msg.flVAX, msg.flVAY, msg.flVAZ); + pose.rotation.set(msg.flQi, msg.flQj, msg.flQk, msg.flQr); + pose.timestamp = msg.llNanoseconds; + pose.systemTimestamp = systemTime; + pose.translation.set(msg.flX, msg.flY, msg.flZ); + pose.velocity.set(msg.flVx, msg.flVy, msg.flVz); + pose.trackerConfidence = msg.dwTrackerConfidence; + pose.mapperConfidence = msg.dwMapperConfidence; + + return pose; +} + +TrackingData::CameraIntrinsics cameraIntrinsicsMessageToClass(perc::camera_intrinsics msg) +{ + TrackingData::CameraIntrinsics intrinsics; + + intrinsics.width = msg.dwWidth; + intrinsics.height = msg.dwHeight; + intrinsics.ppx = msg.flPpx; + intrinsics.ppy = msg.flPpy; + intrinsics.fx = msg.flFx; + intrinsics.fy = msg.flFy; + intrinsics.distortionModel = msg.dwDistortionModel; + for (uint8_t i = 0; i < 5; i++) + { + intrinsics.coeffs[i] = msg.flCoeffs[i]; + } + + return intrinsics; +} + +TrackingData::MotionIntrinsics motionIntrinsicsMessageToClass(perc::motion_intrinsics msg) +{ + TrackingData::MotionIntrinsics intrinsics; + + for (uint8_t i = 0; i < 3; i++) + { + for (uint8_t j = 0; j < 4; j++) + { + intrinsics.data[i][j] = msg.flData[i][j]; + } + } + + for (uint8_t i = 0; i < 3; i++) + { + intrinsics.noiseVariances[i] = msg.flNoiseVariances[i]; + } + + for (uint8_t i = 0; i < 3; i++) + { + intrinsics.biasVariances[i] = msg.flBiasVariances[i]; + } + + return intrinsics; +} + +motion_intrinsics motionIntrinsicsClassToMessage(perc::TrackingData::MotionIntrinsics intrinsics) +{ + motion_intrinsics msg; + + for (uint8_t i = 0; i < 3; i++) + { + for (uint8_t j = 0; j < 4; j++) + { + msg.flData[i][j] = intrinsics.data[i][j]; + } + } + + for (uint8_t i = 0; i < 3; i++) + { + msg.flNoiseVariances[i] = intrinsics.noiseVariances[i]; + } + + for (uint8_t i = 0; i < 3; i++) + { + msg.flBiasVariances[i] = intrinsics.biasVariances[i]; + } + + return msg; +} + +TrackingData::SensorExtrinsics sensorExtrinsicsMessageToClass(perc::sensor_extrinsics msg) +{ + TrackingData::SensorExtrinsics extrinsics; + + for (uint8_t i = 0; i < 9; i++) + { + extrinsics.rotation[i] = msg.flRotation[i]; + } + + for (uint8_t i = 0; i < 3; i++) + { + extrinsics.translation[i] = msg.flTranslation[i]; + } + + extrinsics.referenceSensorId = msg.bReferenceSensorID; + + return extrinsics; +} + + +std::string messageCodeToString(libusb_transfer_type type, uint16_t code) +{ + switch (type) + { + case LIBUSB_TRANSFER_TYPE_BULK: + switch (code) + { + case DEV_GET_DEVICE_INFO: return "DEV_GET_DEVICE_INFO"; + case DEV_GET_TIME: return "DEV_GET_TIME"; + case DEV_GET_AND_CLEAR_EVENT_LOG: return "DEV_GET_AND_CLEAR_EVENT_LOG"; + case DEV_GET_SUPPORTED_RAW_STREAMS: return "DEV_GET_SUPPORTED_RAW_STREAMS"; + case DEV_RAW_STREAMS_CONTROL: return "DEV_RAW_STREAMS_CONTROL"; + case DEV_GET_CAMERA_INTRINSICS: return "DEV_GET_CAMERA_INTRINSICS"; + case DEV_GET_MOTION_INTRINSICS: return "DEV_GET_MOTION_INTRINSICS"; + case DEV_GET_EXTRINSICS: return "DEV_GET_EXTRINSICS"; + case DEV_SET_CAMERA_INTRINSICS: return "DEV_SET_CAMERA_INTRINSICS"; + case DEV_SET_MOTION_INTRINSICS: return "DEV_SET_MOTION_INTRINSICS"; + case DEV_SET_EXTRINSICS: return "DEV_SET_EXTRINSICS"; + case DEV_LOG_CONTROL: return "DEV_LOG_CONTROL"; + case DEV_STREAM_CONFIG: return "DEV_STREAM_CONFIG"; + case DEV_RAW_STREAMS_PLAYBACK_CONTROL: return "DEV_RAW_STREAMS_PLAYBACK_CONTROL"; + case DEV_READ_EEPROM: return "DEV_READ_EEPROM"; + case DEV_WRITE_EEPROM: return "DEV_WRITE_EEPROM"; + case DEV_SAMPLE: return "DEV_SAMPLE"; + case DEV_START: return "DEV_START"; + case DEV_STOP: return "DEV_STOP"; + case DEV_STATUS: return "DEV_STATUS"; + case DEV_GET_POSE: return "DEV_GET_POSE"; + case DEV_EXPOSURE_MODE_CONTROL: return "DEV_EXPOSURE_MODE_CONTROL"; + case DEV_SET_EXPOSURE: return "DEV_SET_EXPOSURE"; + case DEV_GET_TEMPERATURE: return "DEV_GET_TEMPERATURE"; + case DEV_SET_TEMPERATURE_THRESHOLD: return "DEV_SET_TEMPERATURE_THRESHOLD"; + case DEV_SET_GEO_LOCATION: return "DEV_SET_GEO_LOCATION"; + case DEV_FLUSH: return "DEV_FLUSH"; + case DEV_FIRMWARE_UPDATE: return "DEV_FIRMWARE_UPDATE"; + case DEV_GPIO_CONTROL: return "DEV_GPIO_CONTROL"; + case DEV_TIMEOUT_CONFIGURATION: return "DEV_TIMEOUT_CONFIGURATION"; + case DEV_SNAPSHOT: return "DEV_SNAPSHOT"; + case DEV_READ_CONFIGURATION: return "DEV_READ_CONFIGURATION"; + case DEV_WRITE_CONFIGURATION: return "DEV_WRITE_CONFIGURATION"; + case DEV_RESET_CONFIGURATION: return "DEV_RESET_CONFIGURATION"; + case DEV_LOCK_CONFIGURATION: return "DEV_LOCK_CONFIGURATION"; + case DEV_LOCK_EEPROM: return "DEV_LOCK_EEPROM"; + case SLAM_STATUS: return "SLAM_STATUS"; + case SLAM_GET_OCCUPANCY_MAP_TILES: return "SLAM_GET_OCCUPANCY_MAP_TILES"; + case SLAM_GET_LOCALIZATION_DATA: return "SLAM_GET_LOCALIZATION_DATA"; + case SLAM_SET_LOCALIZATION_DATA_STREAM: return "SLAM_SET_LOCALIZATION_DATA_STREAM"; + case SLAM_SET_6DOF_INTERRUPT_RATE: return "SLAM_SET_6DOF_INTERRUPT_RATE"; + case SLAM_6DOF_CONTROL: return "SLAM_6DOF_CONTROL"; + case SLAM_OCCUPANCY_MAP_CONTROL: return "SLAM_OCCUPANCY_MAP_CONTROL"; + case SLAM_RESET_LOCALIZATION_DATA: return "SLAM_RESET_LOCALIZATION_DATA"; + case SLAM_GET_LOCALIZATION_DATA_STREAM: return "SLAM_GET_LOCALIZATION_DATA_STREAM"; + case SLAM_SET_STATIC_NODE: return "SLAM_SET_STATIC_NODE"; + case SLAM_GET_STATIC_NODE: return "SLAM_GET_STATIC_NODE"; + case CONTROLLER_POSE_CONTROL: return "CONTROLLER_POSE_CONTROL"; + case CONTROLLER_STATUS_CHANGE_EVENT: return "CONTROLLER_STATUS_CHANGE_EVENT"; + case CONTROLLER_DEVICE_CONNECT: return "CONTROLLER_DEVICE_CONNECT"; + case CONTROLLER_DEVICE_DISCOVERY_EVENT: return "CONTROLLER_DEVICE_DISCOVERY_EVENT"; + case CONTROLLER_DEVICE_DISCONNECT: return "CONTROLLER_DEVICE_DISCONNECT"; + case CONTROLLER_READ_ASSOCIATED_DEVICES: return "CONTROLLER_READ_ASSOCIATED_DEVICES"; + case CONTROLLER_WRITE_ASSOCIATED_DEVICES: return "CONTROLLER_WRITE_ASSOCIATED_DEVICES"; + case CONTROLLER_DEVICE_DISCONNECTED_EVENT: return "CONTROLLER_DEVICE_DISCONNECTED_EVENT"; + case CONTROLLER_DEVICE_CONNECTED_EVENT: return "CONTROLLER_DEVICE_CONNECTED_EVENT"; + case CONTROLLER_RSSI_TEST_CONTROL: return "CONTROLLER_RSSI_TEST_CONTROL"; + case CONTROLLER_SEND_DATA: return "CONTROLLER_SEND_DATA"; + case CONTROLLER_START_CALIBRATION: return "CONTROLLER_START_CALIBRATION"; + case CONTROLLER_CALIBRATION_STATUS_EVENT: return "CONTROLLER_CALIBRATION_STATUS_EVENT"; + case CONTROLLER_DEVICE_LED_INTENSITY_EVENT: return "CONTROLLER_DEVICE_LED_INTENSITY_EVENT"; + case DEV_ERROR: return "DEV_ERROR"; + case SLAM_ERROR: return "SLAM_ERROR"; + case CONTROLLER_ERROR: return "CONTROLLER_ERROR"; + default: return "UNKNOWN MESSAGE CODE"; + } + + case LIBUSB_TRANSFER_TYPE_CONTROL: + switch (code) + { + case CONTROL_USB_RESET: return "CONTROL_USB_RESET"; + case CONTROL_GET_INTERFACE_VERSION: return "CONTROL_GET_INTERFACE_VERSION"; + default: return "UNKNOWN MESSAGE CODE"; + } + default: return "UNKNOWN TRANSFER TYPE"; + } +} + + +std::string statusCodeToString(perc::MESSAGE_STATUS status) +{ + switch (status) + { + case MESSAGE_STATUS::SUCCESS: return "SUCCESS"; + case MESSAGE_STATUS::UNKNOWN_MESSAGE_ID: return "UNKNOWN_MESSAGE_ID"; + case MESSAGE_STATUS::INVALID_REQUEST_LEN: return "INVALID_REQUEST_LEN"; + case MESSAGE_STATUS::INVALID_PARAMETER: return "INVALID_PARAMETER"; + case MESSAGE_STATUS::INTERNAL_ERROR: return "INTERNAL_ERROR"; + case MESSAGE_STATUS::UNSUPPORTED: return "UNSUPPORTED"; + case MESSAGE_STATUS::LIST_TOO_BIG: return "LIST_TOO_BIG"; + case MESSAGE_STATUS::MORE_DATA_AVAILABLE: return "MORE_DATA_AVAILABLE"; + case MESSAGE_STATUS::DEVICE_BUSY: return "DEVICE_BUSY"; + case MESSAGE_STATUS::TIMEOUT: return "TIMEOUT"; + case MESSAGE_STATUS::TABLE_NOT_EXIST: return "TABLE_NOT_EXIST"; + case MESSAGE_STATUS::TABLE_LOCKED: return "TABLE_LOCKED"; + case MESSAGE_STATUS::DEVICE_STOPPED: return "DEVICE_STOPPED"; + case MESSAGE_STATUS::TEMPERATURE_WARNING: return "TEMPERATURE_WARNING"; + case MESSAGE_STATUS::TEMPERATURE_STOP: return "TEMPERATURE_STOP"; + case MESSAGE_STATUS::CRC_ERROR: return "CRC_ERROR"; + case MESSAGE_STATUS::INCOMPATIBLE: return "INCOMPATIBLE"; + case MESSAGE_STATUS::SLAM_NO_DICTIONARY: return "SLAM_NO_DICTIONARY"; + case MESSAGE_STATUS::NO_BLUETOOTH: return "NO_BLUETOOTH"; + default: return "UNKNOWN STATUS"; + } +} + +std::string slamStatusCodeToString(SLAM_STATUS_CODE status) +{ + switch (status) + { + case SLAM_STATUS_CODE_SUCCESS: return "SLAM_STATUS_CODE_SUCCESS"; + case SLAM_STATUS_CODE_LOCALIZATION_DATA_SET_SUCCESS: return "SLAM_STATUS_CODE_LOCALIZATION_DATA_SET_SUCCESS"; + default: return "UNKNOWN STATUS"; + } +} + +std::string slamErrorCodeToString(SLAM_ERROR_CODE error) +{ + switch (error) + { + case SLAM_ERROR_CODE_NONE: return "SLAM_ERROR_CODE_NONE"; + case SLAM_ERROR_CODE_VISION: return "SLAM_ERROR_CODE_VISION"; + case SLAM_ERROR_CODE_SPEED: return "SLAM_ERROR_CODE_SPEED"; + case SLAM_ERROR_CODE_OTHER: return "SLAM_ERROR_CODE_OTHER"; + default: return "UNKNOWN ERROR"; + } +} + +std::string controllerCalibrationStatusCodeToString(CONTROLLER_CALIBRATION_STATUS_CODE status) +{ + switch (status) + { + case CONTROLLER_CALIBRATION_STATUS_SUCCEEDED: return "CONTROLLER_CALIBRATION_STATUS_SUCCEEDED"; + case CONTROLLER_CALIBRATION_STATUS_VALIDATION_FAILURE: return "CONTROLLER_CALIBRATION_STATUS_VALIDATION_FAILURE"; + case CONTROLLER_CALIBRATION_STATUS_FLASH_ACCESS_FAILURE: return "CONTROLLER_CALIBRATION_STATUS_FLASH_ACCESS_FAILURE"; + case CONTROLLER_CALIBRATION_STATUS_IMU_FAILURE: return "CONTROLLER_CALIBRATION_STATUS_IMU_FAILURE"; + case CONTROLLER_CALIBRATION_STATUS_INTERNAL_FAILURE: return "CONTROLLER_CALIBRATION_STATUS_INTERNAL_FAILURE"; + default: return "UNKNOWN ERROR"; + } +} + +std::string sensorToString(perc::SensorType sensorType) +{ + switch (sensorType) + { + case Fisheye: return "Fisheye"; + case Gyro: return "Gyro"; + case Accelerometer: return "Accelerometer"; + case Controller: return "Controller"; + case Rssi: return "Rssi"; + case Velocimeter: return "Velocimeter"; + default: return "Unknown"; + } +} + diff --git a/third-party/libtm/libtm/src/Common.h b/third-party/libtm/libtm/src/Common.h new file mode 100644 index 0000000000..e83e4e2b94 --- /dev/null +++ b/third-party/libtm/libtm/src/Common.h @@ -0,0 +1,47 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include "TrackingCommon.h" +#include "TrackingData.h" +#include "Message.h" +#include "Device.h" +#include "Utils.h" +#include + +void print_data(IN unsigned char * data, IN unsigned int data_len); + +extern perc::Status fwToHostStatus(perc::MESSAGE_STATUS status); +perc::Status slamToHostStatus(perc::SLAM_STATUS_CODE status); +perc::Status slamErrorToHostStatus(perc::SLAM_ERROR_CODE error); +perc::Status controllerCalibrationToHostStatus(perc::CONTROLLER_CALIBRATION_STATUS_CODE status); +std::string messageCodeToString(libusb_transfer_type type, uint16_t code); +std::string statusCodeToString(perc::MESSAGE_STATUS status); +std::string slamStatusCodeToString(perc::SLAM_STATUS_CODE status); +std::string slamErrorCodeToString(perc::SLAM_ERROR_CODE error); +std::string controllerCalibrationStatusCodeToString(perc::CONTROLLER_CALIBRATION_STATUS_CODE status); +perc::TrackingData::PoseFrame poseMessageToClass(perc::pose_data msg, uint8_t sensorIndex, nsecs_t systemTime); +perc::TrackingData::CameraIntrinsics cameraIntrinsicsMessageToClass(perc::camera_intrinsics msg); +perc::TrackingData::MotionIntrinsics motionIntrinsicsMessageToClass(perc::motion_intrinsics msg); +perc::motion_intrinsics motionIntrinsicsClassToMessage(perc::TrackingData::MotionIntrinsics intrinsics); +perc::TrackingData::SensorExtrinsics sensorExtrinsicsMessageToClass(perc::sensor_extrinsics msg); +std::string sensorToString(perc::SensorType sensorType); + +template +constexpr auto toUnderlying(E e) -> typename std::underlying_type::type +{ + return static_cast::type>(e); +} + +template< typename T > +struct arrayDeleter +{ + void operator ()(T const * p) + { + delete[] p; + } +}; + diff --git a/third-party/libtm/libtm/src/CompleteTask.h b/third-party/libtm/libtm/src/CompleteTask.h new file mode 100644 index 0000000000..ce79e20fa2 --- /dev/null +++ b/third-party/libtm/libtm/src/CompleteTask.h @@ -0,0 +1,730 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "Utils.h" +#include "TrackingCommon.h" +#include "TrackingDevice.h" +#include "TrackingManager.h" +#include +#include +#include + +#define LATENCY_MAX_TIMEDIFF_MSEC 1000 /* Latency warning print is aggregated per this diff */ +#define LATENCY_MAX_EVENTS_PER_SEC 10 /* Latency warning print is printed if exceeding this */ + +perc::Status fwToHostStatus(perc::MESSAGE_STATUS status); + +namespace perc { + enum + { + POSE_TASK = 1, + FRAME_TASK = 2, + USB_DEVICE_TASK = 3, + VIDEO_FRAME_COMPLETE_TASK = 4, + GYRO_FRAME_COMPLETE_TASK = 5, + ACCEL_FRAME_COMPLETE_TASK = 6, + VELOCIMETER_FRAME_COMPLETE_TASK = 7, + RSSI_COMPLETE_TASK = 8, + CONTROLLER_DISCOVERY_EVENT_FRAME_COMPLETE_TASK = 9, + CONTROLLER_EVENT_FRAME_COMPLETE_TASK = 10, + CONTROLLER_CONNECT_EVENT_FRAME_COMPLETE_TASK = 11, + CONTROLLER_DISCONNECT_EVENT_FRAME_COMPLETE_TASK = 12, + CONTROLLER_CALIBRATION_EVENT_FRAME_COMPLETE_TASK = 13, + LOCALIZATION_DATA_STREAM_EVENT_FRAME_COMPLETE_TASK = 14, + ERROR_COMPLETE_TASK = 15, + CONTROLLER_LED_COMPLETE_TASK = 16, + }; + + class CompleteTask + { + public: + CompleteTask(int type, void* owner, bool _mustComplete = false) : Type(type), mOwner(owner), mMustComplete(_mustComplete) {} + virtual ~CompleteTask() {} + virtual void complete() = 0; + virtual bool mustComplete() { return mMustComplete; } + virtual void* getOwner() { return mOwner; } + private: + int Type; + void* mOwner; + bool mMustComplete; /* Tasks that must be completed upon STOP */ + }; + + class PoseCompleteTask : public CompleteTask + { + public: + PoseCompleteTask(TrackingDevice::Listener* l, TrackingData::PoseFrame& message, TrackingDevice* owner) : CompleteTask(POSE_TASK, owner), mListener(l), mOwner(owner), mMessage(message){} + virtual ~PoseCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + static uint32_t latencyEventPerSec = 0; + auto start = systemTime(); + static nsecs_t prevStart = start; + mListener->onPoseFrame(mMessage); + auto finish = systemTime(); + + auto diff = ns2ms(finish - start); + if (diff > 0) + { + /* Latency event occur */ + latencyEventPerSec++; + } + + if (ns2ms(start - prevStart) > LATENCY_MAX_TIMEDIFF_MSEC) + { + /* 1 sec passed between previous latency check */ + if (latencyEventPerSec > LATENCY_MAX_EVENTS_PER_SEC) + { + LOG(mOwner, LOG_WARN, LOG_TAG, __LINE__, "High latency warning (%d msec): %d Pose latency events occurred in the last second, consider onPoseFrame callback optimization to avoid frame drops", diff, latencyEventPerSec); + } + latencyEventPerSec = 0; + prevStart = start; + } + } + } + private: + TrackingDevice::Listener* mListener; + TrackingData::PoseFrame mMessage; + TrackingDevice* mOwner; + }; + + class UsbCompleteTask : public CompleteTask + { + public: + UsbCompleteTask(TrackingManager::Listener* l, TrackingDevice* dev, TrackingData::DeviceInfo* deviceInfo, TrackingManager::EventType e, TrackingManager* owner) : + CompleteTask(USB_DEVICE_TASK, owner, true), mDevice(dev), mDeviceInfo(*deviceInfo), mEventType(e), mListener(l) {} + virtual ~UsbCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + mListener->onStateChanged(mEventType, mDevice, mDeviceInfo); + } + } + private: + TrackingManager::Listener* mListener; + TrackingDevice* mDevice; + TrackingManager::EventType mEventType; + TrackingData::DeviceInfo mDeviceInfo; + }; + + class ErrorTask : public CompleteTask + { + public: + ErrorTask(TrackingManager::Listener* l, TrackingDevice* dev, Status e, TrackingManager* owner) : + CompleteTask(ERROR_COMPLETE_TASK, owner, true), mDevice(dev), mErrorStatus(e), mListener(l) {} + virtual ~ErrorTask() {} + virtual void complete() override + { + if (mListener) + { + mListener->onError(mErrorStatus, mDevice); + } + } + private: + TrackingManager::Listener* mListener; + TrackingDevice* mDevice; + Status mErrorStatus; + + }; + + class FrameBuffersOwner + { + public: + virtual void putBufferBack(SensorId id, std::shared_ptr& frame) = 0; + }; + + class VideoFrameCompleteTask : public CompleteTask + { + public: + VideoFrameCompleteTask(TrackingDevice::Listener* l, std::shared_ptr& frame, FrameBuffersOwner* frameBufferOwner, TrackingDevice* owner, nsecs_t systemTimeStamp, uint16_t width, uint16_t height) : + mFrame(frame), mFrameBufferOwner(frameBufferOwner), mListener(l), mOwner(owner), CompleteTask(VIDEO_FRAME_COMPLETE_TASK, owner) + { + bulk_message_video_stream* frameBuffer = (bulk_message_video_stream*)mFrame.get(); + mVideoFrame.data = (uint8_t*)mFrame.get() + sizeof(bulk_message_video_stream); + mVideoFrame.profile.set(width, height, width, PixelFormat::Y8); + mVideoFrame.systemTimestamp = systemTimeStamp; + mVideoFrame.timestamp = frameBuffer->rawStreamHeader.llNanoseconds; + mVideoFrame.arrivalTimeStamp = frameBuffer->rawStreamHeader.llArrivalNanoseconds; + mVideoFrame.sensorIndex = GET_SENSOR_INDEX(frameBuffer->rawStreamHeader.bSensorID); + mVideoFrame.frameId = frameBuffer->rawStreamHeader.dwFrameId; + mVideoFrame.exposuretime = frameBuffer->metadata.dwExposuretime; + mVideoFrame.gain = frameBuffer->metadata.fGain; + mVideoFrame.frameLength = frameBuffer->metadata.dwFrameLength; + + mId = frameBuffer->rawStreamHeader.bSensorID; + } + virtual ~VideoFrameCompleteTask() + { + mFrameBufferOwner->putBufferBack(mId, mFrame); + } + virtual void complete() override + { + if (mListener) + { + static uint32_t latencyEventPerSec = 0; + auto start = systemTime(); + static nsecs_t prevStart = start; + mListener->onVideoFrame(mVideoFrame); + auto finish = systemTime(); + + auto diff = ns2ms(finish - start); + if (diff > 0) + { + /* Latency event occur */ + latencyEventPerSec++; + } + + if (ns2ms(start - prevStart) > LATENCY_MAX_TIMEDIFF_MSEC) + { + /* 1 sec passed between previous latency check */ + if (latencyEventPerSec > LATENCY_MAX_EVENTS_PER_SEC) + { + LOG(mOwner, LOG_WARN, LOG_TAG, __LINE__, "High latency warning (%d msec): %d Video latency events occurred in the last second, consider onVideoFrame callback optimization to avoid frame drops", diff, latencyEventPerSec); + } + latencyEventPerSec = 0; + prevStart = start; + } + } + } + + public: + TrackingData::VideoFrame mVideoFrame; + + private: + TrackingDevice::Listener* mListener; + std::shared_ptr mFrame; + FrameBuffersOwner* mFrameBufferOwner; + SensorId mId; + TrackingDevice* mOwner; + }; + + class AccelerometerFrameCompleteTask : public CompleteTask + { + public: + AccelerometerFrameCompleteTask(TrackingDevice::Listener* l, interrupt_message_raw_stream_header* imuHeader, int64_t timeStampShift, TrackingDevice* owner) : + mId(imuHeader->bSensorID), mListener(l), mOwner(owner), CompleteTask(ACCEL_FRAME_COMPLETE_TASK, owner) + { + interrupt_message_accelerometer_stream_metadata metaData = ((interrupt_message_accelerometer_stream*)imuHeader)->metadata; + + mFrame.sensorIndex = GET_SENSOR_INDEX(imuHeader->bSensorID); + mFrame.frameId = imuHeader->dwFrameId; + mFrame.acceleration.set(metaData.flAx, metaData.flAy, metaData.flAz); + mFrame.temperature = metaData.flTemperature; + mFrame.timestamp = imuHeader->llNanoseconds; + mFrame.arrivalTimeStamp = imuHeader->llArrivalNanoseconds; + mFrame.systemTimestamp = imuHeader->llNanoseconds + timeStampShift; + } + + virtual ~AccelerometerFrameCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + static uint32_t latencyEventPerSec = 0; + auto start = systemTime(); + static nsecs_t prevStart = start; + mListener->onAccelerometerFrame(mFrame); + auto finish = systemTime(); + + auto diff = ns2ms(finish - start); + if (diff > 0) + { + /* Latency event occur */ + latencyEventPerSec++; + } + + if (ns2ms(start - prevStart) > LATENCY_MAX_TIMEDIFF_MSEC) + { + /* 1 sec passed between previous latency check */ + if (latencyEventPerSec > LATENCY_MAX_EVENTS_PER_SEC) + { + LOG(mOwner, LOG_WARN, LOG_TAG, __LINE__, "High latency warning (%d msec): %d Accelerometer latency events occurred in the last second, consider onAccelerometerFrame callback optimization to avoid frame drops", diff, latencyEventPerSec); + } + latencyEventPerSec = 0; + prevStart = start; + } + } + } + + public: + TrackingData::AccelerometerFrame mFrame; + + private: + TrackingDevice::Listener* mListener; + SensorId mId; + TrackingDevice* mOwner; + }; + + class GyroFrameCompleteTask : public CompleteTask + { + public: + GyroFrameCompleteTask(TrackingDevice::Listener* l, interrupt_message_raw_stream_header* imuHeader, int64_t timeStampShift, TrackingDevice* owner) : mListener(l), mOwner(owner), CompleteTask(GYRO_FRAME_COMPLETE_TASK, owner) + { + interrupt_message_gyro_stream_metadata metaData = ((interrupt_message_gyro_stream*)imuHeader)->metadata; + mFrame.sensorIndex = GET_SENSOR_INDEX(imuHeader->bSensorID); + mFrame.frameId = imuHeader->dwFrameId; + mFrame.angularVelocity.set(metaData.flGx, metaData.flGy, metaData.flGz); + mFrame.temperature = metaData.flTemperature; + mFrame.timestamp = imuHeader->llNanoseconds; + mFrame.arrivalTimeStamp = imuHeader->llArrivalNanoseconds; + mFrame.systemTimestamp = imuHeader->llNanoseconds + timeStampShift; + } + virtual ~GyroFrameCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + static uint32_t latencyEventPerSec = 0; + auto start = systemTime(); + static nsecs_t prevStart = start; + mListener->onGyroFrame(mFrame); + auto finish = systemTime(); + + auto diff = ns2ms(finish - start); + if (diff > 0) + { + /* Latency event occur */ + latencyEventPerSec++; + } + + if (ns2ms(start - prevStart) > LATENCY_MAX_TIMEDIFF_MSEC) + { + /* 1 sec passed between previous latency check */ + if (latencyEventPerSec > LATENCY_MAX_EVENTS_PER_SEC) + { + LOG(mOwner, LOG_WARN, LOG_TAG, __LINE__, "High latency warning (%d msec): %d Gyro latency events occurred in the last second, consider onGyroFrame callback optimization to avoid frame drops", diff, latencyEventPerSec); + } + latencyEventPerSec = 0; + prevStart = start; + } + } + } + + public: + TrackingData::GyroFrame mFrame; + + private: + TrackingDevice::Listener* mListener; + TrackingDevice* mOwner; + }; + + class VelocimeterFrameCompleteTask : public CompleteTask + { + public: + VelocimeterFrameCompleteTask(TrackingDevice::Listener* l, interrupt_message_raw_stream_header* imuHeader, int64_t timeStampShift, TrackingDevice* owner) : mListener(l), mOwner(owner), CompleteTask(VELOCIMETER_FRAME_COMPLETE_TASK, owner) + { + interrupt_message_velocimeter_stream_metadata metaData = ((interrupt_message_velocimeter_stream*)imuHeader)->metadata; + mFrame.sensorIndex = GET_SENSOR_INDEX(imuHeader->bSensorID); + mFrame.frameId = imuHeader->dwFrameId; + mFrame.angularVelocity.set(metaData.flVx, metaData.flVy, metaData.flVz); + mFrame.temperature = metaData.flTemperature; + mFrame.timestamp = imuHeader->llNanoseconds; + mFrame.arrivalTimeStamp = imuHeader->llArrivalNanoseconds; + mFrame.systemTimestamp = imuHeader->llNanoseconds + timeStampShift; + } + virtual ~VelocimeterFrameCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + static uint32_t latencyEventPerSec = 0; + auto start = systemTime(); + static nsecs_t prevStart = start; + mListener->onVelocimeterFrame(mFrame); + auto finish = systemTime(); + + auto diff = ns2ms(finish - start); + if (diff > 0) + { + /* Latency event occur */ + latencyEventPerSec++; + } + + if (ns2ms(start - prevStart) > LATENCY_MAX_TIMEDIFF_MSEC) + { + /* 1 sec passed between previous latency check */ + if (latencyEventPerSec > LATENCY_MAX_EVENTS_PER_SEC) + { + LOG(mOwner, LOG_WARN, LOG_TAG, __LINE__, "High latency warning (%d msec): %d Velocimeter latency events occurred in the last second, consider onVelocimeterFrame callback optimization to avoid frame drops", diff, latencyEventPerSec); + } + latencyEventPerSec = 0; + prevStart = start; + } + } + } + + public: + TrackingData::VelocimeterFrame mFrame; + + private: + TrackingDevice::Listener* mListener; + TrackingDevice* mOwner; + }; + + class ControllerFrameCompleteTask : public CompleteTask + { + public: + ControllerFrameCompleteTask(TrackingDevice::Listener* l, bulk_message_raw_stream_header* header, int64_t timeStampShift, TrackingDevice* owner) : + mListener(l), mOwner(owner), CompleteTask(CONTROLLER_EVENT_FRAME_COMPLETE_TASK, owner) + { + bulk_message_controller_stream_metadata metaData = ((bulk_message_controller_stream*)header)->metadata; + mFrame.sensorIndex = GET_SENSOR_INDEX(header->bSensorID); + mFrame.frameId = header->dwFrameId; + mFrame.timestamp = header->llNanoseconds; + mFrame.arrivalTimeStamp = header->llArrivalNanoseconds; + mFrame.systemTimestamp = header->llNanoseconds + timeStampShift; + mFrame.eventId = metaData.bEventID; + mFrame.instanceId = metaData.bInstanceId; + perc::copy(mFrame.sensorData, metaData.bSensorData, sizeof(mFrame.sensorData)); + } + virtual ~ControllerFrameCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + static uint32_t latencyEventPerSec = 0; + auto start = systemTime(); + static nsecs_t prevStart = start; + mListener->onControllerFrame(mFrame); + auto finish = systemTime(); + + auto diff = ns2ms(finish - start); + if (diff > 0) + { + /* Latency event occur */ + latencyEventPerSec++; + } + + if (ns2ms(start - prevStart) > LATENCY_MAX_TIMEDIFF_MSEC) + { + /* 1 sec passed between previous latency check */ + if (latencyEventPerSec > LATENCY_MAX_EVENTS_PER_SEC) + { + LOG(mOwner, LOG_WARN, LOG_TAG, __LINE__, "High latency warning (%d msec): %d Controller latency events occurred in the last second, consider onControllerFrame callback optimization to avoid frame drops", diff, latencyEventPerSec); + } + latencyEventPerSec = 0; + prevStart = start; + } + } + } + private: + TrackingDevice::Listener* mListener; + TrackingData::ControllerFrame mFrame; + TrackingDevice* mOwner; + }; + + + class RssiFrameCompleteTask : public CompleteTask + { + public: + RssiFrameCompleteTask(TrackingDevice::Listener* l, bulk_message_raw_stream_header* header, int64_t timeStampShift, TrackingDevice* owner) : mListener(l), mOwner(owner), CompleteTask(RSSI_COMPLETE_TASK, owner) + { + bulk_message_rssi_stream_metadata metaData = ((bulk_message_rssi_stream*)header)->metadata; + mFrame.sensorIndex = GET_SENSOR_INDEX(header->bSensorID); + mFrame.frameId = header->dwFrameId; + mFrame.timestamp = header->llNanoseconds; + mFrame.arrivalTimeStamp = header->llArrivalNanoseconds; + mFrame.systemTimestamp = header->llNanoseconds + timeStampShift; + mFrame.signalStrength = metaData.flSignalStrength; + } + virtual ~RssiFrameCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + static uint32_t latencyEventPerSec = 0; + auto start = systemTime(); + static nsecs_t prevStart = start; + mListener->onRssiFrame(mFrame); + auto finish = systemTime(); + + auto diff = ns2ms(finish - start); + if (diff > 0) + { + /* Latency event occur */ + latencyEventPerSec++; + } + + if (ns2ms(start - prevStart) > LATENCY_MAX_TIMEDIFF_MSEC) + { + /* 1 sec passed between previous latency check */ + if (latencyEventPerSec > LATENCY_MAX_EVENTS_PER_SEC) + { + LOG(mOwner, LOG_WARN, LOG_TAG, __LINE__, "High latency warning (%d msec): %d Rssi latency events occurred in the last second, consider onRssiFrame callback optimization to avoid frame drops", diff, latencyEventPerSec); + } + latencyEventPerSec = 0; + prevStart = start; + } + } + } + private: + TrackingDevice::Listener* mListener; + TrackingData::RssiFrame mFrame; + TrackingDevice* mOwner; + }; + + class ControllerDiscoveryEventFrameCompleteTask : public CompleteTask + { + public: + ControllerDiscoveryEventFrameCompleteTask(TrackingDevice::Listener* l, interrupt_message_controller_device_discovery* controllerDeviceDiscovery, TrackingDevice* owner) : + mListener(l), CompleteTask(CONTROLLER_DISCOVERY_EVENT_FRAME_COMPLETE_TASK, owner) + { + perc::copy(mFrame.macAddress, controllerDeviceDiscovery->bMacAddress, sizeof(controllerDeviceDiscovery->bMacAddress)); + mFrame.addressType = (AddressType)controllerDeviceDiscovery->bAddressType; + mFrame.manufacturerId = controllerDeviceDiscovery->info.wManufacturerId; + mFrame.vendorData = controllerDeviceDiscovery->info.bVendorData; + mFrame.protocol.set(controllerDeviceDiscovery->info.bProtocolVersion); + mFrame.app.set(controllerDeviceDiscovery->info.bAppVersionMajor, controllerDeviceDiscovery->info.bAppVersionMinor, controllerDeviceDiscovery->info.bAppVersionPatch); + mFrame.bootLoader.set(controllerDeviceDiscovery->info.bBootloaderVersionMajor, controllerDeviceDiscovery->info.bBootloaderVersionMinor, controllerDeviceDiscovery->info.bBootloaderVersionPatch); + mFrame.softDevice.set(controllerDeviceDiscovery->info.bSoftdeviceVersion); + } + virtual ~ControllerDiscoveryEventFrameCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + mListener->onControllerDiscoveryEventFrame(mFrame); + } + } + private: + TrackingDevice::Listener* mListener; + TrackingData::ControllerDiscoveryEventFrame mFrame; + }; + + class ControllerConnectEventFrameCompleteTask : public CompleteTask + { + public: + ControllerConnectEventFrameCompleteTask(TrackingDevice::Listener* l, interrupt_message_controller_connected* controllerConnected, TrackingDevice* owner) : + mListener(l), CompleteTask(CONTROLLER_CONNECT_EVENT_FRAME_COMPLETE_TASK, owner) + { + mFrame.controllerId = controllerConnected->bControllerID; + mFrame.manufacturerId = controllerConnected->info.wManufacturerId; + mFrame.protocol.set(controllerConnected->info.bProtocolVersion); + mFrame.app.set(controllerConnected->info.bAppVersionMajor, controllerConnected->info.bAppVersionMinor, controllerConnected->info.bAppVersionPatch); + mFrame.bootLoader.set(controllerConnected->info.bBootloaderVersionMajor, controllerConnected->info.bBootloaderVersionMinor, controllerConnected->info.bBootloaderVersionPatch); + mFrame.softDevice.set(controllerConnected->info.bSoftdeviceVersion); + mFrame.status = fwToHostStatus((MESSAGE_STATUS)controllerConnected->wStatus); + } + virtual ~ControllerConnectEventFrameCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + mListener->onControllerConnectedEventFrame(mFrame); + } + } + private: + TrackingDevice::Listener* mListener; + TrackingData::ControllerConnectedEventFrame mFrame; + }; + + class ControllerDisconnectEventFrameCompleteTask : public CompleteTask + { + public: + ControllerDisconnectEventFrameCompleteTask(TrackingDevice::Listener* l, interrupt_message_controller_disconnected* controllerDisconnected, TrackingDevice* owner) : + mListener(l), CompleteTask(CONTROLLER_DISCONNECT_EVENT_FRAME_COMPLETE_TASK, owner) + { + mFrame.controllerId = controllerDisconnected->bControllerID; + } + virtual ~ControllerDisconnectEventFrameCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + mListener->onControllerDisconnectedEventFrame(mFrame); + } + } + private: + TrackingDevice::Listener* mListener; + TrackingData::ControllerDisconnectedEventFrame mFrame; + }; + + class ControllerCalibrationEventFrameCompleteTask : public CompleteTask + { + public: + ControllerCalibrationEventFrameCompleteTask(TrackingDevice::Listener* l, uint8_t controllerID, Status status, TrackingDevice* owner) : + mListener(l), CompleteTask(CONTROLLER_CALIBRATION_EVENT_FRAME_COMPLETE_TASK, owner) + { + mFrame.controllerId = controllerID; + mFrame.status = status; + } + virtual ~ControllerCalibrationEventFrameCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + mListener->onControllerCalibrationEventFrame(mFrame); + } + } + private: + TrackingDevice::Listener* mListener; + TrackingData::ControllerCalibrationEventFrame mFrame; + }; + + class LocalizationDataEventFrameCompleteTask : public CompleteTask + { + public: + /* Get localization data stream */ + LocalizationDataEventFrameCompleteTask(TrackingDevice::Listener* l, std::shared_ptr& frame, FrameBuffersOwner* frameBufferOwner, TrackingDevice* owner) : + mListener(l), mFrameBufferOwner(frameBufferOwner), mFrame(frame), CompleteTask(LOCALIZATION_DATA_STREAM_EVENT_FRAME_COMPLETE_TASK, owner, true) + { + + interrupt_message_get_localization_data_stream* localizationData = (interrupt_message_get_localization_data_stream*)mFrame.get(); + mLocalizationFrame.status = fwToHostStatus((MESSAGE_STATUS)localizationData->wStatus); + if ((MESSAGE_STATUS)localizationData->wStatus == MESSAGE_STATUS::MORE_DATA_AVAILABLE) + { + mLocalizationFrame.moreData = true; + } + + mLocalizationFrame.buffer = localizationData->bLocalizationData; + mLocalizationFrame.chunkIndex = localizationData->wIndex; + mLocalizationFrame.length = localizationData->header.dwLength - offsetof(interrupt_message_get_localization_data_stream, bLocalizationData); + } + + /* Set localization data stream */ + LocalizationDataEventFrameCompleteTask(TrackingDevice::Listener* l, interrupt_message_set_localization_data_stream* localizationData, TrackingDevice* owner) : + mListener(l), mFrame(NULL), mFrameBufferOwner(NULL), CompleteTask(LOCALIZATION_DATA_STREAM_EVENT_FRAME_COMPLETE_TASK, owner, true) + { + mLocalizationFrame.status = fwToHostStatus((MESSAGE_STATUS)localizationData->wStatus); + mLocalizationFrame.buffer = NULL; + mLocalizationFrame.length = 0; + mLocalizationFrame.chunkIndex = 0; + mLocalizationFrame.moreData = false; + } + + virtual ~LocalizationDataEventFrameCompleteTask() + { + if (mLocalizationFrame.buffer != NULL) + { + mFrameBufferOwner->putBufferBack(0, mFrame); + } + } + + virtual void complete() override + { + if (mListener) + { + mListener->onLocalizationDataEventFrame(mLocalizationFrame); + } + } + private: + TrackingDevice::Listener* mListener; + std::shared_ptr mFrame; + FrameBuffersOwner* mFrameBufferOwner; + TrackingData::LocalizationDataFrame mLocalizationFrame; + }; + + class FWUpdateCompleteTask : public CompleteTask + { + public: + + FWUpdateCompleteTask(TrackingDevice::Listener* l, interrupt_message_fw_update_stream* FWImage, TrackingDevice* owner) : + mListener(l), CompleteTask(DEV_FIRMWARE_UPDATE, owner, true) + { + mFrame.status = fwToHostStatus((MESSAGE_STATUS)FWImage->wStatus); + mFrame.progress = FWImage->bProgress; + perc::copy(mFrame.macAddress, FWImage->bMacAddress, sizeof(FWImage->bMacAddress)); + } + + virtual ~FWUpdateCompleteTask() = default; + + virtual void complete() override + { + if (mListener) + { + mListener->onFWUpdateEvent(mFrame); + } + } + private: + TrackingDevice::Listener* mListener; + TrackingData::ControllerFWEventFrame mFrame; + }; + + class StatusEventFrameCompleteTask : public CompleteTask + { + public: + + StatusEventFrameCompleteTask(TrackingDevice::Listener* l, perc::Status status, TrackingDevice* owner) : mListener(l), CompleteTask(DEV_STATUS, owner, true) + { + mFrame.status = status; + } + + virtual ~StatusEventFrameCompleteTask() = default; + + virtual void complete() override + { + if (mListener) + { + mListener->onStatusEvent(mFrame); + } + } + private: + TrackingDevice::Listener* mListener; + TrackingData::StatusEventFrame mFrame; + }; + + class CompleteQueueHandler + { + public: + virtual void addTask(std::shared_ptr&) = 0; + virtual void removeTasks(void* owner, bool completeTasks) = 0; + }; + + class ControllerLedFrameCompleteTask : public CompleteTask + { + public: + ControllerLedFrameCompleteTask(TrackingDevice::Listener* l, interrupt_message_controller_led_intensity* led, int64_t timeStampShift, TrackingDevice* owner) : mListener(l), mOwner(owner), CompleteTask(CONTROLLER_LED_COMPLETE_TASK, owner) + { + mFrame.controllerId = GET_SENSOR_INDEX(led->rawStreamHeader.bSensorID); + mFrame.timestamp = led->rawStreamHeader.llNanoseconds; + mFrame.arrivalTimeStamp = led->rawStreamHeader.llArrivalNanoseconds; + mFrame.systemTimestamp = led->rawStreamHeader.llNanoseconds + timeStampShift; + mFrame.intensity = led->intensity; + mFrame.ledId = led->ledId; + } + virtual ~ControllerLedFrameCompleteTask() {} + virtual void complete() override + { + if (mListener) + { + static uint32_t latencyEventPerSec = 0; + auto start = systemTime(); + static nsecs_t prevStart = start; + mListener->onControllerLedEvent(mFrame); + auto finish = systemTime(); + + auto diff = ns2ms(finish - start); + if (diff > 0) + { + /* Latency event occur */ + latencyEventPerSec++; + } + + if (ns2ms(start - prevStart) > LATENCY_MAX_TIMEDIFF_MSEC) + { + /* 1 sec passed between previous latency check */ + if (latencyEventPerSec > LATENCY_MAX_EVENTS_PER_SEC) + { + LOG(mOwner, LOG_WARN, LOG_TAG, __LINE__, "High latency warning (%d msec): %d led latency events occurred in the last second, consider onled callback optimization to avoid frame drops", diff, latencyEventPerSec); + } + latencyEventPerSec = 0; + prevStart = start; + } + } + } + + public: + TrackingData::ControllerLedEventFrame mFrame; + + private: + TrackingDevice::Listener* mListener; + TrackingDevice* mOwner; + }; + + +} diff --git a/third-party/libtm/libtm/src/Device.cpp b/third-party/libtm/libtm/src/Device.cpp new file mode 100644 index 0000000000..099fc23eab --- /dev/null +++ b/third-party/libtm/libtm/src/Device.cpp @@ -0,0 +1,4221 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#define LOG_TAG "Device" +#define LOG_NDEBUG 0 /* Enable LOGV */ + +#include +#include +#include +#include +#include +#include +#include +#include "Device.h" +#include "Common.h" +#include "CompleteTask.h" +#include "Version.h" +#include "Utils.h" +#include "UsbPlugListener.h" +#include "TrackingData.h" +#include "CentralAppFw.h" +#include "CentralBlFw.h" + +#define CHUNK_SIZE 512 +#define BUFFER_SIZE 1024 +#define SYNC_TIME_FREQUENCY_NS 500000000 // in nano seconds, every 500 msec +#define SYNC_TIME_DIFF_MSEC 1 +#define MAX_6DOF_TIMEDIFF_MSEC 5 /* 6dof must arrive every 5 msec */ +#define WAIT_FOR_STOP_STATUS_MSEC 200 /* Time to wait before closing interrupt/event threads - give opportunity for the FW to send the DEVICE_STOPPED status */ +#define MAX_FRAME_HEIGHT 1080 +#define MAX_FRAME_WIDTH 1920 +#define MAX_BIG_DATA_MESSAGE_LENGTH 10250 //(10K + large message header size) +#define FRAMES_PER_STREAM 100 +#define PERMANENT_LOCK_CONFIGURATION_TOKEN 0xDEAD10CC +#define BULK_ENDPOINT_MESSAGES 2 /* Endpoint address for sending/receiving all bulk messages */ +#define BULK_ENDPOINT_FRAMES 1 /* Endpoint address for sending/receiving all frames (Fisheye, Gyro, Accelerometer) */ +#define TO_HOST LIBUSB_ENDPOINT_IN /* to host */ +#define TO_DEVICE LIBUSB_ENDPOINT_OUT /* to device */ + +namespace perc { + // ---------------------------------------------------------------------------- + Device::Device(libusb_device* dev, Dispatcher* dispatcher, EventHandler* owner, CompleteQueueHandler* taskHandler) : + mListener(nullptr), mLibusbDevice(dev), mDevice(nullptr), mDispatcher(dispatcher), + mStreamEndpointThreadStop(false), mInterruptEndpointThreadStop(false), mStreamEndpointThreadActive(false), mInterruptEndpointThreadActive(false), mOwner(owner), mTaskHandler(taskHandler), mCleared(false), + mEndpointBulkMessages(BULK_ENDPOINT_MESSAGES), mStreamEndpoint(BULK_ENDPOINT_FRAMES), mEepromChunkSize(CHUNK_SIZE), mSyncTimeout(SYNC_TIME_FREQUENCY_NS), + mPlaybackIsOn(false), mSyncTimeEnabled(false), mUsbState(DEVICE_USB_STATE_INIT), mDeviceStatus(Status::SUCCESS), mHasBluetooth(true), mFWInterfaceVersion{0} + { + memset(&mDeviceInfo, 0, sizeof(mDeviceInfo)); + supported_raw_stream_libtm_message streams[MAX_SUPPORTED_STREAMS]; + uint16_t actual = 0; + Status status = Status::SUCCESS; + + DEVICELOGV("Creating Device"); + + ASSERT(mDispatcher); + mFsm.init(FSM(main), this, mDispatcher, LOG_TAG); + + auto rc = libusb_open(mLibusbDevice, &mDevice); + if (rc != 0) + { + if (rc == LIBUSB_ERROR_NOT_SUPPORTED) + { + DEVICELOGE("Error while opening device (%s), Please install driver for Intel(R) RealSense(TM) T250 device", libusb_error_name(rc)); + } + else + { + DEVICELOGE("Error while opening device. LIBUSB_ERROR_CODE: %d (%s)", rc, libusb_error_name(rc)); + } + + return; + } + + /* Device USB marked as open - to close it on destructor */ + mUsbState = DEVICE_USB_STATE_OPENED; + + mEndpointInterrupt = FindInterruptEndpoint(); + if (mEndpointInterrupt == -1) + { + DEVICELOGE("Interrupt endpoint not found! "); + return; + } + + rc = libusb_claim_interface(mDevice, INTERFACE_INDEX); + if (rc != 0) + { + DEVICELOGE("Error: Failed to claim USB interface. LIBUSB_ERROR_CODE: 0x%X (%s)", rc, libusb_error_name(rc)); + return; + } + + /* Device USB marked as claimed - to release it on destructor */ + mUsbState = DEVICE_USB_STATE_CLAIMED; + + status = GetUsbConnectionDescriptor(); + if (status != Status::SUCCESS) + { + DEVICELOGE("Error: Failed to get USB connection descriptor (0x%X)", status); + return; + } + + // Pending for FW + //status = DeviceFlush(); + //if (status != Status::SUCCESS) + //{ + // DEVICELOGE("Error: Failed to flush device (0x%X)", status); + // return; + //} + + status = GetInterfaceVersionInternal(); + if (status != Status::SUCCESS) + { + DEVICELOGE("Error: Failed to get interface (0x%X)", status); + return; + } + + /* Device USB marked as ready - There are no fatal errors on device creation, from this point every error will be forwarded to the device state machine (to support user reset) */ + mUsbState = DEVICE_USB_STATE_READY; + + if ((mFWInterfaceVersion.dwMajor != LIBTM_API_VERSION_MAJOR) || (mFWInterfaceVersion.dwMinor < LIBTM_API_VERSION_MINOR)) + { + DEVICELOGE("Error: Interface version mismatch: Host %d.%d, FW %d.%d", LIBTM_API_VERSION_MAJOR, LIBTM_API_VERSION_MINOR, mFWInterfaceVersion.dwMajor, mFWInterfaceVersion.dwMinor); + mDispatcher->postMessage(&mFsm, Message(ON_ERROR)); + mDeviceStatus = Status::INIT_FAILED; + } + + status = GetDeviceInfoInternal(); + if (status != Status::SUCCESS) + { + DEVICELOGE("Error: Failed to get device info (0x%X)", status); + mDispatcher->postMessage(&mFsm, Message(ON_ERROR)); + mDeviceStatus = Status::INIT_FAILED; + return; + } + + if (mDeviceInfo.bCentralAppVersionMajor == 0 && + mDeviceInfo.bCentralAppVersionMinor == 0 && + mDeviceInfo.bCentralAppVersionPatch == 0 && + mDeviceInfo.dwCentralAppVersionBuild == 0) + { + mHasBluetooth = false; + } + else + { + mHasBluetooth = true; + } + + // Pending for FW + //status = SetDeviceStreamConfig(MAX_BIG_DATA_MESSAGE_LENGTH); + //if (status != Status::SUCCESS) + //{ + // DEVICELOGE("Error: Set device stream config (0x%X)", status); + // mDispatcher->postMessage(&mFsm, Message(ON_ERROR)); + // mDeviceStatus = Status::INIT_FAILED; + // return; + //} + + mFrameTempBufferSize = (MAX_FRAME_HEIGHT * MAX_FRAME_WIDTH) + sizeof(supported_raw_stream_libtm_message); + AllocateBuffers(); + + status = GetSupportedRawStreamsInternal(streams, MAX_SUPPORTED_STREAMS, actual); + if (status != Status::SUCCESS) + { + DEVICELOGE("Error: Failed to get supported raw stream (0x%X)", status); + mDispatcher->postMessage(&mFsm, Message(ON_ERROR)); + mDeviceStatus = Status::INIT_FAILED; + return; + } + + for (int i = 0; i < actual; i++) + { + switch (GET_SENSOR_TYPE(streams[i].bSensorID)) + { + case SensorType::Fisheye: + { + TrackingData::VideoProfile p; + p.set(true, false, streams[i].wFramesPerSecond, GET_SENSOR_INDEX(streams[i].bSensorID)); + p.profile.set(streams[i].wWidth, streams[i].wHeight, streams[i].wStride, static_cast(streams[i].bPixelFormat)); + mVideoProfiles.push_back(p); + break; + } + + case SensorType::Gyro: + { + TrackingData::GyroProfile p; + p.set(true, false, streams[i].wFramesPerSecond, GET_SENSOR_INDEX(streams[i].bSensorID)); + mGyroProfiles.push_back(p); + break; + } + + case SensorType::Accelerometer: + { + TrackingData::AccelerometerProfile p; + p.set(true, false, streams[i].wFramesPerSecond, GET_SENSOR_INDEX(streams[i].bSensorID)); + mAccelerometerProfiles.push_back(p); + break; + } + + case SensorType::Velocimeter: + { + TrackingData::VelocimeterProfile p; + p.set(true, false, streams[i].wFramesPerSecond, GET_SENSOR_INDEX(streams[i].bSensorID)); + mVelocimeterProfiles.push_back(p); + break; + } + + case SensorType::Rssi: + { + break; + } + + default: + DEVICELOGE("Unknown SensorType support (0x%X)", GET_SENSOR_TYPE(streams[i].bSensorID)); + break; + } // end of switch + } + + if (mHasBluetooth) + { + status = CentralFWUpdate(); + if (status != Status::SUCCESS) + { + DEVICELOGE("Central firmware update failed with error (0x%X)", status); + mDispatcher->postMessage(&mFsm, Message(ON_ERROR)); + mDeviceStatus = Status::INIT_FAILED; + return; + } + } + SyncTime(); + + // schedule the listener itself for every 500 msec + mDispatcher->scheduleTimer(this, mSyncTimeout, Message(0)); + + return; + } + + Status Device::SyncTime() + { + bulk_message_request_get_time req = {0}; + bulk_message_response_get_time res = {0}; + nsecs_t start; + nsecs_t finish; + uint32_t syncTry = 1; + + req.header.wMessageID = DEV_GET_TIME; + req.header.dwLength = sizeof(req); + res.header.wStatus = 0xFF; + + while (true) + { + start = systemTime(); + + Bulk_Message msg((uint8_t*)&req, sizeof(req), (uint8_t*)&res, sizeof(res), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mFsm.fireEvent(msg); + + if (msg.Result != 0) + break; + + finish = systemTime(); + + auto diff = ns2ms(finish - start); + if ((diff) <= SYNC_TIME_DIFF_MSEC) + { + mTM2CorrelatedTimeStampShift = start + ((finish-start)/2) - res.llNanoseconds; + break; + } + syncTry++; + } + + if (res.header.wStatus == 0) + { + DEVICELOGV("TM2 and Host clocks were synced in %d nanosec after %d tries", ns2ms(finish - start), syncTry); + } + else + { + DEVICELOGE("Error syncing TM2 and Host clocks"); + } + + return Status::SUCCESS; + } + + const char* temperatureSensor(uint8_t index) + { + switch (index) + { + case 0x00: return "VPU"; + case 0x01: return "IMU"; + case 0x02: return "BLE"; + } + return "Unknown"; + } + + const char* hwVersion(uint8_t hwVersion) + { + switch (hwVersion) + { + case 0x00: return "ES0"; + case 0x01: return "ES1"; + case 0x02: return "ES2"; + case 0x03: return "ES3"; + case 0x04: return "ES4"; + } + return "Unknown"; + } + + const char* fwLogVerbosityLevel(LogVerbosityLevel level) + { + switch (level) + { + case Error: return "[E]"; + case Info: return "[I]"; + case Warning: return "[W]"; + case Debug: return "[D]"; + case Verbose: return "[V]"; + case Trace: return "[T]"; + } + return "[Unknown]"; + } + + enum module_id { + MODULE_UNDEFINED = 0x0000, + MODULE_MAIN = 0x0001, + MODULE_USB = 0x0002, + MODULE_IMU_DRV = 0x0003, + MODULE_VIDEO = 0x0004, + MODULE_MEMPOOL = 0x0005, + MODULE_MESSAGE_IO = 0X0006, + MODULE_LOGGER = 0x0007, + MODULE_LOG_TESTS = 0x0008, + MODULE_CENTRAL_MGR = 0x000A, + MODULE_HMD_TRACKING = 0x000B, + MODULE_CONTROLLERS = 0x000C, + MODULE_PACKET_IO = 0x000D, + MODULE_DFU = 0x000E, + MODULE_CONFIG_TABLES = 0x000F, + MODULE_LAST_VALUE = 0xFFFF + }; + + const char* fwModuleID(module_id module) + { + switch (module) + { + case MODULE_UNDEFINED: return "UNDEFINED"; + case MODULE_MAIN: return "MAIN"; + case MODULE_USB: return "USB"; + case MODULE_IMU_DRV: return "IMU_DRV"; + case MODULE_VIDEO: return "VIDEO"; + case MODULE_MEMPOOL: return "MEMPOOL"; + case MODULE_MESSAGE_IO: return "MESSAGE_IO"; + case MODULE_LOGGER: return "LOGGER"; + case MODULE_LOG_TESTS: return "LOG_TESTS"; + case MODULE_CENTRAL_MGR: return "CENTRAL_MGR"; + case MODULE_HMD_TRACKING: return "HMD_TRACKING"; + case MODULE_CONTROLLERS: return "CONTROLLERS"; + case MODULE_PACKET_IO: return "PACKET_IO"; + case MODULE_DFU: return "DFU"; + case MODULE_CONFIG_TABLES: return "CONFIG_TABLE"; + } + return "UNKNOWN"; + } + + Status Device::GetFWLog(TrackingData::Log& log) + { + bulk_message_request_get_and_clear_event_log request = {0}; + bulk_message_response_get_and_clear_event_log response = {0}; + uint8_t outputMode; + uint8_t verbosity; + uint8_t rolloverMode; + + request.header.wMessageID = DEV_GET_AND_CLEAR_EVENT_LOG; + request.header.dwLength = sizeof(request); + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + return Status::ERROR_USB_TRANSFER; + } + + if (response.header.dwLength > sizeof(bulk_message_response_get_and_clear_event_log)) + { + DEVICELOGE("Error: Log size (%u) is too big for bulk_message_response_get_and_clear_event_log (%u) - Host->FW API issue", response.header.dwLength, sizeof(bulk_message_response_get_and_clear_event_log)); + return Status::BUFFER_TOO_SMALL; + } + + auto logSize = (response.header.dwLength - sizeof(bulk_message_response_header)); + log.entries = (uint32_t)(logSize / sizeof(device_event_log)); + log.maxEntries = MAX_FW_LOG_BUFFER_ENTRIES; + DEVICELOGV("log size = %d, entries = %d, entry size = %d", logSize, log.entries, sizeof(device_event_log)); + + if (logSize > (MAX_LOG_BUFFER_ENTRIES * MAX_LOG_BUFFER_ENTRY_SIZE)) + { + DEVICELOGE("Error: Log size (%u) is too big for TrackingData::Log (%u)", logSize, (MAX_LOG_BUFFER_ENTRIES * MAX_LOG_BUFFER_ENTRY_SIZE)); + return Status::BUFFER_TOO_SMALL; + } + + __perc_Log_Get_Configuration(LogSourceFW, &outputMode, &verbosity, &rolloverMode); + + if (log.entries == log.maxEntries) + { + DEVICELOGW("Got full FW log buffer (%d entries) - increase GetFWLog call rate to retrieve more FW logs", log.entries); + } + + if (outputMode == LogOutputModeScreen) + { + if (log.entries > 0) + { + DEVICELOGD("Received %d FW log entries", log.entries); + DEVICELOGD("FW: [Verbosity] [Thread] [Function] [Module](Line): "); + } + } + + HostLocalTime localTime = getLocalTime(); + TrackingData::Log::LocalTime localTimeStamp; + + localTimeStamp.year = localTime.year; + localTimeStamp.month = localTime.month; + localTimeStamp.dayOfWeek = localTime.dayOfWeek; + localTimeStamp.day = localTime.day; + localTimeStamp.hour = localTime.hour; + localTimeStamp.minute = localTime.minute; + localTimeStamp.second = localTime.second; + localTimeStamp.milliseconds = localTime.milliseconds; + + for (uint32_t i = 0; i < log.entries; i++) + { + /* remove new line character */ + std::remove(response.bEventLog[i].payload, response.bEventLog[i].payload + 44, '\n'); + + /* Build 8 bytes timestamp from 7 bytes FW timestamp */ + perc::copy(&log.entry[i].timeStamp, response.bEventLog[i].timestamp, sizeof(response.bEventLog[i].timestamp)); + log.entry[i].localTimeStamp = localTimeStamp; + log.entry[i].functionSymbol = response.bEventLog[i].functionSymbol; + log.entry[i].lineNumber = response.bEventLog[i].lineNumber; + perc::copy(&log.entry[i].moduleID, fwModuleID((module_id)response.bEventLog[i].moduleID), MAX_LOG_BUFFER_MODULE_SIZE); + log.entry[i].threadID = response.bEventLog[i].threadID; + log.entry[i].verbosity = (LogVerbosityLevel)response.bEventLog[i].source; + log.entry[i].deviceID = (uint64_t)this; + log.entry[i].payloadSize = response.bEventLog[i].payloadSize; + perc::copy(&log.entry[i].payload, response.bEventLog[i].payload, response.bEventLog[i].payloadSize); + + if (outputMode == LogOutputModeScreen) + { + DEVICELOGD("FW: %012llu %s [%02d] [0x%X] [%s](%d): %s", + log.entry[i].timeStamp, + fwLogVerbosityLevel((LogVerbosityLevel)response.bEventLog[i].source), + response.bEventLog[i].threadID, + response.bEventLog[i].functionSymbol, + fwModuleID((module_id)response.bEventLog[i].moduleID), + response.bEventLog[i].lineNumber, + response.bEventLog[i].payload + ); + } + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + void Device::onTimeout(uintptr_t timerId, const Message & msg) + { + if (mSyncTimeEnabled) + { + SyncTime(); + } + + // Schedule the listener itself for every 500 msec + mDispatcher->scheduleTimer(this, mSyncTimeout, Message(0)); + } + + Status Device::Set6DoFControl(TrackingData::SixDofProfile& profile) + { + bulk_message_request_6dof_control req = {0}; + bulk_message_response_6dof_control res = {0}; + + req.header.wMessageID = SLAM_6DOF_CONTROL; + req.header.dwLength = sizeof(req); + req.bEnable = profile.enabled; + req.bMode = profile.mode; + + DEVICELOGD("Set 6Dof Control %s, Mode 0x%X", (req.bEnable) ? "Enabled" : "Disabled", req.bMode); + Bulk_Message msg((uint8_t*)&req, sizeof(req), (uint8_t*)&res, sizeof(res), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + mDispatcher->sendMessage(&mFsm, msg); + + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("Error Transferring SLAM_6DOF_CONTROL"); + return Status::ERROR_USB_TRANSFER; + } + + return Status::SUCCESS; + } + + Status Device::SetController6DoFControl(bool enabled, uint8_t numOfControllers) + { + if (!mHasBluetooth && enabled) + { + DEVICELOGE("cannot SetController6DoFControl with controllers enabled, there is no bluetooth in the device"); + return Status::NO_BLUETOOTH; + } + bulk_message_request_controller_pose_control req; + bulk_message_response_controller_pose_control res = {0}; + + req.header.wMessageID = CONTROLLER_POSE_CONTROL; + req.header.dwLength = sizeof(req); + req.bEnable = enabled; + req.bMode = 0; + req.bNumControllers = numOfControllers; + + DEVICELOGD("Set Controller 6Dof Control %s for %d controllers", (req.bEnable) ? "Enabled" : "Disabled", req.bNumControllers); + Bulk_Message msg((uint8_t*)&req, sizeof(req), (uint8_t*)&res, sizeof(res), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + mDispatcher->sendMessage(&mFsm, msg); + + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("Error Transferring CONTROLLER_POSE_CONTROL"); + return Status::ERROR_USB_TRANSFER; + } + return Status::SUCCESS; + } + + ProfileType Device::getProfileType(uint8_t sensorID) + { + ProfileType profileType = ProfileTypeMax; + + switch (GET_SENSOR_TYPE(sensorID)) + { + case SensorType::Fisheye: + { + profileType = ProfileType::HMD; + } + break; + + case SensorType::Gyro: + { + if (GET_SENSOR_INDEX(sensorID) == GyroProfile0) + { + profileType = ProfileType::HMD; + } + + if (GET_SENSOR_INDEX(sensorID) == GyroProfile1) + { + profileType = ProfileType::Controller1; + } + + if (GET_SENSOR_INDEX(sensorID) == GyroProfile2) + { + profileType = ProfileType::Controller2; + } + } + break; + + case SensorType::Accelerometer: + { + if (GET_SENSOR_INDEX(sensorID) == AccelerometerProfile0) + { + profileType = ProfileType::HMD; + } + + if (GET_SENSOR_INDEX(sensorID) == AccelerometerProfile1) + { + profileType = ProfileType::Controller1; + } + + if (GET_SENSOR_INDEX(sensorID) == AccelerometerProfile2) + { + profileType = ProfileType::Controller2; + } + } + break; + + default: + break; + } + + return profileType; + } + + Status Device::Reset(void) + { + control_message_request_reset request; + request.header.bmRequestType = (LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE); + request.header.bRequest = CONTROL_USB_RESET; + + Control_Message msg((uint8_t*)&request, sizeof(request)); + + DEVICELOGD("Reseting device"); + + /* Calling onControlMessage */ + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("Error Transferring CONTROL_USB_RESET"); + return Status::ERROR_USB_TRANSFER; + } + + return Status::SUCCESS; + } + + void Device::onExit() + { + std::lock_guard lock(mDeletionMutex); + if (mCleared) + return; + + mFsm.fireEvent(Message(ON_STOP)); + + if (mUsbState >= DEVICE_USB_STATE_CLAIMED) + { + libusb_release_interface(mDevice, INTERFACE_INDEX); + } + + if (mUsbState >= DEVICE_USB_STATE_OPENED) + { + libusb_close(mDevice); + } + + mDispatcher->removeHandler(&mFsm); + mFsm.done(); + mCleared = true; + } + + // ---------------------------------------------------------------------------- + Device::~Device() + { + onExit(); + } + + // ---------------------------------------------------------------------------- + Status Device::GetSupportedProfile(TrackingData::Profile& profile) + { + Status st = Status::SUCCESS; + TrackingData::DeviceInfo deviceInfo; + st = this->GetDeviceInfo(deviceInfo); + if (st != Status::SUCCESS) + { + DEVICELOGE("Error: Failed getting device info (0x%X)", st); + return st; + } + + std::vector videoProfiles; + std::vector gyroProfiles; + std::vector velocimeterProfiles; + std::vector accelerometerProfiles; + TrackingData::SixDofProfile sixDofProfile; + + videoProfiles.resize(deviceInfo.numVideoProfiles); + gyroProfiles.resize(deviceInfo.numGyroProfile); + velocimeterProfiles.resize(deviceInfo.numVelocimeterProfile); + accelerometerProfiles.resize(deviceInfo.numAccelerometerProfiles); + + st = this->GetSupportedRawStreams(videoProfiles.data(), gyroProfiles.data(), accelerometerProfiles.data(), velocimeterProfiles.data()); + if (st != Status::SUCCESS) + { + DEVICELOGE("Error: Failed getting supported stream (0x%X)", st); + return st; + } + + for (uint8_t i = 0; i < GyroProfileMax; i++) + { + for (auto it = gyroProfiles.begin(); it != gyroProfiles.end(); ++it) + { + if ((*it).sensorIndex == i) + { + /* Adding the first Gyro profile on every Gyro index or the requested gyro profile according to the FPS */ + if ((profile.gyro[i].fps == 0) || ((*it).fps == profile.gyro[i].fps)) + { + profile.set((*it), false, false); + gyroProfiles.erase(it); + break; + } + } + } + } + + for (uint8_t i = 0; i < AccelerometerProfileMax; i++) + { + for (auto it = accelerometerProfiles.begin(); it != accelerometerProfiles.end(); ++it) + { + if ((*it).sensorIndex == i) + { + /* Adding the first Accelerometer profile on every Accelerometer index or the requested Accelerometer profile according to the FPS */ + if ((profile.accelerometer[i].fps == 0) || ((*it).fps == profile.accelerometer[i].fps)) + { + profile.set((*it), false, false); + accelerometerProfiles.erase(it); + break; + } + } + } + } + + for (uint8_t i = 0; i < VelocimeterProfileMax; i++) + { + for (auto it = velocimeterProfiles.begin(); it != velocimeterProfiles.end(); ++it) + { + if ((*it).sensorIndex == i) + { + /* Adding the first Velocimeter profile on every Velocimeter index or the requested Velocimeter profile according to the FPS */ + profile.set((*it), false, false); + velocimeterProfiles.erase(it); + break; + } + } + } + + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + for (auto it = videoProfiles.begin(); it != videoProfiles.end(); ++it) + { + /* Choose Video profile from supported list according to: Height+Width+FPS or default (first) profile */ + if ((*it).sensorIndex == i) + { + if ((profile.video[i].profile.height * profile.video[i].profile.width * profile.video[i].fps) != 0) + { + /* Adding the requested Video profile from this Video index according to the Height, Width, FPS */ + if ((profile.video[i].profile.height == (*it).profile.height) && (profile.video[i].profile.width == (*it).profile.width) && (profile.video[i].fps == (*it).fps)) + { + profile.set((*it), false, false); + videoProfiles.erase(it); + break; + } + } + else + { + /* Adding the default (first) Video profile from this Video index */ + profile.set((*it), false, false); + videoProfiles.erase(it); + break; + } + } + } + } + + for (uint8_t i = 0; i < SixDofProfileMax; i++) + { + sixDofProfile.set(false, (SIXDOF_MODE_ENABLE_MAPPING | SIXDOF_MODE_ENABLE_RELOCALIZATION), SIXDOF_INTERRUPT_RATE::SIXDOF_INTERRUPT_RATE_IMU, (SixDofProfileType)i); + profile.set(sixDofProfile, false); + } + + profile.playbackEnabled = false; + + return st; + } + + // ---------------------------------------------------------------------------- + Status Device::Start(Listener* listener, TrackingData::Profile* profile) + { + mSyncTimeEnabled = true; + + if (profile != NULL) + { + supported_raw_stream_libtm_message profiles[MAX_SUPPORTED_STREAMS] = { 0 }; + Status status = Status::SUCCESS; + uint8_t sixdofControllerNum = 0; + bool sixdofControllerEnabled = false; + int activeProfiles = 0; + + status = SetPlayback(profile->playbackEnabled); + if (status != Status::SUCCESS) + { + DEVICELOGE("Error: Failed setting playback (0x%X)", status); + return status; + } + + /* Video Profiles */ + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + if (profile->video[i].enabled) + { + profiles[activeProfiles].wFramesPerSecond = profile->video[i].fps; + profiles[activeProfiles].bOutputMode = profile->video[i].outputEnabled; + profiles[activeProfiles].wWidth = profile->video[i].profile.width; + profiles[activeProfiles].wHeight = profile->video[i].profile.height; + profiles[activeProfiles].wStride = profile->video[i].profile.stride; + profiles[activeProfiles].bPixelFormat = profile->video[i].profile.pixelFormat; + profiles[activeProfiles].bSensorID = SET_SENSOR_ID(SensorType::Fisheye, i); + activeProfiles++; + } + } + + /* Gyro Profiles */ + for (uint8_t i = 0; i < GyroProfileMax; i++) + { + if (profile->gyro[i].enabled) + { + profiles[activeProfiles].bSensorID = SET_SENSOR_ID(SensorType::Gyro, i); + profiles[activeProfiles].bOutputMode = profile->gyro[i].outputEnabled; + profiles[activeProfiles].wFramesPerSecond = profile->gyro[i].fps; + activeProfiles++; + } + } + + /* Velocimeter Profiles */ + for (uint8_t i = 0; i < VelocimeterProfileMax; i++) + { + if (profile->velocimeter[i].enabled) + { + profiles[activeProfiles].bSensorID = SET_SENSOR_ID(SensorType::Velocimeter, i); + profiles[activeProfiles].bOutputMode = profile->velocimeter[i].outputEnabled; + profiles[activeProfiles].wFramesPerSecond = 0; + activeProfiles++; + } + } + + /* Accelerometer Profiles */ + for (uint8_t i = 0; i < AccelerometerProfileMax; i++) + { + if (profile->accelerometer[i].enabled) + { + profiles[activeProfiles].bSensorID = SET_SENSOR_ID(SensorType::Accelerometer, i); + profiles[activeProfiles].bOutputMode = profile->accelerometer[i].outputEnabled; + profiles[activeProfiles].wFramesPerSecond = profile->accelerometer[i].fps; + activeProfiles++; + } + } + + if (activeProfiles > 0) + { + status = SetEnabledRawStreams(profiles, activeProfiles); + if (status != Status::SUCCESS) + { + DEVICELOGE("Error: Failed setting Supported Raw Streams (0x%X)", status); + return status; + } + } + + /* 6dof HMD profile */ + status = Set6DoFControl(profile->sixDof[SixDofProfile0]); + if (status != Status::SUCCESS) + { + DEVICELOGE("Error: Failed setting 6dof Control (0x%X)", status); + return status; + } + + /* 6dof Controllers profile */ + for (uint8_t i = (SixDofProfile0+1); i < SixDofProfileMax; i++) + { + if (profile->sixDof[i].enabled == true) + { + sixdofControllerNum++; + sixdofControllerEnabled = true; + } + } + + status = SetController6DoFControl(sixdofControllerEnabled, sixdofControllerNum); + if (status != Status::SUCCESS) + { + DEVICELOGE("Error: Failed setting Controller 6dof Control (0x%X)", status); + return status; + } + + + /* Currently FW doesn't support setting interrupt rate */ + //sixdof_interrupt_rate_libtm_message msgIntRate; + //msgIntRate.bInterruptRate = toUnderlying(profile->sixDof[SixDofHMD].interruptRate); + //status = Set6DofInterruptRate(msgIntRate); + //if (status != Status::SUCCESS) + // return status; + + } + + MessageON_START msg(listener); + mDispatcher->sendMessage(&mFsm, msg); + + if (msg.Result != 0) + { + mSyncTimeEnabled = false; + } + + return msg.Result == 0 ? Status::SUCCESS : Status::COMMON_ERROR; + } + + // ---------------------------------------------------------------------------- + Status Device::Stop(void) + { + mSyncTimeEnabled = false; + + Message msg(ON_STOP); + mDispatcher->sendMessage(&mFsm, msg); + + return msg.Result == 0 ? Status::SUCCESS : Status::COMMON_ERROR; + } + + Status Device::GetSupportedRawStreams(TrackingData::VideoProfile* videoProfilesBuffer, TrackingData::GyroProfile* gyroProfilesBuffer, TrackingData::AccelerometerProfile* accelerometerProfilesBuffer, TrackingData::VelocimeterProfile* velocimeterProfilesBuffer) + { + unsigned int count = 0; + for (auto p : mVideoProfiles) + { + videoProfilesBuffer[count++] = p; + } + + count = 0; + for (auto p : mAccelerometerProfiles) + { + accelerometerProfilesBuffer[count++] = p; + } + + count = 0; + for (auto p : mGyroProfiles) + { + gyroProfilesBuffer[count++] = p; + } + + if (velocimeterProfilesBuffer != nullptr) + { + count = 0; + for (auto p : mVelocimeterProfiles) + { + velocimeterProfilesBuffer[count++] = p; + } + } + + return Status::SUCCESS; + } + + void Device::printSupportedRawStreams(supported_raw_stream_libtm_message* pRawStreams, uint32_t rawStreamsCount) + { + DEVICELOGD("---+----------------------------+--------+-------+--------+--------+--------+--------+------"); + DEVICELOGD(" # | Sensor | Frames | Width | Height | Pixel | Output | Stride | Rsvd "); + DEVICELOGD(" | Name | Type | Idx | PerSec | | | Format | Mode | | "); + DEVICELOGD("---+---------------+------+-----+--------+-------+--------+--------+--------+--------+------"); + + for (uint32_t i = 0; i < rawStreamsCount; i++) + { + DEVICELOGD("%02d | %-13s | 0x%02X | %01d | %-6d | %-5d | %-5d | %-3d | 0x%01X | %-3d | %-3d", i, sensorToString(SensorType(GET_SENSOR_TYPE(pRawStreams[i].bSensorID))).c_str(), GET_SENSOR_TYPE(pRawStreams[i].bSensorID), GET_SENSOR_INDEX(pRawStreams[i].bSensorID), + pRawStreams[i].wFramesPerSecond, pRawStreams[i].wWidth, pRawStreams[i].wHeight, pRawStreams[i].bPixelFormat, pRawStreams[i].bOutputMode, pRawStreams[i].wStride, pRawStreams[i].bReserved); + } + DEVICELOGD("---+---------------+------+-----+--------+-------+--------+--------+--------+--------+------"); + DEVICELOGD(""); + } + + Status Device::GetSupportedRawStreamsInternal(supported_raw_stream_libtm_message * message, uint16_t wBufferSize, uint16_t& wNumSupportedStreams) + { + wNumSupportedStreams = 0; + + if (!message) + { + return Status::ERROR_PARAMETER_INVALID; + } + + bulk_message_request_get_supported_raw_streams req; + + req.header.dwLength = sizeof(req); + req.header.wMessageID = DEV_GET_SUPPORTED_RAW_STREAMS; + + uint8_t responseBuffer[BUFFER_SIZE]; + + Bulk_Message msg((uint8_t*)&req, sizeof(req), responseBuffer, BUFFER_SIZE, mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mFsm.fireEvent(msg); + + bulk_message_response_get_supported_raw_streams* res = (bulk_message_response_get_supported_raw_streams*)responseBuffer; + + uint16_t streamsToCopy; + Status st = Status::SUCCESS; + + if (res->header.wStatus != 0) + return fwToHostStatus((MESSAGE_STATUS)res->header.wStatus); + + if (wBufferSize > res->wNumSupportedStreams) + { + streamsToCopy = res->wNumSupportedStreams; + } + else + { + streamsToCopy = wBufferSize; + st = Status::BUFFER_TOO_SMALL; + } + + wNumSupportedStreams = streamsToCopy; + + perc::copy(message, res->stream, streamsToCopy*sizeof(supported_raw_stream_libtm_message)); + + DEVICELOGD("Get Supported RAW Streams (%d)", streamsToCopy); + printSupportedRawStreams(res->stream, streamsToCopy); + + return st; + } + + void Device::AllocateBuffers() + { + while (mFramesBuffersLists.size() > 0) + { + mFramesBuffersLists.pop_front(); + } + + for (int j = 0; j < FRAMES_PER_STREAM; j++) + { + mFramesBuffersLists.push_back(std::shared_ptr(new uint8_t[mFrameTempBufferSize], arrayDeleter())); + DEVICELOGV("frame buffers pushed back - %p", mFramesBuffersLists.back().get()); + } + } + + Status Device::SetEnabledRawStreams(supported_raw_stream_libtm_message * message, uint16_t wNumEnabledStreams) + { + if (!message) + { + return Status::ERROR_PARAMETER_INVALID; + } + + MessageON_SET_ENABLED_STREAMS setMsg(message, wNumEnabledStreams); + + mDispatcher->sendMessage(&mFsm, setMsg); + + return setMsg.Result == 0 ? Status::SUCCESS : Status::COMMON_ERROR; + } + + Status Device::SetFWLogControl(const TrackingData::LogControl& logControl) + { + /* FW log control */ + bulk_message_request_log_control request = {0}; + bulk_message_response_log_control response = {0}; + + __perc_Log_Set_Configuration(LogSourceFW, logControl.outputMode, logControl.verbosity, logControl.rolloverMode); + + request.header.wMessageID = DEV_LOG_CONTROL; + request.header.dwLength = sizeof(request); + request.bVerbosity = logControl.verbosity; + request.bLogMode = logControl.rolloverMode; + + DEVICELOGD("Set FW Log Control, output to %s, verbosity = 0x%X, Rollover mode = 0x%X", (logControl.outputMode == LogOutputModeScreen) ? "Screen" : "Buffer", request.bVerbosity, request.bLogMode); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::GetCameraIntrinsics(SensorId id, TrackingData::CameraIntrinsics& intrinsics) + { + bulk_message_request_get_camera_intrinsics request = {0}; + bulk_message_response_get_camera_intrinsics response = {0}; + + if ((GET_SENSOR_TYPE(id) != SensorType::Color) && + (GET_SENSOR_TYPE(id) != SensorType::Depth) && + (GET_SENSOR_TYPE(id) != SensorType::IR) && + (GET_SENSOR_TYPE(id) != SensorType::Fisheye)) + { + DEVICELOGE("Unsupported SensorId (0x%X)", id); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.wMessageID = DEV_GET_CAMERA_INTRINSICS; + request.header.dwLength = sizeof(request); + request.bCameraID = id; + + DEVICELOGD("Get camera intrinsics for sensor [%d,%d]", GET_SENSOR_TYPE(id), GET_SENSOR_INDEX(id)); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response.header.wStatus == MESSAGE_STATUS::SUCCESS) + { + intrinsics = cameraIntrinsicsMessageToClass(response.intrinsics); + DEVICELOGD("Width = %d", intrinsics.width); + DEVICELOGD("Height = %d", intrinsics.height); + DEVICELOGD("Ppx = %f", intrinsics.ppx); + DEVICELOGD("Ppy = %f", intrinsics.ppy); + DEVICELOGD("Fx = %f", intrinsics.fx); + DEVICELOGD("Fy = %f", intrinsics.fy); + DEVICELOGD("DistortionModel = %d", intrinsics.distortionModel); + for (int i = 0; i < 5; i++) + { + DEVICELOGD("Coeffs[%d] = %f", i, intrinsics.coeffs[i]); + } + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::SetCameraIntrinsics(SensorId id, const TrackingData::CameraIntrinsics& intrinsics) + { + bulk_message_request_set_camera_intrinsics request = {0}; + bulk_message_response_set_camera_intrinsics response = {0}; + + if ((GET_SENSOR_TYPE(id) != SensorType::Color) && + (GET_SENSOR_TYPE(id) != SensorType::Depth) && + (GET_SENSOR_TYPE(id) != SensorType::IR) && + (GET_SENSOR_TYPE(id) != SensorType::Fisheye)) + { + DEVICELOGE("Unsupported SensorId (0x%X)", id); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.wMessageID = DEV_SET_CAMERA_INTRINSICS; + request.header.dwLength = sizeof(request); + request.bCameraID = id; + + request.intrinsics.dwWidth = intrinsics.width; + request.intrinsics.dwHeight = intrinsics.height; + request.intrinsics.flPpx = intrinsics.ppx; + request.intrinsics.flPpy = intrinsics.ppy; + request.intrinsics.flFx = intrinsics.fx; + request.intrinsics.flFy = intrinsics.fy; + request.intrinsics.dwDistortionModel = intrinsics.distortionModel; + for (uint8_t i = 0; i < 5; i++) + { + request.intrinsics.flCoeffs[i] = intrinsics.coeffs[i]; + } + + DEVICELOGD("Set camera intrinsics for sensor [%d,%d]", GET_SENSOR_TYPE(request.bCameraID), GET_SENSOR_INDEX(request.bCameraID)); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response.header.wStatus == MESSAGE_STATUS::SUCCESS) + { + DEVICELOGD("Width = %d", request.intrinsics.dwWidth); + DEVICELOGD("Height = %d", request.intrinsics.dwHeight); + DEVICELOGD("Ppx = %f", request.intrinsics.flPpx); + DEVICELOGD("Ppy = %f", request.intrinsics.flPpy); + DEVICELOGD("Fx = %f", request.intrinsics.flFx); + DEVICELOGD("Fy = %f", request.intrinsics.flFy); + DEVICELOGD("DistortionModel = %d", request.intrinsics.dwDistortionModel); + for (int i = 0; i < 5; i++) + { + DEVICELOGD("Coeffs[%d] = %f", i, request.intrinsics.flCoeffs[i]); + } + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::GetMotionModuleIntrinsics(SensorId id, TrackingData::MotionIntrinsics& intrinsics) + { + bulk_message_request_get_motion_intrinsics request = {0}; + bulk_message_response_get_motion_intrinsics response = {0}; + + if ((GET_SENSOR_TYPE(id) != SensorType::Gyro) && (GET_SENSOR_TYPE(id) != SensorType::Accelerometer)) + { + DEVICELOGE("Unsupported SensorId (0x%X)", id); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.wMessageID = DEV_GET_MOTION_INTRINSICS; + request.header.dwLength = sizeof(request); + request.bMotionID = id; + + DEVICELOGD("Get motion module intrinsics for sensor [%d,%d]", GET_SENSOR_TYPE(id), GET_SENSOR_INDEX(id)); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response.header.wStatus == MESSAGE_STATUS::SUCCESS) + { + intrinsics = motionIntrinsicsMessageToClass(response.intrinsics); + + for (uint8_t i = 0; i < 3; i++) + { + for (uint8_t j = 0; j < 4; j++) + { + DEVICELOGD("Data[%d][%d] = %f", i, j, intrinsics.data[i][j]); + } + } + + for (uint8_t i = 0; i < 3; i++) + { + DEVICELOGD("NoiseVariances[%d] = %f", i, intrinsics.noiseVariances[i]); + } + + for (uint8_t i = 0; i < 3; i++) + { + DEVICELOGD("BiasVariances[%d] = %f", i, intrinsics.biasVariances[i]); + } + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::SetMotionModuleIntrinsics(SensorId id, const TrackingData::MotionIntrinsics& intrinsics) + { + bulk_message_request_set_motion_intrinsics request = {0}; + bulk_message_response_set_motion_intrinsics response = {0}; + + if ((GET_SENSOR_TYPE(id) != SensorType::Gyro) && (GET_SENSOR_TYPE(id) != SensorType::Accelerometer)) + { + DEVICELOGE("Unsupported SensorId (0x%X)", id); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.wMessageID = DEV_SET_MOTION_INTRINSICS; + request.header.dwLength = sizeof(request); + request.bMotionID = id; + request.intrinsics = motionIntrinsicsClassToMessage(intrinsics); + + DEVICELOGD("Set motion module intrinsics for sensor [%d,%d]", GET_SENSOR_TYPE(id), GET_SENSOR_INDEX(id)); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response.header.wStatus == MESSAGE_STATUS::SUCCESS) + { + for (uint8_t i = 0; i < 3; i++) + { + for (uint8_t j = 0; j < 4; j++) + { + DEVICELOGD("Data[%d][%d] = %f", i, j, request.intrinsics.flData[i][j]); + } + } + + for (uint8_t i = 0; i < 3; i++) + { + DEVICELOGD("NoiseVariances[%d] = %f", i, request.intrinsics.flNoiseVariances[i]); + } + + for (uint8_t i = 0; i < 3; i++) + { + DEVICELOGD("BiasVariances[%d] = %f", i, request.intrinsics.flBiasVariances[i]); + } + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::GetExtrinsics(SensorId id, TrackingData::SensorExtrinsics& extrinsics) + { + bulk_message_request_get_extrinsics request; + bulk_message_response_get_extrinsics response = {0}; + + if (GET_SENSOR_TYPE(id) >= SensorType::Max) + { + DEVICELOGE("Unsupported SensorId (0x%X)", id); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.wMessageID = DEV_GET_EXTRINSICS; + request.header.dwLength = sizeof(request); + request.bSensorID = id; + + DEVICELOGD("Get Extrinsics pose for sensor [%d,%d]", GET_SENSOR_TYPE(id), GET_SENSOR_INDEX(id)); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response.header.wStatus == MESSAGE_STATUS::SUCCESS) + { + extrinsics = sensorExtrinsicsMessageToClass(response.extrinsics); + + DEVICELOGD("Reference sensor [%d,%d]", GET_SENSOR_TYPE(extrinsics.referenceSensorId), GET_SENSOR_INDEX(extrinsics.referenceSensorId)); + + for (uint8_t i = 0; i < 9; i++) + { + DEVICELOGD("Rotation[%d] = %f", i, extrinsics.rotation[i]); + } + + for (uint8_t i = 0; i < 3; i++) + { + DEVICELOGD("Translation[%d] = %f", i, extrinsics.translation[i]); + } + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::SetOccupancyMapControl(uint8_t enable) + { + bulk_message_request_occupancy_map_control request = {0}; + bulk_message_response_occupancy_map_control response = {0}; + + request.header.wMessageID = SLAM_OCCUPANCY_MAP_CONTROL; + request.header.dwLength = sizeof(request); + request.bEnable = enable; + + DEVICELOGD("Set Occupancy Map Control %s", (request.bEnable) ? "Enabled" : "Disabled"); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::GetPose(TrackingData::PoseFrame& pose, uint8_t sourceIndex) + { + bulk_message_request_get_pose request = {0}; + bulk_message_response_get_pose response = {0}; + + request.header.wMessageID = DEV_GET_POSE; + request.header.dwLength = sizeof(request); + request.bIndex = sourceIndex; + + DEVICELOGD("Get Pose"); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response.header.wStatus == MESSAGE_STATUS::SUCCESS) + { + pose = poseMessageToClass(response.pose, request.bIndex, response.pose.llNanoseconds + mTM2CorrelatedTimeStampShift); + DEVICELOGD("SourceIndex = 0x%X", pose.sourceIndex); + DEVICELOGD("Timestamp %" PRId64 "", pose.timestamp); + DEVICELOGD("SystemTimestamp %" PRId64 "", pose.systemTimestamp); + DEVICELOGD("ArrivalTimeStamp %" PRId64 "", pose.arrivalTimeStamp); + DEVICELOGD("translation [X,Y,Z] = [%f,%f,%f]", pose.translation.x, pose.translation.y, pose.translation.z); + DEVICELOGD("rotation [I,J,K,R] = [%f,%f,%f,%f]", pose.rotation.i, pose.rotation.j, pose.rotation.k, pose.rotation.r); + DEVICELOGD("velocity [X,Y,Z] = [%f,%f,%f]", pose.velocity.x, pose.velocity.y, pose.velocity.z); + DEVICELOGD("angularVelocity [X,Y,Z] = [%f,%f,%f]", pose.angularVelocity.x, pose.angularVelocity.y, pose.angularVelocity.z); + DEVICELOGD("acceleration [X,Y,Z] = [%f,%f,%f]", pose.acceleration.x, pose.acceleration.y, pose.acceleration.z); + DEVICELOGD("angularAcceleration [X,Y,Z] = [%f,%f,%f]", pose.angularAcceleration.x, pose.angularAcceleration.y, pose.angularAcceleration.z); + DEVICELOGD("trackerConfidence = 0x%X", pose.trackerConfidence); + DEVICELOGD("mapperConfidence = 0x%X", pose.mapperConfidence); + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::SetExposureModeControl(const TrackingData::ExposureModeControl& mode) + { + bulk_message_request_set_exposure_mode_control request = {0}; + bulk_message_response_set_exposure_mode_control response = {0}; + + request.header.wMessageID = DEV_EXPOSURE_MODE_CONTROL; + request.header.dwLength = sizeof(request); + request.bAntiFlickerMode = mode.antiFlickerMode; + + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + if (mode.videoStreamAutoExposure[i] == true) + { + request.bVideoStreamsMask |= (1 << i); + } + } + + DEVICELOGD("Set Exposure Mode Control, bStreamsMask = 0x%X, AntiFlickerMode = 0x%X", request.bVideoStreamsMask, request.bAntiFlickerMode); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::SetExposure(const TrackingData::Exposure& exposure) + { + if (exposure.numOfVideoStreams > MAX_VIDEO_STREAMS) + { + DEVICELOGE("numOfVideoStreams (%d) too big, max supported %d streams", exposure.numOfVideoStreams, MAX_VIDEO_STREAMS); + return Status::ERROR_PARAMETER_INVALID; + } + + bulk_message_request_set_exposure request = {0}; + bulk_message_response_set_exposure response = {0}; + + uint32_t messageLength = offsetof(bulk_message_request_set_exposure, stream) + exposure.numOfVideoStreams * sizeof(stream_exposure); + + request.header.wMessageID = DEV_SET_EXPOSURE; + request.header.dwLength = messageLength; + request.bNumOfVideoStreams = exposure.numOfVideoStreams; + + for (uint8_t i = 0; i < exposure.numOfVideoStreams; i++) + { + if (GET_SENSOR_TYPE(exposure.stream[i].cameraId) > SensorType::Fisheye) + { + DEVICELOGE("stream[%d] = Unsupported cameraId (0x%X)", i, exposure.stream[i].cameraId); + return Status::ERROR_PARAMETER_INVALID; + } + + request.stream[i].bCameraID = exposure.stream[i].cameraId; + request.stream[i].dwIntegrationTime = exposure.stream[i].integrationTime; + request.stream[i].fGain = exposure.stream[i].gain; + } + + DEVICELOGD("Set Exposure for %d video streams", request.bNumOfVideoStreams); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response.header.wStatus == MESSAGE_STATUS::SUCCESS) + { + for (uint32_t i = 0; i < request.bNumOfVideoStreams; i++) + { + DEVICELOGD("VideoStream [%d]: cameraId 0x%X, dwIntegrationTime %d (usec), Gain %f", i, request.stream[i].bCameraID, request.stream[i].dwIntegrationTime, request.stream[i].fGain); + } + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::GetTemperature(TrackingData::Temperature& temperature) + { + uint8_t responseBuffer[BUFFER_SIZE]; + bulk_message_request_get_temperature request; + bulk_message_response_get_temperature* response = (bulk_message_response_get_temperature*)responseBuffer; + + request.header.wMessageID = DEV_GET_TEMPERATURE; + request.header.dwLength = sizeof(request); + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)response, BUFFER_SIZE, mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST, USB_TRANSFER_MEDIUM_TIMEOUT_MS); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response->header.wStatus == MESSAGE_STATUS::SUCCESS) + { + if (response->dwCount > TemperatureSensorMax) + { + DEVICELOGE("Error: Got Temperature for %u sensors, exceeding max sensors (%d)", response->dwCount, TemperatureSensorMax); + return Status::ERROR_PARAMETER_INVALID; + } + + for (uint8_t i = 0; i < response->dwCount; i++) + { + if (response->temperature[i].dwIndex >= TemperatureSensorMax) + { + DEVICELOGE("Error: Temperature Sensor (%d) is unknown, max temperature sensor = 0x%X", temperature.sensor[i].index, TemperatureSensorMax - 1); + return Status::ERROR_PARAMETER_INVALID; + } + } + + temperature.numOfSensors = response->dwCount; + DEVICELOGD("Got Temperature for %u sensors, (Status 0x%X)", temperature.numOfSensors, response->header.wStatus); + if (temperature.numOfSensors > 0) + { + DEVICELOGD("---+-------------+-------------+-----------"); + DEVICELOGD(" # | Sensor | Temperature | Threshold "); + DEVICELOGD(" | Info | Idx | (Celsius) | (Celsius) "); + DEVICELOGD("---+------+------+-------------+-----------"); + + for (uint32_t i = 0; i < temperature.numOfSensors; i++) + { + temperature.sensor[i].index = (TemperatureSensorType)response->temperature[i].dwIndex; + temperature.sensor[i].temperature = response->temperature[i].fTemperature; + temperature.sensor[i].threshold = response->temperature[i].fThreshold; + + DEVICELOGD("%02d | %s | 0x%02X | %-11.2f | %.2f", i, temperatureSensor(temperature.sensor[i].index), temperature.sensor[i].index, temperature.sensor[i].temperature, temperature.sensor[i].threshold); + } + DEVICELOGD("---+------+------+-------------+-----------"); + } + } + + return fwToHostStatus((MESSAGE_STATUS)response->header.wStatus); + } + + Status Device::SetTemperatureThreshold(const TrackingData::Temperature& temperature, uint32_t token) + { + if (temperature.numOfSensors > TemperatureSensorMax) + { + DEVICELOGE("Error: numOfSensors (%u) is too big, max supported sensors = %d", temperature.numOfSensors, TemperatureSensorMax); + return Status::ERROR_PARAMETER_INVALID; + } + + for (uint8_t i = 0; i < temperature.numOfSensors; i++) + { + if (temperature.sensor[i].index >= TemperatureSensorMax) + { + DEVICELOGE("Error: Temperature Sensor (%d) is unknown, max temperature sensor = 0x%X", temperature.sensor[i].index, TemperatureSensorMax-1); + return Status::ERROR_PARAMETER_INVALID; + } + } + + uint8_t requestBuffer[BUFFER_SIZE]; + bulk_message_request_set_temperature_threshold* request = (bulk_message_request_set_temperature_threshold*)requestBuffer; + bulk_message_response_set_temperature_threshold response = {0}; + + request->header.wMessageID = DEV_SET_TEMPERATURE_THRESHOLD; + request->header.dwLength = offsetof(bulk_message_request_set_temperature_threshold, temperature) + temperature.numOfSensors * sizeof(sensor_set_temperature); + request->wForceToken = token; + request->dwCount = temperature.numOfSensors; + + for (uint8_t i = 0; i < temperature.numOfSensors; i++) + { + request->temperature[i].dwIndex = temperature.sensor[i].index; + request->temperature[i].fThreshold = temperature.sensor[i].threshold; + } + + DEVICELOGD("Set Temperature threshold for %d sensors, Token = 0x%X", temperature.numOfSensors, request->wForceToken); + Bulk_Message msg((uint8_t*)request, request->header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response.header.wStatus == MESSAGE_STATUS::SUCCESS) + { + DEVICELOGD("---+-------------+-----------"); + DEVICELOGD(" # | Sensor | Threshold "); + DEVICELOGD(" | Type | Idx | (Celsius) "); + DEVICELOGD("---+------+------+-----------"); + + for (uint32_t i = 0; i < request->dwCount; i++) + { + DEVICELOGD("%02d | %s | 0x%02X | %.2f", i, temperatureSensor(request->temperature[i].dwIndex), request->temperature[i].dwIndex, request->temperature[i].fThreshold); + } + + DEVICELOGD("---+------+------+-----------"); + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::LockConfiguration(LockType type, bool lock) + { + bulk_message_request_lock_configuration request = {0}; + bulk_message_response_lock_configuration response = {0}; + + switch (type) + { + case HardwareLock: + request.header.wMessageID = DEV_LOCK_EEPROM; + break; + case SoftwareLock: + request.header.wMessageID = DEV_LOCK_CONFIGURATION; + break; + default: + DEVICELOGE("Error: unknown lock type (0x%X)", type); + return Status::ERROR_PARAMETER_INVALID; + break; + } + + request.header.dwLength = sizeof(request); + request.dwLock = uint32_t(lock); + + DEVICELOGD("Set %s Configuration Lock: Lock = %s", (request.header.wMessageID == DEV_LOCK_EEPROM)?"Hardware":"Software", (lock == true) ? "True" : "False"); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::PermanentLockConfiguration(LockType type, uint32_t token) + { + if (token != PERMANENT_LOCK_CONFIGURATION_TOKEN) + { + DEVICELOGE("Error: Bad token (0x%X)", token); + return Status::ERROR_PARAMETER_INVALID; + } + + bulk_message_request_lock_configuration request = {0}; + bulk_message_response_lock_configuration response = {0}; + + switch (type) + { + case HardwareLock: + request.header.wMessageID = DEV_LOCK_EEPROM; + break; + case SoftwareLock: + request.header.wMessageID = DEV_LOCK_CONFIGURATION; + break; + default: + DEVICELOGE("Error: unknown lock type (0x%X)", type); + return Status::ERROR_PARAMETER_INVALID; + break; + } + + request.header.wMessageID = DEV_LOCK_CONFIGURATION; + request.header.dwLength = sizeof(request); + request.dwLock = token; + + DEVICELOGD("Set Permanent %s Configuration Lock", (request.header.wMessageID == DEV_LOCK_EEPROM) ? "Hardware" : "Software"); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::ReadConfiguration(uint16_t configurationId, uint16_t size, uint8_t* buffer, uint16_t* actualSize) + { + if ((buffer == NULL) || (size == 0) || (size > MAX_CONFIGURATION_SIZE)) + { + DEVICELOGE("Error: Invalid parameters: buffer = 0x%p, size = %d", buffer, size); + return Status::ERROR_PARAMETER_INVALID; + } + + uint8_t responseBuffer[BUFFER_SIZE] = {0}; + bulk_message_request_read_configuration request; + bulk_message_response_read_configuration* response = (bulk_message_response_read_configuration*)responseBuffer; + + request.header.wMessageID = DEV_READ_CONFIGURATION; + request.header.dwLength = sizeof(request); + request.wTableId = configurationId; + + DEVICELOGD("Set Read Configuration: configurationId = 0x%X, size = %d", configurationId, size); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)response, BUFFER_SIZE, mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST, USB_TRANSFER_MEDIUM_TIMEOUT_MS); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response->header.wStatus == MESSAGE_STATUS::SUCCESS) + { + uint32_t bufferSize = response->header.dwLength - offsetof(bulk_message_response_read_configuration, bTable); + if (size < bufferSize) + { + DEVICELOGE("Error: Buffer size %d is too small: EEPROM table %d needs at least %d bytes buffer", size, configurationId, bufferSize); + return Status::BUFFER_TOO_SMALL; + } + + perc::copy(buffer, response->bTable, bufferSize); + + if (actualSize != nullptr) + { + *actualSize = bufferSize; + } + } + + return fwToHostStatus((MESSAGE_STATUS)response->header.wStatus); + } + + Status Device::WriteConfiguration(uint16_t configurationId, uint16_t size, uint8_t* buffer) + { + if ((buffer == NULL) || (size == 0) || (size > MAX_CONFIGURATION_SIZE)) + { + DEVICELOGE("Error: Invalid parameters: buffer = 0x%p, size = %d", buffer, size); + return Status::ERROR_PARAMETER_INVALID; + } + + uint8_t requestBuffer[BUFFER_SIZE] = {0}; + bulk_message_request_write_configuration* request = (bulk_message_request_write_configuration*)requestBuffer; + bulk_message_response_write_configuration response = {0}; + + request->header.wMessageID = DEV_WRITE_CONFIGURATION; + request->header.dwLength = size + offsetof(bulk_message_request_write_configuration, bTable); + request->wTableId = configurationId; + perc::copy(request->bTable, buffer, size); + + DEVICELOGD("Set Write Configuration: configurationId = 0x%X, size = %d", configurationId, size); + Bulk_Message msg((uint8_t*)request, request->header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST, USB_TRANSFER_MEDIUM_TIMEOUT_MS); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::DeleteConfiguration(uint16_t configurationId) + { + bulk_message_request_reset_configuration request = {0}; + bulk_message_response_reset_configuration response = {0}; + + request.header.wMessageID = DEV_RESET_CONFIGURATION; + request.header.dwLength = sizeof(request); + request.wTableId = configurationId; + + DEVICELOGD("Set Delete Configuration: configurationId = 0x%X", configurationId); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::SetTimeoutConfiguration(uint16_t timeoutMsec) + { + bulk_message_request_timeout_configuration request = {0}; + bulk_message_response_timeout_configuration response = {0}; + + request.header.wMessageID = DEV_TIMEOUT_CONFIGURATION; + request.header.dwLength = sizeof(request); + request.wTimeoutInMillis = timeoutMsec; + + DEVICELOGD("Set Timeout Configuration: timeoutMsec = %d", timeoutMsec); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::GetLocalizationData(Listener* listener) + { + if (listener == NULL) + { + DEVICELOGE("Error: Invalid parameters: listener = %p, length = %d", listener); + return Status::ERROR_PARAMETER_INVALID; + } + + MessageON_ASYNC_START setMsg(listener, SLAM_GET_LOCALIZATION_DATA, 0, NULL); + mDispatcher->sendMessage(&mFsm, setMsg); + + return setMsg.Result == 0 ? Status::SUCCESS : Status::COMMON_ERROR; + } + + Status Device::SetLocalizationData(Listener* listener, uint32_t length, const uint8_t* buffer) + { + if ((listener == NULL ) || (length <= 0) || (buffer == NULL)) + { + DEVICELOGE("Error: Invalid parameters: listener = %p, buffer = %p, length = %d", listener, buffer, length); + return Status::ERROR_PARAMETER_INVALID; + } + + MessageON_ASYNC_START setMsg(listener, SLAM_SET_LOCALIZATION_DATA_STREAM, length, buffer); + mDispatcher->sendMessage(&mFsm, setMsg); + + return setMsg.Result == 0 ? Status::SUCCESS : Status::COMMON_ERROR; + } + + Status Device::ResetLocalizationData(uint8_t flag) + { + bulk_message_request_reset_localization_data request = {0}; + bulk_message_response_reset_localization_data response = {0}; + + if (flag > 1) + { + DEVICELOGE("Error: Flag (%d) is unknown", flag); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.wMessageID = SLAM_RESET_LOCALIZATION_DATA; + request.header.dwLength = sizeof(request); + request.bFlag = flag; + + DEVICELOGD("Set Reset Localization Data - Flag 0x%X", flag); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::SetStaticNode(const char* guid, const TrackingData::RelativePose& relativePose) + { + bulk_message_request_set_static_node request = { 0 }; + bulk_message_response_set_static_node response = { 0 }; + + auto length = perc::stringLength(guid, MAX_GUID_LENGTH); + if (length > (MAX_GUID_LENGTH-1)) + { + DEVICELOGE("Error: guid length is too big, max length = %d", (MAX_GUID_LENGTH - 1)); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.wMessageID = SLAM_SET_STATIC_NODE; + request.header.dwLength = sizeof(request); + request.data.flX = relativePose.translation.x; + request.data.flY = relativePose.translation.y; + request.data.flZ = relativePose.translation.z; + request.data.flQi = relativePose.rotation.i; + request.data.flQj = relativePose.rotation.j; + request.data.flQk = relativePose.rotation.k; + request.data.flQr = relativePose.rotation.r; + snprintf((char*)request.bGuid, length+1, "%s", guid); + + DEVICELOGD("Set Static Node: guid [%s], translation [%f,%f,%f], rotation [%f,%f,%f,%f]", guid, request.data.flX, request.data.flY, request.data.flZ, request.data.flQi, request.data.flQj, request.data.flQk, request.data.flQr); + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::GetStaticNode(const char* guid, TrackingData::RelativePose& relativePose) + { + bulk_message_request_get_static_node request = { 0 }; + bulk_message_response_get_static_node response = { 0 }; + + auto length = perc::stringLength(guid, MAX_GUID_LENGTH); + if (length > (MAX_GUID_LENGTH - 1)) + { + DEVICELOGE("Error: guid length is too big, max length = %d", (MAX_GUID_LENGTH - 1)); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.wMessageID = SLAM_GET_STATIC_NODE; + request.header.dwLength = sizeof(request); + snprintf((char*)request.bGuid, length + 1, "%s", guid); + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + DEVICELOGD("Get Static Node: guid [%s], translation [%f,%f,%f], rotation [%f,%f,%f,%f]", guid, response.data.flX, response.data.flY, response.data.flZ, response.data.flQi, response.data.flQj, response.data.flQk, response.data.flQr); + + relativePose.translation.x = response.data.flX; + relativePose.translation.y = response.data.flY; + relativePose.translation.z = response.data.flZ; + relativePose.rotation.i = response.data.flQi; + relativePose.rotation.j = response.data.flQj; + relativePose.rotation.k = response.data.flQk; + relativePose.rotation.r = response.data.flQr; + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + + Status Device::SetGeoLocation(const TrackingData::GeoLocalization& geoLocation) + { + bulk_message_request_set_geo_location request = {0}; + bulk_message_response_set_geo_location response = {0}; + + request.header.wMessageID = DEV_SET_GEO_LOCATION; + request.header.dwLength = sizeof(request); + request.dfLatitude = geoLocation.latitude; + request.dfLongitude = geoLocation.longitude; + request.dfAltitude = geoLocation.altitude; + + DEVICELOGD("Set Geo Localization - Latitude = %lf, Longitude = %lf, Altitude = %lf", geoLocation.latitude, geoLocation.longitude, geoLocation.altitude); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::Set6DofInterruptRate(sixdof_interrupt_rate_libtm_message message) + { + bulk_message_request_set_6dof_interrupt_rate request = {0}; + bulk_message_response_set_6dof_interrupt_rate response = {0}; + + if (message.bInterruptRate >= SIXDOF_INTERRUPT_RATE_MAX) + { + DEVICELOGE("Got an invalid 6dof interrupt rate (%d)", message.bInterruptRate); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.wMessageID = SLAM_SET_6DOF_INTERRUPT_RATE; + request.header.dwLength = sizeof(request); + request.message.bInterruptRate = message.bInterruptRate; + + DEVICELOGD("Set 6Dof Interrupt rate to %d", message.bInterruptRate); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + void Device::putBufferBack(SensorId id, std::shared_ptr& frame) + { + std::lock_guard lg(mFramesBuffersMutex); + mFramesBuffersLists.push_back(frame); + DEVICELOGV("frame buffers increased (%d) - %p", mFramesBuffersLists.size(), mFramesBuffersLists.back().get()); + + } + + int Device::FindInterruptEndpoint() + { + int result = -1; + + struct libusb_config_descriptor *config; + int rc = libusb_get_active_config_descriptor(mLibusbDevice, &config); + if (rc < 0) + { + DEVICELOGE("Error while getting active config descriptor. LIBUSB_ERROR_CODE: %d (%s)", rc, libusb_error_name(rc)); + return rc; + } + + /* Looping on all interfaces supported by this USB configuration descriptor */ + for (int iface_idx = 0; iface_idx < config->bNumInterfaces; iface_idx++) + { + const struct libusb_interface *iface = &config->interface[iface_idx]; + + /* Looping on all interface descriptors (alternate settings) for a particular USB interface */ + for (int altsetting_idx = 0; altsetting_idx < iface->num_altsetting; altsetting_idx++) + { + const struct libusb_interface_descriptor *altsetting = &iface->altsetting[altsetting_idx]; + + /* Looping on all endpoint descriptors for a particular USB interface descriptor */ + for (int ep_idx = 0; ep_idx < altsetting->bNumEndpoints; ep_idx++) + { + const struct libusb_endpoint_descriptor *ep = &altsetting->endpoint[ep_idx]; + + /* Searching for interrupt endpoint directed to host */ + if ((TO_HOST == (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)) && (LIBUSB_TRANSFER_TYPE_INTERRUPT == (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK))) + { + result = ep->bEndpointAddress; + goto found; + } + } + } + + } + + found: + DEVICELOGV("Found interrupt endpoint address: %d", result); + libusb_free_config_descriptor(config); + return result; + } + + Status Device::EepromRead(uint16_t offset, uint16_t size, uint8_t * buffer, uint16_t& actual) + { + uint16_t transferred = 0; + Status st = Status::SUCCESS; + uint16_t actualChunk = 0; + actual = 0; + + DEVICELOGV("Reading EEPROM: offset 0x%X, size %d (bytes)", offset, size); + while (transferred < size) + { + unsigned int currentChunkSize; + currentChunkSize = ((size - transferred) > mEepromChunkSize) ? mEepromChunkSize : (size - transferred); + st = ReadEepromChunk(offset + transferred, currentChunkSize, buffer + transferred, actualChunk); + actual += actualChunk; + transferred += currentChunkSize; + if (st != Status::SUCCESS) + { + DEVICELOGE("Error Reading EEPROM chunk: offset 0x%X, size %d (bytes), status 0x%X", offset, size, st); + return st; + } + } + + return st; + } + + Status Device::ReadEepromChunk(uint16_t offset, uint16_t size, uint8_t * buffer, uint16_t& actual) + { + bulk_message_request_read_eeprom request = {0}; + bulk_message_response_read_eeprom response = {0}; + + if (size > MAX_EEPROM_BUFFER_SIZE || !buffer) + { + DEVICELOGE("Parameter error: size %d (bytes), buffer %p", size, buffer); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.dwLength = sizeof(bulk_message_request_read_eeprom); + request.header.wMessageID = DEV_READ_EEPROM; + request.wSize = size; + request.wOffset = offset; + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + mDispatcher->sendMessage(&mFsm, msg); + + if (msg.Result == toUnderlying(Status::SUCCESS)) + { + perc::copy(buffer, &response.bData, size); + } + else + { + return Status::ERROR_USB_TRANSFER; + } + + DEVICELOGV("Reading EEPROM chunk: offset 0x%X, size %d (bytes), actual %d, status 0x%X", offset, size, response.wSize, response.header.wStatus); + if (response.header.wStatus == 0) + { + actual = response.wSize; + return Status::SUCCESS; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::EepromWrite(uint16_t offset, uint16_t size, uint8_t * buffer, uint16_t& actual, bool verify) + { + uint16_t transferred = 0; + Status st = Status::SUCCESS; + uint16_t actualChunk = 0; + actual = 0; + DEVICELOGD("Writing EEPROM: offset 0x%X, size %d (bytes)", offset, size); + while (transferred < size) + { + unsigned int currentChunkSize; + currentChunkSize = ((size - transferred) > mEepromChunkSize) ? mEepromChunkSize : (size - transferred); + st = WriteEepromChunk(offset + transferred, currentChunkSize, buffer + transferred, actualChunk, verify); + actual += actualChunk; + transferred += currentChunkSize; + if (st != Status::SUCCESS) + { + DEVICELOGE("Error Writing EEPROM chunk: offset 0x%X, size %d (bytes), status 0x%X", offset, size, st); + return st; + } + } + return st; + } + + Status Device::WriteEepromChunk(uint16_t offset, uint16_t size, uint8_t * buffer, uint16_t& actual, bool verify) + { + bulk_message_request_write_eeprom request = {0}; + bulk_message_response_write_eeprom response = {0}; + + if (size > mEepromChunkSize) + { + DEVICELOGE("Parameter error: size %d > maxChunkSize %d", size, mEepromChunkSize); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.dwLength = sizeof(bulk_message_request_write_eeprom); + request.header.wMessageID = DEV_WRITE_EEPROM; + request.wSize = size; + request.wOffset = offset; + perc::copy(&request.bData[0], buffer, size); + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + mDispatcher->sendMessage(&mFsm, msg); + + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error Writing EEPROM chunk: offset 0x%X, size %d (bytes), result 0x%X", offset, size, msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + DEVICELOGV("Writing EEPROM chunk: offset 0x%X, size %d (bytes), actual %d, status 0x%X", offset, size, response.wSize, response.header.wStatus); + if (response.header.wStatus == 0) + { + actual = response.wSize; + + if (verify) + { + /* This EEPROM has a write time of 5 msec per PAGE, waiting for write to be completed before verifying */ + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + uint16_t tmp; + std::vector tmpBuf; + tmpBuf.resize(size); + DEVICELOGV("Verifing EEPROM chunk: offset 0x%X, size %d (bytes)", offset, size); + EepromRead(offset, size, (uint8_t*)tmpBuf.data(), tmp); + int rc = memcmp((uint8_t*)tmpBuf.data(), buffer, size); + if (rc) + { + DEVICELOGE("Verifing EEPROM chunk failed: offset 0x%X, size %d (bytes)", offset, size); + return Status::ERROR_EEPROM_VERIFY_FAIL; + } + } + return Status::SUCCESS; + } + + return Status::ERROR_PARAMETER_INVALID; + } + + Status Device::SendFrame(const TrackingData::VelocimeterFrame& frame) + { + std::vector buf; + buf.resize(sizeof(bulk_message_velocimeter_stream)); + + bulk_message_velocimeter_stream* req = (bulk_message_velocimeter_stream*)buf.data(); + + req->rawStreamHeader.header.dwLength = sizeof(bulk_message_velocimeter_stream); + req->rawStreamHeader.header.wMessageID = DEV_SAMPLE; + req->rawStreamHeader.bSensorID = SET_SENSOR_ID(SensorType::Velocimeter, frame.sensorIndex); + req->rawStreamHeader.llNanoseconds = frame.timestamp; + req->rawStreamHeader.bReserved = 0; + req->rawStreamHeader.llArrivalNanoseconds = frame.arrivalTimeStamp; + req->rawStreamHeader.dwFrameId = frame.frameId; + + req->metadata.dwMetadataLength = offsetof(bulk_message_velocimeter_stream_metadata, dwFrameLength) - sizeof(req->metadata.dwMetadataLength); + req->metadata.flTemperature = frame.temperature; + req->metadata.dwFrameLength = sizeof(bulk_message_velocimeter_stream_metadata) - offsetof(bulk_message_velocimeter_stream_metadata, dwFrameLength) - sizeof(req->metadata.dwFrameLength); + req->metadata.flVx = frame.angularVelocity.x; + req->metadata.flVy = frame.angularVelocity.y; + req->metadata.flVz = frame.angularVelocity.z; + + int actual; + auto rc = libusb_bulk_transfer(mDevice, mStreamEndpoint | TO_DEVICE, (unsigned char*)req, (int)buf.size(), &actual, 100); + if (rc != 0 || actual == 0) + { + DEVICELOGE("Error while sending velocimeter frame to device: %d", rc); + return Status::ERROR_USB_TRANSFER; + } + + return Status::SUCCESS; + } + + Status Device::SendFrame(const TrackingData::GyroFrame& frame) + { + std::vector buf; + buf.resize(sizeof(bulk_message_gyro_stream)); + + bulk_message_gyro_stream* req = (bulk_message_gyro_stream*)buf.data(); + + req->rawStreamHeader.header.dwLength = sizeof(bulk_message_gyro_stream); + req->rawStreamHeader.header.wMessageID = DEV_SAMPLE; + req->rawStreamHeader.bSensorID = SET_SENSOR_ID(SensorType::Gyro, frame.sensorIndex); + req->rawStreamHeader.llNanoseconds = frame.timestamp; + req->rawStreamHeader.bReserved = 0; + req->rawStreamHeader.llArrivalNanoseconds = frame.arrivalTimeStamp; + req->rawStreamHeader.dwFrameId = frame.frameId; + + req->metadata.dwMetadataLength = offsetof(bulk_message_gyro_stream_metadata, dwFrameLength) - sizeof(req->metadata.dwMetadataLength); + req->metadata.flTemperature = frame.temperature; + req->metadata.dwFrameLength = sizeof(bulk_message_gyro_stream_metadata) - offsetof(bulk_message_gyro_stream_metadata, dwFrameLength) - sizeof(req->metadata.dwFrameLength); + req->metadata.flGx = frame.angularVelocity.x; + req->metadata.flGy = frame.angularVelocity.y; + req->metadata.flGz = frame.angularVelocity.z; + + int actual; + auto rc = libusb_bulk_transfer(mDevice, mStreamEndpoint | TO_DEVICE, (unsigned char*)req, (int)buf.size(), &actual, 100); + if (rc != 0 || actual == 0) + { + DEVICELOGE("Error while sending gyro frame to device: %d", rc); + return Status::ERROR_USB_TRANSFER; + } + + return Status::SUCCESS; + } + + Status Device::SendFrame(const TrackingData::AccelerometerFrame& frame) + { + std::vector buf; + buf.resize(sizeof(bulk_message_accelerometer_stream)); + + bulk_message_accelerometer_stream* req = (bulk_message_accelerometer_stream*)buf.data(); + req->rawStreamHeader.header.dwLength = sizeof(bulk_message_accelerometer_stream); + req->rawStreamHeader.header.wMessageID = DEV_SAMPLE; + req->rawStreamHeader.bSensorID = SET_SENSOR_ID(SensorType::Accelerometer, frame.sensorIndex); + req->rawStreamHeader.llNanoseconds = frame.timestamp; + req->rawStreamHeader.bReserved = 0; + req->rawStreamHeader.llArrivalNanoseconds = frame.arrivalTimeStamp; + req->rawStreamHeader.dwFrameId = frame.frameId; + + req->metadata.dwMetadataLength = offsetof(bulk_message_accelerometer_stream_metadata, dwFrameLength) - sizeof(req->metadata.dwMetadataLength); + req->metadata.flTemperature = frame.temperature; + req->metadata.dwFrameLength = sizeof(bulk_message_accelerometer_stream_metadata) - offsetof(bulk_message_accelerometer_stream_metadata, dwFrameLength) - sizeof(req->metadata.dwFrameLength); + req->metadata.flAx = frame.acceleration.x; + req->metadata.flAy = frame.acceleration.y; + req->metadata.flAz = frame.acceleration.z; + + int actual; + auto rc = libusb_bulk_transfer(mDevice, mStreamEndpoint | TO_DEVICE, (unsigned char*)req, (int)buf.size(), &actual, 100); + if (rc != 0 || actual == 0) + { + DEVICELOGE("Error while sending accelerometer frame to device: %d", rc); + return Status::ERROR_USB_TRANSFER; + } + + return Status::SUCCESS; + } + + Status Device::SendFrame(const TrackingData::VideoFrame& frame) + { + std::vector buf; + buf.resize(frame.profile.stride * frame.profile.height + sizeof(bulk_message_video_stream)); + + bulk_message_video_stream* msg = (bulk_message_video_stream*)buf.data(); + + msg->rawStreamHeader.header.dwLength = (uint32_t)buf.size(); + msg->rawStreamHeader.header.wMessageID = DEV_SAMPLE; + msg->rawStreamHeader.bSensorID = SET_SENSOR_ID(SensorType::Fisheye, frame.sensorIndex); + msg->rawStreamHeader.bReserved = 0; + msg->rawStreamHeader.llNanoseconds = frame.timestamp; + msg->rawStreamHeader.llArrivalNanoseconds = frame.arrivalTimeStamp; + msg->rawStreamHeader.dwFrameId = frame.frameId; + + msg->metadata.dwMetadataLength = frame.profile.stride*frame.profile.height + sizeof(bulk_message_video_stream_metadata); + msg->metadata.dwExposuretime = frame.exposuretime; + msg->metadata.fGain = frame.gain; + msg->metadata.dwFrameLength = frame.profile.stride*frame.profile.height; + + perc::copy(buf.data() + sizeof(bulk_message_video_stream), frame.data, frame.profile.stride*frame.profile.height); + + int actual; + auto rc = libusb_bulk_transfer(mDevice, mStreamEndpoint | TO_DEVICE, buf.data(), (int)buf.size(), &actual, 100); + if (rc != 0 || actual == 0) + { + DEVICELOGE("Error while sending frame to device: %d", rc); + return Status::ERROR_USB_TRANSFER; + } + + return Status::SUCCESS; + } + + Status Device::ControllerConnect(const TrackingData::ControllerDeviceConnect& device, uint8_t& controllerId) + { + bulk_message_request_controller_device_connect request = {0}; + bulk_message_response_controller_device_connect response = {0}; + + if (device.addressType >= AddressTypeMax) + { + DEVICELOGE("Error: Unsupported addressType (0x%X)", device.addressType); + return Status::ERROR_PARAMETER_INVALID; + } + + request.header.wMessageID = CONTROLLER_DEVICE_CONNECT; + request.header.dwLength = sizeof(request); + request.wTimeout = device.timeout; + request.bAddressType = uint8_t(device.addressType); + perc::copy(request.bMacAddress, device.macAddress, MAC_ADDRESS_SIZE); + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST, USB_TRANSFER_SLOW_TIMEOUT_MS); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if ((MESSAGE_STATUS)response.header.wStatus == MESSAGE_STATUS::SUCCESS) + { + DEVICELOGD("Sent Controller Device Connect request for Controller MacAddress = [%02X:%02X:%02X:%02X:%02X:%02X], [AddressType 0x%X], timeout %d (msec), Received ControllerId %d", + request.bMacAddress[0], request.bMacAddress[1], request.bMacAddress[2], request.bMacAddress[3], request.bMacAddress[4], request.bMacAddress[5], request.bAddressType, request.wTimeout, response.bControllerID); + } + else + { + DEVICELOGE("Error: Failed to send Controller Device Connect request for Controller MacAddress = [%02X:%02X:%02X:%02X:%02X:%02X], [AddressType 0x%X], timeout %d (msec)", + request.bMacAddress[0], request.bMacAddress[1], request.bMacAddress[2], request.bMacAddress[3], request.bMacAddress[4], request.bMacAddress[5], request.bAddressType, request.wTimeout); + } + + controllerId = response.bControllerID; + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::ControllerDisconnect(uint8_t controllerId) + { + bulk_message_request_controller_device_disconnect request = {0}; + bulk_message_response_controller_device_disconnect response = {0}; + + request.header.wMessageID = CONTROLLER_DEVICE_DISCONNECT; + request.header.dwLength = sizeof(request); + request.bControllerID = controllerId; + + DEVICELOGD("Set Controller Device Disconnect request on controllerId %d", controllerId); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST, USB_TRANSFER_SLOW_TIMEOUT_MS); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::ControllerStartCalibration(uint8_t controllerId) + { + bulk_message_request_controller_start_calibration request = { 0 }; + bulk_message_response_controller_start_calibration response = { 0 }; + + request.header.wMessageID = CONTROLLER_START_CALIBRATION; + request.header.dwLength = sizeof(request); + request.bControllerID = controllerId; + + DEVICELOGD("Set Controller calibration request on controllerId %d", controllerId); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST, USB_TRANSFER_SLOW_TIMEOUT_MS); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::GetAssociatedDevices(TrackingData::ControllerAssociatedDevices& devices) + { + bulk_message_request_controller_read_associated_devices request = {0}; + bulk_message_response_controller_read_associated_devices response = {0}; + + request.header.wMessageID = CONTROLLER_READ_ASSOCIATED_DEVICES; + request.header.dwLength = sizeof(request); + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + devices.set(response.bMacAddress1, response.bMacAddress2, (AddressType)response.bAddressType1, (AddressType)response.bAddressType2); + + DEVICELOGD("Got Associated Devices from the EEPROM: Device1 %02X:%02X:%02X:%02X:%02X:%02X, Address type 0x%X, Device2 %02X:%02X:%02X:%02X:%02X:%02X, Address type 0x%X (Status 0x%X)", + devices.macAddress1[0], devices.macAddress1[1], devices.macAddress1[2], devices.macAddress1[3], devices.macAddress1[4], devices.macAddress1[5], devices.addressType1, + devices.macAddress2[0], devices.macAddress2[1], devices.macAddress2[2], devices.macAddress2[3], devices.macAddress2[4], devices.macAddress2[5], devices.addressType2, response.header.wStatus); + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::SetAssociatedDevices(const TrackingData::ControllerAssociatedDevices& devices) + { + bulk_message_request_controller_write_associated_devices request = {0}; + bulk_message_response_controller_write_associated_devices response = {0}; + + request.header.wMessageID = CONTROLLER_WRITE_ASSOCIATED_DEVICES; + request.header.dwLength = sizeof(request); + + if ((devices.macAddress1[0] == 0) && (devices.macAddress1[1] == 0) && (devices.macAddress1[2] == 0) && (devices.macAddress1[3] == 0) && (devices.macAddress1[4] == 0) && (devices.macAddress1[5] == 0)) + { + DEVICELOGE("Error: MacAddress1 can't be zero"); + return Status::ERROR_PARAMETER_INVALID; + } + + if (devices.addressType1 >= AddressTypeMax) + { + DEVICELOGE("Error: Unsupported addressType1 (0x%X)", devices.addressType1); + return Status::ERROR_PARAMETER_INVALID; + } + + if (devices.addressType2 >= AddressTypeMax) + { + DEVICELOGE("Error: Unsupported addressType2 (0x%X)", devices.addressType2); + return Status::ERROR_PARAMETER_INVALID; + } + + perc::copy(&request.bMacAddress1, devices.macAddress1, MAC_ADDRESS_SIZE); + perc::copy(&request.bMacAddress2, devices.macAddress2, MAC_ADDRESS_SIZE); + request.bAddressType1 = devices.addressType1; + request.bAddressType2 = devices.addressType2; + + DEVICELOGD("Set Associated Devices to the EEPROM: Device1 %02X:%02X:%02X:%02X:%02X:%02X, Address type 0x%X, Device2 %02X:%02X:%02X:%02X:%02X:%02X, Address type 0x%X", + request.bMacAddress1[0], request.bMacAddress1[1], request.bMacAddress1[2], request.bMacAddress1[3], request.bMacAddress1[4], request.bMacAddress1[5], request.bAddressType1, + request.bMacAddress2[0], request.bMacAddress2[1], request.bMacAddress2[2], request.bMacAddress2[3], request.bMacAddress2[4], request.bMacAddress2[5], request.bAddressType2); + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::ControllerSendData(const TrackingData::ControllerData& controllerData) + { + if (controllerData.bufferSize + offsetof(bulk_message_request_controller_send_data, bControllerData) > BUFFER_SIZE) + { + DEVICELOGE("BufferSize (%d bytes) must not exceed %d bytes", controllerData.bufferSize, BUFFER_SIZE - offsetof(bulk_message_request_controller_send_data, bControllerData)); + return Status::COMMON_ERROR; + } + + if (controllerData.bufferSize == 0) + { + DEVICELOGE("BufferSize (%d bytes) too small", controllerData.bufferSize); + return Status::BUFFER_TOO_SMALL; + } + + uint8_t requestBuffer[BUFFER_SIZE]; + bulk_message_request_controller_send_data* request = (bulk_message_request_controller_send_data*)requestBuffer; + bulk_message_response_controller_send_data response = {0}; + + request->header.wMessageID = CONTROLLER_SEND_DATA; + request->header.dwLength = offsetof(bulk_message_request_controller_send_data, bControllerData) + controllerData.bufferSize; + request->bControllerID = controllerData.controllerId; + request->bCommandID = controllerData.commandId; + perc::copy(request->bControllerData, controllerData.buffer, controllerData.bufferSize); + + DEVICELOGD("Set Controller Send Data: controllerId = 0x%02X, commandId = 0x%02X, bufferSize = %02d", controllerData.controllerId, controllerData.commandId, controllerData.bufferSize); + Bulk_Message msg((uint8_t*)request, request->header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::ControllerRssiTestControl(uint8_t controllerId, bool testControl) + { + bulk_message_request_controller_rssi_test_control request = {0}; + bulk_message_response_controller_rssi_test_control response = {0}; + + request.header.wMessageID = CONTROLLER_RSSI_TEST_CONTROL; + request.header.dwLength = sizeof(request); + request.bControllerID = controllerId; + request.bTestControl = testControl; + + DEVICELOGD("Set Controller RSSI test Control: controllerId = 0x%X, testControl = %s", controllerId, (testControl == 0)?"False":"True"); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::SetGpioControl(uint8_t gpioControl) + { + bulk_message_request_gpio_control request = {0}; + bulk_message_response_gpio_control response = {0}; + + request.header.wMessageID = DEV_GPIO_CONTROL; + request.header.dwLength = sizeof(request); + request.bGpioControl = gpioControl; + + DEVICELOGD("Set GPIO Control = 0x%X", gpioControl); + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + + mDispatcher->sendMessage(&mFsm, msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::CentralLoadFW(uint8_t* buffer) + { + if (!mHasBluetooth) + { + DEVICELOGE("cannot CentralLoadFW, there is no bluetooth in the device"); + return Status::NO_BLUETOOTH; + } + uint32_t addressSize = offsetof(message_fw_update_request, bNumFiles); + std::vector msgArr(addressSize + CENTRAL_APP_SIZE, 0); + perc::copy(msgArr.data() + addressSize, buffer, CENTRAL_APP_SIZE); + message_fw_update_request* msg = (message_fw_update_request*)(msgArr.data()); + MessageON_ASYNC_START setMsg(&mCentralListener, DEV_FIRMWARE_UPDATE, (uint32_t)msgArr.size(), (uint8_t*)msg); + mFsm.fireEvent(setMsg); + if (setMsg.Result != 0) + { + DEVICELOGE("Failed to start ON_ASYNC state when updating the central fw update : %d", setMsg.Result); + return Status::COMMON_ERROR; + } + + std::mutex asyncMutex; + std::unique_lock lk(asyncMutex); + mAsyncCV.wait(lk); + + MessageON_ASYNC_STOP stopMsg; + mFsm.fireEvent(stopMsg); + if (stopMsg.Result != 0) + { + DEVICELOGE("Failed to stop ON_ASYNC state when updating the central fw update : %d", stopMsg.Result); + return Status::COMMON_ERROR; + } + + return Status::SUCCESS; + } + + Status Device::CentralFWUpdate() + { + if(!mHasBluetooth) + { + DEVICELOGE("cannot CentralFWUpdate, there is no bluetooth in the device"); + return Status::NO_BLUETOOTH; + } + + bool updateApp = false; + + if (CentralBlFw::Version[0] != mDeviceInfo.bCentralBootloaderVersionMajor || + CentralBlFw::Version[1] != mDeviceInfo.bCentralBootloaderVersionMinor || + CentralBlFw::Version[2] != mDeviceInfo.bCentralBootloaderVersionPatch) + { + DEVICELOGD("Updating Central Boot Loader FW [%u.%u.%u] --> [%u.%u.%u]", + mDeviceInfo.bCentralBootloaderVersionMajor, mDeviceInfo.bCentralBootloaderVersionMinor, mDeviceInfo.bCentralBootloaderVersionPatch, + CentralBlFw::Version[0], CentralBlFw::Version[1], CentralBlFw::Version[2]); + auto status = CentralLoadFW((uint8_t*)CentralBlFw::Buffer); + if (status != Status::SUCCESS) + { + return status; + } + updateApp = true; + } + + if (updateApp == true || + CentralAppFw::Version[0] != mDeviceInfo.bCentralAppVersionMajor || + CentralAppFw::Version[1] != mDeviceInfo.bCentralAppVersionMinor || + CentralAppFw::Version[2] != mDeviceInfo.bCentralAppVersionPatch || + CentralAppFw::Version[3] != mDeviceInfo.dwCentralAppVersionBuild) + { + DEVICELOGD("Updating Central Application FW [%u.%u.%u.%u] --> [%u.%u.%u.%u]", + mDeviceInfo.bCentralAppVersionMajor, mDeviceInfo.bCentralAppVersionMinor, mDeviceInfo.bCentralAppVersionPatch, mDeviceInfo.dwCentralAppVersionBuild, + CentralAppFw::Version[0], CentralAppFw::Version[1], CentralAppFw::Version[2], CentralAppFw::Version[3]); + auto status = CentralLoadFW((uint8_t*)CentralAppFw::Buffer); + if (status != Status::SUCCESS) + { + return status; + } + } + return Status::SUCCESS; + } + + Status Device::ControllerFWUpdate(const TrackingData::ControllerFW& fw) + { + if (!mHasBluetooth) + { + DEVICELOGE("cannot ControllerFWUpdate, there is no bluetooth in the device"); + return Status::NO_BLUETOOTH; + } + if (fw.imageSize == 0) + { + DEVICELOGE("FW image size (%d bytes) too small", fw.imageSize); + return Status::BUFFER_TOO_SMALL; + } + + uint32_t addressSize = offsetof(message_fw_update_request, bNumFiles); + std::vector msgArr(addressSize + fw.imageSize, 0); + message_fw_update_request* msg = (message_fw_update_request*)(msgArr.data()); + msg->bAddressType = (uint8_t)fw.addressType; + + if (msg->bAddressType >= AddressTypeMax) + { + DEVICELOGE("Error: Unsupported addressType (0x%X) in FW image", msg->bAddressType); + return Status::ERROR_PARAMETER_INVALID; + } + + perc::copy(msgArr.data(), fw.macAddress, MAC_ADDRESS_SIZE); + perc::copy(msgArr.data() + addressSize, fw.image, fw.imageSize); + + Large_Message setMsg(mListener, DEV_FIRMWARE_UPDATE, (uint32_t)msgArr.size(), (uint8_t*)msg); + mDispatcher->sendMessage(&mFsm, setMsg); + + if (setMsg.Result != 0) + { + DEVICELOGE("Received error when loading controller image : %d", setMsg.Result); + return Status::COMMON_ERROR; + } + return Status::SUCCESS; + } + + /* Stream/Frame Endpoint Thread */ + void Device::streamEndpointThread() + { + /* Making sure this thread gets high CPU priority */ + std::lock_guard lk(streamThreadMutex); + + DEVICELOGD("Thread Start - Stream thread (Video/Controllers/Rssi/Localization frames)"); + + mStreamEndpointThreadActive = true; + + nsecs_t timeOfFirstByte = 0; + uint64_t totalBytesReceived = 0; + + while (mStreamEndpointThreadStop == false) + { + int actual = 0; + auto rc = libusb_bulk_transfer(mDevice, mStreamEndpoint | TO_HOST, mFramesBuffersLists.front().get(), mFrameTempBufferSize, &actual, 100); + if (rc == LIBUSB_ERROR_TIMEOUT) + continue; + + if (rc != 0 || actual == 0) + { + DEVICELOGE("FW crashed - got error in stream endpoint thread function: status = %d (%s), actual = %d", rc, libusb_error_name(rc), actual); + mDispatcher->postMessage(&mFsm, Message(ON_ERROR)); + break; + } + + // Add some statistics calculations + totalBytesReceived += actual; + if (timeOfFirstByte == 0) + timeOfFirstByte = systemTime(); + else + { + auto currentTime = systemTime(); + double diff = (double)currentTime - timeOfFirstByte; + diff /= 1000000000; + if (diff != 0) + { + double speed = ((double)totalBytesReceived) / diff; + DEVICELOGV("Current transfer speed on Frame Endpoint is: %.2f bytes per second, Total bytes received %lld, time passed %.2f", speed, totalBytesReceived, diff); + } + } + + bulk_message_raw_stream_header* header = (bulk_message_raw_stream_header*)mFramesBuffersLists.front().get(); + switch (header->header.wMessageID) + { + case DEV_SAMPLE: + { + switch (GET_SENSOR_TYPE(header->bSensorID)) + { + case SensorType::Fisheye: + { + /* After calling addTask, VideoFrameCompleteTask ptr can be destructed from whoever come last: */ + /* 1. streamEndpointThread (This function) */ + /* 2. Manager::handleEvents */ + /* in case of 1 - The VideoFrameCompleteTask destructor is called inside the brackets (before releasing mFramesBuffersMutex) */ + /* To prevent deadlock we are using recursive_mutex which allows calling lock inside lock in the same thread */ + std::lock_guard lg(mFramesBuffersMutex); + if (mFramesBuffersLists.size() > 1) + { + std::shared_ptr videoFrameCompleteTask = std::make_shared(mListener, + mFramesBuffersLists.front(), + this, + this, + header->llNanoseconds + mTM2CorrelatedTimeStampShift, + mWidthsMap[header->bSensorID], + mHeightsMap[header->bSensorID]); + std::shared_ptr completeTask = videoFrameCompleteTask; + + TrackingData::VideoFrame* frame = &videoFrameCompleteTask->mVideoFrame; + int64_t offset = (int64_t)(frame->frameId) - (int64_t)videoPrevFrame[frame->sensorIndex].prevFrameId; + if (videoPrevFrame[frame->sensorIndex].prevFrameId != 0) + { + if (offset > 1) + { + DEVICELOGW("Video[%d] frame drops occurred - %lld missing frames [FrameId %d-%d], time diff = %d (msec)", + frame->sensorIndex, offset - 1, videoPrevFrame[frame->sensorIndex].prevFrameId + 1, frame->frameId - 1, ns2ms(frame->timestamp - videoPrevFrame[frame->sensorIndex].prevFrameTimeStamp)); + } + else if (offset < 1) + { + DEVICELOGW("Video[%d] frame reorder occurred - prev frameId = %d, new frameId = %d, time diff = %d (msec)", + frame->sensorIndex, videoPrevFrame[frame->sensorIndex].prevFrameId, frame->frameId, ns2ms(frame->timestamp - videoPrevFrame[frame->sensorIndex].prevFrameTimeStamp)); + } + } + + videoPrevFrame[frame->sensorIndex].prevFrameTimeStamp = frame->timestamp; + videoPrevFrame[frame->sensorIndex].prevFrameId = frame->frameId; + + DEVICELOGV("frame buffers decreased (%d) - %p", mFramesBuffersLists.size()-1, mFramesBuffersLists.front().get()); + + mFramesBuffersLists.pop_front(); + mTaskHandler->addTask(completeTask); + } + else + { + /* Todo: Consider allocating more buffers */ + DEVICELOGE("No more frame buffers (%d), dropping frame", mFramesBuffersLists.size()); + } + break; + } // end of switch + + case SensorType::Controller: + { + std::shared_ptr ptr = std::make_shared(mListener, header, mTM2CorrelatedTimeStampShift, this); + mTaskHandler->addTask(ptr); + break; + } + + case SensorType::Rssi: + { + std::shared_ptr ptr = std::make_shared(mListener, header, mTM2CorrelatedTimeStampShift, this); + mTaskHandler->addTask(ptr); + break; + } + + default: + DEVICELOGE("Unsupported sensor (0x%X) on stream endpoint thread function", header->bSensorID); + break; + } + break; + } + + case SLAM_GET_LOCALIZATION_DATA_STREAM: + { + std::lock_guard lg(mFramesBuffersMutex); + interrupt_message_get_localization_data_stream* getLocalizationDataStream = (interrupt_message_get_localization_data_stream*)header; + uint16_t status = getLocalizationDataStream->wStatus; + + DEVICELOGV("Got Localization Data frame: status = 0x%X, moredata = %s, chunkIndex = %d, length = %d", + status, + (((MESSAGE_STATUS)getLocalizationDataStream->wStatus == MESSAGE_STATUS::MORE_DATA_AVAILABLE)) ? "True" : "False", + getLocalizationDataStream->wIndex, + getLocalizationDataStream->header.dwLength - offsetof(interrupt_message_get_localization_data_stream, bLocalizationData)); + + if (mFramesBuffersLists.size() > 1) + { + std::shared_ptr ptr = std::make_shared(mListener, mFramesBuffersLists.front(), this, this); + mFramesBuffersLists.pop_front(); + mTaskHandler->addTask(ptr); + } + else + { + /* Todo: Consider allocating more buffers */ + DEVICELOGE("No more get localization buffers (%d), dropping get localization frame", mFramesBuffersLists.size()); + } + + /* Check if this is the last Get localization frame, and exit ASYNC state */ + if (status == (uint16_t)MESSAGE_STATUS::SUCCESS) + { + MessageON_ASYNC_STOP setMsg; + mDispatcher->postMessage(&mFsm, setMsg); + } + break; + } + + case DEV_STATUS: + { + interrupt_message_status* status = (interrupt_message_status*)header; + DEVICELOGD("Got DEV status %s (0x%X) on stream endpoint", statusCodeToString((MESSAGE_STATUS)status->wStatus).c_str(), status->wStatus); + + auto hostStatus = fwToHostStatus((MESSAGE_STATUS)status->wStatus); + + /* DEVICE_STOPPED is indicated once when device exists from ACTIVE_STATE, all other statuses will be indicated here */ + if (hostStatus != Status::DEVICE_STOPPED) + { + /* Creating onStatusEvent to indicate hostStatus */ + std::shared_ptr ptr = std::make_shared(mListener, hostStatus, this); + mTaskHandler->addTask(ptr); + } + break; + } + + default: + { + DEVICELOGE("Unsupported message (0x%X) on stream endpoint thread function", header->header.wMessageID); + break; + } + } + } // end of while + + mStreamEndpointThreadActive = false; + DEVICELOGD("Thread Stop - Stream thread (Video/Controllers/Rssi/Localization frames)"); + } + + /* Event Endpoint Thread */ + void Device::interruptEndpointThread() + { + /* Making sure this thread gets high CPU priority */ + std::lock_guard lk(eventThreadMutex); + + DEVICELOGD("Thread Start - Interrupt thread (Accelerometer/Velocimeter/Gyro/6DOF/Controller/Localization events)"); + mInterruptEndpointThreadActive = true; + + unsigned char msgBuffer[BUFFER_SIZE]; + interrupt_message_header* header = (interrupt_message_header*)msgBuffer; + + while (mInterruptEndpointThreadStop == false) + { + int actual = 0; + int result; + + result = libusb_interrupt_transfer(mDevice, mEndpointInterrupt, msgBuffer, BUFFER_SIZE, &actual, 100); + if (result == LIBUSB_ERROR_TIMEOUT) + { + continue; + } + + if (result != 0 || actual < sizeof(interrupt_message_header)) + { + DEVICELOGE("FW crashed - got error in interrupt endpoint thread function: status = %d (%s), actual = %d", result, libusb_error_name(result), actual); + mDispatcher->postMessage(&mFsm, Message(ON_ERROR)); + break; + } + + switch (header->wMessageID) + { + case DEV_GET_POSE: + { + interrupt_message_get_pose poseMsg = *((interrupt_message_get_pose*)header); + auto pose = poseMessageToClass(poseMsg.pose, poseMsg.bIndex, poseMsg.pose.llNanoseconds + mTM2CorrelatedTimeStampShift); + std::shared_ptr ptr = std::make_shared(mListener, pose, this); + + /* Pose must arrive every 5 msec */ + int64_t offsetMsec = (pose.timestamp - sixdofPrevFrame[pose.sourceIndex].prevFrameTimeStamp) / ms2ns(MAX_6DOF_TIMEDIFF_MSEC); + if (sixdofPrevFrame[pose.sourceIndex].prevFrameTimeStamp != 0) + { + if (offsetMsec > MAX_6DOF_TIMEDIFF_MSEC) + { + DEVICELOGW("Pose[%d] frame drops occurred - %lld missing frames, prev pose time = %lld, new pose time = %lld, time diff = %d (msec)", + pose.sourceIndex, offsetMsec - 1, sixdofPrevFrame[pose.sourceIndex].prevFrameTimeStamp, pose.timestamp, ns2ms(pose.timestamp - sixdofPrevFrame[pose.sourceIndex].prevFrameTimeStamp)); + } + else if (offsetMsec < 0) + { + DEVICELOGW("Pose[%d] frame reorder occurred - prev pose time = %lld, new pose time = %lld, time diff = %d (msec)", + pose.sourceIndex, sixdofPrevFrame[pose.sourceIndex].prevFrameTimeStamp, pose.timestamp, ns2ms(pose.timestamp - sixdofPrevFrame[pose.sourceIndex].prevFrameTimeStamp)); + } + } + + sixdofPrevFrame[pose.sourceIndex].prevFrameTimeStamp = pose.timestamp; + + mTaskHandler->addTask(ptr); + break; + } + + case DEV_SAMPLE: + { + interrupt_message_raw_stream_header* imuHeader = (interrupt_message_raw_stream_header*)header; + switch (GET_SENSOR_TYPE(imuHeader->bSensorID)) + { + case SensorType::Gyro: + { + std::shared_ptr gyroFrameCompleteTask = std::make_shared(mListener, imuHeader, mTM2CorrelatedTimeStampShift, this); + if (gyroFrameCompleteTask != NULL) + { + TrackingData::GyroFrame* frame = &gyroFrameCompleteTask->mFrame; + int64_t offset = (int64_t)(frame->frameId) - (int64_t)gyroPrevFrame[frame->sensorIndex].prevFrameId; + if (gyroPrevFrame[frame->sensorIndex].prevFrameId != 0) + { + if (offset > 1) + { + DEVICELOGW("Gyro[%d] frame drops occurred - %lld missing frames [FrameId %d-%d], time diff = %d (msec)", + frame->sensorIndex, offset - 1, gyroPrevFrame[frame->sensorIndex].prevFrameId + 1, frame->frameId - 1, ns2ms(frame->timestamp - gyroPrevFrame[frame->sensorIndex].prevFrameTimeStamp)); + } + else if (offset < 1) + { + DEVICELOGW("Gyro[%d] frame reorder occurred - prev frameId = %d, new frameId = %d, time diff = %d (msec)", + frame->sensorIndex, gyroPrevFrame[frame->sensorIndex].prevFrameId, frame->frameId, ns2ms(frame->timestamp - gyroPrevFrame[frame->sensorIndex].prevFrameTimeStamp)); + } + } + + gyroPrevFrame[frame->sensorIndex].prevFrameTimeStamp = frame->timestamp; + gyroPrevFrame[frame->sensorIndex].prevFrameId = frame->frameId; + + + std::shared_ptr completeTask = gyroFrameCompleteTask; + mTaskHandler->addTask(completeTask); + break; + } + } + + case SensorType::Velocimeter: + { + std::shared_ptr velocimeterFrameCompleteTask = std::make_shared(mListener, imuHeader, mTM2CorrelatedTimeStampShift, this); + if (velocimeterFrameCompleteTask != NULL) + { + TrackingData::VelocimeterFrame* frame = &velocimeterFrameCompleteTask->mFrame; + int64_t offset = (int64_t)(frame->frameId) - (int64_t)velocimeterPrevFrame[frame->sensorIndex].prevFrameId; + if (velocimeterPrevFrame[frame->sensorIndex].prevFrameId != 0) + { + if (offset > 1) + { + DEVICELOGW("Velocimeter[%d] frame drops occurred - %lld missing frames [FrameId %d-%d], time diff = %d (msec)", + frame->sensorIndex, offset - 1, velocimeterPrevFrame[frame->sensorIndex].prevFrameId + 1, frame->frameId - 1, ns2ms(frame->timestamp - velocimeterPrevFrame[frame->sensorIndex].prevFrameTimeStamp)); + } + else if (offset < 1) + { + DEVICELOGW("Velocimeter[%d] frame reorder occurred - prev frameId = %d, new frameId = %d, time diff = %d (msec)", + frame->sensorIndex, velocimeterPrevFrame[frame->sensorIndex].prevFrameId, frame->frameId, ns2ms(frame->timestamp - velocimeterPrevFrame[frame->sensorIndex].prevFrameTimeStamp)); + } + } + + velocimeterPrevFrame[frame->sensorIndex].prevFrameTimeStamp = frame->timestamp; + velocimeterPrevFrame[frame->sensorIndex].prevFrameId = frame->frameId; + + std::shared_ptr completeTask = velocimeterFrameCompleteTask; + mTaskHandler->addTask(completeTask); + break; + } + } + + case SensorType::Accelerometer: + { + std::shared_ptr accelerometerFrameCompleteTask = std::make_shared(mListener, imuHeader, mTM2CorrelatedTimeStampShift, this); + if (accelerometerFrameCompleteTask != NULL) + { + TrackingData::AccelerometerFrame* frame = &accelerometerFrameCompleteTask->mFrame; + int64_t offset = (int64_t)(frame->frameId) - (int64_t)accelerometerPrevFrame[frame->sensorIndex].prevFrameId; + if (accelerometerPrevFrame[frame->sensorIndex].prevFrameId != 0) + { + if (offset > 1) + { + DEVICELOGW("Accelerometer[%d] frame drops occurred - %lld missing frames [FrameId %d-%d], time diff = %d (msec)", + frame->sensorIndex, offset - 1, accelerometerPrevFrame[frame->sensorIndex].prevFrameId + 1, frame->frameId - 1, ns2ms(frame->timestamp - accelerometerPrevFrame[frame->sensorIndex].prevFrameTimeStamp)); + } + else if (offset < 1) + { + DEVICELOGW("Accelerometer[%d] frame reorder occurred - prev frameId = %d, new frameId = %d, time diff = %d (msec)", + frame->sensorIndex, accelerometerPrevFrame[frame->sensorIndex].prevFrameId, frame->frameId, ns2ms(frame->timestamp - accelerometerPrevFrame[frame->sensorIndex].prevFrameTimeStamp)); + } + } + + accelerometerPrevFrame[frame->sensorIndex].prevFrameTimeStamp = frame->timestamp; + accelerometerPrevFrame[frame->sensorIndex].prevFrameId = frame->frameId; + + std::shared_ptr completeTask = accelerometerFrameCompleteTask; + mTaskHandler->addTask(completeTask); + break; + } + } + + default: + DEVICELOGE("Unsupported sensor (0x%X) on interrupt endpoint thread function", imuHeader->bSensorID); + break; + } + break; + } + + case DEV_STATUS: + { + interrupt_message_status* status = (interrupt_message_status*)header; + DEVICELOGD("Got DEV status %s (0x%X) on interrupt endpoint", statusCodeToString((MESSAGE_STATUS)status->wStatus).c_str(), status->wStatus); + + auto hostStatus = fwToHostStatus((MESSAGE_STATUS)status->wStatus); + + /* DEVICE_STOPPED is indicated once when device exists from ACTIVE_STATE, all other statuses will be indicated here */ + if (hostStatus != Status::DEVICE_STOPPED) + { + /* Creating onStatusEvent to indicate hostStatus */ + std::shared_ptr ptr = std::make_shared(mListener, hostStatus, this); + mTaskHandler->addTask(ptr); + } + + break; + } + + case SLAM_STATUS: + { + interrupt_message_status* status = (interrupt_message_status*)header; + DEVICELOGD("Got SLAM status %s (0x%X) on interrupt endpoint", slamStatusCodeToString((SLAM_STATUS_CODE)status->wStatus).c_str(), status->wStatus); + + if ((SLAM_STATUS_CODE)status->wStatus != SLAM_STATUS_CODE_SUCCESS) + { + auto hostStatus = slamToHostStatus((SLAM_STATUS_CODE)status->wStatus); + std::shared_ptr ptr = std::make_shared(mListener, hostStatus, this); + mTaskHandler->addTask(ptr); + } + + break; + } + + case SLAM_ERROR: + { + interrupt_message_slam_error* error = (interrupt_message_slam_error*)header; + DEVICELOGD("Got SLAM error %s (0x%X) on interrupt endpoint", slamErrorCodeToString((SLAM_ERROR_CODE)error->wStatus).c_str(), error->wStatus); + + if ((SLAM_ERROR_CODE)error->wStatus != SLAM_ERROR_CODE_NONE) + { + auto hostStatus = slamErrorToHostStatus((SLAM_ERROR_CODE)error->wStatus); + std::shared_ptr ptr = std::make_shared(mListener, hostStatus, this); + mTaskHandler->addTask(ptr); + } + + break; + } + + case CONTROLLER_DEVICE_DISCOVERY_EVENT: + { + interrupt_message_controller_device_discovery* controllerDeviceDiscovery = (interrupt_message_controller_device_discovery*)header; + std::shared_ptr ptr = std::make_shared(mListener, controllerDeviceDiscovery, this); + + DEVICELOGD("Controller discovered: Mac Address [%02X:%02X:%02X:%02X:%02X:%02X], AddressType [0x%X], Manufacturer ID [0x%X], Vendor Data [0x%X], App [%u.%u.%u], Boot Loader [%u.%u.%u], Soft Device [%u], Protocol [%u]", + controllerDeviceDiscovery->bMacAddress[0], controllerDeviceDiscovery->bMacAddress[1], controllerDeviceDiscovery->bMacAddress[2], controllerDeviceDiscovery->bMacAddress[3], controllerDeviceDiscovery->bMacAddress[4], controllerDeviceDiscovery->bMacAddress[5], + controllerDeviceDiscovery->bAddressType, + controllerDeviceDiscovery->info.wManufacturerId, + controllerDeviceDiscovery->info.bVendorData, + controllerDeviceDiscovery->info.bAppVersionMajor, controllerDeviceDiscovery->info.bAppVersionMinor, controllerDeviceDiscovery->info.bAppVersionPatch, + controllerDeviceDiscovery->info.bBootloaderVersionMajor, controllerDeviceDiscovery->info.bBootloaderVersionMinor, controllerDeviceDiscovery->info.bBootloaderVersionPatch, + controllerDeviceDiscovery->info.bSoftdeviceVersion, + controllerDeviceDiscovery->info.bProtocolVersion); + + mTaskHandler->addTask(ptr); + break; + } + + case CONTROLLER_CALIBRATION_STATUS_EVENT: + { + interrupt_message_controller_calibration_status* calibrationStatus = (interrupt_message_controller_calibration_status*)header; + DEVICELOGD("Got Controller[%d] calibration status %s (0x%X) on interrupt endpoint", calibrationStatus->bControllerID, controllerCalibrationStatusCodeToString((CONTROLLER_CALIBRATION_STATUS_CODE)calibrationStatus->wStatus).c_str(), calibrationStatus->wStatus); + + auto hostStatus = controllerCalibrationToHostStatus((CONTROLLER_CALIBRATION_STATUS_CODE)calibrationStatus->wStatus); + std::shared_ptr ptr = std::make_shared(mListener, calibrationStatus->bControllerID, hostStatus, this); + mTaskHandler->addTask(ptr); + break; + } + + case CONTROLLER_DEVICE_CONNECTED_EVENT: + { + interrupt_message_controller_connected* controllerDeviceConnect = (interrupt_message_controller_connected*)header; + std::shared_ptr ptr = std::make_shared(mListener, controllerDeviceConnect, this); + + std::string status = "Error"; + switch ((MESSAGE_STATUS)controllerDeviceConnect->wStatus) + { + case MESSAGE_STATUS::SUCCESS: + status = "Success"; + break; + case MESSAGE_STATUS::TIMEOUT: + status = "Timeout"; + break; + case MESSAGE_STATUS::INCOMPATIBLE: + status = "Incompatible"; + break; + } + + DEVICELOGD("---------------------------------------"); + DEVICELOGD("Controller %d connection Info", controllerDeviceConnect->bControllerID); + DEVICELOGD("--------------------+------------------"); + DEVICELOGD("Status | %s (0x%X)", status.c_str(), controllerDeviceConnect->wStatus); + DEVICELOGD("Controller ID | %d", controllerDeviceConnect->bControllerID); + DEVICELOGD("Manufacturer ID | 0x%X", controllerDeviceConnect->info.wManufacturerId); + DEVICELOGD("App Version | %u.%u.%u", controllerDeviceConnect->info.bAppVersionMajor, controllerDeviceConnect->info.bAppVersionMinor, controllerDeviceConnect->info.bAppVersionPatch); + DEVICELOGD("Boot Loader Version | %u.%u.%u", controllerDeviceConnect->info.bBootloaderVersionMajor, controllerDeviceConnect->info.bBootloaderVersionMinor, controllerDeviceConnect->info.bBootloaderVersionPatch); + DEVICELOGD("Soft Device Version | %u", controllerDeviceConnect->info.bSoftdeviceVersion); + DEVICELOGD("Protocol Version | %u", controllerDeviceConnect->info.bProtocolVersion); + DEVICELOGD("--------------------+------------------"); + + mTaskHandler->addTask(ptr); + break; + } + + case CONTROLLER_DEVICE_DISCONNECTED_EVENT: + { + interrupt_message_controller_disconnected* controllerDeviceDisconnect = (interrupt_message_controller_disconnected*)header; + std::shared_ptr ptr = std::make_shared(mListener, controllerDeviceDisconnect, this); + mTaskHandler->addTask(ptr); + break; + } + + case CONTROLLER_DEVICE_LED_INTENSITY_EVENT: + { + interrupt_message_controller_led_intensity *ledMessage = (interrupt_message_controller_led_intensity*)msgBuffer; + std::shared_ptr ptr = std::make_shared(mListener, ledMessage, mTM2CorrelatedTimeStampShift, this); + mTaskHandler->addTask(ptr); + break; + } + + case SLAM_SET_LOCALIZATION_DATA_STREAM: + { + interrupt_message_set_localization_data_stream* setLocalizationDataStream = (interrupt_message_set_localization_data_stream*)header; + std::shared_ptr ptr = std::make_shared(mListener, setLocalizationDataStream, this); + + DEVICELOGV("Got Set Localization Data frame complete: status = 0x%X", setLocalizationDataStream->wStatus); + + MessageON_ASYNC_STOP setMsg; + mDispatcher->postMessage(&mFsm, setMsg); + + mTaskHandler->addTask(ptr); + break; + } + + case DEV_FIRMWARE_UPDATE: + { + interrupt_message_fw_update_stream* fwUpdateStream = (interrupt_message_fw_update_stream*)header; + std::shared_ptr ptr = std::make_shared(mListener, fwUpdateStream, this); + + uint32_t isCentralFWUpdate = 0; + for (int i = 0; i < MAC_ADDRESS_SIZE; i++) + { + isCentralFWUpdate += fwUpdateStream->bMacAddress[i]; + } + if (isCentralFWUpdate == 0) + { + DEVICELOGD("Got Central FW update frame: status = 0x%X, progress = %u%%", fwUpdateStream->wStatus, fwUpdateStream->bProgress); + if (fwUpdateStream->wStatus != (uint16_t)MESSAGE_STATUS::SUCCESS) + { + DEVICELOGE("Failed on BLE FW update with status = 0x%X", fwUpdateStream->wStatus); + mDispatcher->postMessage(&mFsm, Message(ON_ERROR)); + } + } + else + { + DEVICELOGD("Got Controller FW update frame: MAC address [%02X:%02X:%02X:%02X:%02X:%02X], status = 0x%X, progress = %u%%", + fwUpdateStream->bMacAddress[0], fwUpdateStream->bMacAddress[1], fwUpdateStream->bMacAddress[2], fwUpdateStream->bMacAddress[3], fwUpdateStream->bMacAddress[4], fwUpdateStream->bMacAddress[5], + fwUpdateStream->wStatus, fwUpdateStream->bProgress); + } + + if ((fwUpdateStream->wStatus != (uint16_t)MESSAGE_STATUS::SUCCESS || + fwUpdateStream->bProgress == 100) && mFsm.getCurrentState() == ASYNC_STATE) + { + mAsyncCV.notify_one(); + } + mTaskHandler->addTask(ptr); + break; + } + + default: + DEVICELOGE("Unsupported message (0x%X) on interrupt endpoint thread function", header->wMessageID); + break; + } + } + + mInterruptEndpointThreadActive = false; + DEVICELOGD("Thread Stop - Interrupt thread (Accelerometer/Velocimeter/Gyro/6DOF/Controller/Localization events)"); + return; + } + + Status Device::GetDeviceInfoInternal() + { + bulk_message_request_get_device_info request = {0}; + bulk_message_response_get_device_info response = {0}; + std::stringstream portChainString; + + request.header.wMessageID = DEV_GET_DEVICE_INFO; + request.header.dwLength = sizeof(request); + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + mFsm.fireEvent(msg); + + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + if (memcmp(&mDeviceInfo, &response.message, sizeof(mDeviceInfo)) == 0) + { + /* Device info didn't change */ + return Status::SUCCESS; + } + + mDeviceInfo = response.message; + + if (mUsbDescriptor.portChainDepth > 1) + { + const char* seperator = ""; + portChainString << "(PC "; + + for (uint8_t i = 0; i < mUsbDescriptor.portChainDepth; i++) + { + portChainString << seperator << (unsigned int)mUsbDescriptor.portChain[i]; + seperator = "-->"; + } + portChainString << " Camera)"; + } + + auto libusbVersion = libusb_get_version(); + + DEVICELOGD("-----------------------------------------------------------------------------"); + DEVICELOGD("Device Info"); + DEVICELOGD("-----------------------------------------------------------------------------"); + DEVICELOGD("USB | Vendor ID | 0x%04X", mUsbDescriptor.idVendor); + DEVICELOGD(" | Product ID | 0x%04X", mUsbDescriptor.idProduct); + DEVICELOGD(" | Speed | %s (0x%04X)", UsbPlugListener::usbSpeed(mUsbDescriptor.bcdUSB), mUsbDescriptor.bcdUSB); + DEVICELOGD(" | Bus Number | %u", mUsbDescriptor.bus); + DEVICELOGD(" | Port Number | %u %s", mUsbDescriptor.port, portChainString.str().c_str()); + DEVICELOGD("HW | Type | 0x%X (%s)", mDeviceInfo.bDeviceType, (mDeviceInfo.bDeviceType == 0x1) ? "TM2" : "Error"); + DEVICELOGD(" | Status | 0x%X (%s)", (mDeviceInfo.bStatus & 0x7), ((mDeviceInfo.bStatus & 0x7) == 0) ? "Ok" : "Error"); + DEVICELOGD(" | Status Code | 0x%X", mDeviceInfo.dwStatusCode); + DEVICELOGD(" | Extended Status | 0x%X", mDeviceInfo.dwExtendedStatus); + DEVICELOGD(" | HW Version | 0x%X (%s)", mDeviceInfo.bHwVersion, hwVersion(mDeviceInfo.bHwVersion)); + DEVICELOGD(" | ROM Version | 0x%08X", mDeviceInfo.dwRomVersion); + DEVICELOGD(" | EEPROM Version | %u.%u", mDeviceInfo.bEepromDataMajor, mDeviceInfo.bEepromDataMinor); + DEVICELOGD(" | EEPROM Lock State | %s (0x%X)", (mDeviceInfo.bEepromLocked == EEPROM_LOCK_STATE_PERMANENT_LOCKED) ? "Permanent locked" : ((mDeviceInfo.bEepromLocked == EEPROM_LOCK_STATE_LOCKED) ? "Locked" : (mDeviceInfo.bEepromLocked == EEPROM_LOCK_STATE_WRITEABLE) ? "Writeable" : "Unknown"), mDeviceInfo.bEepromLocked); + DEVICELOGD(" | Serial Number | %" PRIx64, bytesSwap(mDeviceInfo.llSerialNumber) >> 16); + DEVICELOGD("FW | FW Version | %u.%u.%u.%u", mDeviceInfo.bFWVersionMajor, mDeviceInfo.bFWVersionMinor, mDeviceInfo.bFWVersionPatch, mDeviceInfo.dwFWVersionBuild); + DEVICELOGD(" | FW Interface Version | %u.%u", mFWInterfaceVersion.dwMajor, mFWInterfaceVersion.dwMinor); + DEVICELOGD(" | Central App Version | %u.%u.%u.%u", mDeviceInfo.bCentralAppVersionMajor, mDeviceInfo.bCentralAppVersionMinor, mDeviceInfo.bCentralAppVersionPatch, mDeviceInfo.dwCentralAppVersionBuild); + DEVICELOGD(" | Central Boot Loader Version | %u.%u.%u", mDeviceInfo.bCentralBootloaderVersionMajor, mDeviceInfo.bCentralBootloaderVersionMinor, mDeviceInfo.bCentralBootloaderVersionPatch); + DEVICELOGD(" | Central Soft Device Version | %u", mDeviceInfo.bCentralSoftdeviceVersion); + DEVICELOGD(" | Central Protocol Version | %u", mDeviceInfo.bCentralProtocolVersion); + DEVICELOGD("Host | Host Version | %u.%u.%u.%u", LIBTM_VERSION_MAJOR, LIBTM_VERSION_MINOR, LIBTM_VERSION_PATCH, LIBTM_VERSION_BUILD); + DEVICELOGD(" | Host Interface Version | %u.%u", LIBTM_API_VERSION_MAJOR, LIBTM_API_VERSION_MINOR); + DEVICELOGD(" | Status | 0x%X (%s)", mDeviceStatus, (mDeviceStatus == Status::SUCCESS) ? "Ok" : "Error"); + DEVICELOGD(" | Device ID | 0x%p", this); + DEVICELOGD("LibUsb | LibUsb Version | %u.%u.%u.%u %s", libusbVersion->major, libusbVersion->minor, libusbVersion->micro, libusbVersion->nano, libusbVersion->rc); + DEVICELOGD("-----------------------------------------------------------------------------"); + DEVICELOGD(" "); + + switch (mDeviceInfo.dwStatusCode) + { + case FW_STATUS_CODE_OK: + return (Status)msg.Result; + break; + case FW_STATUS_CODE_FAIL: + DEVICELOGE("FW Error: Device info returned init failed"); + return Status::ERROR_FW_INTERNAL; + break; + case FW_STATUS_CODE_NO_CALIBRATION_DATA: + DEVICELOGW("FW Error: ******** Device is not calibrated! ********"); + return (Status)msg.Result; + break; + default: + DEVICELOGE("FW Error: Device unknown error (0x%X)", mDeviceInfo.dwStatusCode); + return Status::ERROR_FW_INTERNAL; + break; + } + } + + Status Device::DeviceFlush() + { + bulk_message_request_flush request = {0}; + unsigned char msgBuffer[BUFFER_SIZE] = {0}; + bulk_message_response_flush* response = (bulk_message_response_flush*)&msgBuffer; + bool stopFlush = false; + int actual = 0; + + request.header.wMessageID = DEV_FLUSH; + request.header.dwLength = sizeof(request); + request.ddwToken = std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + + DEVICELOGD("Flushing Command EndPoint - Start"); + + int rc = libusb_bulk_transfer(mDevice, mEndpointBulkMessages | TO_DEVICE, (uint8_t*)&request, BUFFER_SIZE, &actual, USB_TRANSFER_FAST_TIMEOUT_MS); + if (rc != 0 || actual != BUFFER_SIZE) // lets assume no message will be more than 2Gb size + { + DEVICELOGE("USB Error (0x%X)",rc); + return Status::ERROR_USB_TRANSFER; + } + + while (stopFlush == false) + { + DEVICELOGD("Flushing Command EndPoint..."); + rc = libusb_bulk_transfer(mDevice, mEndpointBulkMessages | TO_HOST, (unsigned char*)response, BUFFER_SIZE, &actual, USB_TRANSFER_FAST_TIMEOUT_MS); + + if (response->header.wStatus == toUnderlying(MESSAGE_STATUS::UNKNOWN_MESSAGE_ID)) + { + DEVICELOGE("Command %s is not supported by FW", messageCodeToString(LIBUSB_TRANSFER_TYPE_BULK, response->header.wMessageID).c_str()); + //return Status::NOT_SUPPORTED_BY_FW; + stopFlush = true; + } + + switch (response->header.wMessageID) + { + case DEV_FLUSH: + { + if (response->ddwToken == request.ddwToken) + { + stopFlush = true; + } + break; + } + } + } + + DEVICELOGD("Flushing Command EndPoint - Finish"); + + DEVICELOGD("Flushing Stream EndPoint - Start"); + stopFlush = false; + + while (stopFlush == false) + { + DEVICELOGD("Flushing Stream EndPoint..."); + auto rc = libusb_bulk_transfer(mDevice, mStreamEndpoint | TO_HOST, (unsigned char *)response, BUFFER_SIZE, &actual, USB_TRANSFER_FAST_TIMEOUT_MS); + if (rc == LIBUSB_ERROR_TIMEOUT) + continue; + + if (rc != 0 || actual == 0) + { + DEVICELOGE("Error while flushing stream endpoint (0x%X)", rc); + mDispatcher->postMessage(&mFsm, Message(ON_ERROR)); + break; + } + + if ((response->header.wMessageID == DEV_FLUSH) && (response->ddwToken == request.ddwToken)) + { + stopFlush = true; + } + } + + DEVICELOGD("Flushing Stream EndPoint - Finish"); + + DEVICELOGD("Flushing Event EndPoint - Start"); + stopFlush = false; + + while (stopFlush == false) + { + DEVICELOGD("Flushing Event EndPoint..."); + auto rc = libusb_interrupt_transfer(mDevice, mEndpointInterrupt, (unsigned char *)response, BUFFER_SIZE, &actual, USB_TRANSFER_FAST_TIMEOUT_MS); + if (rc == LIBUSB_ERROR_TIMEOUT) + { + continue; + } + + if (rc != 0 || actual == 0) + { + DEVICELOGE("Error while flushing event endpoint (0x%X)", rc); + mDispatcher->postMessage(&mFsm, Message(ON_ERROR)); + break; + } + + if ((response->header.wMessageID == DEV_FLUSH) && (response->ddwToken == request.ddwToken)) + { + stopFlush = true; + } + } + DEVICELOGD("Flushing Event EndPoint - Finish"); + + return Status::SUCCESS; + } + + Status Device::SetDeviceStreamConfig(uint32_t maxSize) + { + bulk_message_request_stream_config request = {0}; + bulk_message_response_stream_config response = {0}; + + request.header.wMessageID = DEV_STREAM_CONFIG; + request.header.dwLength = sizeof(request); + request.dwMaxSize = maxSize; + + DEVICELOGD("Set device stream config - MaxStreamEndpointSize = %d", request.dwMaxSize); + + Bulk_Message msg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST, USB_TRANSFER_FAST_TIMEOUT_MS); + mFsm.fireEvent(msg); + + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", msg.Result); + return Status::ERROR_USB_TRANSFER; + } + + return fwToHostStatus((MESSAGE_STATUS)response.header.wStatus); + } + + Status Device::GetUsbConnectionDescriptor() + { + libusb_device_descriptor desc = { 0 }; + auto rc = libusb_get_device_descriptor(mLibusbDevice, &desc); + if (rc != 0) + { + DEVICELOGE("Error: Failed to get device descriptor. LIBUSB_ERROR_CODE: 0x%X (%s)", rc, libusb_error_name(rc)); + return Status::COMMON_ERROR; + } + + mUsbDescriptor.idProduct = desc.idProduct; + mUsbDescriptor.idVendor = desc.idVendor; + mUsbDescriptor.bcdUSB = desc.bcdUSB; + mUsbDescriptor.bus = libusb_get_bus_number(mLibusbDevice); + mUsbDescriptor.port = libusb_get_port_number(mLibusbDevice); + mUsbDescriptor.portChainDepth = libusb_get_port_numbers(mLibusbDevice, &mUsbDescriptor.portChain[0], 64); + + return Status::SUCCESS; + } + + Status Device::GetInterfaceVersionInternal() + { + control_message_request_get_interface_version request = {0}; + control_message_response_get_interface_version response = {0}; + + request.header.bmRequestType = (LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE); + request.header.bRequest = CONTROL_GET_INTERFACE_VERSION; + + Control_Message msg((uint8_t*)&request, sizeof(request), (uint8_t*)&response, sizeof(response)); + + /* Calling onControlMessage */ + mFsm.fireEvent(msg); + if (msg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("Error Transferring CONTROL_GET_INTERFACE_VERSION"); + return Status::ERROR_USB_TRANSFER; + } + + mFWInterfaceVersion = response.message; + DEVICELOGV("Interface Version = %d.%d", mFWInterfaceVersion.dwMajor, mFWInterfaceVersion.dwMinor); + + return Status::SUCCESS; + } + + Status Device::GetDeviceInfo(TrackingData::DeviceInfo& info) + { + Status status = Status::SUCCESS; + status = GetDeviceInfoInternal(); + if (status != Status::SUCCESS) + { + DEVICELOGE("Error: Get Device Info error (0x%X)", status); + return status; + } + + if (mDeviceInfo.bDeviceType > 1) + { + DEVICELOGE("Error reading device info"); + return Status::COMMON_ERROR; + } + + info.usbDescriptor = mUsbDescriptor; + info.version.deviceInterface.set(mFWInterfaceVersion.dwMajor, mFWInterfaceVersion.dwMinor); + info.version.eeprom.set(mDeviceInfo.bEepromDataMajor, mDeviceInfo.bEepromDataMinor); + info.version.host.set(LIBTM_VERSION_MAJOR, LIBTM_VERSION_MINOR, LIBTM_VERSION_PATCH, LIBTM_VERSION_BUILD); + info.version.fw.set(mDeviceInfo.bFWVersionMajor, mDeviceInfo.bFWVersionMinor, mDeviceInfo.bFWVersionPatch, mDeviceInfo.dwFWVersionBuild); + info.version.rom.set(mDeviceInfo.dwRomVersion); + info.version.centralApp.set(mDeviceInfo.bCentralAppVersionMajor, mDeviceInfo.bCentralAppVersionMinor, mDeviceInfo.bCentralAppVersionPatch, mDeviceInfo.dwCentralAppVersionBuild); + info.version.centralBootLoader.set(mDeviceInfo.bCentralBootloaderVersionMajor, mDeviceInfo.bCentralBootloaderVersionMinor, mDeviceInfo.bCentralBootloaderVersionPatch); + info.version.centralSoftDevice.set(mDeviceInfo.bCentralSoftdeviceVersion); + info.version.centralProtocol.set(mDeviceInfo.bCentralProtocolVersion); + info.version.hw.set(mDeviceInfo.bHwVersion); + info.serialNumber = bytesSwap(mDeviceInfo.llSerialNumber); + info.deviceType = mDeviceInfo.bDeviceType; + info.status.host = mDeviceStatus; + + switch (mDeviceInfo.dwStatusCode) + { + case FW_STATUS_CODE_OK: + info.status.hw = Status::SUCCESS; + break; + case FW_STATUS_CODE_FAIL: + info.status.hw = Status::INIT_FAILED; + break; + case FW_STATUS_CODE_NO_CALIBRATION_DATA: + info.status.hw = Status::NO_CALIBRATION_DATA; + break; + default: + info.status.hw = Status::COMMON_ERROR; + break; + } + + switch (mDeviceInfo.bEepromLocked) + { + case EEPROM_LOCK_STATE_WRITEABLE: + info.eepromLockState = LockStateWriteable; + break; + case EEPROM_LOCK_STATE_LOCKED: + info.eepromLockState = LockStateLocked; + break; + case EEPROM_LOCK_STATE_PERMANENT_LOCKED: + info.eepromLockState = LockStatePermanentLocked; + break; + default: + info.eepromLockState = LockStateMax; + break; + } + + info.numAccelerometerProfiles = (uint8_t)mAccelerometerProfiles.size(); + info.numGyroProfile = (uint8_t)mGyroProfiles.size(); + info.numVelocimeterProfile = (uint8_t)mVelocimeterProfiles.size(); + info.numVideoProfiles = (uint8_t)mVideoProfiles.size(); + + return Status::SUCCESS; + } + + Status Device::SetPlayback(bool on) + { + MessageON_PLAYBACK msg(on); + + mDispatcher->sendMessage(&mFsm, msg); + return (Status)msg.Result; + } + + // -[FSM]---------------------------------------------------------------------- + // + // [FSM State::ANY] + + // [FSM State::IDLE] + DEFINE_FSM_STATE_ENTRY(Device, IDLE_STATE) + { + // DEVICELOGD("Entry to IDLE_STATE"); + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_INIT, msg) + { + msg.Result = toUnderlying(Status::SUCCESS); + } + DEFINE_FSM_GUARD(Device, IDLE_STATE, ON_START, msg) + { + /* Reset 6dof/IMU/Video device statistics from previous run to eliminate wrong drop/reorder errors */ + ResetStatistics(); + + StartThreads(true, true); + + bulk_message_request_start req; + req.header.dwLength = sizeof(bulk_message_request_start); + req.header.wMessageID = DEV_START; + bulk_message_response_start res = {0}; + + DEVICELOGD("Set Start"); + Bulk_Message msg1((uint8_t*)&req, req.header.dwLength, (uint8_t*)&res, sizeof(res), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST, USB_TRANSFER_MEDIUM_TIMEOUT_MS); + onBulkMessage(msg1); + + msg.Result = msg1.Result; + + if (msg1.Result == 0) + { + if (res.header.wStatus == 0) + { + return true; + } + + msg.Result = toUnderlying(fwToHostStatus((MESSAGE_STATUS)res.header.wStatus)); + } + + DEVICELOGE("Error: Set Start Failed (0x%X)", msg.Result); + + StopThreads(true, true, true); + + return false; + } + + DEFINE_FSM_GUARD(Device, IDLE_STATE, ON_ASYNC_START, msg) + { + StartThreads(true, true); + return true; + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_START, msg) + { + // update listener + MessageON_START m = dynamic_cast(msg); + mListener = m.listener; + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_STOP, msg) + { + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_GUARD(Device, ACTIVE_STATE, ON_STOP, msg) + { + bulk_message_request_stop req; + req.header.dwLength = sizeof(bulk_message_request_stop); + req.header.wMessageID = DEV_STOP; + bulk_message_response_stop res = {0}; + + DEVICELOGD("Set Stop"); + Bulk_Message msg1((uint8_t*)&req, req.header.dwLength, (uint8_t*)&res, sizeof(res), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST, USB_TRANSFER_MEDIUM_TIMEOUT_MS); + onBulkMessage(msg1); + + msg.Result = msg1.Result; + if (msg1.Result == 0) + { + if (res.header.wStatus == 0) + { + msg.Result = 0; + return true; + } + + msg.Result = toUnderlying(fwToHostStatus((MESSAGE_STATUS)res.header.wStatus)); + return false; + } + return false; + + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_DETACH, msg) + { + msg.Result = toUnderlying(Status::COMMON_ERROR); + } + + // [FSM State::ACTIVE] + DEFINE_FSM_STATE_ENTRY(Device, ACTIVE_STATE) + { + //DEVICELOGD("Entry to ACTIVE_STATE"); + } + + DEFINE_FSM_STATE_EXIT(Device, ACTIVE_STATE) + { + StopThreads(true, true, true); + + if (mTaskHandler) + { + /* Creating onStatusEvent to indicate DEVICE_STOPPED status */ + std::shared_ptr ptr = std::make_shared(mListener, Status::DEVICE_STOPPED, this); + mTaskHandler->addTask(ptr); + + /* Clean all events from our device completion queue */ + mTaskHandler->removeTasks(this, false); + } + + mListener = NULL; + } + + void Device::StartThreads(bool interruptThread, bool frameThread) + { + DEVICELOGV("Starting interruptThread = %s, frameThread = %s", (interruptThread ? "True" : "False"), (frameThread ? "True" : "False")); + + mStreamEndpointThreadStop = false; + mInterruptEndpointThreadStop = false; + + if (interruptThread == true) + { + /* Start Interrupt thread */ + mInterruptEPThread = std::thread(&Device::interruptEndpointThread, this); + + /* Wait for interrupt thread to start */ + while (mInterruptEndpointThreadActive == false); + } + + if (frameThread == true) + { + /* Start Bulk thread */ + mBulkEPThread = std::thread(&Device::streamEndpointThread, this); + + + /* Wait for Bulk thread to start */ + while (mStreamEndpointThreadActive == false); + } + + DEVICELOGV("All threads started"); + } + + void Device::StopThreads(bool force, bool interruptThread, bool frameThread) + { + DEVICELOGV("Stopping interruptThread = %s, frameThread = %s, force = %s", (interruptThread?"True":"False"), (frameThread ? "True" : "False"), (force ? "True" : "False")); + + if (force == true) + { + /* Time to wait before closing interrupt / event threads - give opportunity for the FW to send the DEVICE_STOPPED status to all endpoint threads */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_STOP_STATUS_MSEC)); + + mStreamEndpointThreadStop = true; + mInterruptEndpointThreadStop = true; + } + + if (interruptThread == true) + { + /* Stop Interrupt thread and wait for it to stop */ + if (mInterruptEPThread.joinable()) + { + mInterruptEPThread.join(); + } + } + + if (frameThread == true) + { + /* Stop Bulk thread and wait for it to stop */ + if (mBulkEPThread.joinable()) + { + mBulkEPThread.join(); + } + } + + DEVICELOGV("All threads stopped"); + } + + DEFINE_FSM_STATE_ENTRY(Device, ASYNC_STATE) + { + + } + + DEFINE_FSM_STATE_EXIT(Device, ASYNC_STATE) + { + StopThreads(true, true, true); + + // clean all events from our device completion queue + if (mTaskHandler) + { + mTaskHandler->removeTasks(this, true); + } + + mListener = NULL; + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_INIT, msg) + { + DEVICELOGE("State [ASYNC_STATE] got event [ON_INIT] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_DONE, msg) + { + DEVICELOGE("State [ASYNC_STATE] got event [ON_DONE] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_START, msg) + { + DEVICELOGE("State [ASYNC_STATE] got event [ON_START] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_STOP, msg) + { + DEVICELOGE("State [ASYNC_STATE] got event [ON_STOP] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_DETACH, msg) + { + DEVICELOGE("State [ASYNC_STATE] got event [ON_DETACH] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_ERROR, msg) + { + DEVICELOGE("State [ASYNC_STATE] got event [ON_ERROR] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_BULK_MESSAGE, msg) + { + onBulkMessage(msg); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_LARGE_MESSAGE, msg) + { + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_CONTROL_MESSAGE, msg) + { + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_PLAYBACK_SET, msg) + { + DEVICELOGE("State [ASYNC_STATE] got event [ON_PLAYBACK_SET] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_SET_ENABLED_STREAMS, msg) + { + DEVICELOGE("State [ASYNC_STATE] got event [ON_SET_ENABLED_STREAMS] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_ASYNC_START, msg) + { + DEVICELOGE("State [ASYNC_STATE] got event [ON_ASYNC_START] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ASYNC_STATE, ON_ASYNC_STOP, msg) + { + msg.Result = toUnderlying(Status::SUCCESS); + } + + + /** + * @brief onBulkMessage - Bulk Endpoint Protocol + * + * The bulk IN/OUT endpoints are used for general communication from the host. + * The protocol on the bulk endpoints is in a request/response convention, + * i.e. a bulk OUT transfer (request) is always followed by a bulk IN transfer (response). + */ + void Device::onBulkMessage(const Message& msg) + { + Bulk_Message usbMsg = dynamic_cast(msg); + int actual = 0; + unsigned char buffer[BUFFER_SIZE] = { 0 }; + perc::copy(buffer, usbMsg.mSrc, usbMsg.srcSize); + bulk_message_request_header* header = (bulk_message_request_header*)buffer; + + int rc = libusb_bulk_transfer(mDevice, usbMsg.mEndpointOut, buffer, BUFFER_SIZE, &actual, usbMsg.mTimeoutInMs); + + DEVICELOGV("Sent request - MessageID: 0x%X (%s), Len: %d, UsbLen: %d, Actual: %d, rc: %d (%s)", + header->wMessageID, messageCodeToString(LIBUSB_TRANSFER_TYPE_BULK, header->wMessageID).c_str(), header->dwLength, BUFFER_SIZE, actual, rc, libusb_error_name(rc)); + if (rc != 0 || actual != BUFFER_SIZE ) // lets assume no message will be more than 2Gb size + { + DEVICELOGE("ERROR: Bulk transfer message 0x%X (%s) request to device got %s. Bytes requested %d, bytes transferred %d", + header->wMessageID, messageCodeToString(LIBUSB_TRANSFER_TYPE_BULK, header->wMessageID).c_str(), libusb_error_name(rc), usbMsg.srcSize, actual); + msg.Result = toUnderlying(Status::ERROR_USB_TRANSFER); + return; + } + + rc = libusb_bulk_transfer(mDevice, usbMsg.mEndpointIn, usbMsg.mDst, usbMsg.dstSize, &actual, usbMsg.mTimeoutInMs); + + bulk_message_response_header* res = (bulk_message_response_header*)usbMsg.mDst; + + DEVICELOGV("Got response - MessageID: 0x%X (%s), Len: %d, Status: 0x%X, UsbLen: %d, Actual: %d, rc: %d (%s)", + res->wMessageID, messageCodeToString(LIBUSB_TRANSFER_TYPE_BULK, res->wMessageID).c_str(), res->dwLength, res->wStatus, usbMsg.dstSize, actual, rc, libusb_error_name(rc)); + + if ((rc == 0) && (header->wMessageID != res->wMessageID)) + { + DEVICELOGE("Command mismatch - Expected 0x%X (%s) length %d, Received 0x%X (%s) length %d", + header->wMessageID, messageCodeToString(LIBUSB_TRANSFER_TYPE_BULK, header->wMessageID).c_str(), usbMsg.dstSize, + res->wMessageID, messageCodeToString(LIBUSB_TRANSFER_TYPE_BULK, res->wMessageID).c_str(), res->dwLength); + } + + if (rc != 0 || actual != usbMsg.dstSize) // lets assume no message will be more than 2Gb size + { + msg.Result = toUnderlying(Status::ERROR_USB_TRANSFER); + if (actual == 0) + { + DEVICELOGE("ERROR: Bulk transfer message 0x%X (%s) response to host got %s. Host did not return answer", header->wMessageID, messageCodeToString(LIBUSB_TRANSFER_TYPE_BULK, header->wMessageID).c_str(),libusb_error_name(rc)); + msg.Result = toUnderlying(Status::ERROR_USB_TRANSFER); + } + else + { + if (res->wStatus == toUnderlying(MESSAGE_STATUS::UNKNOWN_MESSAGE_ID)) + { + DEVICELOGE("Command %s is not supported by FW", messageCodeToString(LIBUSB_TRANSFER_TYPE_BULK, res->wMessageID).c_str()); + msg.Result = toUnderlying(Status::NOT_SUPPORTED_BY_FW); + } + else if(actual > usbMsg.dstSize) + { + DEVICELOGD("WARNING: Bulk transfer message 0x%X (%s) response to host got %s. Bytes requested %d, bytes transferred %d", header->wMessageID, messageCodeToString(LIBUSB_TRANSFER_TYPE_BULK, header->wMessageID).c_str(), libusb_error_name(rc), usbMsg.dstSize, actual); + msg.Result = toUnderlying(Status::ERROR_USB_TRANSFER); + } + else /* actual < usbMsg.dstSize*/ + { + msg.Result = toUnderlying(Status::SUCCESS); + } + } + + return; + } + + if (res->wStatus != toUnderlying(MESSAGE_STATUS::SUCCESS)) + { + DEVICELOGE("MessageID 0x%X (%s) failed with status 0x%X", res->wMessageID, messageCodeToString(LIBUSB_TRANSFER_TYPE_BULK, header->wMessageID).c_str(), res->wStatus); + msg.Result = toUnderlying(Status::COMMON_ERROR); + } + else + { + msg.Result = toUnderlying(Status::SUCCESS); + } + } + + void Device::onControlMessage(const Message& msg) + { + Control_Message usbMsg = dynamic_cast(msg); + control_message_request_header* header = (control_message_request_header*)usbMsg.mSrc; + + DEVICELOGD("Sending Control request - MessageID: 0x%X (%s)", header->bRequest, messageCodeToString(LIBUSB_TRANSFER_TYPE_CONTROL, header->bRequest).c_str()); + int result = libusb_control_transfer(mDevice, header->bmRequestType, header->bRequest, usbMsg.mValue, usbMsg.mIndex, usbMsg.mDst, usbMsg.dstSize, usbMsg.mTimeoutInMs); + + /* Control request Successed if result == expected dstSize or got PIPE error on reset message */ + if ((result == usbMsg.dstSize) || ((header->bRequest == CONTROL_USB_RESET) && (result == LIBUSB_ERROR_PIPE))) + { + msg.Result = toUnderlying(Status::SUCCESS); + return; + } + + DEVICELOGE("ERROR %s while control transfer of messageID: 0x%X (%s)", libusb_error_name(result), header->bRequest, messageCodeToString(LIBUSB_TRANSFER_TYPE_CONTROL, header->bRequest).c_str()); + msg.Result = toUnderlying(Status::ERROR_USB_TRANSFER); + } + + void Device::SendLargeMessage(const Message& msg) + { + Large_Message* pMsg = (Large_Message*)&msg; + const uint32_t length = pMsg->mLength; + const uint8_t* buffer = pMsg->mBuffer; + mListener = pMsg->mListener; + + uint32_t maxChunkLength = MAX_BIG_DATA_MESSAGE_LENGTH - offsetof(bulk_message_large_stream, bPayload); + uint32_t leftLength = length; + uint32_t chunkLength = 0; + uint16_t index = 0; + int actual = 0; + + DEVICELOGD("Set large message send - Total length %d", length); + + auto start = systemTime(); + + bulk_message_large_stream* stream = (bulk_message_large_stream*)malloc(MAX_BIG_DATA_MESSAGE_LENGTH); + if (stream == NULL) + { + LOGE("Error allocating %d buffer", MAX_BIG_DATA_MESSAGE_LENGTH); + msg.Result = toUnderlying(Status::ALLOC_FAILED); + return; + } + + while (leftLength > 0) + { + if (leftLength > maxChunkLength) + { + chunkLength = maxChunkLength; + stream->wStatus = (uint16_t)MESSAGE_STATUS::MORE_DATA_AVAILABLE; + } + else + { + chunkLength = leftLength; + stream->wStatus = (uint16_t)MESSAGE_STATUS::SUCCESS; + } + + DEVICELOGD("Set large message - Chunk %03d: [%09d - %09d], Length %d, Left %d, Status 0x%X", index, (length - leftLength), (length - leftLength + chunkLength), chunkLength, leftLength, stream->wStatus); + + stream->header.wMessageID = pMsg->mMessageId; + stream->header.dwLength = chunkLength + offsetof(bulk_message_large_stream, bPayload); + stream->wIndex = index++; + perc::copy(&stream->bPayload, buffer + (length - leftLength), chunkLength); + + actual = 0; + auto rc = libusb_bulk_transfer(mDevice, mStreamEndpoint | TO_DEVICE, (unsigned char*)stream, stream->header.dwLength, &actual, 5000); + if (rc != 0 || actual == 0) + { + DEVICELOGE("Error while sending large message chunk %d (0x%X)", index, rc); + msg.Result = toUnderlying(Status::ERROR_USB_TRANSFER); + return; + } + leftLength -= chunkLength; + } + + auto finish = systemTime(); + DEVICELOGD("Finished setting large message data - Total length %d with %d chunks in %d (msec)", length, index, ns2ms(finish - start)); + + free(stream); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_ASYNC_START, msg) + { + MessageON_ASYNC_START pMsg = dynamic_cast(msg); + switch (pMsg.mMessageId) + { + case SLAM_GET_LOCALIZATION_DATA: + { + bulk_message_request_get_localization_data request = { 0 }; + bulk_message_response_get_localization_data response = { 0 }; + + mListener = pMsg.mListener; + request.header.wMessageID = pMsg.mMessageId; + request.header.dwLength = sizeof(request); + + DEVICELOGD("Get Localization Data"); + + Bulk_Message bulkMsg((uint8_t*)&request, request.header.dwLength, (uint8_t*)&response, sizeof(response), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + onBulkMessage(bulkMsg); + + if (bulkMsg.Result != toUnderlying(Status::SUCCESS)) + { + DEVICELOGE("USB Error (0x%X)", bulkMsg.Result); + msg.Result = toUnderlying(Status::ERROR_USB_TRANSFER); + return; + } + + msg.Result = response.header.wStatus; + break; + } + + case SLAM_SET_LOCALIZATION_DATA_STREAM: + { + return SendLargeMessage(msg); + } + + case DEV_FIRMWARE_UPDATE: + { + return SendLargeMessage(msg); + } + } + + return; + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_ASYNC_STOP, msg) + { + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_SET_ENABLED_STREAMS, msg) + { + MessageON_SET_ENABLED_STREAMS pMsg = dynamic_cast(msg); + auto message = pMsg.mMessage; + auto wNumEnabledStreams = pMsg.mNumEnabledStreams; + + for (int i = 0; i < wNumEnabledStreams; i++) + { + if (message[i].bPixelFormat != PixelFormat::ANY) + { + mFrameTempBufferSize = message[i].wHeight*message[i].wStride + sizeof(bulk_message_video_stream); + + AllocateBuffers(); + break; + } + } + + for (int i = 0; i < wNumEnabledStreams; i++) + { + if (GET_SENSOR_TYPE(message[i].bSensorID) <= SensorType::Fisheye) + { + mWidthsMap[message[i].bSensorID] = message[i].wWidth; + mHeightsMap[message[i].bSensorID] = message[i].wHeight; + } + } + + uint8_t reqBuffer[BUFFER_SIZE]; + bulk_message_request_raw_streams_control* req = (bulk_message_request_raw_streams_control*)reqBuffer; + + req->header.wMessageID = mPlaybackIsOn ? DEV_RAW_STREAMS_PLAYBACK_CONTROL : DEV_RAW_STREAMS_CONTROL; + req->wNumEnabledStreams = wNumEnabledStreams; + req->header.dwLength = wNumEnabledStreams * sizeof(supported_raw_stream_libtm_message) + sizeof(req->header) + sizeof(req->wNumEnabledStreams); + + for (int i = 0; i < wNumEnabledStreams; i++) + { + req->stream[i] = message[i]; + } + + bulk_message_response_raw_streams_control res; + + DEVICELOGD("Set %d Supported RAW Streams %sControl", req->wNumEnabledStreams, (mPlaybackIsOn)?"Playback ":""); + printSupportedRawStreams(message, wNumEnabledStreams); + + Bulk_Message msgUsb((uint8_t*)req, req->header.dwLength, (uint8_t*)&res, sizeof(res), mEndpointBulkMessages | TO_DEVICE, mEndpointBulkMessages | TO_HOST); + onBulkMessage(msgUsb); + + msg.Result = msgUsb.Result; + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_BULK_MESSAGE, msg) + { + onBulkMessage(msg); + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_LARGE_MESSAGE, msg) + { + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_PLAYBACK_SET, msg) + { + MessageON_PLAYBACK pMsg = dynamic_cast(msg); + this->mPlaybackIsOn = pMsg.set; + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_CONTROL_MESSAGE, msg) + { + onControlMessage(msg); + } + + DEFINE_FSM_ACTION(Device, IDLE_STATE, ON_ERROR, msg) + { + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ERROR_STATE, ON_STOP, msg) + { + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ERROR_STATE, ON_CONTROL_MESSAGE, msg) + { + onControlMessage(msg); + } + + DEFINE_FSM_ACTION(Device, ERROR_STATE, ON_BULK_MESSAGE, msg) + { + //DEVICELOGE("State [ERROR_STATE] got event [ON_BULK_MESSAGE] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ERROR_STATE, ON_LARGE_MESSAGE, msg) + { + //DEVICELOGE("State [ERROR_STATE] got event [ON_LARGE_MESSAGE] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_STATE_ENTRY(Device, ERROR_STATE) + { + DEVICELOGE("Entered state [ERROR_STATE]"); + mSyncTimeEnabled = false; + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_BULK_MESSAGE, msg) + { + onBulkMessage(msg); + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_LARGE_MESSAGE, msg) + { + SendLargeMessage(msg); + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_CONTROL_MESSAGE, msg) + { + onControlMessage(msg); + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_STOP, msg) + { + + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_DETACH, msg) + { + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_ERROR, msg) + { + DEVICELOGE("State [ACTIVE_STATE] got event [ON_ERROR] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_INIT, msg) + { + DEVICELOGE("State [ACTIVE_STATE] got event [ON_INIT] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_DONE, msg) + { + DEVICELOGE("State [ACTIVE_STATE] got event [ON_DONE] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_START, msg) + { + DEVICELOGE("State [ACTIVE_STATE] got event [ON_START] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_PLAYBACK_SET, msg) + { + DEVICELOGE("State [ACTIVE_STATE] got event [ON_PLAYBACK_SET] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_ASYNC_START, msg) + { + DEVICELOGE("State [ACTIVE_STATE] got event [ON_ASYNC_START] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + DEFINE_FSM_ACTION(Device, ACTIVE_STATE, ON_ASYNC_STOP, msg) + { + DEVICELOGE("State [ACTIVE_STATE] got event [ON_ASYNC_STOP] ==> [ERROR_STATE]"); + msg.Result = toUnderlying(Status::SUCCESS); + } + + // [FSM State::ERROR] + /*DEFINE_FSM_ACTION(Device, ERROR_STATE, ON_DONE, msg) + { + }*/ + + // -[Definition]--------------------------------------------------------------- + #define FSM_ERROR_TIMEOUT 1000 + + // [FSM State::IDLE] + DEFINE_FSM_STATE_BEGIN(Device, IDLE_STATE) + TRANSITION_INTERNAL(ON_INIT, 0, ACTION(Device, IDLE_STATE, ON_INIT)) + TRANSITION(ON_DETACH, 0, ACTION(Device, IDLE_STATE, ON_DETACH), ERROR_STATE) + TRANSITION(ON_START, GUARD(Device, IDLE_STATE, ON_START), ACTION(Device, IDLE_STATE, ON_START), ACTIVE_STATE) + TRANSITION(ON_STOP, 0, ACTION(Device, IDLE_STATE, ON_STOP), IDLE_STATE) + TRANSITION(ON_ERROR, 0, ACTION(Device, IDLE_STATE, ON_ERROR), ERROR_STATE) + TRANSITION(ON_ASYNC_STOP, 0, ACTION(Device, IDLE_STATE, ON_ERROR), ERROR_STATE) + TRANSITION(ON_ASYNC_START, GUARD(Device, IDLE_STATE, ON_ASYNC_START), ACTION(Device, IDLE_STATE, ON_ASYNC_START), ASYNC_STATE) + TRANSITION_INTERNAL(ON_BULK_MESSAGE, 0, ACTION(Device, IDLE_STATE, ON_BULK_MESSAGE)) + TRANSITION_INTERNAL(ON_LARGE_MESSAGE, 0, ACTION(Device, IDLE_STATE, ON_LARGE_MESSAGE)) + TRANSITION_INTERNAL(ON_CONTROL_MESSAGE, 0, ACTION(Device, IDLE_STATE, ON_CONTROL_MESSAGE)) + TRANSITION_INTERNAL(ON_PLAYBACK_SET, 0, ACTION(Device, IDLE_STATE, ON_PLAYBACK_SET)) + TRANSITION_INTERNAL(ON_SET_ENABLED_STREAMS, 0, ACTION(Device, IDLE_STATE, ON_SET_ENABLED_STREAMS)) + DEFINE_FSM_STATE_END(Device, IDLE_STATE, ENTRY(Device, IDLE_STATE), 0) + + // [FSM State::ASYNC] + DEFINE_FSM_STATE_BEGIN(Device, ASYNC_STATE) + TRANSITION(ON_INIT, 0, ACTION(Device, ASYNC_STATE, ON_INIT), ERROR_STATE) + TRANSITION(ON_DONE, 0, ACTION(Device, ASYNC_STATE, ON_DONE), ERROR_STATE) + TRANSITION(ON_START, 0, ACTION(Device, ASYNC_STATE, ON_START), ERROR_STATE) + TRANSITION(ON_STOP, 0, ACTION(Device, ASYNC_STATE, ON_STOP), ERROR_STATE) + TRANSITION(ON_DETACH, 0, ACTION(Device, ASYNC_STATE, ON_DETACH), ERROR_STATE) + TRANSITION(ON_ERROR, 0, ACTION(Device, ASYNC_STATE, ON_ERROR), ERROR_STATE) + TRANSITION(ON_PLAYBACK_SET, 0, ACTION(Device, ASYNC_STATE, ON_PLAYBACK_SET), ERROR_STATE) + TRANSITION(ON_ASYNC_START, 0, ACTION(Device, ASYNC_STATE, ON_ASYNC_START), ERROR_STATE) + TRANSITION(ON_ASYNC_STOP, 0, ACTION(Device, ASYNC_STATE, ON_ASYNC_STOP), IDLE_STATE) + TRANSITION_INTERNAL(ON_BULK_MESSAGE, 0, ACTION(Device, ASYNC_STATE, ON_BULK_MESSAGE)) + TRANSITION_INTERNAL(ON_LARGE_MESSAGE, 0, ACTION(Device, ASYNC_STATE, ON_LARGE_MESSAGE)) + TRANSITION_INTERNAL(ON_CONTROL_MESSAGE, 0, ACTION(Device, ASYNC_STATE, ON_CONTROL_MESSAGE)) + TRANSITION_AFTER(0, ACTION(Device, ASYNC_STATE, ON_ERROR), ERROR_STATE, 60000) /* Protect ASYNC state - can't be in this state more than 60 sec, usually means the async operation never completed */ + DEFINE_FSM_STATE_END(Device, ASYNC_STATE, ENTRY(Device, ASYNC_STATE), EXIT(Device, ASYNC_STATE)) + + // [FSM State::ACTIVE] + DEFINE_FSM_STATE_BEGIN(Device, ACTIVE_STATE) + TRANSITION(ON_STOP, GUARD(Device, ACTIVE_STATE, ON_STOP), ACTION(Device, ACTIVE_STATE, ON_STOP), IDLE_STATE) + TRANSITION(ON_STOP, 0, ACTION(Device, ACTIVE_STATE, ON_STOP), ERROR_STATE) + TRANSITION(ON_DETACH, 0, ACTION(Device, ACTIVE_STATE, ON_DETACH), ERROR_STATE) + TRANSITION(ON_ERROR, 0, ACTION(Device, ACTIVE_STATE, ON_ERROR), ERROR_STATE) + TRANSITION(ON_INIT, 0, ACTION(Device, ACTIVE_STATE, ON_INIT), ERROR_STATE) + TRANSITION(ON_DONE, 0, ACTION(Device, ACTIVE_STATE, ON_DONE), ERROR_STATE) + TRANSITION(ON_START, 0, ACTION(Device, ACTIVE_STATE, ON_START), ERROR_STATE) + TRANSITION(ON_PLAYBACK_SET, 0, ACTION(Device, ACTIVE_STATE, ON_PLAYBACK_SET), ERROR_STATE) + TRANSITION(ON_ASYNC_START, 0, ACTION(Device, ACTIVE_STATE, ON_ASYNC_START), ERROR_STATE) + TRANSITION(ON_ASYNC_STOP, 0, ACTION(Device, ACTIVE_STATE, ON_ASYNC_STOP), IDLE_STATE) + TRANSITION_INTERNAL(ON_BULK_MESSAGE, 0, ACTION(Device, ACTIVE_STATE, ON_BULK_MESSAGE)) + TRANSITION_INTERNAL(ON_LARGE_MESSAGE, 0, ACTION(Device, ACTIVE_STATE, ON_LARGE_MESSAGE)) + TRANSITION_INTERNAL(ON_CONTROL_MESSAGE, 0, ACTION(Device, ACTIVE_STATE, ON_CONTROL_MESSAGE)) + DEFINE_FSM_STATE_END(Device, ACTIVE_STATE, ENTRY(Device, ACTIVE_STATE), EXIT(Device, ACTIVE_STATE)) + + // [FSM State::ERROR] + DEFINE_FSM_STATE_BEGIN(Device, ERROR_STATE) + //TRANSITION(ON_DONE, 0, ACTION(ERROR_STATE, ON_DONE), IDLE_STATE) + TRANSITION_INTERNAL(ON_STOP, 0, ACTION(Device, ERROR_STATE, ON_STOP)) + TRANSITION_INTERNAL(ON_BULK_MESSAGE, 0, ACTION(Device, ERROR_STATE, ON_BULK_MESSAGE)) + TRANSITION_INTERNAL(ON_LARGE_MESSAGE, 0, ACTION(Device, ERROR_STATE, ON_LARGE_MESSAGE)) + TRANSITION_INTERNAL(ON_CONTROL_MESSAGE, 0, ACTION(Device, ERROR_STATE, ON_CONTROL_MESSAGE)) + DEFINE_FSM_STATE_END(Device, ERROR_STATE, ENTRY(Device, ERROR_STATE), 0) + + // -[FSM: Definition]---------------------------------------------------------- + DEFINE_FSM_BEGIN(Device, main) + STATE(Device, IDLE_STATE) + STATE(Device, ASYNC_STATE) + STATE(Device, ACTIVE_STATE) + STATE(Device, ERROR_STATE) + DEFINE_FSM_END() + + // ---------------------------------------------------------------------------- +} // namespace diff --git a/third-party/libtm/libtm/src/Device.h b/third-party/libtm/libtm/src/Device.h new file mode 100644 index 0000000000..dc5bb85846 --- /dev/null +++ b/third-party/libtm/libtm/src/Device.h @@ -0,0 +1,416 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "TrackingCommon.h" +#include "TrackingDevice.h" +#include "TrackingData.h" +#include "Fsm.h" +#include "Message.h" +#include +#include "CompleteTask.h" +#include +#include +#include +#include + +#define INTERFACE_INDEX 0 /* TM2 device interface descriptor index */ + +#ifdef _WIN32 +#pragma warning (push) +#pragma warning (disable : 4200) +#endif +#include "libusb.h" +#ifdef _WIN32 +#pragma warning (pop) +#endif + +namespace perc +{ + class Device : public TrackingDevice, + public EventHandler, + public FrameBuffersOwner + { + public: + Device(libusb_device*, Dispatcher* dispatcher, EventHandler* owner, CompleteQueueHandler* taskHandler); + virtual ~Device(); + + // [interface] - TrackingDevice + virtual Status GetSupportedProfile(TrackingData::Profile& profile); + virtual Status Start(Listener*, TrackingData::Profile* = NULL); + virtual Status Stop(); + virtual Status GetDeviceInfo(TrackingData::DeviceInfo& info); + virtual Status GetSupportedRawStreams(TrackingData::VideoProfile* videoProfiles, TrackingData::GyroProfile* gyroProfiles, TrackingData::AccelerometerProfile* accelerometerProfiles, TrackingData::VelocimeterProfile* velocimeterProfiles = nullptr); + virtual Status SetFWLogControl(const TrackingData::LogControl& logControl); + virtual Status GetFWLog(TrackingData::Log& log); + virtual Status GetCameraIntrinsics(SensorId id, TrackingData::CameraIntrinsics& intrinsics); + virtual Status SetCameraIntrinsics(SensorId id, const TrackingData::CameraIntrinsics& intrinsics); + virtual Status GetMotionModuleIntrinsics(SensorId id, TrackingData::MotionIntrinsics& intrinsics); + virtual Status SetMotionModuleIntrinsics(SensorId id, const TrackingData::MotionIntrinsics& intrinsics); + virtual Status GetExtrinsics(SensorId id, TrackingData::SensorExtrinsics& extrinsics); + virtual Status SetOccupancyMapControl(uint8_t enable); + virtual Status GetPose(TrackingData::PoseFrame& pose, uint8_t sourceIndex); + virtual Status SetExposureModeControl(const TrackingData::ExposureModeControl& mode); + virtual Status SetExposure(const TrackingData::Exposure& exposure); + virtual Status GetTemperature(TrackingData::Temperature& temperature); + virtual Status SetTemperatureThreshold(const TrackingData::Temperature& temperature, uint32_t token); + virtual Status LockConfiguration(LockType type, bool lock); + virtual Status PermanentLockConfiguration(LockType type, uint32_t token); + virtual Status ReadConfiguration(uint16_t tableType, uint16_t size, uint8_t* buffer, uint16_t* actualSize = nullptr); + virtual Status WriteConfiguration(uint16_t tableType, uint16_t size, uint8_t* buffer); + virtual Status DeleteConfiguration(uint16_t tableType); + virtual Status GetLocalizationData(Listener* listener); + virtual Status SetLocalizationData(Listener* listener, uint32_t length, const uint8_t* buffer); + virtual Status ResetLocalizationData(uint8_t flag); + virtual Status SetStaticNode(const char* guid, const TrackingData::RelativePose& relativePose); + virtual Status GetStaticNode(const char* guid, TrackingData::RelativePose& relativePose); + virtual Status SetGeoLocation(const TrackingData::GeoLocalization& geoLocation); + virtual Status EepromRead(uint16_t offset, uint16_t size, uint8_t* buffer, uint16_t& actual); + virtual Status EepromWrite(uint16_t offset, uint16_t size, uint8_t* buffer, uint16_t& actual, bool verify = false); + virtual Status Reset(void); + virtual Status SendFrame(const TrackingData::VelocimeterFrame& frame); + virtual Status SendFrame(const TrackingData::VideoFrame& frame); + virtual Status SendFrame(const TrackingData::GyroFrame& frame); + virtual Status SendFrame(const TrackingData::AccelerometerFrame& frame); + virtual Status ControllerConnect(const TrackingData::ControllerDeviceConnect& device, uint8_t& controllerId); + virtual Status ControllerDisconnect(uint8_t controllerId); + virtual Status ControllerStartCalibration(uint8_t controllerId); + virtual Status GetAssociatedDevices(TrackingData::ControllerAssociatedDevices& devices); + virtual Status SetAssociatedDevices(const TrackingData::ControllerAssociatedDevices& devices); + virtual Status ControllerSendData(const TrackingData::ControllerData& controllerData); + virtual Status ControllerRssiTestControl(uint8_t controllerId, bool testControl); + virtual Status SetGpioControl(uint8_t gpioControl); + virtual Status ControllerFWUpdate(const TrackingData::ControllerFW& FW) override; + // [interface] EventHandler + virtual void onTimeout(uintptr_t timerId, const Message &msg); + + bool IsDeviceReady() { return (mUsbState == DEVICE_USB_STATE_READY); } + + protected: + // [interface] - EventHandler + virtual void onMessage(const Message &) override {}; + virtual void onExit() override; + + virtual void putBufferBack(SensorId id, std::shared_ptr& frame); + + // [USB States] + typedef enum { + DEVICE_USB_STATE_INIT = 0, + DEVICE_USB_STATE_OPENED, + DEVICE_USB_STATE_CLAIMED, + DEVICE_USB_STATE_READY, + DEVICE_USB_STATE_MAX + } DEVICE_USB_STATE; + + // [FSM] declaration + Fsm mFsm; + DECLARE_FSM(main); + + // [States] + enum { + IDLE_STATE = FSM_STATE_USER_DEFINED, + ASYNC_STATE, + ACTIVE_STATE, + ERROR_STATE, + }; + + // [Messages] + enum { + ON_INIT = FSM_EVENT_USER_DEFINED, + ON_DONE, + ON_START, + ON_STOP, + ON_DETACH, + ON_ERROR, + ON_BULK_MESSAGE, + ON_CONTROL_MESSAGE, + ON_PLAYBACK_SET, + ON_SET_ENABLED_STREAMS, + ON_ASYNC_START, + ON_ASYNC_STOP, + ON_LARGE_MESSAGE + }; + + /* The timeout that USB transfer should wait before giving up due to no response being received */ + enum { + USB_TRANSFER_FAST_TIMEOUT_MS = 100, /* Most of the messages uses fast timeout */ + USB_TRANSFER_MEDIUM_TIMEOUT_MS = 5000, /* All Control messages + Start/Stop/GetTemperature may need medium timeout to complete */ + USB_TRANSFER_SLOW_TIMEOUT_MS = 16000, /* Controller connect/disconnect may need slow timeout to complete */ + }; + + // [State] IDLE + DECLARE_FSM_STATE(IDLE_STATE); + DECLARE_FSM_STATE_ENTRY(IDLE_STATE); + DECLARE_FSM_ACTION(IDLE_STATE, ON_INIT); + DECLARE_FSM_ACTION(IDLE_STATE, ON_START); + DECLARE_FSM_ACTION(IDLE_STATE, ON_DETACH); + DECLARE_FSM_ACTION(IDLE_STATE, ON_PLAYBACK_SET); + DECLARE_FSM_ACTION(IDLE_STATE, ON_STOP); + DECLARE_FSM_ACTION(IDLE_STATE, ON_ERROR); + DECLARE_FSM_ACTION(IDLE_STATE, ON_BULK_MESSAGE); + DECLARE_FSM_ACTION(IDLE_STATE, ON_LARGE_MESSAGE); + DECLARE_FSM_ACTION(IDLE_STATE, ON_CONTROL_MESSAGE); + DECLARE_FSM_ACTION(IDLE_STATE, ON_SET_ENABLED_STREAMS); + DECLARE_FSM_ACTION(IDLE_STATE, ON_ASYNC_START); + DECLARE_FSM_ACTION(IDLE_STATE, ON_ASYNC_STOP); + DECLARE_FSM_GUARD(IDLE_STATE, ON_START); + DECLARE_FSM_GUARD(IDLE_STATE, ON_ASYNC_START); + + + // [State] ASYNC + DECLARE_FSM_STATE(ASYNC_STATE); + DECLARE_FSM_STATE_ENTRY(ASYNC_STATE); + DECLARE_FSM_STATE_EXIT(ASYNC_STATE); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_INIT); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_DONE); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_START); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_STOP); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_DETACH); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_ERROR); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_BULK_MESSAGE); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_LARGE_MESSAGE); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_CONTROL_MESSAGE); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_PLAYBACK_SET); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_SET_ENABLED_STREAMS); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_ASYNC_START); + DECLARE_FSM_ACTION(ASYNC_STATE, ON_ASYNC_STOP); + + + // [State] ACTIVE + DECLARE_FSM_STATE(ACTIVE_STATE); + DECLARE_FSM_STATE_ENTRY(ACTIVE_STATE); + DECLARE_FSM_STATE_EXIT(ACTIVE_STATE); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_DETACH); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_INIT); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_DONE); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_START); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_PLAYBACK_SET); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_ASYNC_START); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_ASYNC_STOP); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_ERROR); + DECLARE_FSM_GUARD(ACTIVE_STATE, ON_STOP); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_STOP); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_BULK_MESSAGE); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_LARGE_MESSAGE); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_CONTROL_MESSAGE); + + + // [State] ERROR + DECLARE_FSM_STATE(ERROR_STATE); + DECLARE_FSM_STATE_ENTRY(ERROR_STATE); + DECLARE_FSM_ACTION(ERROR_STATE, ON_STOP); + DECLARE_FSM_ACTION(ERROR_STATE, ON_BULK_MESSAGE); + DECLARE_FSM_ACTION(ERROR_STATE, ON_LARGE_MESSAGE); + DECLARE_FSM_ACTION(ERROR_STATE, ON_CONTROL_MESSAGE); + //DECLARE_FSM_ACTION(ERROR_STATE, ON_DONE); + + class CentralListener : public TrackingDevice::Listener + { + virtual void onFWUpdateEvent(OUT TrackingData::ControllerFWEventFrame& frame) override {} + }; + + class Bulk_Message : public Message { + public: + Bulk_Message(unsigned char* request, unsigned int sizeOfRequest, unsigned char* response, unsigned int sizeOfResponse, int endpointOut, int endpointIn, int timeoutInMs = USB_TRANSFER_FAST_TIMEOUT_MS) : + Message(ON_BULK_MESSAGE), srcSize(sizeOfRequest), dstSize(sizeOfResponse), mSrc(request), mDst(response), mEndpointIn(endpointIn), mEndpointOut(endpointOut), mTimeoutInMs(timeoutInMs) + { } + unsigned char* mSrc; + unsigned char* mDst; + int srcSize; + int dstSize; + int mEndpointIn; + int mEndpointOut; + int mTimeoutInMs; + }; + + class Large_Message : public Message { + public: + Large_Message(TrackingDevice::Listener *listener, const uint16_t messageId, const uint32_t length, const uint8_t* buffer) : + Message(ON_LARGE_MESSAGE), mListener(listener), mMessageId(messageId), mLength(length), mBuffer(buffer) {} + TrackingDevice::Listener *mListener; + const uint16_t mMessageId; + const uint32_t mLength; + const uint8_t* mBuffer; + }; + + class Control_Message : public Message { + public: + Control_Message(unsigned char* request, unsigned int sizeOfRequest, unsigned char* response = NULL, unsigned int sizeOfResponse = 0, uint16_t value = 0, uint16_t index = 0, unsigned int timeoutInMs = USB_TRANSFER_MEDIUM_TIMEOUT_MS) : + Message(ON_CONTROL_MESSAGE), srcSize(sizeOfRequest), dstSize(sizeOfResponse), mSrc(request), mDst(response), mValue(value), mIndex(index), mTimeoutInMs(timeoutInMs) + { } + unsigned char* mSrc; + unsigned char* mDst; + int srcSize; + int dstSize; + int mTimeoutInMs; + uint16_t mValue; + uint16_t mIndex; + }; + + // private messages + class MessageON_START : public Message { + public: + MessageON_START(TrackingDevice::Listener *l) : Message(ON_START), + listener(l){} + TrackingDevice::Listener *listener; + }; + + class MessageON_SET_ENABLED_STREAMS : public Message { + public: + MessageON_SET_ENABLED_STREAMS(supported_raw_stream_libtm_message * message, uint16_t wNumEnabledStreams) : Message(ON_SET_ENABLED_STREAMS), + mMessage(message), mNumEnabledStreams(wNumEnabledStreams) {} + supported_raw_stream_libtm_message * mMessage; + uint16_t mNumEnabledStreams; + }; + + class MessageON_PLAYBACK : public Message { + public: + MessageON_PLAYBACK(bool on) : Message(ON_PLAYBACK_SET), + set(on) {} + bool set; + }; + + class MessageON_ASYNC_START : public Message { + public: + MessageON_ASYNC_START(TrackingDevice::Listener *listener, const uint16_t messageId, const uint32_t length, const uint8_t* buffer) : + Message(ON_ASYNC_START), mListener(listener), mMessageId(messageId), mLength(length), mBuffer(buffer) {} + TrackingDevice::Listener *mListener; + const uint16_t mMessageId; + const uint32_t mLength; + const uint8_t* mBuffer; + }; + + class MessageON_ASYNC_STOP : public Message { + public: + MessageON_ASYNC_STOP() : Message(ON_ASYNC_STOP) {} + }; + + private: + CentralListener mCentralListener; + TrackingDevice::Listener* mListener; + libusb_device* mLibusbDevice; + libusb_device_handle* mDevice; + + Dispatcher* mDispatcher; + EventHandler* mOwner; + CompleteQueueHandler* mTaskHandler; + std::condition_variable mAsyncCV; + void interruptEndpointThread(); + void streamEndpointThread(); + std::thread mInterruptEPThread; + std::thread mBulkEPThread; + std::atomic mStreamEndpointThreadStop; + std::atomic mInterruptEndpointThreadStop; + std::atomic mStreamEndpointThreadActive; + std::atomic mInterruptEndpointThreadActive; + + std::mutex streamThreadMutex; + std::mutex eventThreadMutex; + + class PrevFrameData { + public: + PrevFrameData() : prevFrameTimeStamp(0), prevFrameId(0) {}; + PrevFrameData(int64_t _prevFrameTimeStamp, uint32_t _prevFrameId) : prevFrameTimeStamp(_prevFrameTimeStamp), prevFrameId(_prevFrameId) {}; + void Reset() + { + prevFrameTimeStamp = 0; + prevFrameId = 0; + }; + + int64_t prevFrameTimeStamp; + uint32_t prevFrameId; + }; + + void ResetStatistics() + { + for (uint8_t i = 0; i < SixDofProfileMax; i++) + { + sixdofPrevFrame[i].Reset(); + } + + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + videoPrevFrame[i].Reset(); + } + + for (uint8_t i = 0; i < GyroProfileMax; i++) + { + gyroPrevFrame[i].Reset(); + } + + for (uint8_t i = 0; i < AccelerometerProfileMax; i++) + { + accelerometerPrevFrame[i].Reset(); + } + } + + PrevFrameData sixdofPrevFrame[SixDofProfileMax]; + PrevFrameData videoPrevFrame[VideoProfileMax]; + PrevFrameData gyroPrevFrame[GyroProfileMax]; + PrevFrameData velocimeterPrevFrame[VelocimeterProfileMax]; + PrevFrameData accelerometerPrevFrame[AccelerometerProfileMax]; + + int mEndpointInterrupt; + int mEndpointBulkMessages; + int mStreamEndpoint; + int mEepromChunkSize; + + int FindInterruptEndpoint(); + Status SetPlayback(bool on); + Status Set6DofInterruptRate(sixdof_interrupt_rate_libtm_message message); + + void printSupportedRawStreams(supported_raw_stream_libtm_message* pRawStreams, uint32_t rawStreamsCount); + Status SetEnabledRawStreams(supported_raw_stream_libtm_message * message, uint16_t wNumEnabledStreams); + Status GetSupportedRawStreamsInternal(supported_raw_stream_libtm_message * message, uint16_t wBufferSize, uint16_t& wNumSupportedStreams); + Status SetTimeoutConfiguration(uint16_t timeoutMsec); + void onBulkMessage(const Message& msg); + void onControlMessage(const Message& msg); + void SendLargeMessage(const Message& msg); + Status CentralFWUpdate(); + Status CentralLoadFW(uint8_t* buffer); + std::mutex mDeletionMutex; + bool mCleared; + unsigned int mFrameTempBufferSize; + + device_info_libtm_message mDeviceInfo; + + Status WriteEepromChunk(uint16_t offset, uint16_t size, uint8_t* buffer, uint16_t& actual, bool verify = false); + Status ReadEepromChunk(uint16_t offset, uint16_t size, uint8_t * buffer, uint16_t& actual); + Status GetUsbConnectionDescriptor(); + Status GetDeviceInfoInternal(); + Status DeviceFlush(); + Status SetDeviceStreamConfig(uint32_t maxSize); + Status GetInterfaceVersionInternal(); + interface_version_libtm_message mFWInterfaceVersion; + TrackingData::DeviceInfo::UsbConnectionDescriptor mUsbDescriptor; + Status SyncTime(); + void AllocateBuffers(); + Status Set6DoFControl(TrackingData::SixDofProfile& profile); + Status SetController6DoFControl(bool enabled, uint8_t numOfControllers); + ProfileType getProfileType(uint8_t sensorID); + + long long mTM2CorrelatedTimeStampShift; + nsecs_t mSyncTimeout; + + void StartThreads(bool interruptThread, bool frameThread); + void StopThreads(bool force, bool interruptThread, bool frameThread); + + std::recursive_mutex mFramesBuffersMutex; /* This mutex can be acquired several times by the same thread */ + std::list> mFramesBuffersLists; + std::map mWidthsMap; + std::map mHeightsMap; + + bool mPlaybackIsOn; + bool mSyncTimeEnabled; + bool mHasBluetooth; + DEVICE_USB_STATE mUsbState; + Status mDeviceStatus; + + std::vector mGyroProfiles; + std::vector mVelocimeterProfiles; + std::vector mAccelerometerProfiles; + std::vector mVideoProfiles; + }; +} diff --git a/third-party/libtm/libtm/src/Loader.cpp b/third-party/libtm/libtm/src/Loader.cpp new file mode 100644 index 0000000000..5b0b9b480f --- /dev/null +++ b/third-party/libtm/libtm/src/Loader.cpp @@ -0,0 +1,134 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include +#include +#include "Loader.h" + +#include +#include +#include +#include + + +using namespace std; +using namespace tracking; + + + +static inline void highres_gettime(highres_time_t *ptr) { + clock_gettime(CLOCK_REALTIME, ptr); +} + +static inline double highres_elapsed_ms(highres_time_t *start, highres_time_t *end) { + struct timespec temp; + if((end->tv_nsec - start->tv_nsec) < 0) { + temp.tv_sec = end->tv_sec - start->tv_sec - 1; + temp.tv_nsec = 1000000000 + end->tv_nsec-start->tv_nsec; + } else { + temp.tv_sec = end->tv_sec - start->tv_sec; + temp.tv_nsec = end->tv_nsec - start->tv_nsec; + } + return (double)(temp.tv_sec * 1000) + (((double)temp.tv_nsec) * 0.000001); +} + + +Status tracking::loader::send_file(const char *fname) { + FILE *file; + uint8_t *tx_buf, *p; + int res; + int wb, twb, wbr; + unsigned long filesize; + double elapsedTime; + highres_time_t t1, t2; + + file = fopen(fname, "rb"); + if(file == NULL) { + perror(fname); + return FILE_IO_ERROR; + } + + fseek(file, 0, SEEK_END); + filesize = ftell(file); + rewind(file); + + if(!(tx_buf = (uint8_t*)malloc(filesize))) { + perror("buffer"); + fclose(file); + return INTERNAL_ERROR; + } + + if(fread(tx_buf, 1, filesize, file) != filesize) { + perror(fname); + fclose(file); + free(tx_buf); + return FILE_IO_ERROR; + } + fclose(file); + + elapsedTime = 0; + twb = 0; + p = tx_buf; + + printf("Performing bulk write of %lu byte%s from %s...\n", filesize, filesize == 1 ? "" : "s", fname); + + while(twb < filesize) { + highres_gettime(&t1); + wb = filesize - twb; + if(wb > DEFAULT_CHUNKSZ) + wb = DEFAULT_CHUNKSZ; + wbr = 0; + res = libusb_bulk_transfer(m_dev_handle, EP_OUT, p, wb, &wbr, TIMEOUT_MS); + if((res == USB_ERR_TIMEOUT) && (twb != 0)) + break; + highres_gettime(&t2); + if((res < 0) || (wb != wbr)) { + fprintf(stdout, "bulk write: %d\n", res); + free(tx_buf); + return USB_OPERATION_FAILED; + } + elapsedTime += highres_elapsed_ms(&t1, &t2); + twb += wbr; + p += wbr; + } + + double MBpS = + ((double)filesize / 1048576.) / + (elapsedTime * 0.001); + + printf("Successfully sent %ld bytes of data in %lf ms (%lf MB/s)\n", filesize, elapsedTime, MBpS); + + free(tx_buf); + + return SUCCESS; +} + +Status tracking::loader::load_image(char *filename) +{ + libusb_context *context = NULL; + + Status ret_val = SUCCESS; + + int st = libusb_init(&context); + + if (st!=0) + { + printf("Error while initializing libusb\n"); + return USB_INIT_ERROR; + } + + m_dev_handle = libusb_open_device_with_vid_pid(context, m_vid, m_pid); + + if (m_dev_handle == NULL) { + return INTERNAL_ERROR; + } + + ret_val = send_file(filename); + + libusb_close(m_dev_handle); + + return ret_val; +} + diff --git a/third-party/libtm/libtm/src/Loader.h b/third-party/libtm/libtm/src/Loader.h new file mode 100644 index 0000000000..1e04bdb468 --- /dev/null +++ b/third-party/libtm/libtm/src/Loader.h @@ -0,0 +1,52 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include +#include +#include +#include "Common.h" +#include "libtm_message.h" + + +#define DEV_PID 0x2150 +#define DEV_VID 0x03e7 + +#define EP_OUT 1 + +#define TIMEOUT_MS 60000 +#define DEFAULT_CHUNKSZ 1024 + +#define DEV_INTERFACE 0 + +#define USB_ERR_NONE 0 +#define USB_ERR_TIMEOUT -1 +#define USB_ERR_FAILED -2 +#define USB_ERR_INVALID -3 + +namespace tracking +{ + typedef struct timespec highres_time_t; + + class loader + { + public: + loader() : m_dev_handle(0), m_pid(DEV_PID), m_vid(DEV_VID) + { + } + + void set_pid(int pid) { m_pid = pid; } + void set_vid(int vid) { m_vid = vid; } + + Status load_image(char * filename); + + private: + + Status send_file(const char *fname); + + libusb_device_handle *m_dev_handle; + + int m_pid, m_vid; + }; +} diff --git a/third-party/libtm/libtm/src/Main.cpp b/third-party/libtm/libtm/src/Main.cpp new file mode 100644 index 0000000000..7eafaa794b --- /dev/null +++ b/third-party/libtm/libtm/src/Main.cpp @@ -0,0 +1,655 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include +#include +#include +#include "Protocol.h" +#include "Message.h" + +#define PRODUCT_ID 0xF63B +#define VENDOR_ID 0x040E +#define MAX_MESSAGE_SIZE 1024 + +void send_bulk_message_get_device_info(libusb_device_handle * device_handle) +{ + bulk_message_request_get_device_info request; + bulk_message_response_get_device_info response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 6, DEV_GET_DEVICE_INFO); + + /* Print message request */ + printf("DEV_GET_DEVICE_INFO request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_GET_DEVICE_INFO response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_get_time(libusb_device_handle * device_handle) +{ + bulk_message_request_get_time request; + bulk_message_response_get_time response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 6, DEV_GET_TIME); + + /* Print message request */ + printf("DEV_GET_TIME request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_GET_TIME response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_get_supported_raw_streams(libusb_device_handle * device_handle) +{ + bulk_message_request_get_supported_raw_streams request; + uint8_t response[MAX_MESSAGE_SIZE]; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 6, DEV_GET_SUPPORTED_RAW_STREAMS); + + /* Print message request */ + printf("DEV_GET_SUPPORTED_RAW_STREAMS request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_GET_SUPPORTED_RAW_STREAMS response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_raw_streams_control(libusb_device_handle * device_handle) +{ + bulk_message_request_raw_streams_control* request = NULL; + bulk_message_response_raw_streams_control response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + int request_length = sizeof(bulk_message_request_raw_streams_control) + sizeof(supported_raw_stream_libtm_message); + + /* request message will include one stream */ + request = (bulk_message_request_raw_streams_control*)malloc(request_length); + + /* Prepare get device info message */ + memset(request, 0, request_length); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)request, request_length, DEV_RAW_STREAMS_CONTROL); + request->wNumEnabledStreams = 1; + request->stream[0].bSensorID = SET_SENSOR_ID(SENSOR_TYPE_COLOR, 0); + request->stream[0].wFramesPerSecond = 30; + request->stream[0].wWidth = 1920; + request->stream[0].wHeight = 1080; + request->stream[0].bReserved = 0; + + /* Print message request */ + printf("DEV_RAW_STREAMS_CONTROL request\n"); + print_message((unsigned char *)request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)request, request_length, &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_RAW_STREAMS_CONTROL response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_get_camera_intrinsics(libusb_device_handle * device_handle) +{ + bulk_message_request_get_camera_intrinsics request; + bulk_message_response_get_camera_intrinsics response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 7, DEV_GET_CAMERA_INTRINSICS); + request.bCameraID = SET_SENSOR_ID(SENSOR_TYPE_COLOR, 0); + + /* Print message request */ + printf("DEV_GET_CAMERA_INTRINSICS request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_GET_CAMERA_INTRINSICS response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_get_motion_intrinsics(libusb_device_handle * device_handle) +{ + bulk_message_request_get_motion_intrinsics request; + bulk_message_response_get_motion_intrinsics response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 7, DEV_GET_MOTION_INTRINSICS); + request.bMotionID = SET_SENSOR_ID(SENSOR_TYPE_GYRO, 0); + + /* Print message request */ + printf("DEV_GET_MOTION_INTRINSICS request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_GET_MOTION_INTRINSICS response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_get_extrinsics(libusb_device_handle * device_handle) +{ + bulk_message_request_get_extrinsics request; + bulk_message_response_get_extrinsics response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 8, DEV_GET_EXTRINSICS); + request.bSensorID = SET_SENSOR_ID(SENSOR_TYPE_COLOR, 0); + request.bReferenceSensorID = SET_SENSOR_ID(SENSOR_TYPE_DEPTH, 1); + + /* Print message request */ + printf("DEV_GET_EXTRINSICS request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_GET_EXTRINSICS response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_set_camera_intrinsics(libusb_device_handle * device_handle) +{ + bulk_message_request_set_camera_intrinsics request; + bulk_message_response_set_camera_intrinsics response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + int i = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 56, DEV_SET_CAMERA_INTRINSICS); + request.bCameraID = SET_SENSOR_ID(SENSOR_TYPE_COLOR, 0); + request.intrinsics.dwWidth = 1; + request.intrinsics.dwHeight = 2; + request.intrinsics.flPpx = 3; + request.intrinsics.flPpy = 4; + request.intrinsics.flFx = 5; + request.intrinsics.flFy = 6; + request.intrinsics.dwDistortionModel = 7; + for (i = 0; i < 5; i++) + { + request.intrinsics.flCoeffs[i] = i+8; + } + + /* Print message request */ + printf("DEV_SET_CAMERA_INTRINSICS request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_SET_CAMERA_INTRINSICS response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_set_motion_intrinsics(libusb_device_handle * device_handle) +{ + bulk_message_request_set_motion_intrinsics request; + bulk_message_response_set_motion_intrinsics response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + int i = 0; + int j = 0; + int k = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 80, DEV_SET_MOTION_INTRINSICS); + request.bMotionID = SET_SENSOR_ID(SENSOR_TYPE_GYRO, 0); + + for (i = 0; i < 3; i++, k++) + { + for (j = 0; j < 4; j++, k++) + { + request.intrinsics.flData[i][j] = k; + } + } + + for (i = 0; i < 3; i++, k++) + { + request.intrinsics.flNoiseVariances[i] = k; + } + + for (i = 0; i < 3; i++, k++) + { + request.intrinsics.flBiasVariances[i] = k; + } + + /* Print message request */ + printf("DEV_SET_MOTION_INTRINSICS request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_SET_MOTION_INTRINSICS response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_set_extrinsics(libusb_device_handle * device_handle) +{ + bulk_message_request_set_extrinsics request; + bulk_message_response_set_extrinsics response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + int i = 0; + int j = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 56, DEV_SET_EXTRINSICS); + request.bSensorID = SET_SENSOR_ID(SENSOR_TYPE_COLOR, 0); + request.bReferenceSensorID = SET_SENSOR_ID(SENSOR_TYPE_DEPTH, 1); + + for (i = 0; i < 9; i++, j++) + { + request.extrinsics.flRotation[i] = j; + } + + for (i = 0; i < 3; i++, j++) + { + request.extrinsics.flTranslation[i] = j; + } + + /* Print message request */ + printf("DEV_SET_EXTRINSICS request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_SET_EXTRINSICS response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_log_control(libusb_device_handle * device_handle) +{ + bulk_message_request_log_control request; + bulk_message_response_log_control response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 56, DEV_LOG_CONTROL); + request.bVerbosity = 0; + request.bLogMode = 0; + + /* Print message request */ + printf("DEV_LOG_CONTROL request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_LOG_CONTROL response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_get_pose(libusb_device_handle * device_handle) +{ + bulk_message_request_get_pose request; + bulk_message_response_get_pose response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 6, DEV_GET_POSE); + + /* Print message request */ + printf("DEV_GET_POSE request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("DEV_GET_POSE response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_get_occupancy_map_tiles(libusb_device_handle * device_handle) +{ + bulk_message_request_get_occupancy_map_tiles request; + bulk_message_response_get_occupancy_map_tiles response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 6, SLAM_GET_OCCUPANCY_MAP_TILES); + + /* Print message request */ + printf("SLAM_GET_OCCUPANCY_MAP_TILES request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("SLAM_GET_OCCUPANCY_MAP_TILES response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_get_localization_data(libusb_device_handle * device_handle) +{ + bulk_message_request_get_localization_data request; + bulk_message_response_get_localization_data response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 6, SLAM_GET_LOCALIZATION_DATA); + + /* Print message request */ + printf("SLAM_GET_LOCALIZATION_DATA request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("SLAM_GET_LOCALIZATION_DATA response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_set_localization_data(libusb_device_handle * device_handle) +{ + bulk_message_request_set_localization_data request; + bulk_message_response_set_localization_data response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 7, SLAM_SET_LOCALIZATION_DATA); + + /* Print message request */ + printf("SLAM_SET_LOCALIZATION_DATA request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("SLAM_SET_LOCALIZATION_DATA response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_set_6dof_interrupt_rate(libusb_device_handle * device_handle) +{ + bulk_message_request_set_6dof_interrupt_rate request; + bulk_message_response_set_6dof_interrupt_rate response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 7, SLAM_SET_6DOF_INTERRUPT_RATE); + request.message.bInterruptRate = 1; + + /* Print message request */ + printf("SLAM_SET_6DOF_INTERRUPT_RATE request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("SLAM_SET_6DOF_INTERRUPT_RATE response\n"); + print_message((unsigned char *)&response); +} + +void send_bulk_message_6dof_control_enable(libusb_device_handle * device_handle) +{ + bulk_message_request_6dof_control request; + bulk_message_response_6dof_control response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 7, SLAM_6DOF_CONTROL); + request.bEnable = 1; + + /* Print message request */ + printf("SLAM_6DOF_CONTROL request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("SLAM_6DOF_CONTROL response\n"); + print_message((unsigned char *)&response); +} + +int receive_interrupt_message_6dof_pose_data(libusb_device_handle * device_handle) +{ + interrupt_message_get_pose response; + int host_data_in_bytes_received = 0; + int result = 0; + + /* Prepare get device info message */ + memset(&response, 0, sizeof(response)); + + /* receive interrupt 6dof pose data */ + result = interrupt_transfer_read_message(device_handle, (unsigned char *)&response, &host_data_in_bytes_received); + if (result == 0) + { + /* Print message response */ + printf("Interrupt 6dof pose data message\n"); + print_message((unsigned char *)&response); + } + + return result; +} + +void send_bulk_message_6dof_control_disable(libusb_device_handle * device_handle) +{ + bulk_message_request_6dof_control request; + bulk_message_response_6dof_control response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + int i = 0; + int result = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 7, SLAM_6DOF_CONTROL); + request.bEnable = 0; + + /* Print message request */ + printf("SLAM_6DOF_CONTROL request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + result = bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("SLAM_6DOF_CONTROL response\n"); + print_message((unsigned char *)&response); + + if (result == 0) + { + printf("Waiting for interrupt endpoint to be cleaned\n"); + while (result == 0) + { + result = receive_interrupt_message_6dof_pose_data(device_handle); + printf("Received interrupt 6dof pose data message %d\n",i); + i++; + } + printf("interrupt endpoint is now cleaned\n"); + + } + else + { + printf("Error disabling 6DOF\n"); + } +} + +void send_bulk_message_enable_occupancy_map(libusb_device_handle * device_handle) +{ + bulk_message_request_occupancy_map_control request; + bulk_message_response_occupancy_map_control response; + int host_data_out_bytes_sent = 0; + int host_data_in_bytes_received = 0; + + /* Prepare get device info message */ + memset(&request, 0, sizeof(request)); + memset(&response, 0, sizeof(response)); + init_message_request_header((unsigned char *)&request, 7, SLAM_OCCUPANCY_MAP_CONTROL); + request.message.bEnable = 1; + + /* Print message request */ + printf("SLAM_OCCUPANCY_MAP_CONTROL request\n"); + print_message((unsigned char *)&request); + + /* Send and receive device info */ + bulk_transfer_exchange_message(device_handle, (unsigned char *)&request, sizeof(request), &host_data_out_bytes_sent, (unsigned char *)&response, &host_data_in_bytes_received); + + /* Print message response */ + printf("SLAM_OCCUPANCY_MAP_CONTROL response\n"); + print_message((unsigned char *)&response); +} + + +int main(void) +{ + struct libusb_device_handle * device_handle = NULL; + int result = 0; + int i = 0; + int max_interrupt_message = 100; + + printf("TM2 Demo (version %d)\n", TM2_API_VERSION); + + /* Initialise libusb, must be called before other libusb routines are used */ + result = libusb_init(NULL); + if (result < 0) + { + fprintf(stderr, "Error initializing libusb: %s\n", libusb_error_name(result)); + exit(1); + } + + /* Finding a device with a particular idVendor/idProduct combination */ + device_handle = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID); + if (device_handle == NULL) + { + fprintf(stderr, "Error finding USB device\n"); + goto out; + } + + /* Claim an interface on a given device handle - must claim the interface before performing I/O on any of its endpoints */ + result = libusb_claim_interface(device_handle, INTERFACE_INDEX); + if (result < 0) + { + fprintf(stderr, "Error claiming interface: %s\n", libusb_error_name(result)); + goto out; + } + + /* Sending all supported messages */ + send_bulk_message_get_device_info(device_handle); + send_bulk_message_get_time(device_handle); + send_bulk_message_get_supported_raw_streams(device_handle); + send_bulk_message_raw_streams_control(device_handle); + send_bulk_message_set_camera_intrinsics(device_handle); + send_bulk_message_get_camera_intrinsics(device_handle); + send_bulk_message_set_motion_intrinsics(device_handle); + send_bulk_message_get_motion_intrinsics(device_handle); + send_bulk_message_set_extrinsics(device_handle); + send_bulk_message_get_extrinsics(device_handle); + send_bulk_message_log_control(device_handle); + send_bulk_message_get_pose(device_handle); + send_bulk_message_get_occupancy_map_tiles(device_handle); + send_bulk_message_get_localization_data(device_handle); + send_bulk_message_set_localization_data(device_handle); + send_bulk_message_set_6dof_interrupt_rate(device_handle); + send_bulk_message_6dof_control_enable(device_handle); + for (i = 0; i +#include +#include "Manager.h" +#include +#include "Version.h" +#include +#include "fw.h" +#include "Common.h" + +using namespace std::chrono; +using namespace std; + +#define DEFAULT_CHUNKSZ 1024*32 +#define EP_OUT 1 +#define TIMEOUT_MS 3000 + +namespace perc { +// -[factory]------------------------------------------------------------------ + +TrackingManager* TrackingManager::CreateInstance(Listener* lis, void* param) +{ + try + { + return new Manager(lis, param); + } + catch (std::exception& e) + { + LOGE(e.what()); + return nullptr; + } +} + +void TrackingManager::ReleaseInstance(TrackingManager*& manager) +{ + try + { + if (manager) + { + delete manager; + manager = nullptr; + } + } + catch (std::exception& e) + { + LOGE(e.what()); + } +} + +// -[interface]---------------------------------------------------------------- +Manager::Manager(Listener* lis, void* param) : mDispatcher(new Dispatcher()), mListener(nullptr), mContext(nullptr), mFwFileName(""), mLibUsbDeviceToTrackingDeviceMap(), mEvent(), mCompleteQMutex(), mCompleteQ(), mTrackingDeviceInfoMap() +{ + // start running context + mThread = std::thread([this] { + mFsm.init(FSM(main), this, mDispatcher.get(), LOG_TAG); + while (this->mDispatcher->handleEvents() >= 0); + mFsm.done(); + }); + if (init(lis, param) != Status::SUCCESS) + { + throw std::runtime_error("Failed to init manager"); + } +} + + +Manager::~Manager() +{ + done(); +} + +Status Manager::onAttach(libusb_device * device) +{ + MessageON_LIBUSB_EVENT msg(true, device); + mFsm.fireEvent(msg); + if (msg.Result == 0) + return Status::SUCCESS; + return Status::INIT_FAILED; +} + +void Manager::onDetach(libusb_device * device) +{ + mFsm.fireEvent(MessageON_LIBUSB_EVENT(false, device)); +} + +void Manager::addTask(std::shared_ptr& newTask) +{ + //mDispatcher->postMessage(&mFsm, MessageON_NEW_TASK(newTask)); + lock_guard lock(mCompleteQMutex); + mCompleteQ.push_back(newTask); + mEvent.signal(); +} + +Status Manager::init(Listener *listener, void *param) +{ + return processMessage(MessageON_INIT(listener, param)); +} + +void Manager::removeTasks(void * owner, bool completeTasks) +{ + mFsm.fireEvent(MessageON_REMOVE_TASKS(owner, completeTasks)); +} + +// ---------------------------------------------------------------------------- +Status Manager::handleEvents(bool blocking) +{ + shared_ptr task = nullptr; + size_t count; + + { + lock_guard lock(mCompleteQMutex); + count = mCompleteQ.size(); + mEvent.reset(); + } + + LOGV("Manager::handleEvents is about to handle %d tasks", count); + while (true) + { + task = nullptr; + { + lock_guard lock(mCompleteQMutex); + if ( count > 0 && mCompleteQ.size() > 0 ) + { + task = mCompleteQ.front(); + mCompleteQ.pop_front(); + count--; + } + } + if (task) + task->complete(); + else + break; + } + return Status::SUCCESS; +} + + +// ---------------------------------------------------------------------------- +void Manager::done() +{ + processMessage(Message(ON_DONE)); + + if (mThread.joinable()) { + try { + mDispatcher->endEventsLoop(); + mThread.join(); + } + catch (...) { + LOGE("mThread.join() exception"); + } + } + + for (auto item : mLibUsbDeviceToTrackingDeviceMap) + { + delete item.second; + libusb_unref_device(item.first); + } +} + +size_t Manager::getDeviceList(TrackingDevice ** list, unsigned int maxListSize) +{ + // TODO: move to sendmessage and dispatcher context + auto count = maxListSize > mLibUsbDeviceToTrackingDeviceMap.size() ? mLibUsbDeviceToTrackingDeviceMap.size() : maxListSize; + + size_t i = 0; + for (auto item : mLibUsbDeviceToTrackingDeviceMap) + { + *list++ = item.second; + i--; + if (i == count) + break; + } + return count; +} + +Status Manager::setHostLogControl(IN const TrackingData::LogControl& logControl) +{ + LOGF("Set Host log Control, output to %s, verbosity = 0x%X, Rollover mode = 0x%X", (logControl.outputMode == LogOutputModeScreen)?"Screen":"Buffer", logControl.verbosity, logControl.rolloverMode); + __perc_Log_Set_Configuration(LogSourceHost, logControl.outputMode, logControl.verbosity, logControl.rolloverMode); + return Status::SUCCESS; +} + +Status Manager::getHostLog(OUT TrackingData::Log* log) +{ + __perc_Log_Get_Log((void*)log); + return Status::SUCCESS; +} + +// -[impl]--------------------------------------------------------------------- +Status Manager::processMessage(const Message &msg, int prio) +{ + msg.Result = toUnderlying(Status::COMMON_ERROR); + + int ret = mDispatcher->sendMessage(&mFsm, msg, prio); + if (ret < 0) { + LOGE("mDispatcher->sendMessage ret %d", ret); + return Status::COMMON_ERROR; + } + return (Status)msg.Result; +} + + + +Status Manager::loadFileToDevice(libusb_device * device, const char * fileName) +{ + Status status = Status::SUCCESS; + + if (!fileName || !device) + { + LOGE("Error while loading file %s to device 0x%p - bad input", fileName, device); + return Status::INIT_FAILED; + } + + FILE* file; + auto res = fopen_s(&file, fileName, "rb"); + if (res != 0) + { + LOGE("Error while loading file %s to device 0x%p - can't open file", fileName, device); + return Status::INIT_FAILED; + } + + unsigned int size; + + fseek(file, 0, SEEK_END); + size = ftell(file); + rewind(file); + + unsigned char* txBuf; + + if (!(txBuf = (unsigned char*)malloc(size))) + { + LOGE("Error while loading file %s (size %d) to device 0x%p - memory allocation error", fileName, size, device); + status = Status::INIT_FAILED; + goto cleanup; + } + + if (fread(txBuf, 1, size, file) != size) + { + LOGE("Error while loading file %s (size %d) to device 0x%p - read file error", fileName, size, device); + status = Status::INIT_FAILED; + goto cleanup; + } + + status = loadBufferToDevice(device, txBuf, size); + +cleanup: + fclose(file); + if (txBuf) + { + free(txBuf); + } + return status; +} + + +Status Manager::loadBufferToDevice(libusb_device * device, unsigned char * buffer, size_t size) +{ + LOGV("loading image to device 0x%p", device); + + if (!device || !buffer || size == 0) + { + LOGE("Error while loading image - device 0x%p, buffer 0x%p, size %d", device, buffer, size); + return Status::INIT_FAILED; + } + + libusb_device_handle * devHandle; + auto start = high_resolution_clock::now(); + uint64_t duration = 0; + int bytesWritten = 0; + + auto rc = libusb_open(device, &devHandle); + if (rc != 0) + { + LOGE("Error while loading image - libusb_open failed (0x%X), will try again later", rc); + return Status::INIT_FAILED; + } + + rc = libusb_claim_interface(devHandle, INTERFACE_INDEX); + if (rc != 0) + { + LOGE("Error while loading image - libusb_claim_interface failed (0x%X)", rc); + libusb_close(devHandle); + return Status::INIT_FAILED; + } + + rc = libusb_bulk_transfer(devHandle, EP_OUT, buffer, (int)size, &bytesWritten, TIMEOUT_MS); + if (rc != 0 || size != bytesWritten) + { + LOGE("Error while loading image - libusb_bulk_transfer failed. LIBUSB_ERROR_CODE: %d (%s)", rc, libusb_error_name(rc)); + libusb_release_interface(devHandle, INTERFACE_INDEX); + libusb_close(devHandle); + return Status::INIT_FAILED; + } + + //W\A for pipe error bug + rc = libusb_bulk_transfer(devHandle, EP_OUT, buffer, 0, &bytesWritten, TIMEOUT_MS); + // status check ignored according to Movidius sample code + //if (rc != 0 || 0 != bytesWritten) + //{ + // LOGE("Error while loading image - libusb_bulk_transfer failed. LIBUSB_ERROR_CODE: %d (%s)", rc, libusb_error_name(rc)); + // libusb_release_interface(devHandle, INTERFACE_INDEX); + // libusb_close(devHandle); + // return Status::INIT_FAILED; + //} + + duration = duration_cast(high_resolution_clock::now() - start).count(); + LOGD("USB Device FW Load finish - FW image loaded in %d mSec", duration); + + libusb_release_interface(devHandle, INTERFACE_INDEX); + libusb_close(devHandle); + return Status::SUCCESS; +} + +uint64_t Manager::version() +{ + return LIBTM_VERSION; +} +// -[FSM]---------------------------------------------------------------------- +// +// [FSM State::ANY] + +// [FSM State::IDLE] +DEFINE_FSM_STATE_ENTRY(Manager, IDLE_STATE) +{ +} + +DEFINE_FSM_GUARD(Manager, IDLE_STATE, ON_INIT, msg) +{ + return 0 == libusb_init(&mContext); +} + +DEFINE_FSM_ACTION(Manager, IDLE_STATE, ON_INIT, msg) +{ + auto initMsg = dynamic_cast(msg); + mListener = initMsg.listener; + char* t = (char*)initMsg.context; + if (t) + { + string tmp(t); + mFwFileName = tmp; + } + msg.Result = toUnderlying(Status::SUCCESS); +} + +// [FSM State::ACTIVE] +DEFINE_FSM_STATE_ENTRY(Manager, ACTIVE_STATE) +{ + mUsbPlugListener = make_shared(*this); +} + +DEFINE_FSM_STATE_EXIT(Manager, ACTIVE_STATE) +{ +} + +DEFINE_FSM_GUARD(Manager, ACTIVE_STATE, ON_REMOVE_TASKS, msg) +{ + auto m = dynamic_cast(msg); + if (!m.mOwner) + return false; + return true; +} + +DEFINE_FSM_ACTION(Manager, ACTIVE_STATE, ON_REMOVE_TASKS, msg) +{ + lock_guard lock(mCompleteQMutex); + uint32_t nonCompleteTasks = 0; + + auto it = mCompleteQ.begin(); + auto m = dynamic_cast(msg); + + while (it != mCompleteQ.end()) + { + if ((*it)->getOwner() == m.mOwner) + { + auto itTmp = it; + it++; + + /* Check if asked to complete all pending tasks (upon exiting from ASYNC state) or this is a mustComplete tasks (status / error / localization / fw update tasks) */ + if ((m.mCompleteTasks == true) || (itTmp->get()->mustComplete() == true)) + { + itTmp->get()->complete(); + } + else + { + nonCompleteTasks++; + } + + mCompleteQ.erase(itTmp); + continue; + } + it++; + } + + if (nonCompleteTasks > 0) + { + /* If there are non completed tasks in this queue, this means that some of the callbacks (onVideo, onPose, onGyro, onAccelerometer) have a high latency */ + /* Which prevent the processor from processing all tasks in this queue while on ACTIVE state, which means loosing callbacks data */ + LOGE("Stopping Manager - Cleaned %d non complete callbacks (onVideoFrame, onPoseFrame, onGyroFrame, onAccelerometerFrame, onControllerframe) - latency is too high!", nonCompleteTasks); + } + +} + +DEFINE_FSM_GUARD(Manager, ACTIVE_STATE, ON_NEW_TASK, msg) +{ + auto m = dynamic_cast(msg); + if (!m.task) + return false; + return true; +} + +DEFINE_FSM_ACTION(Manager, ACTIVE_STATE, ON_NEW_TASK, msg) +{ +/* lock_guard lock(mCompleteQMutex); + auto m = dynamic_cast(msg); + mCompleteQ.push_back(m.task); + mEvent.signal();*/ +} + +DEFINE_FSM_ACTION(Manager, ACTIVE_STATE, ON_ATTACH, msg) +{ + Status status = Status::SUCCESS; + msg.Result = toUnderlying(Status::ERROR_USB_TRANSFER); + + auto m = dynamic_cast(msg); + libusb_device_descriptor desc = { 0 }; + + auto rc = libusb_get_device_descriptor(m.device, &desc); + if (rc) + { + LOGE("Error while getting device descriptor. LIBUSB_ERROR_CODE: %d (%s)", rc, libusb_error_name(rc)); + return; + } + + /* USB Device Firmware Upgrade (DFU) */ + if (mUsbPlugListener->identifyUDFDevice(&desc) == true) + { + libusb_ref_device(m.device); + + // load firmware here + if (mFwFileName != "") + { + LOGD("USB Device FW Load start - loading external FW image from %s", mFwFileName.c_str()); + status = loadFileToDevice(m.device, mFwFileName.c_str()); + } + else + { + LOGD("USB Device FW Load start - loading internal FW image \"%s\"", FW_VERSION); + auto size = sizeof(target_hex); + status = loadBufferToDevice(m.device, (unsigned char*)target_hex, size); + } + } + + if (status != Status::SUCCESS) + { + msg.Result = toUnderlying(Status::INIT_FAILED); + return; + } + + /* Real device arrived - do something */ + if (mUsbPlugListener->identifyDevice(&desc) == true) + { + /* To debug libusb issues, enable logs using libusb_set_debug(mContext, LIBUSB_LOG_LEVEL_INFO) */ + + auto device = new Device(m.device, mDispatcher.get(), this, this); + if (device->IsDeviceReady() == false) + { + delete device; + msg.Result = toUnderlying(Status::INIT_FAILED); + return; + } + + libusb_ref_device(m.device); + mLibUsbDeviceToTrackingDeviceMap[m.device] = device; + + shared_ptr ptr; + TrackingData::DeviceInfo deviceInfo; + device->GetDeviceInfo(deviceInfo); + mTrackingDeviceInfoMap[device] = deviceInfo; + + ptr = make_shared(mListener, device, &deviceInfo, ATTACH, this); + this->addTask(ptr); + + if (deviceInfo.status.hw != Status::SUCCESS) + { + ptr = make_shared(mListener, device, deviceInfo.status.hw, this); + this->addTask(ptr); + } + else if (deviceInfo.status.host != Status::SUCCESS) + { + ptr = make_shared(mListener, device, deviceInfo.status.host, this); + this->addTask(ptr); + } + + mDispatcher->registerHandler(device); + } + //libusb_ref_device(m.device); + msg.Result = toUnderlying(Status::SUCCESS); +} + +DEFINE_FSM_ACTION(Manager, ACTIVE_STATE, ON_DETACH, msg) +{ + msg.Result = toUnderlying(Status::ERROR_USB_TRANSFER); + libusb_device_descriptor desc = { 0 }; + auto m = dynamic_cast(msg); + auto rc = libusb_get_device_descriptor(m.device, &desc); + if (rc) + { + LOGE("Error while getting device descriptor. LIBUSB_ERROR_CODE: %d (%s)", rc, libusb_error_name(rc)); + return; + } + + if (mUsbPlugListener->identifyDevice(&desc) == true) + { + auto device = mLibUsbDeviceToTrackingDeviceMap[m.device]; + mLibUsbDeviceToTrackingDeviceMap.erase(m.device); + mDispatcher->removeHandler(device); + + auto deviceInfo = mTrackingDeviceInfoMap[device]; + mTrackingDeviceInfoMap.erase(device); + + // notify listener on device erased + shared_ptr ptr = make_shared(mListener, device, &deviceInfo, DETACH, this); + this->addTask(ptr); + delete device; + libusb_unref_device(m.device); + } + else if (mUsbPlugListener->identifyUDFDevice(&desc) == true) + { + libusb_unref_device(m.device); + } + + //libusb_unref_device(m.device); + msg.Result = toUnderlying(Status::SUCCESS); +} + +DEFINE_FSM_ACTION(Manager, ACTIVE_STATE, ON_DONE, msg) +{ + msg.Result = toUnderlying(Status::SUCCESS); +} + +DEFINE_FSM_ACTION(Manager, ACTIVE_STATE, ON_ERROR, msg) +{ + msg.Result = toUnderlying(Status::SUCCESS); +} + +// [FSM State::ERROR] +DEFINE_FSM_ACTION(Manager, ERROR_STATE, ON_DONE, msg) +{ +} + +// -[Definition]--------------------------------------------------------------- +#define FSM_ERROR_TIMEOUT 1000 + +// [FSM State::IDLE] +DEFINE_FSM_STATE_BEGIN(Manager, IDLE_STATE) + TRANSITION(ON_INIT, GUARD(Manager, IDLE_STATE, ON_INIT), ACTION(Manager, IDLE_STATE, ON_INIT), ACTIVE_STATE) +DEFINE_FSM_STATE_END(Manager, IDLE_STATE, ENTRY(Manager, IDLE_STATE), 0) + +// [FSM State::ACTIVE] +DEFINE_FSM_STATE_BEGIN(Manager, ACTIVE_STATE) + TRANSITION(ON_DONE, 0, ACTION(Manager, ACTIVE_STATE, ON_DONE), IDLE_STATE) + TRANSITION_INTERNAL(ON_ATTACH, 0, ACTION(Manager, ACTIVE_STATE, ON_ATTACH)) + TRANSITION_INTERNAL(ON_DETACH, 0, ACTION(Manager, ACTIVE_STATE, ON_DETACH)) + TRANSITION_INTERNAL(ON_NEW_TASK, GUARD(Manager, ACTIVE_STATE, ON_NEW_TASK), ACTION(Manager, ACTIVE_STATE, ON_NEW_TASK)) + TRANSITION_INTERNAL(ON_REMOVE_TASKS, GUARD(Manager, ACTIVE_STATE, ON_REMOVE_TASKS), ACTION(Manager, ACTIVE_STATE, ON_REMOVE_TASKS)) + TRANSITION(ON_ERROR, 0, ACTION(Manager, ACTIVE_STATE, ON_ERROR), ERROR_STATE) +DEFINE_FSM_STATE_END(Manager, ACTIVE_STATE, ENTRY(Manager, ACTIVE_STATE), EXIT(Manager, ACTIVE_STATE)) + +// [FSM State::ERROR] +DEFINE_FSM_STATE_BEGIN(Manager, ERROR_STATE) + TRANSITION(ON_DONE, 0, ACTION(Manager, ERROR_STATE, ON_DONE), IDLE_STATE) +DEFINE_FSM_STATE_END(Manager, ERROR_STATE, 0, 0) + +// -[FSM: Definition]---------------------------------------------------------- +DEFINE_FSM_BEGIN(Manager, main) + STATE(Manager, IDLE_STATE) + STATE(Manager, ACTIVE_STATE) + STATE(Manager, ERROR_STATE) +DEFINE_FSM_END() + +// ---------------------------------------------------------------------------- +} // namespace tracking diff --git a/third-party/libtm/libtm/src/Manager.h b/third-party/libtm/libtm/src/Manager.h new file mode 100644 index 0000000000..55e74b8a35 --- /dev/null +++ b/third-party/libtm/libtm/src/Manager.h @@ -0,0 +1,157 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include +#include +#include +#include +#include "Dispatcher.h" +#include "Fsm.h" +#include "TrackingManager.h" +#include "Device.h" +#include "TrackingCommon.h" +#include "UsbPlugListener.h" +#include "Event.h" +#include "CompleteTask.h" +#include + +namespace perc +{ + class Manager : public TrackingManager, + public EventHandler, + public CompleteQueueHandler, + public UsbPlugListener::Owner + { + public: + + Manager(Listener*, void* param = 0); + virtual ~Manager(); + // [interface] TrackingManager + virtual Handle getHandle() { return mEvent.handle(); }; + virtual Status handleEvents(bool blocking); + virtual size_t getDeviceList(TrackingDevice** list, unsigned int maxListSize); + virtual Status setHostLogControl(IN const TrackingData::LogControl& logControl); + virtual Status getHostLog(OUT TrackingData::Log* log); + virtual uint64_t version(); + + // [interface] CompleteQueueHandler + virtual void addTask(std::shared_ptr&) override; + virtual void removeTasks(void* owner, bool completeTasks) override; + + protected: + std::thread mThread; + std::shared_ptr mDispatcher; + + // [interface] EventHandler + virtual void onMessage(const Message &) override {} + virtual void onTimeout(uintptr_t timerId, const Message &msg) override {} + virtual void onRead(int fd, void *act) {} + virtual void onException(int fd, void *act) {} + + // [interface] UsbPlugListener::Owner + virtual Status onAttach(libusb_device*) override; + virtual void onDetach(libusb_device*) override; + virtual libusb_context* context() override { return mContext; } + virtual Dispatcher& dispatcher() override { return *mDispatcher.get(); } + + + // [FSM] declaration + Fsm mFsm; + DECLARE_FSM(main); + + // [States] + enum { + IDLE_STATE = FSM_STATE_USER_DEFINED, + ACTIVE_STATE, + ERROR_STATE, + }; + + // [Events] + enum { + ON_INIT = FSM_EVENT_USER_DEFINED, + ON_DONE, + ON_ATTACH, + ON_DETACH, + ON_ERROR, + ON_NEW_TASK, + ON_REMOVE_TASKS + }; + + // [State] IDLE + DECLARE_FSM_STATE(IDLE_STATE); + DECLARE_FSM_STATE_ENTRY(IDLE_STATE); + DECLARE_FSM_GUARD(IDLE_STATE, ON_INIT); + DECLARE_FSM_ACTION(IDLE_STATE, ON_INIT); + + // [State] ACTIVE + DECLARE_FSM_STATE(ACTIVE_STATE); + DECLARE_FSM_STATE_ENTRY(ACTIVE_STATE); + DECLARE_FSM_STATE_EXIT(ACTIVE_STATE); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_ATTACH); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_DETACH); + DECLARE_FSM_GUARD(ACTIVE_STATE, ON_NEW_TASK); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_NEW_TASK); + DECLARE_FSM_GUARD(ACTIVE_STATE, ON_REMOVE_TASKS); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_REMOVE_TASKS); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_DONE); + DECLARE_FSM_ACTION(ACTIVE_STATE, ON_ERROR); + + // [State] ERROR + DECLARE_FSM_STATE(ERROR_STATE); + DECLARE_FSM_ACTION(ERROR_STATE, ON_DONE); + + // private messages + class MessageON_INIT: public Message { + public: + MessageON_INIT(TrackingManager::Listener *l, void *c) : Message(ON_INIT), + listener(l), context(c) {} + TrackingManager::Listener *listener; + void *context; + }; + + class MessageON_LIBUSB_EVENT : public Message { + public: + MessageON_LIBUSB_EVENT(bool attach, libusb_device* dev) : Message(attach ? ON_ATTACH : ON_DETACH), device(dev) + { } + libusb_device* device; + }; + + class MessageON_NEW_TASK : public Message { + public: + MessageON_NEW_TASK(std::shared_ptr& newTask) : Message(ON_NEW_TASK), task(newTask) + { } + std::shared_ptr task; + }; + + class MessageON_REMOVE_TASKS : public Message { + public: + MessageON_REMOVE_TASKS(void * owner, bool completeTasks) : Message(ON_REMOVE_TASKS), mOwner(owner), mCompleteTasks(completeTasks) + { } + void* mOwner; + bool mCompleteTasks; + }; + + Status processMessage(const Message &, int prio = Dispatcher::PRIORITY_IDLE); + + private: + + Status loadBufferToDevice(libusb_device* device, unsigned char* buffer, size_t size); + Status loadFileToDevice(libusb_device* device, const char* fileName); + Status init(TrackingManager::Listener*, void*); + void done(); + + TrackingManager::Listener* mListener; + std::map mLibUsbDeviceToTrackingDeviceMap; + libusb_context * mContext; + + std::string mFwFileName; + std::shared_ptr mUsbPlugListener; + std::list> mCompleteQ; + std::mutex mCompleteQMutex; + Event mEvent; + std::map mTrackingDeviceInfoMap; + }; +} diff --git a/third-party/libtm/libtm/src/Message.cpp b/third-party/libtm/libtm/src/Message.cpp new file mode 100644 index 0000000000..65d1ccaecc --- /dev/null +++ b/third-party/libtm/libtm/src/Message.cpp @@ -0,0 +1,500 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include +#include +#include "Message.h" +#include "Common.h" + +/** +* @brief This function initilize the message request header with needed length and message ID +* +* @param[in] message_request - message request buffer. +* @param[in] dwLength - message request length. +* @param[in] wMessageID - message ID. +* @return None +*/ +void init_message_request_header(IN unsigned char * message_request, IN uint32_t dwLength, IN uint16_t wMessageID) +{ + ((bulk_message_request_header*)message_request)->dwLength = dwLength; + ((bulk_message_request_header*)message_request)->wMessageID = wMessageID; +} + + +/** +* @brief This function prints all supported request/response messages +* +* @param[in] message - message buffer. +* @return None +*/ +void print_message(IN unsigned char * message) +{ + bulk_message_request_header* message_header = ((bulk_message_request_header*)message); + unsigned int i = 0; + unsigned int j = 0; + + printf("message->header.dwLength = %d\n", message_header->dwLength); + printf("message->header.wMessageID = 0x%X\n", message_header->wMessageID); + + switch (message_header->wMessageID) + { + case (DEV_GET_DEVICE_INFO): + { + if (message_header->dwLength > sizeof(bulk_message_request_get_device_info)) + { + /* bulk_message_response_get_device_info */ + bulk_message_response_get_device_info* message_response = ((bulk_message_response_get_device_info*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + if (message_response->header.wStatus == 0) + { + printf("message->bDeviceType = 0x%X\n", message_response->message.bDeviceType); + printf("message->bHwVersion = 0x%X\n", message_response->message.bHwVersion); + printf("message->bStatus = 0x%X\n", message_response->message.bStatus); + printf("message->wReserved = 0x%X\n", message_response->message.wReserved); + printf("message->dwRomVersion = 0x%X\n", message_response->message.dwRomVersion); + printf("message->dwFWVersion = 0x%X\n", message_response->message.dwFWVersion); + printf("message->dwCalibrationVersion = 0x%X\n", message_response->message.dwCalibrationVersion); + printf("message->llExtendedStatus = %lu\n", message_response->message.llExtendedStatus); + } + } + break; + } + + case (DEV_GET_TIME): + { + if (message_header->dwLength > sizeof(bulk_message_request_get_time)) + { + /* bulk_message_response_get_time */ + bulk_message_response_get_time* message_response = ((bulk_message_response_get_time*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + if (message_response->header.wStatus == 0) + { + printf("message->llNanoseconds = %lu\n", message_response->llNanoseconds); + } + } + break; + } + + case (DEV_GET_SUPPORTED_RAW_STREAMS): + { + if (message_header->dwLength > sizeof(bulk_message_request_get_supported_raw_streams)) + { + /* bulk_message_response_get_supported_raw_streams */ + bulk_message_response_get_supported_raw_streams* message_response = ((bulk_message_response_get_supported_raw_streams*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + if (message_response->header.wStatus == 0) + { + printf("message->wNumSupportedStreams = %d\n", message_response->wNumSupportedStreams); + if (message_response->wNumSupportedStreams > 0) + { + for (i = 0; i < message_response->wNumSupportedStreams; i++) + { + printf("message->stream[%d].bSensorID = 0x%X\n", i, message_response->stream[i].bSensorID); + printf("message->stream[%d].wFramesPerSecond = %d\n", i, message_response->stream[i].wFramesPerSecond); + printf("message->stream[%d].wWidth = %d\n", i, message_response->stream[i].wWidth); + printf("message->stream[%d].wHeight = %d\n", i, message_response->stream[i].wHeight); + printf("message->stream[%d].wReserved = %d\n", i, message_response->stream[i].wReserved); + } + } + } + } + break; + } + + case (DEV_RAW_STREAMS_CONTROL): + { + if (message_header->dwLength > sizeof(bulk_message_response_raw_streams_control)) + { + /* bulk_message_request_raw_streams_control */ + bulk_message_request_raw_streams_control* message_request = ((bulk_message_request_raw_streams_control*)message); + printf("message->wNumEnabledStreams = %d\n", message_request->wNumEnabledStreams); + if (message_request->wNumEnabledStreams > 0) + { + for (i = 0; i < message_request->wNumEnabledStreams; i++) + { + printf("message->stream[%d].bSensorID = 0x%X\n", i, message_request->stream[i].bSensorID); + printf("message->stream[%d].wFramesPerSecond = %d\n", i, message_request->stream[i].wFramesPerSecond); + printf("message->stream[%d].wWidth = %d\n", i, message_request->stream[i].wWidth); + printf("message->stream[%d].wHeight = %d\n", i, message_request->stream[i].wHeight); + printf("message->stream[%d].wReserved = %d\n", i, message_request->stream[i].wReserved); + } + } + } + else + { + /* bulk_message_response_raw_streams_control */ + bulk_message_response_raw_streams_control* message_response = ((bulk_message_response_raw_streams_control*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + } + + break; + } + + case (DEV_GET_CAMERA_INTRINSICS): + { + if (message_header->dwLength == sizeof(bulk_message_request_get_camera_intrinsics)) + { + /* bulk_message_request_get_camera_intrinsics */ + bulk_message_request_get_camera_intrinsics* message_request = ((bulk_message_request_get_camera_intrinsics*)message); + printf("message->bCameraID = 0x%X\n", message_request->bCameraID); + } + else + { + /* bulk_message_response_get_camera_intrinsics */ + bulk_message_response_get_camera_intrinsics* message_response = ((bulk_message_response_get_camera_intrinsics*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + if (message_response->header.wStatus == 0) + { + printf("message->intrinsics.dwWidth = %d\n", message_response->intrinsics.dwWidth); + printf("message->intrinsics.dwHeight = %d\n", message_response->intrinsics.dwHeight); + printf("message->intrinsics.flPpx = %f\n", message_response->intrinsics.flPpx); + printf("message->intrinsics.flPpy = %f\n", message_response->intrinsics.flPpy); + printf("message->intrinsics.flFx = %f\n", message_response->intrinsics.flFx); + printf("message->intrinsics.flFy = %f\n", message_response->intrinsics.flFy); + printf("message->intrinsics.dwDistortionModel = %d\n", message_response->intrinsics.dwDistortionModel); + for (i = 0; i < 5; i++) + { + printf("message->intrinsics.flCoeffs[%d] = %f\n", i, message_response->intrinsics.flCoeffs[i]); + } + } + } + break; + } + + case (DEV_GET_MOTION_INTRINSICS): + { + if (message_header->dwLength == sizeof(bulk_message_request_get_motion_intrinsics)) + { + /* bulk_message_request_get_motion_module_intrinsics */ + bulk_message_request_get_motion_intrinsics* message_request = ((bulk_message_request_get_motion_intrinsics*)message); + printf("message->bMotionID = 0x%X\n", message_request->bMotionID); + } + else + { + /* bulk_message_response_get_motion_module_intrinsics */ + bulk_message_response_get_motion_intrinsics* message_response = ((bulk_message_response_get_motion_intrinsics*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + if (message_response->header.wStatus == 0) + { + for (i = 0; i < 3; i++) + { + for (j = 0; j < 4; j++) + { + printf("message->intrinsics.flData[%d][%d] = %f\n", i, j, message_response->intrinsics.flData[i][j]); + } + } + + for (i = 0; i < 3; i++) + { + printf("message->intrinsics.flNoiseVariances[%d] = %f\n", i, message_response->intrinsics.flNoiseVariances[i]); + } + + for (i = 0; i < 3; i++) + { + printf("message->intrinsics.flBiasVariances[%d] = %f\n", i, message_response->intrinsics.flBiasVariances[i]); + } + } + } + break; + } + + case (DEV_GET_EXTRINSICS): + { + if (message_header->dwLength == sizeof(bulk_message_request_get_extrinsics)) + { + /* bulk_message_request_get_motion_module_intrinsics */ + bulk_message_request_get_extrinsics* message_request = ((bulk_message_request_get_extrinsics*)message); + printf("message->bSensorID = 0x%X\n", message_request->bSensorID); + printf("message->bReferenceSensorID = 0x%X\n", message_request->bReferenceSensorID); + } + else + { + /* bulk_message_response_get_extrinsics */ + bulk_message_response_get_extrinsics* message_response = ((bulk_message_response_get_extrinsics*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + if (message_response->header.wStatus == 0) + { + for (i = 0; i < 9; i++) + { + printf("message->extrinsics.flRotation[%d] = %f\n", i, message_response->extrinsics.flRotation[i]); + } + + for (i = 0; i < 3; i++) + { + printf("message->extrinsics.flTranslation[%d] = %f\n", i, message_response->extrinsics.flTranslation[i]); + } + } + } + break; + } + + case (DEV_SET_CAMERA_INTRINSICS): + { + if (message_header->dwLength == sizeof(bulk_message_request_set_camera_intrinsics)) + { + /* bulk_message_request_set_camera_intrinsics */ + bulk_message_request_set_camera_intrinsics* message_request = ((bulk_message_request_set_camera_intrinsics*)message); + printf("message->bCameraID = 0x%X\n", message_request->bCameraID); + printf("message->bReserved = %d\n", message_request->bReserved); + printf("message->intrinsics.dwWidth = %d\n", message_request->intrinsics.dwWidth); + printf("message->intrinsics.dwHeight = %d\n", message_request->intrinsics.dwHeight); + printf("message->intrinsics.flPpx = %f\n", message_request->intrinsics.flPpx); + printf("message->intrinsics.flPpy = %f\n", message_request->intrinsics.flPpy); + printf("message->intrinsics.flFx = %f\n", message_request->intrinsics.flFx); + printf("message->intrinsics.flFy = %f\n", message_request->intrinsics.flFy); + printf("message->intrinsics.dwDistortionModel = %d\n", message_request->intrinsics.dwDistortionModel); + for (i = 0; i < 5; i++) + { + printf("message->intrinsics.flCoeffs[%d] = %f\n", i, message_request->intrinsics.flCoeffs[i]); + } + } + else + { + /* bulk_message_response_set_camera_intrinsics */ + bulk_message_response_set_camera_intrinsics* message_response = ((bulk_message_response_set_camera_intrinsics*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + } + break; + } + + case (DEV_SET_MOTION_INTRINSICS): + { + if (message_header->dwLength == sizeof(bulk_message_request_set_motion_intrinsics)) + { + /* bulk_message_request_set_camera_intrinsics */ + bulk_message_request_set_motion_intrinsics* message_request = ((bulk_message_request_set_motion_intrinsics*)message); + printf("message->bMotionID = 0x%X\n", message_request->bMotionID); + printf("message->bReserved = %d\n", message_request->bReserved); + for (i = 0; i < 3; i++) + { + for (j = 0; j < 4; j++) + { + printf("message->intrinsics.flData[%d][%d] = %f\n", i, j, message_request->intrinsics.flData[i][j]); + } + } + + for (i = 0; i < 3; i++) + { + printf("message->intrinsics.flNoiseVariances[%d] = %f\n", i, message_request->intrinsics.flNoiseVariances[i]); + } + + for (i = 0; i < 3; i++) + { + printf("message->intrinsics.flBiasVariances[%d] = %f\n", i, message_request->intrinsics.flBiasVariances[i]); + } + } + else + { + /* bulk_message_response_set_motion_intrinsics */ + bulk_message_response_set_motion_intrinsics* message_response = ((bulk_message_response_set_motion_intrinsics*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + } + break; + } + + case (DEV_SET_EXTRINSICS): + { + if (message_header->dwLength == sizeof(bulk_message_request_set_extrinsics)) + { + /* bulk_message_request_set_extrinsics */ + bulk_message_request_set_extrinsics* message_request = ((bulk_message_request_set_extrinsics*)message); + printf("message->bSensorID = 0x%X\n", message_request->bSensorID); + printf("message->bReferenceSensorID = %d\n", message_request->bReferenceSensorID); + for (i = 0; i < 9; i++) + { + printf("message->extrinsics.flRotation[%d] = %f\n", i, message_request->extrinsics.flRotation[i]); + } + + for (i = 0; i < 3; i++) + { + printf("message->extrinsics.flTranslation[%d] = %f\n", i, message_request->extrinsics.flTranslation[i]); + } + } + else + { + /* bulk_message_response_set_extrinsics */ + bulk_message_response_set_extrinsics* message_response = ((bulk_message_response_set_extrinsics*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + } + break; + } + + case (DEV_LOG_CONTROL): + { + if (message_header->dwLength == sizeof(bulk_message_request_log_control)) + { + /* bulk_message_request_log_control */ + bulk_message_request_log_control* message_request = ((bulk_message_request_log_control*)message); + printf("message->bVerbosity = %d\n", message_request->bVerbosity); + printf("message->bLogMode = 0x%X\n", message_request->bLogMode); + } + else + { + /* bulk_message_response_log_control */ + bulk_message_response_log_control* message_response = ((bulk_message_response_log_control*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + } + break; + } + + case (DEV_GET_POSE): + { + if (message_header->dwLength > sizeof(bulk_message_request_get_pose)) + { + /* bulk_message_response_get_pose & interrupt_message_get_pose */ + bulk_message_response_get_pose* message_response = ((bulk_message_response_get_pose*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + if (message_response->header.wStatus == 0) + { + printf("message->pose.flX = %f\n", message_response->pose.flX); + printf("message->pose.flY = %f\n", message_response->pose.flY); + printf("message->pose.flZ = %f\n", message_response->pose.flZ); + printf("message->pose.flQi = %f\n", message_response->pose.flQi); + printf("message->pose.flQj = %f\n", message_response->pose.flQj); + printf("message->pose.flQk = %f\n", message_response->pose.flQk); + printf("message->pose.flQr = %f\n", message_response->pose.flQr); + printf("message->pose.flVx = %f\n", message_response->pose.flVx); + printf("message->pose.flVy = %f\n", message_response->pose.flVy); + printf("message->pose.flVz = %f\n", message_response->pose.flVz); + printf("message->pose.flVAX = %f\n", message_response->pose.flVAX); + printf("message->pose.flVAY = %f\n", message_response->pose.flVAY); + printf("message->pose.flVAZ = %f\n", message_response->pose.flVAZ); + printf("message->pose.flAx = %f\n", message_response->pose.flAx); + printf("message->pose.flAy = %f\n", message_response->pose.flAy); + printf("message->pose.flAz = %f\n", message_response->pose.flAz); + printf("message->pose.flAAX = %f\n", message_response->pose.flAAX); + printf("message->pose.flAAY = %f\n", message_response->pose.flAAY); + printf("message->pose.flAAZ = %f\n", message_response->pose.flAAZ); + printf("message->pose.llNanoseconds = %lu\n", message_response->pose.llNanoseconds); + } + } + break; + } + + case (SLAM_GET_OCCUPANCY_MAP_TILES): + { + if (message_header->dwLength > sizeof(bulk_message_request_get_occupancy_map_tiles)) + { + /* bulk_message_response_get_occupancy_map_tiles */ + bulk_message_response_get_occupancy_map_tiles* message_response = ((bulk_message_response_get_occupancy_map_tiles*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + if (message_response->header.wStatus == 0) + { + printf("message->bAccuracy = %d\n", message_response->map_tiles.bAccuracy); + printf("message->wTileCount = %d\n", message_response->map_tiles.wTileCount); + + if (message_response->map_tiles.wTileCount > 0) + { + for (i = 0; i < message_response->map_tiles.wTileCount; i++) + { + printf("message->tiles[%d].dwX = %d\n", i, message_response->map_tiles.tiles[i].dwX); + printf("message->tiles[%d].dwY = %d\n", i, message_response->map_tiles.tiles[i].dwY); + printf("message->tiles[%d].dwZ = %d\n", i, message_response->map_tiles.tiles[i].dwZ); + } + } + } + } + break; + } + + case (SLAM_GET_LOCALIZATION_DATA): + { + if (message_header->dwLength > sizeof(bulk_message_request_get_localization_data)) + { + /* bulk_message_response_get_localization_data */ + bulk_message_response_get_localization_data* message_response = ((bulk_message_response_get_localization_data*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + if (message_response->header.wStatus == 0) + { + int localizationDataSize = message_response->header.dwLength - offsetof(bulk_message_response_get_localization_data, message); + if (localizationDataSize > 0) + { + printf("message->bLocalizationData:\n"); + print_data(&message_response->message.bLocalizationData[0], localizationDataSize); + } + } + } + break; + } + + case (SLAM_SET_LOCALIZATION_DATA): + { + if (message_header->dwLength > sizeof(bulk_message_response_set_localization_data)) + { + /* bulk_message_request_set_localization_data - Printing bLocalizationData is TBD*/ + /* bulk_message_request_set_localization_data* message_request = ((bulk_message_request_set_localization_data*)message); */ + /* Printing bLocalizationData is TBD */ + } + else + { + /* bulk_message_response_set_localization_data */ + bulk_message_response_set_localization_data* message_response = ((bulk_message_response_set_localization_data*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + } + break; + } + + case (SLAM_SET_6DOF_INTERRUPT_RATE): + { + if (message_header->dwLength == sizeof(bulk_message_request_set_6dof_interrupt_rate)) + { + /* bulk_message_request_set_6dof_interrupt_rate */ + bulk_message_request_set_6dof_interrupt_rate* message_request = ((bulk_message_request_set_6dof_interrupt_rate*)message); + printf("message->bInterruptRate = %d\n", message_request->message.bInterruptRate); + } + else + { + /* bulk_message_response_set_6dof_interrupt_rate */ + bulk_message_response_set_6dof_interrupt_rate* message_response = ((bulk_message_response_set_6dof_interrupt_rate*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + } + break; + } + + case (SLAM_6DOF_CONTROL): + { + if (message_header->dwLength == sizeof(bulk_message_request_6dof_control)) + { + /* bulk_message_request_6dof_control */ + bulk_message_request_6dof_control* message_request = ((bulk_message_request_6dof_control*)message); + printf("message->bEnable = %d\n", message_request->bEnable); + } + else + { + /* bulk_message_response_6dof_control */ + bulk_message_response_6dof_control* message_response = ((bulk_message_response_6dof_control*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + } + break; + } + + case (SLAM_OCCUPANCY_MAP_CONTROL): + { + if (message_header->dwLength > sizeof(bulk_message_response_occupancy_map_control)) + { + /* bulk_message_request_occupancy_map_control */ + bulk_message_request_occupancy_map_control* message_request = ((bulk_message_request_occupancy_map_control*)message); + printf("message->bEnable = %d\n", message_request->message.bEnable); + } + else + { + /* bulk_message_response_occupancy_map_control */ + bulk_message_response_occupancy_map_control* message_response = ((bulk_message_response_occupancy_map_control*)message); + printf("message->header->wStatus = %d\n", message_response->header.wStatus); + } + break; + } + + case (DEV_GET_AND_CLEAR_EVENT_LOG): /* Fall through */ + default: + { + printf("Print unsupported\n"); + break; + } + + } + printf("\n"); + +} diff --git a/third-party/libtm/libtm/src/Message.h b/third-party/libtm/libtm/src/Message.h new file mode 100644 index 0000000000..51256fcaf3 --- /dev/null +++ b/third-party/libtm/libtm/src/Message.h @@ -0,0 +1,1876 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +/** +* @brief This file contains protocols definitions for TM2 USB vendor specific host/device interface +* +* Bulk In/Out endpoints - for general communication, the protocol on the bulk endpoints is in a request/response convention +* Additional Bulk In endpoint - for video stream (Stream endpoint) +* Interrupt endpoint - for periodic low latency data (namely 6DoF output) and IMU outputs +* All structures below should be treated as little-endian +*/ + +#pragma once + +#include +#include + +#pragma pack(push, 1) + +#ifdef _WIN32 +#pragma warning (push) +#pragma warning (disable : 4200) +#endif + +#define MAX_FW_LOG_BUFFER_ENTRIES (512) +#define MAX_LOG_PAYLOAD_SIZE (44) +#define MAX_MESSAGE_LEN 1024 +#define MAX_EEPROM_BUFFER_SIZE (MAX_MESSAGE_LEN - sizeof(bulk_message_request_header) - 4) +#define MAX_TABLE_SIZE (MAX_MESSAGE_LEN - sizeof(bulk_message_response_header)) +#define MAX_EEPROM_CONFIGURATION_SIZE 1200 +#define MAX_GUID_LENGTH 128 +#define MAX_FW_UPDATE_FILE_COUNT 6 + +namespace perc +{ + /** + * @brief Defines all bulk messages ids + */ + typedef enum { + + /* Core device messages */ + DEV_GET_DEVICE_INFO = 0x0001, + DEV_GET_TIME = 0x0002, + DEV_GET_AND_CLEAR_EVENT_LOG = 0x0003, + DEV_GET_SUPPORTED_RAW_STREAMS = 0x0004, + DEV_RAW_STREAMS_CONTROL = 0x0005, + DEV_GET_CAMERA_INTRINSICS = 0x0006, + DEV_GET_MOTION_INTRINSICS = 0x0007, + DEV_GET_EXTRINSICS = 0x0008, + DEV_SET_CAMERA_INTRINSICS = 0x0009, + DEV_SET_MOTION_INTRINSICS = 0x000A, + DEV_SET_EXTRINSICS = 0x000B, + DEV_LOG_CONTROL = 0x000C, + DEV_STREAM_CONFIG = 0x000D, + DEV_RAW_STREAMS_PLAYBACK_CONTROL = 0x000E, + DEV_READ_EEPROM = 0x000F, + DEV_WRITE_EEPROM = 0x0010, + DEV_SAMPLE = 0x0011, + DEV_START = 0x0012, + DEV_STOP = 0x0013, + DEV_STATUS = 0x0014, + DEV_GET_POSE = 0x0015, + DEV_EXPOSURE_MODE_CONTROL = 0x0016, + DEV_SET_EXPOSURE = 0x0017, + DEV_GET_TEMPERATURE = 0x0018, + DEV_SET_TEMPERATURE_THRESHOLD = 0x0019, + DEV_SET_GEO_LOCATION = 0x001A, + DEV_FLUSH = 0x001B, + DEV_FIRMWARE_UPDATE = 0x001C, + DEV_GPIO_CONTROL = 0x001D, + DEV_TIMEOUT_CONFIGURATION = 0x001E, + DEV_SNAPSHOT = 0x001F, + DEV_READ_CONFIGURATION = 0x0020, + DEV_WRITE_CONFIGURATION = 0x0021, + DEV_RESET_CONFIGURATION = 0x0022, + DEV_LOCK_CONFIGURATION = 0x0023, + DEV_LOCK_EEPROM = 0x0024, + + /* SLAM messages */ + SLAM_STATUS = 0x1001, + SLAM_GET_OCCUPANCY_MAP_TILES = 0x1002, + SLAM_GET_LOCALIZATION_DATA = 0x1003, + SLAM_SET_LOCALIZATION_DATA_STREAM = 0x1004, + SLAM_SET_6DOF_INTERRUPT_RATE = 0x1005, + SLAM_6DOF_CONTROL = 0x1006, + SLAM_OCCUPANCY_MAP_CONTROL = 0x1007, + SLAM_RESET_LOCALIZATION_DATA = 0x1008, + SLAM_GET_LOCALIZATION_DATA_STREAM = 0x1009, + SLAM_SET_STATIC_NODE = 0x100A, + SLAM_GET_STATIC_NODE = 0x100B, + + /* Controller messages */ + CONTROLLER_POSE_CONTROL = 0x2002, + CONTROLLER_STATUS_CHANGE_EVENT = 0x2003, + CONTROLLER_DEVICE_CONNECT = 0x2004, + CONTROLLER_DEVICE_DISCOVERY_EVENT = 0x2005, + CONTROLLER_DEVICE_DISCONNECT = 0x2006, + CONTROLLER_READ_ASSOCIATED_DEVICES = 0x2007, + CONTROLLER_WRITE_ASSOCIATED_DEVICES = 0x2008, + CONTROLLER_DEVICE_DISCONNECTED_EVENT = 0x2009, + CONTROLLER_DEVICE_CONNECTED_EVENT = 0x200A, + CONTROLLER_RSSI_TEST_CONTROL = 0x200B, + CONTROLLER_SEND_DATA = 0x200C, + CONTROLLER_START_CALIBRATION = 0x200D, + CONTROLLER_CALIBRATION_STATUS_EVENT = 0x200E, + CONTROLLER_DEVICE_LED_INTENSITY_EVENT = 0xF000, + + /* Error messages */ + DEV_ERROR = 0x8000, + SLAM_ERROR = 0x9000, + CONTROLLER_ERROR = 0xA000, + + /* Message IDs are 16-bits */ + MAX_MESSAGE_ID = 0xFFFF, + } BULK_MESSAGE_ID; + + + /** + * @brief Defines all bulk message return statuses + */ + enum class MESSAGE_STATUS { + SUCCESS = 0X0000, + UNKNOWN_MESSAGE_ID = 0x0001, + INVALID_REQUEST_LEN = 0x0002, + INVALID_PARAMETER = 0x0003, + INTERNAL_ERROR = 0x0004, + UNSUPPORTED = 0x0005, + LIST_TOO_BIG = 0x0006, + MORE_DATA_AVAILABLE = 0x0007, + DEVICE_BUSY = 0x0008, /* Indicates that this command is not supported in the current device state, e.g.trying to change configuration after START */ + TIMEOUT = 0x0009, + TABLE_NOT_EXIST = 0x000A, /* The requested configuration table does not exists on the EEPROM */ + TABLE_LOCKED = 0x000B, /* The configuration table is locked for writing or permanently locked from unlocking */ + DEVICE_STOPPED = 0x000C, /* Used with DEV_STATUS messages to mark the last message over an endpoint after a DEV_STOP command */ + TEMPERATURE_WARNING = 0x0010, /* The device temperature reached 10 % from its threshold */ + TEMPERATURE_STOP = 0x0011, /* The device temperature reached its threshold, and the device stopped tracking */ + CRC_ERROR = 0x0012, /* CRC Error in firmware update */ + INCOMPATIBLE = 0x0013, /* Controller version is incompatible with TM2 version */ + AUTH_ERROR = 0x0014, /* Authentication error in firmware update */ + DEVICE_RESET = 0x0015, /* A device reset has occurred. The user may read the FW log for additional detail */ + NO_BLUETOOTH = 0x0016, /* The device doesn't have bluetooth, so the command failed */ + SLAM_NO_DICTIONARY = 0x9001, /* No relocalization dictionary was loaded */ + }; + + /** + * @brief Defines EEPROM lock states + */ + typedef enum { + EEPROM_LOCK_STATE_WRITEABLE = 0x0000, + EEPROM_LOCK_STATE_LOCKED = 0x0001, + EEPROM_LOCK_STATE_PERMANENT_LOCKED = 0x0003, + EEPROM_LOCK_STATE_MAX = 0xFFFF, + } EEPROM_LOCK_STATE; + + /** + * @brief Defines all control messages ids + */ + typedef enum { + CONTROL_USB_RESET = 0x0010, + CONTROL_GET_INTERFACE_VERSION = 0x0011, + + /* Message IDs are 16-bits */ + MAX_CONTROL_ID = 0xFFFF, + } CONTROL_MESSAGE_ID; + + /** + * @brief Defines all FW status codes + */ + typedef enum { + FW_STATUS_CODE_OK = 0, + FW_STATUS_CODE_FAIL = 1, + FW_STATUS_CODE_NO_CALIBRATION_DATA = 1000 + } FW_STATUS_CODE; + + /** + * @brief Defines all SLAM status codes + */ + typedef enum { + SLAM_STATUS_CODE_SUCCESS = 0, /**< No error has occurred. */ + SLAM_STATUS_CODE_LOCALIZATION_DATA_SET_SUCCESS = 1, /**< Reading all localization data chunks completed successfully */ + } SLAM_STATUS_CODE; + + /** + * @brief Defines all SLAM error codes + */ + typedef enum { + SLAM_ERROR_CODE_NONE = 0, /**< No error has occurred. */ + SLAM_ERROR_CODE_VISION = 1, /**< No visual features were detected in the most recent image. This is normal in some circumstances, such as quick motion or if the device temporarily looks at a blank wall. */ + SLAM_ERROR_CODE_SPEED = 2, /**< The device moved more rapidly than expected for typical handheld motion. This may indicate that rc_Tracker has failed and is providing invalid data. */ + SLAM_ERROR_CODE_OTHER = 3, /**< A fatal internal error has occurred. */ + } SLAM_ERROR_CODE; + + /** + * @brief Defines all controller calibration error codes + */ + typedef enum { + CONTROLLER_CALIBRATION_STATUS_SUCCEEDED = 0, /**< No error has occurred. */ + CONTROLLER_CALIBRATION_STATUS_VALIDATION_FAILURE = 1, /**< Validation failure has occurred. */ + CONTROLLER_CALIBRATION_STATUS_FLASH_ACCESS_FAILURE = 2, /**< Flash access failure has occurred. */ + CONTROLLER_CALIBRATION_STATUS_IMU_FAILURE = 3, /**< IMU failure has occurred. */ + CONTROLLER_CALIBRATION_STATUS_INTERNAL_FAILURE = 4, /**< A fatal internal error has occurred. */ + } CONTROLLER_CALIBRATION_STATUS_CODE; + + /** + * @brief Bulk message request header struct + * + * Start of all USB message requests. + */ + typedef struct { + uint32_t dwLength; /**< Message length in bytes */ + uint16_t wMessageID; /**< ID of message */ + } bulk_message_request_header; + + + /** + * @brief Bulk message response header struct + * + * Start of all USB message responses. + */ + typedef struct { + uint32_t dwLength; /**< Message length in bytes */ + uint16_t wMessageID; /**< ID of message */ + uint16_t wStatus; /**< Status of request (MESSAGE_STATUS) */ + } bulk_message_response_header; + + + /** + * @brief Device Info libtm Message + * + * Retrieve information on the TM2 device. + */ + typedef struct { + uint8_t bDeviceType; /**< Device identifier: 0x1 = T250 */ + uint8_t bHwVersion; /**< ASIC Board version: 0x00 = ES0, 0x01 = ES1, 0x02 = ES2, 0x03 = ES3, 0xFF = Unknown */ + uint8_t bStatus; /**< Bits 0-3: device status: 0x0 = device functional, 0x1 = error, Bits 4-7: Reserved */ + uint8_t bEepromDataMajor; /**< Major part of the EEPROM data version */ + uint8_t bEepromDataMinor; /**< Minor part of the EEPROM data version */ + uint8_t bFWVersionMajor; /**< Major part of the Myriad firmware version */ + uint8_t bFWVersionMinor; /**< Minor part of the Myriad firmware version */ + uint8_t bFWVersionPatch; /**< Patch part of the Myriad firmware version */ + uint32_t dwFWVersionBuild; /**< Build part of the Myriad firmware version */ + uint32_t dwRomVersion; /**< Myriad ROM version */ + uint32_t dwStatusCode; /**< Status Code: S_OK = 0, E_FAIL = 1, E_NO_CALIBRATION_DATA = 1000 */ + uint32_t dwExtendedStatus; /**< Extended status information (details TBD) */ + uint64_t llSerialNumber; /**< Device serial number */ + uint8_t bCentralProtocolVersion; /**< Central BLE Protocol Version */ + uint8_t bCentralAppVersionMajor; /**< Major part of the Central firmware version */ + uint8_t bCentralAppVersionMinor; /**< Minor part of the Minor Central firmware version */ + uint8_t bCentralAppVersionPatch; /**< Patch part of the Patch Central firmware version */ + uint8_t bCentralSoftdeviceVersion; /**< Central BLE Softdevice Version */ + uint8_t bCentralBootloaderVersionMajor; /**< Major part of the Central firmware version */ + uint8_t bCentralBootloaderVersionMinor; /**< Minor part of the Minor Central firmware version */ + uint8_t bCentralBootloaderVersionPatch; /**< Patch part of the Patch Central firmware version */ + uint8_t bEepromLocked; /**< 0x0 – The EEPROM is fully writeable */ + /**< 0x1 – The upper quarter of the EEPROM memory is write-protected */ + /**< 0x3 – The upper quarter of the EEPROM memory is permanently write-protected */ + uint32_t dwCentralAppVersionBuild; /**< Build part of the Build Central firmware version */ + + } device_info_libtm_message; + + + /** + * @brief Bulk Get Device Info Message + * + * Retrieve TM2 device information. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 6 bytes, wMessageID = DEV_GET_DEVICE_INFO */ + } bulk_message_request_get_device_info; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 49 or 8 bytes, wMessageID = DEV_GET_DEVICE_INFO */ + device_info_libtm_message message; /**< Device info */ + } bulk_message_response_get_device_info; + + + /** + * @brief Bulk Device stream config Message + * + * Sets the maximum message size the host can receive + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 10 bytes, wMessageID = DEV_STREAM_CONFIG */ + uint16_t wReserved; /**< Reserved = 0 */ + uint32_t dwMaxSize; /**< Max stream endpoint message buffer size (Android 16K, Windows/Linux 678,400) */ + } bulk_message_request_stream_config; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = DEV_STREAM_CONFIG */ + } bulk_message_response_stream_config; + + /** + * @brief Bulk Get Time Message + * + * Retrieve the TM2 time, representred in nanoseconds since device initialization. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 6 bytes, wMessageID = DEV_GET_TIME */ + } bulk_message_request_get_time; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 16 or 8 bytes, wMessageID = DEV_GET_TIME */ + uint64_t llNanoseconds; /**< Number of nanoseconds since device initialization */ + } bulk_message_response_get_time; + + + /** + * @brief Bulk Get and Clear Event Log Message + * + * Retrieves the device event log and clears it, used for debugging purposes. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 6 bytes, wMessageID = DEV_GET_AND_CLEAR_EVENT_LOG */ + } bulk_message_request_get_and_clear_event_log; + + typedef struct { + uint8_t source; /**< Log level and source - Bits 0-3: Verbosity Level, Bits 4-6: Log index, Bit 7 - log source. */ + uint8_t timestamp[7]; /**< Timestamp - Lowest 56bits of MV2 timestamp in nanoseconds. */ + uint16_t eventID; /**< Event ID - Bits 0-14: Event ID from an event enum which the host software shall be able to map to a descriptive message. */ + /**< - Bit 15: 0 - the payload type (if exists) is according to bits 7-8 from the "Payload size" field. */ + /**< - : 1 - the payload is a null-terminated C-string (bits 7-8 can be ignored). */ + uint8_t payloadSize; /**< Actual payload size - Bits 0-6: Actual payload size in bytes, 0 - 44 bytes. */ + /**< size = 0-12, the entry is a basic entry (32 bytes long). */ + /**< size = 13-44, the entry is an extended entry(64 bytes long). */ + /**< Bits 7-8: Payload data type: 0x0 - 32 bits integers array */ + /**< 0x1 - 32 bits floats array */ + /**< 0x2 - 8 bits byte array(binary data) */ + /**< 0x3 - Event specific defined structure */ + uint8_t threadID; /**< RTEMS thread ID. */ + uint16_t moduleID; /**< Module ID. */ + uint16_t lineNumber; /**< Line number in source code. */ + uint32_t functionSymbol; /**< Address of the current function. */ + uint8_t payload[MAX_LOG_PAYLOAD_SIZE]; /**< Event specific payload - This field is 12 bytes long for basic entries, and 44 bytes long for extended entries. */ + } device_event_log; + + typedef struct { + bulk_message_response_header header; /**< Message response header: wMessageID = DEV_GET_AND_CLEAR_EVENT_LOG */ + device_event_log bEventLog[MAX_FW_LOG_BUFFER_ENTRIES]; /**< Event log buffers */ + } bulk_message_response_get_and_clear_event_log; + + + /** + * @brief Supported Raw Stream libtm Message + * + * Single supported raw stream that can be streamed out of the device. + */ + typedef struct { + uint8_t bSensorID; /**< Bits 0-4: Type of sensor, supported values are: Color = 0, Depth = 1, IR = 2, Fisheye = 3, Gyro = 4, Accelerometer = 5, Controller = 6 */ + /**< Bits 5-7: Sensor index - Zero based index of sensor with the same type within device. For example if the device supports two fisheye cameras, */ + /**< The first will use index 0 (bSensorID = 0x03) and the second will use index 1 (bSensorID = 0x23) */ + uint8_t bReserved; /**< Reserved = 0 */ + uint16_t wWidth; /**< Supported width (in pixels) of first stream, 0 for non-camera streams */ + uint16_t wHeight; /**< Supported height (in pixels) or first stream, 0 for non-camera streams */ + uint8_t bPixelFormat; /**< Pixel format of the stream, according to enum PixelFormat */ + union { /**< */ + uint8_t bOutputMode; /**< 0x0 - Send sensor outputs to the internal middlewares only, 0x1 - Send this sensor outputs also to the host over the USB interface. */ + uint8_t bReserved2; /**< Reserved and always 0. Sent from device to host. */ + }; /**< */ + uint16_t wStride; /**< Length in bytes of each line in the image (including padding). 0 for non-camera streams. */ + uint16_t wFramesPerSecond; /**< Supported FPS for first stream to be enabled */ + } supported_raw_stream_libtm_message; + + /** + * @brief Bulk Get Supported Raw Streams Message + * + * Retrieves a list of supported raw streams that can be streamed out of the device. + * Note that multiple entries indicated multiple stream configurations may be supported on the same camera. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 6 bytes, wMessageID = DEV_GET_SUPPORTED_RAW_STREAMS */ + } bulk_message_request_get_supported_raw_streams; + + typedef struct { + bulk_message_response_header header; /**< Message response header: wMessageID = DEV_GET_SUPPORTED_RAW_STREAMS */ + uint16_t wNumSupportedStreams; /**< Number of supported streams in list below */ + uint16_t wReserved; /**< Reserved = 0 */ + supported_raw_stream_libtm_message stream[]; /**< Supported stream info variable sized array */ + } bulk_message_response_get_supported_raw_streams; + + + /** + * @brief Bulk Raw Streams Control Message + * + * Enables streams to be directly streamed to the host. + * Following a successful invocation of this command, and a call to the start command, the enabled streams shall be sent at the expected frame rate to the algorithms, and every streams whose bOutputMode bit was set to 1 shall be sent over the stream endpoint. + * Other streams will be disabled, including those that were enabled by previous invocations. + * To disable all image streaming, this command should be called with wNumEnabledStreams = 0, in this case, all fields after wNumEnabledStreams are dropped. + * If there is a mismatch between wNumEnabledStreams and the size of the message, the firmware will return an INVALID_REQUEST_LEN error message. + * If the profile is not supported, the firmware shall return an INVALID_PARAMETER error message (e.g. trying to open an unsupported configuration by a sensor, not valid FPS or requesting two profiles of the same sensor). + * If the device is already streaming live from its sensors sending a DEV_ RAW_STREAMS_PLAYBACK_CONTROL shall fail with a DEVICE_BUSY error message. + * If no DEV_RAW_STREAMS_CONTROL command was called before the host calls to DEV_START, the first stream configuration for each sensor as reported by DEV_GET_SUPPORTED_RAW_STREAMS shall be used as default. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: wMessageID = DEV_RAW_STREAMS_CONTROL */ + uint16_t wNumEnabledStreams; /**< Number of enabled streams in list below */ + supported_raw_stream_libtm_message stream[]; /**< Supported stream info variable sized array */ + } bulk_message_request_raw_streams_control; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = DEV_RAW_STREAMS_CONTROL */ + } bulk_message_response_raw_streams_control; + + + /** + * @brief Bulk Raw Streams Playback Control Message + * + * Enables receiving streams from the host to be streamed to the MWs instead from the real sensors. + * Following a successful invocation of this command, and a call to the start command, the enabled streams shall be sent at the expected frame rate to the algorithms, and every streams whose bOutputMode bit was set to 1 shall be sent over the stream endpoint (even in the playback case, effectively loopback-ing the samples back to the host). + * Other streams will be disabled, including those that were enabled by previous invocations. + * To disable all image streaming, this command should be called with wNumEnabledStreams = 0, in this case, all fields after wNumEnabledStreams are dropped. + * If there is a mismatch between wNumEnabledStreams and the size of the message, the firmware will return an INVALID_REQUEST_LEN error message. + * If the profile is not supported, the firmware shall return an INVALID_PARAMETER error message (e.g. trying to open an unsupported configuration by a sensor, not valid FPS or requesting two profiles of the same sensor). + * If the device is already streaming live from its sensors sending a DEV_ RAW_STREAMS_PLAYBACK_CONTROL shall fail with a DEVICE_BUSY error message. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: wMessageID = DEV_RAW_STREAMS_PLAYBACK_CONTROL */ + uint16_t wNumEnabledStreams; /**< Number of enabled streams in list below */ + supported_raw_stream_libtm_message stream[]; /**< Supported stream info variable sized array */ + } bulk_message_request_raw_streams_playback_control; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = DEV_RAW_STREAMS_PLAYBACK_CONTROL */ + } bulk_message_response_raw_streams_playback_control; + + + /** + * @brief Bulk Get Camera Intrinsics Message + * + * Retrieves the intrinsic parameters of an individual camera in the device. + */ + typedef struct { + uint32_t dwWidth; /**< Width of the image in pixels */ + uint32_t dwHeight; /**< Height of the image in pixels */ + float_t flPpx; /**< Horizontal coordinate of the principal point of the image, as a pixel offset from the left edge */ + float_t flPpy; /**< Vertical coordinate of the principal point of the image, as a pixel offset from the top edge */ + float_t flFx; /**< Focal length of the image plane, as a multiple of pixel width */ + float_t flFy; /**< Focal length of the image plane, as a multiple of pixel Height */ + uint32_t dwDistortionModel; /**< Distortion model of the image: NONE = 0, MODIFIED_BROWN_CONRADY = 1, INVERSE_BROWN_CONRADY = 2, FTHETA = 3, KANNALA_BRANDT4 = 4 */ + float_t flCoeffs[5]; /**< Distortion coefficients */ + } camera_intrinsics; + + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 7 bytes, wMessageID = DEV_GET_CAMERA_INTRINSICS */ + uint8_t bCameraID; /**< Bits 0-4: Type of requested camera intrinsics, supported values are: Color = 0, Depth = 1, IR = 2, Fisheye = 3 */ + /**< Bits 5-7: Camera index - Zero based index of camera with the same type within device. For example if the device supports two fisheye cameras, */ + /**< The first will use index 0 (bCameraID = 0x03) and the second will use index 1 (bCameraID = 0x23) */ + } bulk_message_request_get_camera_intrinsics; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 56 or 8 bytes, wMessageID = DEV_GET_CAMERA_INTRINSICS */ + camera_intrinsics intrinsics; /**< Intrinsics parameters of an individual camera in the device */ + } bulk_message_response_get_camera_intrinsics; + + + /** + * @brief Bulk Get Motion Module Intrinsics Message + * + * Retrieves the intrinsic parameters of an individual motion module in the device. + */ + typedef struct { + float_t flData[3][4]; /**< Scale matrix */ + float_t flNoiseVariances[3]; /**< Noise variances */ + float_t flBiasVariances[3]; /**< Bias variances */ + } motion_intrinsics; + + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 7 bytes, wMessageID = DEV_GET_MOTION_INTRINSICS */ + uint8_t bMotionID; /**< Bits 0-4: Type of requested motion module, supported values are: Gyro = 4, Accelerometer = 5 */ + /**< Bits 5-7: Motion module index - Zero based index of module with the same type within device. For example if the device supports two gyroscopes, */ + /**< The first gyroscope will use index 0 (bMotionID = 0x04) and the second gyroscope will use index 1 (bMotionID = 0x24) */ + } bulk_message_request_get_motion_intrinsics; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 80 or 8 bytes, wMessageID = DEV_GET_MOTION_INTRINSICS */ + motion_intrinsics intrinsics; /**< Intrinsics parameters of an individual motion module in the device */ + } bulk_message_response_get_motion_intrinsics; + + + /** + * @brief Bulk Get Extrinsics Message + * + * Retrieves the extrinsic pose of on individual sensor in the device relative to another one. + */ + typedef struct { + float_t flRotation[9]; /**< Column-major 3x3 rotation matrix */ + float_t flTranslation[3]; /**< 3 element translation vector, in meters */ + uint8_t bReferenceSensorID; /**< Reference sensor uses for extrinsics calculation */ + } sensor_extrinsics; + + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8 bytes, wMessageID = DEV_GET_EXTRINSICS */ + uint8_t bSensorID; /**< Bits 0-4: Type of requested sensor extrinsics, supported values are: Color = 0, Depth = 1, IR = 2, Fisheye = 3, Gyro = 4, Accelerometer = 5, */ + /**< Bits 5-7: Camera index - Zero based index of sensor with the same type within device. For example if the device supports two fisheye cameras, */ + /**< The first will use index 0 (bSensorID = 0x03) and the second will use index 1 (bSensorID = 0x23) */ + } bulk_message_request_get_extrinsics; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 56 or 8 bytes, wMessageID = DEV_GET_EXTRINSICS */ + sensor_extrinsics extrinsics; /**< Extrinsics pose of an individual sensor in the device relative to another one */ + } bulk_message_response_get_extrinsics; + + + /** + * @brief Bulk Set Camera Intrinsics Message + * + * Sets the intrinsic parameters of an individual camera in the device. + * These parameters shall not be written to the EEPROM. They shall be used in the next streaming session only and shall be discarded once a "stop" command is called. + * This command is available only when the middlewares (6DoF / Occupancy map) are disabled. + * If any middleware is enabled, this command will return an UNSUPPORTED error. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 56 bytes, wMessageID = DEV_SET_CAMERA_INTRINSICS */ + uint8_t bCameraID; /**< Bits 0-4: Type of requested camera intrinsics, supported values are: Color = 0, Depth = 1, IR = 2, Fisheye = 3 */ + /**< Bits 5-7: Camera index - Zero based index of camera with the same type within device. For example if the device supports two fisheye cameras, */ + /**< The first will use index 0 (bCameraID = 0x03) and the second will use index 1 (bCameraID = 0x23) */ + uint8_t bReserved; /**< Reserved = 0 */ + camera_intrinsics intrinsics; /**< Intrinsics parameters of an individual camera in the device */ + } bulk_message_request_set_camera_intrinsics; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = DEV_SET_CAMERA_INTRINSICS */ + } bulk_message_response_set_camera_intrinsics; + + + /** + * @brief Bulk Set Motion Module Intrinsics Message + * + * Sets the intrinsic parameters of an individual motion module in the device. + * These parameters shall not be written to the EEPROM. They shall be used in the next streaming session only and shall be discarded once a "stop" command is called. + * This command is available only when the middlewares (6DoF / Occupancy map) are disabled. + * If any middleware is enabled, this command will return an UNSUPPORTED error. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 80 bytes, wMessageID = DEV_SET_MOTION_INTRINSICS */ + uint8_t bMotionID; /**< Bits 0-4: Type of requested motion module, supported values are: Gyro = 4, Accelerometer = 5 */ + /**< Bits 5-7: Motion module index - Zero based index of module with the same type within device. For example if the device supports two gyroscopes, */ + /**< The first gyroscope will use index 0 (bMotionID = 0x04) and the second gyroscope will use index 1 (bMotionID = 0x24) */ + uint8_t bReserved; /**< Reserved = 0 */ + motion_intrinsics intrinsics; /**< Intrinsics parameters of an individual motion module in the device */ + } bulk_message_request_set_motion_intrinsics; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = DEV_SET_MOTION_INTRINSICS */ + } bulk_message_response_set_motion_intrinsics; + + + /** + * @brief Bulk Log Control Message + * + * Controls the logging parameters, such as verbosity and log mode. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8 bytes, wMessageID = DEV_LOG_CONTROL */ + uint8_t bVerbosity; /**< Verbosity level */ + uint8_t bLogMode; /**< 0x00 - No rollover, logging will be paused after log is filled. Until cleared, first events will be stored */ + /**< 0x01 - Rollover mode */ + } bulk_message_request_log_control; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = DEV_LOG_CONTROL */ + } bulk_message_response_log_control; + + + /** + * @brief Bulk Reset Message + * + * Resets the device. The command supports a firmware reset with and without reload of the firmware. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8 bytes, wMessageID = DEV_RESET */ + uint8_t bVerbosity; /**< Reset action: 0 - reset the VPU device and boot from the firmware already loaded in memory */ + /**< 1 - reset the VPU device to a "pre-load" state, i.e. firmware is loaded from ROM and awaits new firmware to be pushed from host via USB */ + uint8_t bReserved; /**< Reserved = 0 */ + } bulk_message_request_reset; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = DEV_RESET */ + } bulk_message_response_reset; + + + /** + * @brief Bulk Read EEPROM Message + * + * Read data from the EEPROM memory. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 10 bytes, wMessageID = DEV_READ_EEPROM */ + uint16_t wOffset; /**< EEPROM offset address to read from */ + uint16_t wSize; /**< The requested size in bytes of the data to read */ + } bulk_message_request_read_eeprom; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 10+wSize bytes, wMessageID = DEV_READ_EEPROM */ + uint16_t wSize; /**< Size in bytes of the read data */ + uint8_t bData[MAX_EEPROM_BUFFER_SIZE]; /**< The requested EEPROM data array */ + } bulk_message_response_read_eeprom; + + + /** + * @brief Bulk Write EEPROM data Message + * + * Write data to the EEPROM memory. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 10+wSize bytes, wMessageID = DEV_WRITE_EEPROM */ + uint16_t wOffset; /**< EEPROM offset address to write to */ + uint16_t wSize; /**< The size in bytes of the data to write to */ + uint8_t bData[MAX_EEPROM_BUFFER_SIZE]; /**< The requested EEPROM data array */ + } bulk_message_request_write_eeprom; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 10+wSize bytes, wMessageID = DEV_WRITE_EEPROM */ + uint16_t wSize; /**< The size of bytes written */ + } bulk_message_response_write_eeprom; + + + /** + * @brief Bulk Start Message + * + * Starts all the enabled streams and middlewares, using either default configuration or the parameters from calls to any of the configuration commands. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 6 bytes, wMessageID = DEV_START */ + } bulk_message_request_start; + + typedef struct { + bulk_message_response_header header; /**< Message request header: dwLength = 8 bytes, wMessageID = DEV_START */ + } bulk_message_response_start; + + + /** + * @brief Bulk Stop Message + * + * Stops all streams and middlewares running on the device. + * After a "stop" command the device shall suspend to a low-power state. It shall also reset all saved configuration parameters, + * Such as the next call to start shall return to use all the default parameters (unless new calls to any of the configuration commands are made). + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 6 bytes, wMessageID = DEV_STOP */ + } bulk_message_request_stop; + + typedef struct { + bulk_message_response_header header; /**< Message request header: dwLength = 8 bytes, wMessageID = DEV_STOP */ + } bulk_message_response_stop; + + + /** + * @brief Bulk Get Pose Message + * + * Returns the latest pose of the camera relative to its initial position, + * If relocalization data was set, this pose is relative to the relocalization database. + */ + typedef struct { + float_t flX; /**< X value of translation, in meters (relative to initial position) */ + float_t flY; /**< Y value of translation, in meters (relative to initial position) */ + float_t flZ; /**< Z value of translation, in meters (relative to initial position) */ + float_t flQi; /**< Qi component of rotation as represented in quaternion rotation (relative to initial position) */ + float_t flQj; /**< Qj component of rotation as represented in quaternion rotation (relative to initial position) */ + float_t flQk; /**< Qk component of rotation as represented in quaternion rotation (relative to initial position) */ + float_t flQr; /**< Qr component of rotation as represented in quaternion rotation (relative to initial position) */ + float_t flVx; /**< X value of velocity, in meter/sec */ + float_t flVy; /**< Y value of velocity, in meter/sec */ + float_t flVz; /**< Z value of velocity, in meter/sec */ + float_t flVAX; /**< X value of angular velocity, in RAD/sec */ + float_t flVAY; /**< Y value of angular velocity, in RAD/sec */ + float_t flVAZ; /**< Z value of angular velocity, in RAD/sec */ + float_t flAx; /**< X value of acceleration, in meter/sec^2 */ + float_t flAy; /**< Y value of acceleration, in meter/sec^2 */ + float_t flAz; /**< Z value of acceleration, in meter/sec^2 */ + float_t flAAX; /**< X value of angular acceleration, in RAD/sec^2 */ + float_t flAAY; /**< Y value of angular acceleration, in RAD/sec^2 */ + float_t flAAZ; /**< Z value of angular acceleration, in RAD/sec^2 */ + uint64_t llNanoseconds; /**< Timestamp of pose, measured in nanoseconds since device system initialization */ + uint32_t dwTrackerConfidence; /**< pose data confidence 0x0 - Failed, 0x1 - Low, 0x2 - Medium, 0x3 - High */ + uint32_t dwMapperConfidence; /**< Bits 0-1: 0x0 – Failed, 0x1 – Low, 0x2 – Medium, 0x3 - High, Bits 2-31: Reserved */ + } pose_data; + + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 7 bytes, wMessageID = DEV_GET_POSE */ + uint8_t bIndex; /**< Index of HMD or controller - 0x0 = HMD, 0x1 - controller 1, 0x2 - controller 2 */ + } bulk_message_request_get_pose; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 100 or 8 bytes, wMessageID = DEV_GET_POSE */ + pose_data pose; /**< Short low-latency data (namely 6DoF pose data) */ + } bulk_message_response_get_pose; + + + /** + * @brief Bulk Set Exposure Control Message + * Enable/disable the auto-exposure management of the different video streams, and configure the powerline frequency rate. + * Calling this message is only supported before the streams are started. + * The default values for video streams 0 and 1 shall be auto-exposure enable with 50Hz flicker rate. + * The firmware only supports the following: + * 1. Disabling auto-exposure for all streams (bVideoStreamsMask==0) + * 2. Enabling auto-exposure for both video stream 0 and 1 together (bVideoStreamsMask ==0x3). + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8 bytes, wMessageID = DEV_EXPOSURE_MODE_CONTROL */ + uint8_t bVideoStreamsMask; /**< Bitmask of the streams to apply the configuration to. bit X -> stream X: 0 - disable, 1 - enable */ + uint8_t bAntiFlickerMode; /**< Anti Flicker Mode: 0 - disable (e.g. for outside use), 1 - 50Hz, 2 - 60Hz, 3 - Auto (currently not supported) */ + } bulk_message_request_set_exposure_mode_control; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = DEV_EXPOSURE_MODE_CONTROL */ + } bulk_message_response_set_exposure_mode_control; + + + /** + * @brief Bulk Set Exposure Message + * + * Sets manual values for the video streams integration time and gain. + * The command supports setting these values to all required streams with a single call. + * The command shall return UNSUPPORTED if the stream is configure to use auto-exposure, and INVALID_PARAMETER if any of the values are out of range. + */ + typedef struct { + uint8_t bCameraID; /**< Bits 0-4: Type of requested camera, supported values are: Color = 0, Depth = 1, IR = 2, Fisheye = 3 */ + /**< Bits 5-7: Camera index - Zero based index of camera with the same type within device */ + uint8_t bReserved[3]; /**< Reserved = 0 */ + uint32_t dwIntegrationTime; /**< Integration time in micro-seconds */ + float_t fGain; /**< Digital gain */ + } stream_exposure; + + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = (8 + bNumberOfStreams*8 bytes), wMessageID = DEV_SET_EXPOSURE */ + uint8_t bNumOfVideoStreams; /**< Number of video streams to configure: Bits 0-2: stream index, Bits 3-7: reserved */ + uint8_t bReserved; /**< Reserved = 0 */ + stream_exposure stream[MAX_VIDEO_STREAMS]; /**< Stream exposure data variable sized array, according to wNumberOfStreams */ + } bulk_message_request_set_exposure; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = DEV_SET_EXPOSURE */ + } bulk_message_response_set_exposure; + + + /** + * @brief Bulk Get Temperature Message + * + * Returns temperature and temperature threshold from all temperature sensors (VPU, IMU, BLE) + */ + typedef struct { + uint32_t dwIndex; /**< Temperature sensor index: 0x0 - VPU, 0x1 - IMU, 0x2 - BLE */ + float_t fTemperature; /**< Sensor's temperature in Celius */ + float_t fThreshold; /**< The sensor's threshold temperature */ + } sensor_temperature; + + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 6 bytes, wMessageID = DEV_GET_TEMPERATURE */ + } bulk_message_request_get_temperature; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLendth = (12 + 12 * dwCount) bytes, wMessageID = DEV_GET_TEMPERATURE */ + uint32_t dwCount; /**< Number or temperature sensors */ + sensor_temperature temperature[]; /**< temperature variable sized array */ + } bulk_message_response_get_temperature; + + + /** + * @brief Bulk Set Temperature Threshold Message + * + * Set temperature threshold to requested sensors (VPU, IMU, BLE) + * The firmware shall actively monitor the temperature of the underlying sensors. + * When a component temperature is 10% close to its defined threshold, the firmware shall send a DEV_ERROR message with a TEMPERATURE_WARNING status to the host, + * When the temperature reach the threshold, the firmware shall stop all running algorithms and sensors (as if DEV_STOP was called), and send a TEMPERATURE_SHUTDOWN status to the user. + */ + typedef struct { + uint32_t dwIndex; /**< Temperature sensor index: 0x0 - VPU, 0x1 - IMU, 0x2 - BLE */ + float_t fThreshold; /**< The new sensor's threshold temperature */ + } sensor_set_temperature; + + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = (12 + 8 * dwCount) bytes, wMessageID = DEV_SET_TEMPERATURE_THRESHOLD */ + uint16_t wForceToken; /**< Token to force higher temperature threshold (80-100) */ + uint32_t dwCount; /**< Number or temperature sensors */ + sensor_set_temperature temperature[]; /**< temperature variable sized array */ + } bulk_message_request_set_temperature_threshold; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLendth = 8, wMessageID = DEV_SET_TEMPERATURE_THRESHOLD */ + } bulk_message_response_set_temperature_threshold; + + + /** + * @brief Bulk Set Geo Location Message + * + * Sets the geographical location (e.g. GPS data). This data can be later used by the algorithms to correct IMU readings. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 32 bytes, wMessageID = DEV_SET_GEO_LOCATION */ + uint16_t wReserved; /**< Reserved = 0 */ + double_t dfLatitude; /**< Latitude in degrees */ + double_t dfLongitude; /**< Longitude in degrees */ + double_t dfAltitude; /**< Altitude in meters above the WGS 84 reference ellipsoid */ + } bulk_message_request_set_geo_location; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLendth = 8, wMessageID = DEV_SET_GEO_LOCATION */ + } bulk_message_response_set_geo_location; + + + /** + * @brief Bulk Flush Message + * + * Sends in addition to the standard response over the commands endpoint, a response message over all the device-to-host endpoints - events & streams endpoints. + * This command shall be the first command requested by the host in order to "flush" any possible messages left from a previous session that wasn't terminated properly (e.g. host app crashed or timed-out) + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 16 bytes, wMessageID = DEV_FLUSH */ + uint16_t wReserved; /**< Reserved = 0 */ + uint64_t ddwToken; /**< 64 bit token that will be received in the response */ + } bulk_message_request_flush; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLendth = 16, wMessageID = DEV_FLUSH */ + uint64_t ddwToken; /**< 64 bit token from the request */ + } bulk_message_response_flush; + + + /** + * @brief Bulk GPIO control Message + * + * Enable manufacturing tools to directly control the following GPIOs - 74, 75, 76 & 77. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 7 bytes, wMessageID = DEV_GPIO_CONTROL */ + uint8_t bGpioControl; /**< Reserved = 0 */ + } bulk_message_request_gpio_control; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLendth = 8, wMessageID = DEV_GPIO_CONTROL */ + } bulk_message_response_gpio_control; + + + /** + * @brief Bulk configuration Lock Message + * + * Write-protect the manufactoring configuration tables from future changes. + * on DEV_LOCK_CONFIGURATION - The locking is done in software by the firmware managing a lock bits in each configuration table metadata. + * on DEV_LOCK_EEPROM - The locking is done in hardware by locking the upper quarter of the EEPROM memory + * The lock can be applied permanently, meaning it cannot be latter un-locked. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 12 bytes, wMessageID = DEV_LOCK_CONFIGURATION or DEV_LOCK_EEPROM */ + uint16_t wReserved; /**< Reserved = 0 */ + uint32_t dwLock; /**< 0x0 - Unlock, 0x1 - Lock */ + /**< 0xDEAD10CC – the configuration data shall be permanently locked. *** WARNING *** this might be an irreversible action. */ + /**< After calling this the write protection the settings cannot be modified and therefore the memory write protection is frozen. */ + } bulk_message_request_lock_configuration; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLendth = 8, wMessageID = DEV_LOCK_CONFIGURATION */ + } bulk_message_response_lock_configuration; + + + /** + * @brief Bulk read configuration Message + * + * Reads a configuration table from the device memory. + * The device shall return UNSUPPORTED if it does not recognize the requested TableType. + * The device shall return TABLE_NOT_EXIST if it recognize the table type but no such table exists yet in the EEPROM. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8, wMessageID = DEV_READ_CONFIGURATION */ + uint16_t wTableId; /**< The ID of the requested configuration table */ + } bulk_message_request_read_configuration; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLendth = 8 bytes + size of table, wMessageID = DEV_READ_CONFIGURATION */ + uint8_t bTable[MAX_TABLE_SIZE]; /**< The requested configuration table, starting with the "standard" table header. */ + } bulk_message_response_read_configuration; + + + /** + * @brief Bulk write configuration Message + * + * Writes a configuration table to the device's EEPROM memory. + * This command shall only be supported while the device is stopped, otherwise it shall return DEVICE_BUSY. + * The device shall return UNSUPPORTED if it does not recognize the requested TableType. + * The device shall return TABLE_LOCKED if the configuration table is write protected and cannot be overridden. + * The new data shall be available immediately after completion without requiring a device reset, both to any firmware code or to an external client through a "read configuration" command. + * All internal data object in the firmware memory that were already initialized from some EEPROM data or previous "write configuration" command shall be invalidated and refreshed to the new written data. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8 + size of table bytes, wMessageID = DEV_WRITE_CONFIGURATION */ + uint16_t wTableId; /**< The ID of the requested configuration table */ + uint8_t bTable[MAX_TABLE_SIZE]; /**< The requested configuration table, starting with the "standard" table header. */ + } bulk_message_request_write_configuration; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLendth = 8, wMessageID = DEV_WRITE_CONFIGURATION */ + } bulk_message_response_write_configuration; + + + /** + * @brief Bulk reset configuration Message + * + * Delete a configuration table from the internal EEPROM storage. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8, wMessageID = DEV_RESET_CONFIGURATION */ + uint16_t wTableId; /**< The ID of the requested configuration table */ + } bulk_message_request_reset_configuration; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLendth = 8, wMessageID = DEV_RESET_CONFIGURATION */ + } bulk_message_response_reset_configuration; + + + /** + * @brief Bulk timeout configuration Message + * + * Host timeout - When the firmware is actively streaming or tracking, it shall monitor the host status to identify the cases where the host app crashed or stopped responding. + * The firmware shall use the host USB reads as the host's "heartbeat", so if outgoing packets were continuously dropped for over 1 second the firmware shall stop sending packets and stop all operations, + * as if a DEV_STOP command was sent, but without sending the DEV_STOPPED status event packet. + * The timeout shall be configurable or disabled through this DEV_TIMEOUT_CONFIGURATION command. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8, wMessageID = DEV_TIMEOUT_CONFIGURATION */ + uint16_t wTimeoutInMillis; /**< The commands TX timeout in milliseconds to use. Zero to disable timeout. */ + } bulk_message_request_timeout_configuration; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLendth = 8, wMessageID = DEV_TIMEOUT_CONFIGURATION */ + } bulk_message_response_timeout_configuration; + + + /** + * @brief Bulk Get Occupancy Map Tiles Message + * + * Returns new real world tiles detected by the SLAM middleware (tiles reported earlier in the session will not be returned). + * Tiles are represented in location relative to the device's initial position, + * If relocalization data was set, this pose is relative to the relocalization database. + */ + typedef struct { + uint32_t dwX; /**< X value of first tile, in meters (relative to initial position) */ + uint32_t dwY; /**< Y value of first tile, in meters (relative to initial position) */ + uint32_t dwW; /**< W value of first tile, in meters (relative to initial position) or 0 if value unknown */ + } map_tiles; + + typedef struct { + uint8_t bAccuracy; /**< Accuracy of occupancy map calculation: 0x0 - low accuracy, 0x1 - medium accuracy, 0x2 - high accuracy */ + uint8_t bReserved; /**< Reserved = 0 */ + uint16_t wTileCount; /**< Number of tiles in the following array */ + uint64_t llNanoseconds; /**< Timestamp of tile sample, measured in nanoseconds since device system initialization */ + map_tiles tiles[]; /**< Occupancy map tiles variable sized array */ + } occupancy_map_tiles; + + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 6 bytes, wMessageID = SLAM_GET_OCCUPANCY_MAP_TILES */ + } bulk_message_request_get_occupancy_map_tiles; + + typedef struct { + bulk_message_response_header header; /**< Message response header: wMessageID = SLAM_GET_OCCUPANCY_MAP_TILES */ + occupancy_map_tiles map_tiles; /**< Occupancy map tiles */ + } bulk_message_response_get_occupancy_map_tiles; + + + /** + * @brief Bulk Get Localization Data Message + * + * Trigger the FW to send interrupt_message_get_localization_data_stream with the localization data as created during a 6DoF session + * The response to this message is generated and streamed by the underlying firmware algorithm in run-time, so the total size of the data cannot be known in advance. + * The entire data will be streams in "chunks" sent using SLAM_GET_LOCALIZATION_DATA_STREAM messages over the stream endpoint. + * The firmware shall use a MORE_DATA_AVAILABLE status to indicate there are more data to send. The last chunk (possibly even a zero-size chunk) shall be marked with a SUCCESS status code. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 7 bytes, wMessageID = SLAM_GET_LOCALIZATION_DATA */ + uint8_t bReserved; /**< Reserved = 0 */ + } bulk_message_request_get_localization_data; + + typedef struct { + bulk_message_response_header header; /**< Message response header: wMessageID = SLAM_GET_LOCALIZATION_DATA */ + } bulk_message_response_get_localization_data; + + /** + * @brief Bulk Set Localization Data Stream Message + * + * Sets the localization data to be used to localize the 6DoF reports. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 10 bytes + bPayload size, wMessageID = according to large message type */ + uint16_t wStatus; /**< SUCCESS: indicate last chunk, MORE_DATA_AVAILABLE: any other chunk */ + uint16_t wIndex; /**< A running counter starting at 0 identifying the chunk index in a single data transaction */ + uint8_t bPayload[]; /**< payload data variable sized array. Data format is algorithm specific and opaque to the USB and host stack */ + } bulk_message_large_stream; + + + /** + * @brief Bulk reset Localization Data Message + * + * Resets the localization data + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8 bytes, wMessageID = SLAM_RESET_LOCALIZATION_DATA */ + uint8_t bFlag; /**< 0 - Reset all localization data, 1 - Reset only the map by its bMapIndex */ + uint8_t bReserved; /**< Reserved = 0 */ + } bulk_message_request_reset_localization_data; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = SLAM_RESET_LOCALIZATION_DATA */ + } bulk_message_response_reset_localization_data; + + + /** + * @brief Bulk Set Static Node Message + * + * Set relative position of a static node + */ + typedef struct { + float_t flX; /**< X value of translation, in meters (in the coordinate system of the tracker relative to the current position) */ + float_t flY; /**< Y value of translation, in meters (in the coordinate system of the tracker relative to the current position) */ + float_t flZ; /**< Z value of translation, in meters (in the coordinate system of the tracker relative to the current position) */ + float_t flQi; /**< Qi component of rotation as represented in quaternion rotation (in the coordinate system of the tracker relative to the current position) */ + float_t flQj; /**< Qj component of rotation as represented in quaternion rotation (in the coordinate system of the tracker relative to the current position) */ + float_t flQk; /**< Qk component of rotation as represented in quaternion rotation (in the coordinate system of the tracker relative to the current position) */ + float_t flQr; /**< Qr component of rotation as represented in quaternion rotation (in the coordinate system of the tracker relative to the current position) */ + } static_node_data; + + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 164 bytes, wMessageID = SLAM_SET_STATIC_NODE */ + uint16_t wReserved; /**< Reserved = 0 */ + uint8_t bGuid[MAX_GUID_LENGTH]; /**< Null-terminated C-string, with max length 127 bytes plus one byte for the terminating null character */ + static_node_data data; /**< Static node data */ + } bulk_message_request_set_static_node; + + typedef struct { + bulk_message_response_header header; /**< Message response header: wMessageID = SLAM_SET_STATIC_NODE */ + } bulk_message_response_set_static_node; + + /** + * @brief Bulk Get Static Node Message + * + * Get relative position of a static node + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 136 bytes, wMessageID = SLAM_GET_STATIC_NODE */ + uint16_t wReserved; /**< Reserved = 0 */ + uint8_t bGuid[MAX_GUID_LENGTH]; /**< Null-terminated C-string, with max length 127 bytes plus one byte for the terminating null character */ + } bulk_message_request_get_static_node; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 36 byte, wMessageID = SLAM_GET_STATIC_NODE */ + static_node_data data; /**< Static node data */ + } bulk_message_response_get_static_node; + + + /** + * @brief Bulk Set 6DoF Interrupt Rate Message + * + * Sets the rate of 6DoF interrupts. This is typically related to the host rendering rate. + */ + typedef struct { + uint8_t bInterruptRate; /**< Rate of 6DoF interrupts. The following values are supported: */ + /**< 0x0 - no interrupts */ + /**< 0x1 - interrupts on every fisheye camera update (30 interrupts per second for TM2) */ + /**< 0x2 - interrupts on every IMU update (400-500 interrupts per second for TM2) - default value */ + } sixdof_interrupt_rate_libtm_message; + + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 7 bytes, wMessageID = SLAM_SET_6DOF_INTERRUPT_RATE */ + sixdof_interrupt_rate_libtm_message message; /**< Rate of 6DoF interrupts */ + } bulk_message_request_set_6dof_interrupt_rate; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = SLAM_SET_6DOF_INTERRUPT_RATE */ + } bulk_message_response_set_6dof_interrupt_rate; + + + /** + * @brief Bulk 6DoF Control Message + * + * Enables / disables 6DoF calculation. + * If no SLAM_6DOF_CONTROL command was called before the host calls to DEV_START, the default value used shall be "Disable 6DoF". + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 9 bytes, wMessageID = SLAM_6DOF_CONTROL */ + uint8_t bEnable; /**< 0x00 - Disable 6DoF, 0x01 - Enable 6DoF */ + uint8_t bMode; /**< 0x00 - Normal Mode, 0x01 - Fast Playback, 0x02 - Mapping Enabled , 0x04 - Relocalization Enabled */ + } bulk_message_request_6dof_control; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = SLAM_6DOF_CONTROL */ + } bulk_message_response_6dof_control; + + + /** + * @brief Bulk Occupancy Map Control Message + * + * Enables/disables occupancy map calculation. Occupancy map calculation is based on 6DoF calculation, + * So it cannot be enabled when 6DoF is disabled, and an UNSUPPORTED error will be returned. + * If no SLAM_6DOF_CONTROL command was called before the host calls to DEV_START, the default value used shall be "Disable occupancy map". + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 7 bytes, wMessageID = SLAM_OCCUPANCY_MAP_CONTROL */ + uint8_t bEnable; /**< 0x00 - Disable occupancy map, 0x01 - Enable occupancy map */ + } bulk_message_request_occupancy_map_control; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = SLAM_OCCUPANCY_MAP_CONTROL */ + } bulk_message_response_occupancy_map_control; + + + /** + * @brief Stream Endpoint Protocol + * + * The stream endpoint is a bulk IN / OUT endpoint, used to stream out image streams as raw streams, as enabled in the "Enable Raw Streams" command, + * Or stream in image and IMU streams, as enabled in the "Enable Playback Streams" command. (IMU outputs are sent over the interrupt endpoint). + * Streaming streams both out and in at the same time shall be supported (effectively loopback - ing the input streams). + * The host reads from this endpoint to get new streaming data, and write new samples to playback them. + * Every sample is made from the common fields from below, with a variable specific metadata and data part (starting at byte 24 below). + */ + + + /** + * @brief Bulk raw stream header + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 28 + dwMetadataLength + dwFrameLength bytes, wMessageID = DEV_SAMPLE */ + uint8_t bSensorID; /**< Bits 0-4: Type of sensor, supported values are: Color = 0, Depth = 1, IR = 2, Fisheye = 3, Gyro = 4, Accelerometer = 5, Controller = 6 */ + /**< Bits 5-7: Sensor index - Zero based index of sensor with the same type within device. For example if the device supports two fisheye cameras, */ + /**< The first will use index 0 (bSensorID = 0x03) and the second will use index 1 (bSensorID = 0x23) */ + uint8_t bReserved; /**< Reserved = 0 */ + uint64_t llNanoseconds; /**< Frame integration timestamp, as measured in nanoseconds since device initialization */ + uint64_t llArrivalNanoseconds; /**< Frame arrival timestamp, as measured in nanoseconds since device initialization */ + uint32_t dwFrameId; /**< A running index of frames from every unique sensor. Starting from 0. */ + } bulk_message_raw_stream_header; + + + /** + * @brief Bulk raw video stream metadata + */ + typedef struct + { + uint32_t dwMetadataLength; /**< Metadata length in bytes (8 bytes) */ + uint32_t dwExposuretime; /**< Exposure time of this frame in microseconds */ + float_t fGain; /**< Gain multiplier of this frame */ + uint32_t dwFrameLength; /**< Length of frame below, in bytes, shall be equal to Stride X Height X BPP */ + uint8_t bFrameData[]; /**< Frame data variable sized array */ + } bulk_message_video_stream_metadata; + + + /** + * @brief Bulk raw video stream message + * + * Specific frame metadata and data for sensor IDs, color, depth, IR, Fisheye + */ + typedef struct + { + bulk_message_raw_stream_header rawStreamHeader; + bulk_message_video_stream_metadata metadata; + } bulk_message_video_stream; + + + /** + * @brief Bulk raw accelerometer stream metadata + */ + typedef struct + { + uint32_t dwMetadataLength; /**< Metadata length in bytes (4 bytes) */ + float_t flTemperature; /**< Accelerometer temperature */ + uint32_t dwFrameLength; /**< Length of frame below (12 bytes) */ + float_t flAx; /**< X value of acceleration, in meter/sec^2 */ + float_t flAy; /**< Y value of acceleration, in meter/sec^2 */ + float_t flAz; /**< Z value of acceleration, in meter/sec^2 */ + } bulk_message_accelerometer_stream_metadata; + + /** + * @brief Bulk raw accelerometer stream message + * + * Specific frame metadata and data for sensor IDs - accelerometer + */ + typedef struct + { + bulk_message_raw_stream_header rawStreamHeader; + bulk_message_accelerometer_stream_metadata metadata; + } bulk_message_accelerometer_stream; + + + /** + * @brief Bulk raw gyro stream metadata + */ + typedef struct + { + uint32_t dwMetadataLength; /**< Metadata length in bytes (4 bytes) */ + float_t flTemperature; /**< Gyro temperature */ + uint32_t dwFrameLength; /**< Length of frame below (12 bytes) */ + float_t flGx; /**< X value of gyro, in radians/sec */ + float_t flGy; /**< Y value of gyro, in radians/sec */ + float_t flGz; /**< Z value of gyro, in radians/sec */ + } bulk_message_gyro_stream_metadata; + + /** + * @brief Bulk raw gyro stream message + * + * Specific frame metadata and data for sensor IDs - gyro + */ + typedef struct + { + bulk_message_raw_stream_header rawStreamHeader; + bulk_message_gyro_stream_metadata metadata; + } bulk_message_gyro_stream; + + + /** + * @brief Bulk raw controller stream metadata + */ + typedef struct + { + uint32_t dwMetadataLength; /**< Metadata length in bytes (0 bytes) */ + uint32_t dwFrameLength; /**< Length of frame below (8 bytes) */ + uint8_t bEventID; /**< Event ID - button, trackpad or battery (vendor specific), supported values 0-63 */ + uint8_t bInstanceId; /**< Instance of the sensor in case of multiple sensors */ + uint8_t bSensorData[CONTROLLER_SENSOR_DATA_SIZE]; /**< Sensor data that is pass-through from the controller firmware */ + } bulk_message_controller_stream_metadata; + + + /** + * @brief Bulk raw controller stream message + * + * Specific frame metadata and data for sensor IDs - controller + */ + typedef struct + { + bulk_message_raw_stream_header rawStreamHeader; + bulk_message_controller_stream_metadata metadata; + } bulk_message_controller_stream; + + + /** + * @brief Bulk raw rssi stream metadata + */ + typedef struct + { + uint32_t dwMetadataLength; /**< Metadata length in bytes (0 bytes) */ + uint32_t dwFrameLength; /**< Length of frame below (8 bytes) */ + float_t flSignalStrength; /**< Sampled signal strength (dB), a value closer to 0 indicates a stronger signal */ + } bulk_message_rssi_stream_metadata; + + + /** + * @brief Bulk raw rssi stream message + * + * Specific frame metadata and data for sensor IDs – BLE Signal Strength (sent during a BLE RSSI test) + */ + typedef struct + { + bulk_message_raw_stream_header rawStreamHeader; + bulk_message_rssi_stream_metadata metadata; + } bulk_message_rssi_stream; + + /** + * @brief Bulk raw velocimeter stream metadata + */ + typedef struct + { + uint32_t dwMetadataLength; /**< Metadata length in bytes (4 bytes) */ + float_t flTemperature; /**< velocimeter temperature */ + uint32_t dwFrameLength; /**< Length of frame below (12 bytes) */ + float_t flVx; /**< X value of velocimeter, in radians/sec */ + float_t flVy; /**< Y value of velocimeter, in radians/sec */ + float_t flVz; /**< Z value of velocimeter, in radians/sec */ + } bulk_message_velocimeter_stream_metadata; + + /** + * @brief Bulk raw velocimeter stream message + * + * Specific frame metadata and data for sensor IDs - velocimeter + */ + typedef struct + { + bulk_message_raw_stream_header rawStreamHeader; + bulk_message_velocimeter_stream_metadata metadata; + } bulk_message_velocimeter_stream; + + /** + * @brief Bulk Controller pose Control Message + * + * Enables / disables pose calculation for the controllers. + * If no CONTROLLER_POSE_CONTROL command was called before the host calls to DEV_START, the default value used shall be "Disable Controllers 6DoF". + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 9 bytes, wMessageID = CONTROLLER_POSE_CONTROL */ + uint8_t bEnable; /**< 0x00 - Disable 6DoF, 0x01 - Enable 6DoF */ + uint8_t bMode; /**< 0x00 - Normal Mode, 0x01 - Fast Playback */ + uint8_t bNumControllers; /**< Number of controllers to be tracked. Only values of 1 or 2 supported. Ignored if bEnable == 0x0 */ + } bulk_message_request_controller_pose_control; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = CONTROLLER_POSE_CONTROL */ + } bulk_message_response_controller_pose_control; + + + /** + * @brief Bulk Controller Device Connect Message + * + * Connect the controller to the device. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 15 bytes, wMessageID = CONTROLLER_DEVICE_CONNECT */ + uint16_t wTimeout; /**< Connect timeout, in milliseconds */ + uint8_t bMacAddress[MAC_ADDRESS_SIZE]; /**< MAC address of controller to be connected */ + uint8_t bAddressType; /**< BLE address type (as received in advertisement) */ + } bulk_message_request_controller_device_connect; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = CONTROLLER_DEVICE_CONNECT */ + uint8_t bControllerID; /**< Assigned Controller identifier (1 or 2) */ + } bulk_message_response_controller_device_connect; + + + /** + * @brief Bulk Controller Device Disconnect Message + * + * Disconnect the controller from the device. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 7 bytes, wMessageID = CONTROLLER_DEVICE_DISCONNECT */ + uint8_t bControllerID; /**< Controller identifier (1 or 2) */ + } bulk_message_request_controller_device_disconnect; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = CONTROLLER_DEVICE_DISCONNECT */ + } bulk_message_response_controller_device_disconnect; + + + /** + * @brief Bulk Controller Read Associated Devices Message + * + * Reads the associated devices from the EEPROM. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 6 bytes, wMessageID = CONTROLLER_READ_ASSOCIATED_DEVICES */ + } bulk_message_request_controller_read_associated_devices; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 22 bytes, wMessageID = CONTROLLER_READ_ASSOCIATED_DEVICES */ + uint8_t bMacAddress1[MAC_ADDRESS_SIZE]; /**< MAC address of controller 1, set to all zeros if controller is not setup */ + uint8_t bAddressType1; /**< Controller 1 MAC address type. (0 = public, 1 = random static) */ + uint8_t bMacAddress2[MAC_ADDRESS_SIZE]; /**< MAC address of controller 2, set to all zeros if controller is not setup */ + uint8_t bAddressType2; /**< Controller 2 MAC address type. (0 = public, 1 = random static) */ + } bulk_message_response_controller_read_associated_devices; + + + /** + * @brief Bulk Controller Write Associated Devices Message + * + * Writes the associated devices to the EEPROM. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 20 bytes, wMessageID = CONTROLLER_WRITE_ASSOCIATED_DEVICES */ + uint8_t bMacAddress1[MAC_ADDRESS_SIZE]; /**< MAC address of controller 1, set to all zeros if controller is not setup */ + uint8_t bAddressType1; /**< Controller 1 MAC address type. (0 = public, 1 = random static) */ + uint8_t bMacAddress2[MAC_ADDRESS_SIZE]; /**< MAC address of controller 2, set to all zeros if controller is not setup */ + uint8_t bAddressType2; /**< Controller 2 MAC address type. (0 = public, 1 = random static) */ + } bulk_message_request_controller_write_associated_devices; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = CONTROLLER_WRITE_ASSOCIATED_DEVICES */ + } bulk_message_response_controller_write_associated_devices; + + + /** + * @brief Bulk Controller Send Data Message + * + * Sends opaque data to the controller. The vendor's controller code is responsible for interpreting the data. + * Example data could be a command to set a LED on the controller or set a vibration pattern on a haptic device. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 18 bytes, wMessageID = CONTROLLER_SEND_DATA */ + uint8_t bControllerID; /**< Controller identifier (1 or 2) */ + uint8_t bCommandID; /**< Command to be sent to the controller (vendor specific) - values 0-63 supported */ + uint8_t bControllerData[]; /**< Controller data to be sent. Data format is vendor specific */ + } bulk_message_request_controller_send_data; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = CONTROLLER_SEND_DATA */ + } bulk_message_response_controller_send_data; + + + /** + * @brief Bulk Controller Send Data Message + * + * Start the controller calibration process + * When the controller receives the indication to start calibration, it starts reading its gyroscope output for 30 seconds. + * The controller averages the readings over the first 25 seconds and uses this value as the gyro bias. + * The controller then averages the readings over the next 5 seconds and compares the value to the gyro bias from the first 25 seconds. + * If the values are the same (up to TBD noise value), then the gyro bias is stored to flash and the controller sends the "controller calibration status" event with success. + * Otherwise, the controller reports a failure. + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8 bytes, wMessageID = CONTROLLER_START_CALIBRATION */ + uint8_t bControllerID; /**< Controller identifier (1 or 2) */ + uint8_t reserved; /**< Reserved = 0 */ + } bulk_message_request_controller_start_calibration; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 10 bytes, wMessageID = CONTROLLER_START_CALIBRATION */ + } bulk_message_response_controller_start_calibration; + + + /** + * @brief Bulk Controller RSSI Test Control Message + * + * Start or Stop the RSSI test + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 8 bytes, wMessageID = CONTROLLER_RSSI_TEST_CONTROL */ + uint8_t bControllerID; /**< Controller identifier (1 or 2) */ + uint8_t bTestControl; /**< 1 to start the test, 0 to stop the test */ + } bulk_message_request_controller_rssi_test_control; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = CONTROLLER_RSSI_TEST_CONTROL */ + } bulk_message_response_controller_rssi_test_control; + + + /** + * @brief Bulk Controller Central FW Update Message + * + * Updates the FW image on the BLE central device + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 6 + image length in bytes, wMessageID = CONTROLLER_CENTRAL_FW_UPDATE */ + uint8_t bUpdateImage[]; /**< New Central FW image to be updated */ + } bulk_message_request_controller_central_fw_update; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = CONTROLLER_CENTRAL_FW_UPDATE */ + } bulk_message_response_controller_central_fw_update; + + + /** + * @brief Bulk Controller Controller-FW Update Message + * + * Updates the FW image on a connected controller device + */ + typedef struct { + bulk_message_request_header header; /**< Message request header: dwLength = 12 + image length in bytes, wMessageID = CONTROLLER_CONTROLLER_FW_UPDATE */ + uint8_t bMacAddress[MAC_ADDRESS_SIZE]; /**< MAC address of controller to be updated */ + uint8_t bUpdateImage[]; /**< New Controller FW image to be updated */ + } bulk_message_request_controller_controller_fw_update; + + typedef struct { + bulk_message_response_header header; /**< Message response header: dwLength = 8 bytes, wMessageID = CONTROLLER_CONTROLLER_FW_UPDATE */ + } bulk_message_response_controller_controller_fw_update; + + + /** + * @brief Interrupt Endpoint Protocol + * + * The interrupt endpoint is used for short low-latency data (namely 6DoF pose data). + * The rate of the interrupts is determined by using the "Set 6DoF Interrupt Data" command. + */ + + /** + * @brief Interrupt message header struct + * + * Start of all Interrupt messages. + */ + typedef struct { + uint32_t dwLength; /**< Message length in bytes */ + uint16_t wMessageID; /**< ID of message */ + } interrupt_message_header; + + + /** + * @brief Interrupt error message + * + * A general error message + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 8 bytes, wMessageID = DEV_ERROR */ + uint16_t wStatus; /**< Error code */ + } interrupt_message_general_error; + + + /** + * @brief Interrupt status message + * + * A status message + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 8 bytes, wMessageID = DEV_STATUS */ + uint16_t wStatus; /**< Status of request (MESSAGE_STATUS) */ + } interrupt_message_status; + + /** + * @brief Interrupt get pose message + * + * returns the latest pose of the camera relative to its initial position + * If relocalization data was set, this pose is relative to the relocalization database + * The rate of the interrupts is determined by using the SLAM_SET_6DOF_INTERRUPT_RATE command. + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 92 bytes, wMessageID = DEV_GET_POSE */ + uint8_t bIndex; /**< Index of HMD or controller - 0x0 = HMD, 0x1 - controller 1, 0x2 - controller 2 */ + uint8_t wReserved; /**< Reserved = 0 */ + pose_data pose; /**< Short low-latency data (namely 6DoF pose data) */ + } interrupt_message_get_pose; + + + /** + * @brief Interrupt raw stream header + */ + typedef struct { + interrupt_message_header header; /**< Message request header: dwLength = 28 + dwMetadataLength + dwFrameLength bytes, wMessageID = DEV_SAMPLE */ + uint8_t bSensorID; /**< Bits 0-4: Type of sensor, supported values are: Color = 0, Depth = 1, IR = 2, Fisheye = 3, Gyro = 4, Accelerometer = 5, Controller = 6 */ + /**< Bits 5-7: Sensor index - Zero based index of sensor with the same type within device. For example if the device supports two fisheye cameras, */ + /**< The first will use index 0 (bSensorID = 0x03) and the second will use index 1 (bSensorID = 0x23) */ + uint8_t bReserved; /**< Reserved = 0 */ + uint64_t llNanoseconds; /**< Frame integration timestamp, as measured in nanoseconds since device initialization */ + uint64_t llArrivalNanoseconds; /**< Frame arrival timestamp, as measured in nanoseconds since device initialization */ + uint32_t dwFrameId; /**< A running index of frames from every unique sensor. Starting from 0. */ + } interrupt_message_raw_stream_header; + + + /** + * @brief Interrupt raw accelerometer stream metadata + */ + typedef struct + { + uint32_t dwMetadataLength; /**< Metadata length in bytes (4 bytes) */ + float_t flTemperature; /**< Accelerometer temperature */ + uint32_t dwFrameLength; /**< Length of frame below (12 bytes) */ + float_t flAx; /**< X value of acceleration, in meter/sec^2 */ + float_t flAy; /**< Y value of acceleration, in meter/sec^2 */ + float_t flAz; /**< Z value of acceleration, in meter/sec^2 */ + } interrupt_message_accelerometer_stream_metadata; + + + /** + * @brief Interrupt raw accelerometer stream message + * + * Specific frame metadata and data for sensor IDs - accelerometer + */ + typedef struct + { + interrupt_message_raw_stream_header rawStreamHeader; + interrupt_message_accelerometer_stream_metadata metadata; + } interrupt_message_accelerometer_stream; + + + /** + * @brief Interrupt raw gyro stream metadata + */ + typedef struct + { + uint32_t dwMetadataLength; /**< Metadata length in bytes (4 bytes) */ + float_t flTemperature; /**< Gyro temperature */ + uint32_t dwFrameLength; /**< Length of frame below (12 bytes) */ + float_t flGx; /**< X value of gyro, in radians/sec */ + float_t flGy; /**< Y value of gyro, in radians/sec */ + float_t flGz; /**< Z value of gyro, in radians/sec */ + } interrupt_message_gyro_stream_metadata; + + + /** + * @brief Interrupt raw gyro stream message + * + * Specific frame metadata and data for sensor IDs - gyro + */ + typedef struct + { + interrupt_message_raw_stream_header rawStreamHeader; + interrupt_message_gyro_stream_metadata metadata; + } interrupt_message_gyro_stream; + + + /** + * @brief Interrupt raw velocimeter stream metadata + */ + typedef struct + { + uint32_t dwMetadataLength; /**< Metadata length in bytes (4 bytes) */ + float_t flTemperature; /**< velocimeter temperature */ + uint32_t dwFrameLength; /**< Length of frame below (12 bytes) */ + float_t flVx; /**< X value of velocimeter, in radians/sec */ + float_t flVy; /**< Y value of velocimeter, in radians/sec */ + float_t flVz; /**< Z value of velocimeter, in radians/sec */ + } interrupt_message_velocimeter_stream_metadata; + + + /** + * @brief Interrupt raw velocimeter stream message + * + * Specific frame metadata and data for sensor IDs - velocimeter + */ + typedef struct + { + interrupt_message_raw_stream_header rawStreamHeader; + interrupt_message_velocimeter_stream_metadata metadata; + } interrupt_message_velocimeter_stream; + + + /** + * @brief Interrupt raw controller stream metadata + */ + typedef struct + { + uint32_t dwMetadataLength; /**< Metadata length in bytes (0 bytes) */ + uint32_t dwFrameLength; /**< Length of frame below (8 bytes) */ + uint8_t bEventID; /**< Event ID - button, trackpad or battery (vendor specific), supported values 0-63 */ + uint8_t bInstanceId; /**< Instance of the sensor in case of multiple sensors */ + uint8_t bSensorData[CONTROLLER_SENSOR_DATA_SIZE]; /**< Sensor data that is pass-through from the controller firmware */ + } interrupt_message_controller_stream_metadata; + + + /** + * @brief Interrupt raw controller stream message + * + * Specific frame metadata and data for sensor IDs - controller + */ + typedef struct + { + interrupt_message_raw_stream_header rawStreamHeader; + interrupt_message_controller_stream_metadata metadata; + } interrupt_message_controller_stream; + + + /** + * @brief Interrupt controller status changed message + * + * The Controller Pose algorithm shall provide estimated accuracy of the tracking state + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 8 bytes, wMessageID = CONTROLLER_STATUS_CHANGE_EVENT */ + uint16_t wStatus; /**< Status: 0x0 - Failed, 0x1 - Low, 0x2 - Medium, 0x3 - High */ + } interrupt_message_controller_status_change; + + + /** + * @brief Interrupt controller device discovery event message + * + * The Controller Pose algorithm shall indicate on discovered controllers + */ + typedef struct { + uint16_t wManufacturerId; /**< Identifier of the controller manufacturer */ + uint8_t bVendorData; /**< vendor specific data copied from the controller advertisement. */ + /**< The least significant bit is reserved for pairing status (set if controller is in pairing mode) */ + uint8_t bProtocolVersion; /**< BLE protocol version supported by the controller */ + uint8_t bAppVersionMajor; /**< Major number of the app version */ + uint8_t bAppVersionMinor; /**< Minor number of the app version */ + uint8_t bAppVersionPatch; /**< Patch number of the app version */ + uint8_t bSoftdeviceVersion; /**< soft device version */ + uint8_t bBootloaderVersionMajor; /**< Major number of the boot loader version */ + uint8_t bBootloaderVersionMinor; /**< Minor number of the boot loader version */ + uint8_t bBootloaderVersionPatch; /**< Patch number of the boot loader version */ + } controller_discovery_info; + + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 25 bytes, wMessageID = CONTROLLER_DEVICE_DISCOVERY_EVENT */ + uint8_t bMacAddress[MAC_ADDRESS_SIZE]; /**< Discovered device byte array of MAC address */ + uint8_t bAddressType; /**< Discovered device address type */ + uint8_t bReserved; /**< Reserved = 0 */ + controller_discovery_info info; /**< Discovered controller versions */ + } interrupt_message_controller_device_discovery; + + + /** + * @brief Interrupt controller connected event message + * + * The Controller algorithm shall indicate on connect + */ + typedef struct { + uint8_t bProtocolVersion; /**< BLE protocol version supported by the controller */ + uint16_t wManufacturerId; /**< Identifier of the controller manufacturer */ + uint8_t bAppVersionMajor; /**< Major number of the app version */ + uint8_t bAppVersionMinor; /**< Minor number of the app version */ + uint8_t bAppVersionPatch; /**< Patch number of the app version */ + uint8_t bSoftdeviceVersion; /**< soft device version */ + uint8_t bBootloaderVersionMajor; /**< Major number of the boot loader version */ + uint8_t bBootloaderVersionMinor; /**< Minor number of the boot loader version */ + uint8_t bBootloaderVersionPatch; /**< Patch number of the boot loader version */ + } controller_connected_info; + + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 17 bytes, wMessageID = CONTROLLER_DEVICE_CONNECTED_EVENT */ + uint16_t wStatus; /**< Connection status: SUCCESS – connection succeeded */ + /**< Connection status: TIMEOUT – connection timed out */ + /**< Connection status: INCOMPATIBLE – connection succeeded but controller version is incompatible with TM2 version */ + uint8_t bControllerID; /**< Connected controller identifier (1 or 2) */ + controller_connected_info info; /**< Connected controller versions */ + } interrupt_message_controller_connected; + + + /** + * @brief Interrupt controller disconnected event message + * + * The Controller algorithm shall indicate on disconnect + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 7 bytes, wMessageID = CONTROLLER_DEVICE_DISCONNECTED_EVENT */ + uint8_t bControllerID; /**< Disconnected controller identifier (1 or 2) */ + } interrupt_message_controller_disconnected; + + + /** + * @brief Interrupt Controller led intensity + */ + typedef struct + { + interrupt_message_raw_stream_header rawStreamHeader; /**< Interrupt message header: wMessageID = CONTROLLER_DEVICE_LED_INTENSITY_EVENT */ + uint32_t packetType; /**< Packet type */ + uint8_t ledId; /**< Controller Led identifier (1 or 2) */ + uint32_t intensity; /**< Controller Led intensity [0-100] */ + } interrupt_message_controller_led_intensity; + + /** + * @brief Interrupt controller calibration status event message + * + * This event is sent by the controller when the calibration process is finished or failed + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 9 bytes, wMessageID = CONTROLLER_CALIBRATION_STATUS_EVENT */ + uint16_t wStatus; /**< Calibration status: 0x0 – calibration succeeded */ + /**< Calibration status: 0x1 – validation failed */ + /**< Calibration status: 0x2 – flash access failure */ + /**< Calibration status: 0x3 – IMU failure */ + /**< Calibration status: 0x4 – internal error */ + uint8_t bControllerID; /**< Calibrated controller identifier (1 or 2) */ + } interrupt_message_controller_calibration_status; + + + /** + * @brief Interrupt SLAM error message + * + * SLAM error code message + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 8 bytes, wMessageID = SLAM_ERROR */ + uint16_t wStatus; /**< Status: SLAM_ERROR_CODE_NONE = 0 - No error has occurred. */ + /**< SLAM_ERROR_CODE_VISION = 1 - No visual features were detected in the most recent image. */ + /**< This is normal in some circumstances, such as quick motion or if the device temporarily looks at a blank wall. */ + /**< SLAM_ERROR_CODE_SPEED = 2 - The device moved more rapidly than expected for typical handheld motion. */ + /**< This may indicate that rc_Tracker has failed and is providing invalid data. */ + /**< SLAM_ERROR_CODE_OTHER = 3 - A fatal internal error has occurred. */ + } interrupt_message_slam_error; + + + /** + * @brief Interrupt Controller error message + * + * Controller error code message + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 8 bytes, wMessageID = CONTROLLER_ERROR */ + uint16_t wStatus; /**< Status: rc_E_ERROR_NONE = 0 - No error has occurred. */ + /**< rc_E_ERROR_VISION = 1 - No visual features were detected in the most recent image. */ + /**< This is normal in some circumstances, such as quick motion or if the device temporarily looks at a blank wall. */ + /**< rc_E_ERROR_SPEED = 2 - The device moved more rapidly than expected for typical handheld motion. */ + /**< This may indicate that rc_Tracker has failed and is providing invalid data. */ + /**< rc_E_ERROR_OTHER = 3 - A fatal internal error has occurred. */ + } interrupt_message_controller_error; + + + /** + * @brief Interrupt Get Localization Data Stream message + * + * Triggers by bulk_message_request_get_localization_data. + * Returns the localization data as created during a 6DoF session. + * This message is generated and streamed by the underlying firmware algorithm in run-time, so the total size of the data cannot be known in advance. + * The entire data will be streams in "chunks" and the firmware will use the MORE_DATA_AVAILABLE to indicate there are more data to send. + * The last chunk (possibly even a zero-size chunk) will be marked with a SUCCESS status code. + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 8 bytes + bLocalizationData size, wMessageID = SLAM_GET_LOCALIZATION_DATA_STREAM */ + uint16_t wStatus; /**< SUCCESS: indicate last chunk, MORE_DATA_AVAILABLE: any other chunk */ + uint16_t wIndex; /**< A running counter starting at 0 identifying the chunk index in a single data transaction */ + uint8_t bLocalizationData[]; /**< Localization data variable sized array. Data format is algorithm specific and opaque to the USB and host stack */ + } interrupt_message_get_localization_data_stream; + + + /** + * @brief Interrupt Set Localization Data Stream message + * + * Response after setting localization data using bulk_message_set_localization_data_stream + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 8 bytes, wMessageID = SLAM_SET_LOCALIZATION_DATA_STREAM */ + uint16_t wStatus; /**< Status */ + uint8_t bReserved; /**< Reserved = 0 */ + } interrupt_message_set_localization_data_stream; + + /** + * @brief Interrupt firmware update stream message + * + * Response after sending firmware update command using bulk_message_large_stream + */ + typedef struct { + interrupt_message_header header; /**< Interrupt message header: dwLength = 8 bytes, wMessageID = DEV_FIRMWARE_UPDATE */ + uint16_t wStatus; /**< Status */ + uint8_t bMacAddress[MAC_ADDRESS_SIZE]; /**< MAC address of updated device. All zeros for central FW update */ + uint8_t bProgress; /**< Progress counter (percentage of update complete) */ + } interrupt_message_fw_update_stream; + + typedef struct { + uint8_t bMacAddress[MAC_ADDRESS_SIZE]; /**< MAC address of updated device. Shall be all zeros for Central Firmware update */ + uint8_t bAddressType; /**< BLE address type (as received in advertisement) */ + uint8_t bNumFiles; /**< Number of firmware update files sent below (n) */ + uint32_t dwFileSize[MAX_FW_UPDATE_FILE_COUNT]; /**< Length (in bytes) of the files */ + uint8_t bContcatenatedFiles[]; /**< Length of Concatenation of all files to be used for firmware update (length of this field is dwFileSize1 +...+ dwFileSize n) */ + } message_fw_update_request; + + /** + * @brief Control message request header struct + * + * Start of all USB control message requests. + */ + typedef struct { + uint8_t bmRequestType; /**< Bit 7: Request direction (0 = Host to device - Out, 1 = Device to host - In) */ + /**< Bits 5 - 6 : Request type (0 = standard, 1 = class, 2 = vendor, 3 = reserved) */ + /**< Bits 0 - 4 : Recipient (0 = device, 1 = interface, 2 = endpoint, 3 = other) */ + uint8_t bRequest; /**< Actual request ID */ + uint8_t wValueL; /**< Unused */ + uint8_t wValueH; /**< Unused */ + uint8_t wIndexL; /**< Unused */ + uint8_t wIndexH; /**< Unused */ + uint8_t wLengthL; /**< Unused */ + uint8_t wLengthH; /**< Unused */ + } control_message_request_header; + + /** + * @brief Control reset Message + * + * Reset the device + */ + typedef struct { + control_message_request_header header; /**< Message request header: bmRequestType = 0x40 (Direction - host to device, type - vendor, recipient - device), wMessageID = CONTROL_USB_RESET */ + } control_message_request_reset; + + /** + * @brief Control get interface version Message + * + * Get FW interface version + */ + typedef struct { + uint32_t dwMajor; /**< Major part of the device supported interface API version, updated upon an incompatible API change */ + uint32_t dwMinor; /**< Minor part of the device supported interface API version, updated upon a backwards-compatible change */ + } interface_version_libtm_message; + + typedef struct { + control_message_request_header header; /**< Message request header: bmRequestType = 0xC0 (Direction - device to host, type - vendor, recipient - device), wMessageID = CONTROL_GET_INTERFACE_VERSION */ + } control_message_request_get_interface_version; + + typedef struct { + interface_version_libtm_message message; /**< Interface version */ + } control_message_response_get_interface_version; + + +#pragma pack(pop) + + + /** + * @brief This function initilize the message request header with needed length and message ID + * + * @param[in] message_request - message request buffer. + * @param[in] dwLength - message request length. + * @param[in] wMessageID - message ID. + * @return None + */ + void init_message_request_header(IN unsigned char * message_request, IN uint32_t dwLength, IN uint16_t wMessageID); + + + /** + * @brief This function prints all supported request/response messages + * + * @param[in] message - message buffer. + * @return None + */ + void print_message(IN unsigned char * message); +} +#ifdef _WIN32 +#pragma warning (pop) +#endif diff --git a/third-party/libtm/libtm/src/Protocol.cpp b/third-party/libtm/libtm/src/Protocol.cpp new file mode 100644 index 0000000000..17f39b67d5 --- /dev/null +++ b/third-party/libtm/libtm/src/Protocol.cpp @@ -0,0 +1,363 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include +#include "Protocol.h" +#include "Common.h" + +#define TIMEOUT_MS 5000 + +/* Bulk Endpoint Protocol */ +#define BULK_IN_ENDPOINT 0x81 +#define BULK_OUT_ENDPOINT 0x01 +#define BULK_IN_MAX_TRANSFER_SIZE 1024 +#define BULK_OUT_MAX_TRANSFER_SIZE 1024 + +/* Control Endpoint Protocol */ +#define CONTROL_IN_MAX_TRANSFER_SIZE 1024 +#define CONTROL_OUT_MAX_TRANSFER_SIZE 1024 +#define CONTROL_IN_REQUEST_TYPE (LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_INTERFACE) +#define CONTROL_OUT_REQUEST_TYPE (LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_INTERFACE) +#define CONTROL_REQUEST_1 0x01 +#define CONTROL_REQUEST_2 0x02 + +/* Interrupt Endpoint Protocol */ +#define INTERRUPT_IN_ENDPOINT 0x82 +#define INTERRUPT_OUT_ENDPOINT 0x02 +#define INTERRUPT_IN_MAX_TRANSFER_SIZE 1024 +#define INTERRUPT_OUT_MAX_TRANSFER_SIZE 1024 + + +/* Description: This function uses synchronous bulk transfers to write message to the device */ +/* Parameters: IN device_handle - USB device handle */ +/* IN host_data_out - Host buffer with the message to send */ +/* IN host_data_out_len - Host buffer length */ +/* OUT host_data_out_bytes_sent - Actual buffer size transferred */ +/* Returns: Zero on success, libusb_error code on failure */ +int bulk_transfer_write_message(IN libusb_device_handle * device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent) +{ + int result = 0; + + *host_data_out_bytes_sent = 0; + + /* Write message to the device */ + result = libusb_bulk_transfer(device_handle, BULK_OUT_ENDPOINT, host_data_out, host_data_out_len, host_data_out_bytes_sent, TIMEOUT_MS); + if (result >= 0) + { + printf("Message sent via bulk transfer:\n"); + print_data(host_data_out, *host_data_out_bytes_sent); + } + else + { + fprintf(stderr, "Error sending message via bulk transfer (%s)\n", libusb_error_name(result)); + } + + return result; +} + + +/* Description: This function uses synchronous bulk transfers to receive message from the device */ +/* Parameters: IN device_handle - USB device handle */ +/* OUT host_data_in - Host buffer to save the message */ +/* OUT host_data_in_bytes_received - Actual buffer size received */ +/* Returns: Zero on success, libusb_error code on failure */ +int bulk_transfer_read_message(IN libusb_device_handle * device_handle, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received) +{ + int result = 0; + + *host_data_in_bytes_received = 0; + + /* Read message from the device */ + result = libusb_bulk_transfer(device_handle, BULK_IN_ENDPOINT, host_data_in, BULK_IN_MAX_TRANSFER_SIZE, host_data_in_bytes_received, TIMEOUT_MS); + if (result >= 0) + { + if (*host_data_in_bytes_received > 0) + { + printf("Message received via bulk transfer:\n"); + print_data(host_data_in, *host_data_in_bytes_received); + } + else + { + fprintf(stderr, "No message received via bulk transfer (%d)\n", result); + return LIBUSB_ERROR_OTHER; + } + } + else + { + fprintf(stderr, "Error receiving message via bulk transfer (%s)\n", libusb_error_name(result)); + } + + return result; +} + + + +/* Description: This function uses synchronous bulk transfers to write data to the device and receive data from the device */ +/* Parameters: IN device_handle - USB device handle */ +/* IN host_data_out - Host buffer with the message to send */ +/* IN host_data_out_len - Host buffer length */ +/* OUT host_data_out_bytes_sent - Actual buffer size transferred */ +/* OUT host_data_in - Host buffer to save the message */ +/* OUT host_data_in_bytes_received - Actual buffer size received */ +/* Returns: Zero on success, libusb_error code on failure */ +int bulk_transfer_exchange_message(IN libusb_device_handle *device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received) +{ + int result = 0; + + *host_data_out_bytes_sent = 0; + *host_data_in_bytes_received = 0; + + /* Write data to the device */ + result = libusb_bulk_transfer(device_handle, BULK_OUT_ENDPOINT, host_data_out, host_data_out_len, host_data_out_bytes_sent, TIMEOUT_MS); + if (result >= 0) + { + printf("Message sent via bulk transfer:\n"); + print_data(host_data_out, *host_data_out_bytes_sent); + + /* Read data from the device */ + result = libusb_bulk_transfer(device_handle, BULK_IN_ENDPOINT, host_data_in, BULK_IN_MAX_TRANSFER_SIZE, host_data_in_bytes_received, TIMEOUT_MS); + if (result >= 0) + { + if (*host_data_in_bytes_received > 0) + { + printf("Message received via bulk transfer:\n"); + print_data(host_data_in, *host_data_in_bytes_received); + } + else + { + fprintf(stderr, "No message received via bulk transfer (%d)\n", result); + result = LIBUSB_ERROR_OTHER; + } + } + else + { + fprintf(stderr, "Error receiving message via bulk transfer (%s)\n", libusb_error_name(result)); + } + } + else + { + fprintf(stderr, "Error sending message via bulk transfer (%s)\n", libusb_error_name(result)); + } + + return result; +} + + +/* Description: This function uses synchronous control transfers to write data to the device */ +/* Parameters: IN device_handle - USB device handle */ +/* IN host_data_out - Host buffer with the message to send */ +/* IN host_data_out_len - Host buffer length */ +/* OUT host_data_out_bytes_sent - Actual buffer size transferred */ +/* Returns: Zero on success, libusb_error code on failure */ +int control_transfer_write_message(IN libusb_device_handle *device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent) +{ + int result = 0; + + *host_data_out_bytes_sent = 0; + + /* Write data to the device */ + result = libusb_control_transfer(device_handle, CONTROL_OUT_REQUEST_TYPE, CONTROL_REQUEST_1, 0, INTERFACE_NUMBER, host_data_out, host_data_out_len, TIMEOUT_MS); + if (result >= 0) + { + *host_data_out_bytes_sent = result; + printf("Message sent via control transfer:\n"); + print_data(host_data_out, *host_data_out_bytes_sent); + } + else + { + fprintf(stderr, "Error sending data via control transfer (%s)\n", libusb_error_name(result)); + } + + return result; +} + + +/* Description: This function uses synchronous control transfers to receive data from the device */ +/* Parameters: IN device_handle - USB device handle */ +/* OUT host_data_in - Host buffer to save the message */ +/* OUT host_data_in_bytes_received - Actual buffer size received */ +/* Returns: Zero on success, libusb_error code on failure */ +int control_transfer_read_message(IN libusb_device_handle *device_handle, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received) +{ + int result = 0; + + *host_data_in_bytes_received = 0; + + /* Read message from the device */ + result = libusb_control_transfer(device_handle, CONTROL_IN_REQUEST_TYPE, CONTROL_REQUEST_2, 0, INTERFACE_NUMBER, host_data_in, CONTROL_IN_MAX_TRANSFER_SIZE, TIMEOUT_MS); + if (result >= 0) + { + *host_data_in_bytes_received = result; + printf("Message received via control transfer:\n"); + print_data(host_data_in, *host_data_in_bytes_received); + } + else + { + fprintf(stderr, "Error receiving message via control transfer (%s)\n", libusb_error_name(result)); + } + + return result; +} + + +/* Description: This function uses synchronous control transfers to write data to the device and receive data from the device */ +/* Parameters: IN device_handle - USB device handle */ +/* IN host_data_out - Host buffer with the message to send */ +/* IN host_data_out_len - Host buffer length */ +/* OUT host_data_out_bytes_sent - Actual buffer size transferred */ +/* OUT host_data_in - Host buffer to save the message */ +/* OUT host_data_in_bytes_received - Actual buffer size received */ +/* Returns: Zero on success, libusb_error code on failure */ +int control_transfer_exchange_message(IN libusb_device_handle *device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received) +{ + int result = 0; + + *host_data_out_bytes_sent = 0; + *host_data_in_bytes_received = 0; + + /* Send message to the device */ + result = libusb_control_transfer(device_handle, CONTROL_OUT_REQUEST_TYPE, CONTROL_REQUEST_1, 0, INTERFACE_NUMBER, host_data_out, host_data_out_len, TIMEOUT_MS); + if (result >= 0) + { + *host_data_out_bytes_sent = result; + printf("Message sent via control transfer:\n"); + print_data(host_data_out, *host_data_out_bytes_sent); + + /* Read message from the device */ + result = libusb_control_transfer(device_handle, CONTROL_IN_REQUEST_TYPE, CONTROL_REQUEST_2, 0, INTERFACE_NUMBER, host_data_in, CONTROL_IN_MAX_TRANSFER_SIZE, TIMEOUT_MS); + if (result >= 0) + { + *host_data_in_bytes_received = result; + printf("Message received via control transfer:\n"); + print_data(host_data_in, *host_data_in_bytes_received); + } + else + { + fprintf(stderr, "Error receiving message via control transfer (%s)\n", libusb_error_name(result)); + } + } + else + { + fprintf(stderr, "Error sending data via control transfer (%s)\n", libusb_error_name(result)); + } + + return result; +} + + + +/* Description: This function uses synchronous interrupt transfers to write data to the device */ +/* Parameters: IN device_handle - USB device handle */ +/* IN host_data_out - Host buffer with the message to send */ +/* IN host_data_out_len - Host buffer length */ +/* OUT host_data_out_bytes_sent - Actual buffer size transferred */ +/* Returns: Zero on success, libusb_error code on failure */ +int interrupt_transfer_write_message(IN libusb_device_handle *device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent) +{ + int result = 0; + + *host_data_out_bytes_sent = 0; + + /* Send message to the device */ + result = libusb_interrupt_transfer(device_handle, INTERRUPT_OUT_ENDPOINT, host_data_out, host_data_out_len, host_data_out_bytes_sent, TIMEOUT_MS); + if (result >= 0) + { + printf("Message sent via interrupt transfer:\n"); + print_data(host_data_out, *host_data_out_bytes_sent); + } + else + { + fprintf(stderr, "Error sending message via interrupt transfer (%s)\n", libusb_error_name(result)); + } + + return result; +} + + +/* Description: This function uses synchronous interrupt transfers to receive data from the device */ +/* Parameters: IN device_handle - USB device handle */ +/* OUT host_data_in - Host buffer to save the message */ +/* OUT host_data_in_bytes_received - Actual buffer size received */ +/* Returns: Zero on success, libusb_error code on failure */ +int interrupt_transfer_read_message(IN libusb_device_handle *device_handle, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received) +{ + int result = 0; + + *host_data_in_bytes_received = 0; + + /* Read message from the device */ + result = libusb_interrupt_transfer(device_handle, INTERRUPT_IN_ENDPOINT, host_data_in, INTERRUPT_OUT_MAX_TRANSFER_SIZE, host_data_in_bytes_received, TIMEOUT_MS); + if (result >= 0) + { + if (*host_data_in_bytes_received > 0) + { + printf("Message received via interrupt transfer:\n"); + print_data(host_data_in, *host_data_in_bytes_received); + } + else + { + fprintf(stderr, "No message received in interrupt transfer (%s)\n", libusb_error_name(result)); + result = LIBUSB_ERROR_OTHER; + } + } + else + { + fprintf(stderr, "Error receiving message via interrupt transfer (%s)\n", libusb_error_name(result)); + } + + return result; +} + + +/* Description: This function uses synchronous interrupt transfers to write data to the device and receive data from the device */ +/* Parameters: IN device_handle - USB device handle */ +/* IN host_data_out - Host buffer with the message to send */ +/* IN host_data_out_len - Host buffer length */ +/* OUT host_data_out_bytes_sent - Actual buffer size transferred */ +/* OUT host_data_in - Host buffer to save the message */ +/* OUT host_data_in_bytes_received - Actual buffer size received */ +/* Returns: Zero on success, libusb_error code on failure */ +int interrupt_transfer_exchange_message(IN libusb_device_handle *device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received) +{ + int result = 0; + + *host_data_out_bytes_sent = 0; + *host_data_in_bytes_received = 0; + + /* Send message to the device */ + result = libusb_interrupt_transfer(device_handle, INTERRUPT_OUT_ENDPOINT, host_data_out, host_data_out_len, host_data_out_bytes_sent, TIMEOUT_MS); + if (result >= 0) + { + printf("Message sent via interrupt transfer:\n"); + print_data(host_data_out, *host_data_out_bytes_sent); + + /* Read message from the device */ + result = libusb_interrupt_transfer(device_handle, INTERRUPT_IN_ENDPOINT, host_data_in, INTERRUPT_OUT_MAX_TRANSFER_SIZE, host_data_in_bytes_received, TIMEOUT_MS); + if (result >= 0) + { + if (*host_data_in_bytes_received > 0) + { + printf("Message received via interrupt transfer:\n"); + print_data(host_data_in, *host_data_in_bytes_received); + } + else + { + fprintf(stderr, "No message received in interrupt transfer (%s)\n", libusb_error_name(result)); + result = LIBUSB_ERROR_OTHER; + } + } + else + { + fprintf(stderr, "Error receiving message via interrupt transfer (%s)\n", libusb_error_name(result)); + } + } + else + { + fprintf(stderr, "Error sending message via interrupt transfer (%s)\n", libusb_error_name(result)); + } + + return result; +} + diff --git a/third-party/libtm/libtm/src/Protocol.h b/third-party/libtm/libtm/src/Protocol.h new file mode 100644 index 0000000000..1dc28c7ce3 --- /dev/null +++ b/third-party/libtm/libtm/src/Protocol.h @@ -0,0 +1,31 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#ifndef __TM2_PROTOCOL_H +#define __TM2_PROTOCOL_H + +#include +#include "Common.h" + +#define INTERFACE_NUMBER 0 + +/* Synchronous device I/O */ +int bulk_transfer_write_message(IN libusb_device_handle * device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent); +int bulk_transfer_read_message(IN libusb_device_handle * device_handle, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received); +int bulk_transfer_exchange_message(IN libusb_device_handle *device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received); + +int control_transfer_write_message(IN libusb_device_handle *device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent); +int control_transfer_read_message(IN libusb_device_handle *device_handle, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received); +int control_transfer_exchange_message(IN libusb_device_handle *device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received); + +int interrupt_transfer_write_message(IN libusb_device_handle *device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent); +int interrupt_transfer_read_message(IN libusb_device_handle *device_handle, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received); +int interrupt_transfer_exchange_message(IN libusb_device_handle *device_handle, IN unsigned char * host_data_out, IN unsigned int host_data_out_len, OUT int * host_data_out_bytes_sent, OUT unsigned char * host_data_in, OUT int * host_data_in_bytes_received); + +/* Asynchronous device I/O */ +/* TBD */ + +#endif // __TM2_PROTOCOL_H + diff --git a/third-party/libtm/libtm/src/RcSerializer/Packet.cpp b/third-party/libtm/libtm/src/RcSerializer/Packet.cpp new file mode 100644 index 0000000000..99109d4e6e --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Packet.cpp @@ -0,0 +1,373 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#define LOG_TAG "Packet" + +#include +#include "Packet.h" +#include "Utils.h" + +using namespace perc; +const uint32_t TS_MAX_GAP_US = 200; + +inline uint64_t ns2us(int64_t ts) +{ + std::chrono::duration ns(ts); + return std::chrono::duration_cast>(ns).count(); +} + +ImagePacket::ImagePacket(TrackingData::VideoFrame& frame) : mExposurePacket(frame.exposuretime, static_cast(frame.gain), frame.sensorIndex, ns2us(frame.timestamp)) +{ + uint32_t bufferSize = frame.profile.stride * frame.profile.height; + mPacket = std::shared_ptr((packet_image_raw_t*) ::operator new (sizeof(packet_image_raw_t) + bufferSize)); + mPacket->height = frame.profile.height; + mPacket->width = frame.profile.width; + mPacket->stride = frame.profile.stride; + mPacket->format = 0; + mPacket->exposure_time_us = static_cast(frame.exposuretime); + perc::copy(mPacket->data, frame.data, bufferSize); + + mPacket->header.sensor_id = frame.sensorIndex; + mPacket->header.type = packet_image_raw; + mPacket->header.bytes = sizeof(packet_image_raw_t) + bufferSize; + mPacket->header.time = ns2us(frame.timestamp); + +} +const uint8_t* ImagePacket::getBytes() +{ + return (uint8_t*)mPacket.get(); +} +size_t ImagePacket::getSize() +{ + return sizeof(packet_image_raw_t) + (mPacket->height * mPacket->stride); +}; +packet_type ImagePacket::getType() +{ + return packet_image_raw; +} +uint16_t ImagePacket::getSensorId() +{ + return mPacket->header.sensor_id; +} +std::shared_ptr ImagePacket::getRCPacket() +{ + return mPacket; +} +ExposurePacket ImagePacket::getExposurePacket() +{ + return mExposurePacket; +} + +const uint8_t* StereoPacket::getBytes() +{ + if (isComplete() == false) + { + return nullptr; + } + mPacket.header.sensor_id = mImagePackets[0]->header.sensor_id / 2; + mPacket.header.type = packet_stereo_raw; + mPacket.header.time = mImagePackets[0]->header.time; + mPacket.height = mImagePackets[0]->height; + mPacket.width = mImagePackets[0]->width; + mPacket.format = mImagePackets[0]->format; + mPacket.exposure_time_us = mImagePackets[0]->exposure_time_us; + mPacket.stride1 = mImagePackets[0]->stride; + mPacket.stride2 = mImagePackets[1]->stride; + mPacket.header.bytes = sizeof(packet_stereo_raw_t) + mPacket.height * (mPacket.stride1 + mPacket.stride2); + + return (uint8_t*)&mPacket; +} +size_t StereoPacket::getSize() +{ + if (isComplete() == false) + { + return 0; + } + return sizeof(packet_stereo_raw_t); +} +packet_type StereoPacket::getType() +{ + return packet_stereo_raw; +} +bool StereoPacket::addPacket(ImagePacket* packet) +{ + std::shared_ptr imagePacket = packet->getRCPacket(); + uint16_t sensorIndex = imagePacket->header.sensor_id % 2; + if (mImagePackets.find(sensorIndex) != mImagePackets.end()) + { + LOGE("Image packet is out of order"); + return false; + } + uint16_t coupeledSensorIndex = (sensorIndex + 1) % 2; + if (mImagePackets.find(coupeledSensorIndex) != mImagePackets.end() && + !areCoupeled(mImagePackets[coupeledSensorIndex]->header.time, imagePacket->header.time)) + { + LOGE("Image packet is out of order"); + return false; + } + mImagePackets[sensorIndex] = imagePacket; + packet_exposure_t imageExposurePacket = packet->getExposurePacket().getRCPacket(); + mExposurePacket = ExposurePacket(imageExposurePacket.exposure_time_us, imageExposurePacket.gain, imagePacket->header.sensor_id / 2, imageExposurePacket.header.time); + return true; +} +bool StereoPacket::areCoupeled(uint64_t time_us1, uint64_t time_us2) +{ + return std::abs(static_cast(time_us1) - static_cast(time_us2)) < TS_MAX_GAP_US; +} +bool StereoPacket::isComplete() +{ + return mImagePackets.size() == 2; +} +const uint8_t* StereoPacket::getImageBytes(uint16_t index) +{ + if (mImagePackets[index]) + { + return (uint8_t*)mImagePackets[index].get()->data; + } + LOGE("Invalid read of stereo packet"); + return nullptr; +} +size_t StereoPacket::getImageSize(uint16_t index) +{ + if (mImagePackets[index]) + { + return mImagePackets[index]->height * mImagePackets[index]->stride; + } + LOGE("Invalid read of stereo packet size"); + return 0; +} +void StereoPacket::clear() +{ + mImagePackets.clear(); +} +ExposurePacket StereoPacket::getExposurePacket() +{ + return mExposurePacket; +} + +GyroPacket::GyroPacket(TrackingData::GyroFrame& frame) : mThermometerPacket(frame.temperature, frame.sensorIndex, frame.timestamp) +{ + mPacket.w[0] = frame.angularVelocity.x; + mPacket.w[1] = frame.angularVelocity.y; + mPacket.w[2] = frame.angularVelocity.z; + + mPacket.header.sensor_id = frame.sensorIndex; + mPacket.header.type = packet_gyroscope; + mPacket.header.bytes = sizeof(packet_gyroscope_t); + mPacket.header.time = ns2us(frame.timestamp); +} +const uint8_t* GyroPacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t GyroPacket::getSize() +{ + return sizeof(mPacket); +} +packet_type GyroPacket::getType() +{ + return packet_gyroscope; +} +ThermometerPacket GyroPacket::getThermometerPacket() +{ + return mThermometerPacket; +} + +VelocimeterPacket::VelocimeterPacket(TrackingData::VelocimeterFrame& frame) : mThermometerPacket(frame.temperature, frame.sensorIndex, frame.timestamp) +{ + mPacket.v[0] = frame.angularVelocity.x; + mPacket.v[1] = frame.angularVelocity.y; + mPacket.v[2] = frame.angularVelocity.z; + + mPacket.header.sensor_id = frame.sensorIndex; + mPacket.header.type = packet_velocimeter; + mPacket.header.bytes = sizeof(packet_velocimeter_t); + mPacket.header.time = ns2us(frame.timestamp); +} +const uint8_t* VelocimeterPacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t VelocimeterPacket::getSize() +{ + return sizeof(mPacket); +} +packet_type VelocimeterPacket::getType() +{ + return packet_velocimeter; +} +ThermometerPacket VelocimeterPacket::getThermometerPacket() +{ + return mThermometerPacket; +} + +AcclPacket::AcclPacket(TrackingData::AccelerometerFrame& frame) : mThermometerPacket(frame.temperature, frame.sensorIndex, frame.timestamp) +{ + mPacket.a[0] = frame.acceleration.x; + mPacket.a[1] = frame.acceleration.y; + mPacket.a[2] = frame.acceleration.z; + + mPacket.header.sensor_id = frame.sensorIndex; + mPacket.header.type = packet_accelerometer; + mPacket.header.bytes = sizeof(packet_accelerometer_t); + mPacket.header.time = ns2us(frame.timestamp); +} +const uint8_t* AcclPacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t AcclPacket::getSize() +{ + return sizeof(mPacket); +} +packet_type AcclPacket::getType() +{ + return packet_accelerometer; +} +ThermometerPacket AcclPacket::getThermometerPacket() +{ + return mThermometerPacket; +} + +ThermometerPacket::ThermometerPacket(float temperature, uint16_t sensor_index, uint64_t time) +{ + mPacket.temperature_C = temperature; + + mPacket.header.sensor_id = sensor_index; + mPacket.header.type = packet_thermometer; + mPacket.header.bytes = sizeof(packet_thermometer_t); + mPacket.header.time = ns2us(time); +} +const uint8_t* ThermometerPacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t ThermometerPacket::getSize() +{ + return sizeof(mPacket); +} +packet_type ThermometerPacket::getType() +{ + return packet_thermometer; +} + +ArrivalTimePacket::ArrivalTimePacket(uint64_t arrivalTime) +{ + mPacket.header.sensor_id = 0; + mPacket.header.type = packet_arrival_time; + mPacket.header.bytes = sizeof(packet_arrival_time_t); + mPacket.header.time = ns2us(arrivalTime); +} +const uint8_t* ArrivalTimePacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t ArrivalTimePacket::getSize() +{ + return sizeof(mPacket); +} +packet_type ArrivalTimePacket::getType() +{ + return packet_arrival_time; +} + +CalibrationPacket::CalibrationPacket(const uint8_t* buffer, uint32_t size) +{ + mPacket = std::shared_ptr((packet_calibration_bin_t*) ::operator new (sizeof(packet_calibration_bin_t) + size)); + mPacket->header.sensor_id = 0; + mPacket->header.type = packet_calibration_bin; + mPacket->header.bytes = sizeof(packet_calibration_bin_t) + size; + mPacket->header.time = 0; + perc::copy(mPacket->data, buffer, size); +} +const uint8_t* CalibrationPacket::getBytes() +{ + return (uint8_t*)mPacket.get(); +} +size_t CalibrationPacket::getSize() +{ + return mPacket->header.bytes; +} +packet_type CalibrationPacket::getType() +{ + return packet_calibration_bin; +} + +ExposurePacket::ExposurePacket(uint64_t exposure_time, float gain, uint16_t sensor_index, uint64_t time) +{ + mPacket.exposure_time_us = exposure_time; + mPacket.gain = gain; + + mPacket.header.sensor_id = sensor_index; + mPacket.header.type = packet_exposure; + mPacket.header.bytes = sizeof(packet_exposure_t); + mPacket.header.time = time; +} +const uint8_t* ExposurePacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t ExposurePacket::getSize() +{ + return sizeof(mPacket); +} +packet_type ExposurePacket::getType() +{ + return packet_exposure; +} +packet_exposure_t ExposurePacket::getRCPacket() +{ + return mPacket; +} + +PhysicalInfoPacket::PhysicalInfoPacket(uint16_t sensor_index, const uint8_t* buffer, uint32_t size) +{ + uint32_t packetSize = sizeof(packet_controller_physical_info_t) + size; + mPacket = std::shared_ptr((packet_controller_physical_info_t*) ::operator new (packetSize)); + mPacket->header.sensor_id = sensor_index; + mPacket->header.type = packet_controller_physical_info; + mPacket->header.bytes = packetSize; + mPacket->header.time = 0; + perc::copy(mPacket->data, buffer, size); +} +const uint8_t* PhysicalInfoPacket::getBytes() +{ + return (uint8_t*)mPacket.get(); +} +size_t PhysicalInfoPacket::getSize() +{ + return mPacket->header.bytes; +} +packet_type PhysicalInfoPacket::getType() +{ + return packet_controller_physical_info; +} + +LedPacket::LedPacket(TrackingData::ControllerLedEventFrame& frame) +{ + uint32_t packetSize = sizeof(packet_led_intensity_t); + mPacket = std::shared_ptr((packet_led_intensity_t*) ::operator new (packetSize)); + mPacket->header.sensor_id = frame.controllerId; + mPacket->header.type = packet_led_intensity; + mPacket->header.bytes = packetSize; + mPacket->header.time = ns2us(frame.timestamp); + mPacket->intensity = frame.intensity; + mPacket->led_id = frame.ledId; +} +const uint8_t* LedPacket::getBytes() +{ + return (uint8_t*)mPacket.get(); +} +size_t LedPacket::getSize() +{ + return mPacket->header.bytes; +} +packet_type LedPacket::getType() +{ + return packet_led_intensity; +} + + + diff --git a/third-party/libtm/libtm/src/RcSerializer/Packet.h b/third-party/libtm/libtm/src/RcSerializer/Packet.h new file mode 100644 index 0000000000..a01c1f56f8 --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Packet.h @@ -0,0 +1,185 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include +#include +#include +#include "TrackingData.h" +#include "rc_packet.h" + +namespace perc +{ + class Packet + { + public: + virtual const uint8_t* getBytes() = 0; + virtual size_t getSize() = 0; + virtual packet_type getType() = 0; + }; + + class ThermometerPacket : public Packet + { + public: + ThermometerPacket(float temperature, uint16_t sensor_index, uint64_t time); + virtual ~ThermometerPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + + private: + packet_thermometer_t mPacket; + }; + + class ExposurePacket : public Packet + { + public: + ExposurePacket() : mPacket({ 0 }) {} + ExposurePacket(uint64_t exposure_time_us, float gain, uint16_t sensor_index, uint64_t time); + virtual ~ExposurePacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + packet_exposure_t getRCPacket(); + + private: + packet_exposure_t mPacket; + }; + + class ImagePacket : public Packet + { + public: + ImagePacket(TrackingData::VideoFrame& frame); + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + uint16_t getSensorId(); + std::shared_ptr getRCPacket(); + ExposurePacket getExposurePacket(); + private: + std::shared_ptr mPacket; + ExposurePacket mExposurePacket; + }; + + class StereoPacket : public Packet + { + public: + virtual ~StereoPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + bool addPacket(ImagePacket* packet); + bool isComplete(); + const uint8_t* getImageBytes(uint16_t index); + size_t getImageSize(uint16_t index); + void clear(); + ExposurePacket getExposurePacket(); + private: + static bool areCoupeled(uint64_t time_us1, uint64_t time_us2); + private: + std::map> mImagePackets; + ExposurePacket mExposurePacket; + packet_stereo_raw_t mPacket; + }; + + class GyroPacket : public Packet + { + public: + GyroPacket(TrackingData::GyroFrame& frame); + ~GyroPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + ThermometerPacket getThermometerPacket(); + + private: + packet_gyroscope_t mPacket; + ThermometerPacket mThermometerPacket; + }; + + class VelocimeterPacket : public Packet + { + public: + VelocimeterPacket(TrackingData::VelocimeterFrame& frame); + ~VelocimeterPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + ThermometerPacket getThermometerPacket(); + + private: + packet_velocimeter_t mPacket; + ThermometerPacket mThermometerPacket; + }; + + class AcclPacket : public Packet + { + public: + AcclPacket(TrackingData::AccelerometerFrame& frame); + ~AcclPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + ThermometerPacket getThermometerPacket(); + + private: + packet_accelerometer_t mPacket; + ThermometerPacket mThermometerPacket; + }; + + class ArrivalTimePacket : public Packet + { + public: + ArrivalTimePacket(uint64_t arrivalTime); + ~ArrivalTimePacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + + private: + packet_arrival_time_t mPacket; + }; + + class CalibrationPacket : public Packet + { + public: + CalibrationPacket(const uint8_t* buffer, uint32_t size); + ~CalibrationPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + + private: + std::shared_ptr mPacket; + }; + + class PhysicalInfoPacket : public Packet + { + public: + PhysicalInfoPacket(uint16_t sensor_index, const uint8_t* buffer, uint32_t size); + ~PhysicalInfoPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + + private: + std::shared_ptr mPacket; + }; + + class LedPacket : public Packet + { + public: + LedPacket(TrackingData::ControllerLedEventFrame& frame); + ~LedPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + + private: + std::shared_ptr mPacket; + + }; +} diff --git a/third-party/libtm/libtm/src/RcSerializer/Player.cpp b/third-party/libtm/libtm/src/RcSerializer/Player.cpp new file mode 100644 index 0000000000..ed1fbf4d3c --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Player.cpp @@ -0,0 +1,390 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ +#define LOG_TAG "Player" +#include +#include "Player.h" +#include "rc_packet.h" +#include "Utils.h" + +using namespace perc; +const int LEFT_FISHEYE_INDEX = 0; +const int RIGHT_FISHEYE_INDEX = 1; + +TrackingPlayer* TrackingPlayer::CreateInstance(TrackingDevice::Listener* listener, const char* file) +{ + if (listener == nullptr || file == nullptr) + { + LOGE("TrackingRecorder::CreateInstance : Invalid parameters"); + return nullptr; + } + try + { + return new PlayerImpl(listener, file); + } + catch (std::exception& e) + { + std::string error = std::string("Failed to create Player : ") + e.what(); + LOGE(error.c_str()); + return nullptr; + } +} + +PlayerImpl::PlayerImpl(TrackingDevice::Listener* listener, const char* file) : + mListener(listener), mThreadIsAlive(true), mIsStreaming(false), mfilePath(file), mDevice(nullptr), mDeviceListener(nullptr), mProfile(nullptr) +{ + LOGD("By default the device will start streaming after reading the calibration packet"); +} + +bool PlayerImpl::device_configure(TrackingDevice* device, TrackingDevice::Listener* listener, TrackingData::Profile* profile) +{ + if (!device || !listener) + { + LOGE("One of the parameters is invalid"); + return false; + } + mDeviceListener = listener; + mDevice = device; + mProfile = profile; + return true; +} + +inline int64_t us2ns(uint64_t ts) +{ + std::chrono::duration ns(ts); + return std::chrono::duration_cast>(ns).count(); +} + +void PlayerImpl::sendStereoFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime, uint32_t index) +{ + packet_stereo_raw_t *_packet = (packet_stereo_raw_t *)packet.get(); + TrackingData::VideoFrame frame; + frame.profile.height = _packet->height; + frame.profile.width = _packet->width; + frame.exposuretime = static_cast(exposureTime); + frame.profile.pixelFormat = PixelFormat::Y8; + frame.timestamp = us2ns(_packet->header.time); + frame.arrivalTimeStamp = us2ns(arrivalTime); + frame.systemTimestamp = 0; + frame.frameLength = frame.profile.height * frame.profile.width; + frame.gain = gain; + frame.frameId = 0; + if (index == LEFT_FISHEYE_INDEX) + { + frame.sensorIndex = (_packet->header.sensor_id) * 2; + frame.profile.stride = _packet->stride1; + frame.data = reinterpret_cast(_packet->data); + } + else if (index == RIGHT_FISHEYE_INDEX) + { + frame.sensorIndex = (_packet->header.sensor_id) * 2 + 1; + frame.profile.stride = _packet->stride2; + frame.data = reinterpret_cast(_packet->data + (_packet->stride1 * _packet->height)); + } + else + { + LOGE("Invalid stereo packet"); + } + mListener->onVideoFrame(frame); +} + +void PlayerImpl::sendImageFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime) +{ + packet_image_raw_t *_packet = (packet_image_raw_t *)packet.get(); + TrackingData::VideoFrame frame; + frame.profile.height = _packet->height; + frame.profile.width = _packet->width; + frame.exposuretime = static_cast(exposureTime); + frame.profile.pixelFormat = PixelFormat::Y8; + frame.timestamp = us2ns(_packet->header.time); + frame.arrivalTimeStamp = us2ns(arrivalTime); + frame.systemTimestamp = 0; + frame.frameLength = frame.profile.height * frame.profile.width; + frame.gain = gain; + frame.frameId = 0; + frame.sensorIndex = static_cast(_packet->header.sensor_id); + frame.profile.stride = _packet->stride; + frame.data = reinterpret_cast(_packet->data); + mListener->onVideoFrame(frame); +} + +void PlayerImpl::sendGyroFrame(std::shared_ptr packet, uint64_t arrivalTime, float temperature) +{ + packet_gyroscope_t *_packet = (packet_gyroscope_t *)packet.get(); + TrackingData::GyroFrame frame; + frame.sensorIndex = static_cast(_packet->header.sensor_id); + frame.angularVelocity.set(_packet->w[0], _packet->w[1], _packet->w[2]); + frame.timestamp = us2ns(_packet->header.time); + frame.temperature = temperature; + frame.arrivalTimeStamp = us2ns(arrivalTime); + frame.systemTimestamp = 0; + frame.frameId = 0; + mListener->onGyroFrame(frame); +} + +void PlayerImpl::sendAccelFrame(std::shared_ptr packet, uint64_t arrivalTime, float temperature) +{ + packet_accelerometer_t *_packet = (packet_accelerometer_t *)packet.get(); + TrackingData::AccelerometerFrame frame; + frame.sensorIndex = static_cast(_packet->header.sensor_id); + frame.acceleration.set(_packet->a[0], _packet->a[1], _packet->a[2]); + frame.timestamp = us2ns(_packet->header.time); + frame.temperature = temperature; + frame.arrivalTimeStamp = us2ns(arrivalTime); + frame.systemTimestamp = 0; + frame.frameId = 0; + mListener->onAccelerometerFrame(frame); +} + +bool PlayerImpl::startDevice() +{ + if (!mDevice) return false; + + mProfile->playbackEnabled = true; + auto status = mDevice->Start(mDeviceListener, mProfile); + if (status != Status::SUCCESS) + { + LOGE("Failed to start device : (0x%X)", status); + return false; + } + return true; +} + +void PlayerImpl::start(bool start_after_calibration) +{ + LOGD("Set playing of file %s", mfilePath.c_str()); + mReadingThread = std::thread([&]() -> void { + + if (!start_after_calibration && mDevice) + { + if (!startDevice()) return; + } + mIsStreaming = true; + + std::ifstream ifs(mfilePath.c_str(), std::fstream::in | std::fstream::binary); + if (!setProcessPriorityToRealtime()) + { + throw std::runtime_error("Failed to set process priority"); + } + packet_header_t header; + static const uint64_t INITIAL_ARRIVAL_TIME = 0; + static const uint64_t INITIAL_EXPOSURE_TIME = 0; + static const float INITIAL_TEMPERATURE = -100; + static const float INITIAL_GAIN = -100; + + uint64_t arrivalTime = INITIAL_ARRIVAL_TIME, exposureTime = INITIAL_EXPOSURE_TIME; + float temperature = INITIAL_TEMPERATURE, gain = INITIAL_GAIN; + while (ifs.read((char*)&header, sizeof(packet_header_t)) && mThreadIsAlive) + { + if (header.bytes == 0) + { + LOGE("Failed to read a packet - the header is empty"); + mIsStreaming = false; + return; + } + + std::shared_ptr packet = std::shared_ptr((packet_t *) ::operator new (header.bytes)); + packet->header = header; + if (packet->header.bytes - sizeof(packet_header_t) > 0) + { + if (!ifs.read((char*)&packet->data, packet->header.bytes - sizeof(packet_header_t))) + { + LOGE("Failed to read the packet"); + mIsStreaming = false; + return; + } + } + + switch (packet->header.type) + { + case packet_controller_physical_info: + { + if (!mDevice) break; + size_t size = packet->header.bytes - sizeof(packet_controller_physical_info_t); + writePhysicalInfo(packet->header.sensor_id, packet->data, size); + break; + } + case packet_calibration_bin: + { + if (!mDevice) break; + size_t size = packet->header.bytes - sizeof(packet_calibration_bin_t); + writeCalibration(packet->data, size); + if (start_after_calibration) + { + if (!startDevice()) + { + mIsStreaming = false; + return; + } + } + break; + } + case packet_arrival_time: + { + if (arrivalTime != INITIAL_ARRIVAL_TIME) + { + LOGW("packet_arrival_time: arrivalTime packet was not consumed\n"); + } + else + { + arrivalTime = packet->header.time; + } + break; + } + case packet_thermometer: + { + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_thermometer: arrivalTime packet was not consumed\n"); + } + if (temperature != INITIAL_TEMPERATURE) + { + LOGW("packet_thermometer: thermometer packet was not consumed\n"); + } + else + { + packet_thermometer_t *thermometer = (packet_thermometer_t *)packet.get(); + temperature = thermometer->temperature_C; + arrivalTime = INITIAL_ARRIVAL_TIME; + } + break; + } + case packet_exposure: + { + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_exposure: arrivalTime packet was not consumed\n"); + } + if (exposureTime != INITIAL_EXPOSURE_TIME && gain != INITIAL_GAIN) + { + LOGW("packet_exposure: exposure packet was not consumed\n"); + } + packet_exposure_t *exposure = (packet_exposure_t *)packet.get(); + gain = exposure->gain; + exposureTime = exposure->exposure_time_us; + arrivalTime = INITIAL_ARRIVAL_TIME; + break; + } + case packet_stereo_raw: + { + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_stereo_raw: arrivalTime packet was not consumed\n"); + } + packet_stereo_raw_t *stereo = (packet_stereo_raw_t *)packet.get(); + int leftIndex = stereo->header.sensor_id * 2 + LEFT_FISHEYE_INDEX; + int rightIndex = stereo->header.sensor_id * 2 + RIGHT_FISHEYE_INDEX; + + sendStereoFrame(packet, gain, exposureTime, arrivalTime, LEFT_FISHEYE_INDEX); + sendStereoFrame(packet, gain, exposureTime, arrivalTime, RIGHT_FISHEYE_INDEX); + arrivalTime = INITIAL_ARRIVAL_TIME; + exposureTime = INITIAL_EXPOSURE_TIME; + gain = INITIAL_GAIN; + break; + } + case packet_image_raw: + { + if (exposureTime == INITIAL_EXPOSURE_TIME) + { + LOGW("packet_image_raw: exposure packet was not consumed\n"); + } + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_image_raw: arrivalTime packet was not consumed\n"); + } + packet_image_raw_t *image = (packet_image_raw_t *)packet.get(); + sendImageFrame(packet, gain, exposureTime, arrivalTime); + arrivalTime = INITIAL_ARRIVAL_TIME; + exposureTime = INITIAL_EXPOSURE_TIME; + gain = INITIAL_GAIN; + break; + + } + case packet_accelerometer: + { + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_accelerometer: arrivalTime packet was not consumed\n"); + } + if (temperature == INITIAL_TEMPERATURE) + { + LOGW("packet_accelerometer: thermometer packet was not consumed\n"); + } + sendAccelFrame(packet, arrivalTime, temperature); + temperature = INITIAL_TEMPERATURE; + arrivalTime = INITIAL_ARRIVAL_TIME; + break; + } + case packet_gyroscope: + { + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_gyroscope: arrivalTime packet was not consumed\n"); + } + if (temperature == INITIAL_TEMPERATURE) + { + LOGW("packet_gyroscope: thermometer packet was not consumed\n"); + } + sendGyroFrame(packet, arrivalTime, temperature); + temperature = INITIAL_TEMPERATURE; + arrivalTime = INITIAL_ARRIVAL_TIME; + break; + } + default: + { + LOGW("one of the packets has unknown type : %d", packet->header.type); + } + } + } + mIsStreaming = false; + }); +} + +void PlayerImpl::stop() +{ + mIsStreaming = false; + mThreadIsAlive = false; + if (mReadingThread.joinable()) + { + mReadingThread.join(); + } +} + +bool PlayerImpl::isStreaming() +{ + return mIsStreaming; +} + +void PlayerImpl::writeCalibration(uint8_t* buffer, size_t size) +{ + if (mDevice == nullptr) return; + const static int CALIBRATION_TABLE_TYPE = 0x0; + auto status = mDevice->WriteConfiguration(CALIBRATION_TABLE_TYPE, static_cast(size), buffer); + if (status != perc::Status::SUCCESS) + { + LOGE("Failed to write write calibration table with status : (0x%X)", status); + } +} + +void PlayerImpl::writePhysicalInfo(uint16_t sensorIndex, uint8_t* buffer, size_t size) +{ + if (mDevice == nullptr) return; + if(sensorIndex != 1 && sensorIndex != 2) + { + LOGE("Invalid sensor id on physical info message %d", sensorIndex); + return; + } + + int tableType = 0x100 + sensorIndex; + auto status = mDevice->WriteConfiguration(tableType, static_cast(size), buffer); + if (status != perc::Status::SUCCESS) + { + LOGE("Failed to write write calibration table with status : (0x%X)", status); + } +} + +PlayerImpl::~PlayerImpl() +{ + stop(); +} diff --git a/third-party/libtm/libtm/src/RcSerializer/Player.h b/third-party/libtm/libtm/src/RcSerializer/Player.h new file mode 100644 index 0000000000..cd0ff05e02 --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Player.h @@ -0,0 +1,50 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +#include "Packet.h" +#include "TrackingDevice.h" +#include "TrackingSerializer.h" + +namespace perc +{ + class PlayerImpl : public TrackingPlayer + { + public: + + PlayerImpl(TrackingDevice::Listener* listener, const char* file); + virtual ~PlayerImpl(); + virtual bool device_configure(TrackingDevice* device, TrackingDevice::Listener* listener, TrackingData::Profile* profile = nullptr) override; + virtual void start(bool start_after_calibration = true) override; + virtual bool isStreaming() override; + virtual void stop() override; + + private: + void sendImageFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime); + void sendStereoFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime, uint32_t index); + void sendAccelFrame(std::shared_ptr packet, uint64_t arrival_time, float temperature); + void sendGyroFrame(std::shared_ptr packet, uint64_t arrival_time, float temperature); + void writeCalibration(uint8_t* buffer, size_t size); + void writePhysicalInfo(uint16_t sensorIndex, uint8_t* buffer, size_t size); + bool startDevice(); + + private: + TrackingDevice * mDevice; + TrackingDevice::Listener * mListener; + std::thread mReadingThread; + std::atomic mThreadIsAlive; + std::atomic mIsStreaming; + std::string mfilePath; + TrackingDevice::Listener* mDeviceListener; + TrackingData::Profile* mProfile; + }; +} \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp b/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp new file mode 100644 index 0000000000..b709d8e328 --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp @@ -0,0 +1,249 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ +#define LOG_TAG "Recorder" +#include +#include "Recorder.h" +#include "Packet.h" +#include "Utils.h" + +using namespace perc; +const uint64_t MAX_LATENCY = 1000LL * 1000LL * 1000LL; + +TrackingRecorder* TrackingRecorder::CreateInstance(TrackingDevice* device, TrackingDevice::Listener* listener, const char* file, bool stereoMode) +{ + if (device == nullptr || listener == nullptr || file == nullptr) + { + LOGE("TrackingRecorder::CreateInstance : Invalid parameters"); + return nullptr; + } + try + { + return new RecorderImpl(device, listener, file, stereoMode); + } + catch (std::exception& e) + { + std::string error = std::string("Failed to create Recorder : ") + e.what(); + LOGE(error.c_str()); + return nullptr; + } +} + +RecorderImpl::RecorderImpl(TrackingDevice* device, TrackingDevice::Listener* listener, const char* file, bool stereoMode) : mDevice(device), mListener(listener), mThreadIsAlive(true), mWritingQueue(0), mVideoFrames(), mStereoMode(stereoMode), +mLatencyQueue(MAX_LATENCY, [&](uint64_t arrivalTime, std::shared_ptr packet) -> bool +{ + switch (packet->getType()) + { + case packet_image_raw: + { + ImagePacket * imagePacket = (ImagePacket*)packet.get(); + if (mStereoMode == false) + { + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(imagePacket->getExposurePacket())); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::shared_ptr(packet)); + } + else + { + uint16_t stereoSensorId = imagePacket->getSensorId() / 2; + StereoPacket& _packet = mVideoFrames[stereoSensorId]; + if (_packet.addPacket(imagePacket) == false) + { + LOGE("Failed to record an image packet"); + return false; + } + if (_packet.isComplete()) + { + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(_packet.getExposurePacket())); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(_packet)); + mVideoFrames[stereoSensorId].clear(); + } + } + break; + } + case packet_gyroscope: + { + GyroPacket * _packet = (GyroPacket*)packet.get(); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(_packet->getThermometerPacket())); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::shared_ptr(packet)); + break; + } + case packet_accelerometer: + { + AcclPacket * _packet = (AcclPacket*)packet.get(); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(_packet->getThermometerPacket())); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::shared_ptr(packet)); + break; + } + case packet_velocimeter: + { + VelocimeterPacket * _packet = (VelocimeterPacket*)packet.get(); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(_packet->getThermometerPacket())); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::shared_ptr(packet)); + break; + } + case packet_led_intensity: + { + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::shared_ptr(packet)); + break; + } + default: LOGW("Invalid packet type : %d", packet->getType()); + } + return true; +}) +{ + LOGD("Set recording to file %s", file); + uint16_t actualSize = 0; + + if (!setProcessPriorityToRealtime()) + { + throw std::runtime_error("Failed to set process priority"); + } + + uint8_t buffer[MAX_CONFIGURATION_SIZE] = { 0 }; + const static int CALIBRATION_TABLE_TYPE = 0x0; + if (mDevice->ReadConfiguration(CALIBRATION_TABLE_TYPE, MAX_CONFIGURATION_SIZE, buffer, &actualSize) != Status::SUCCESS) + { + throw std::runtime_error("Failed to read calibration table from device"); + } + mWritingQueue.enqueue(std::make_shared(buffer, actualSize)); + + mOutputFile.open(file, std::fstream::out | std::ofstream::binary); + + mWritingThread = std::thread([&]() -> void { + while (mThreadIsAlive || mWritingQueue.size() > 0) + { + if (mWritingQueue.size() == 0) + { + static uint32_t WAIT_FOR_PACKETS = 10; + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_PACKETS)); + continue; + } + std::shared_ptr packet; + mWritingQueue.dequeue(&packet); //check output + mOutputFile.write((const char*)packet->getBytes(), packet->getSize()); + if (packet->getType() == packet_stereo_raw) + { + StereoPacket * _packet = (StereoPacket*)packet.get(); + mOutputFile.write((const char*)_packet->getImageBytes(0), _packet->getImageSize(0)); + mOutputFile.write((const char*)_packet->getImageBytes(1), _packet->getImageSize(1)); + } + } + }); +} + +RecorderImpl::~RecorderImpl() +{ + mLatencyQueue.drain(); + mThreadIsAlive = false; + if (mWritingThread.joinable()) + { + mWritingThread.join(); + } + mOutputFile.close(); + mWritingQueue.clear(); +} + +void RecorderImpl::onPoseFrame(OUT TrackingData::PoseFrame& pose) +{ + mListener->onPoseFrame(pose); +} + +void RecorderImpl::onVideoFrame(OUT TrackingData::VideoFrame& frame) +{ + mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); + mListener->onVideoFrame(frame); +} + +void RecorderImpl::onAccelerometerFrame(OUT TrackingData::AccelerometerFrame& frame) +{ + mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); + mListener->onAccelerometerFrame(frame); +} + +void RecorderImpl::onGyroFrame(OUT TrackingData::GyroFrame& frame) +{ + mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); + mListener->onGyroFrame(frame); +} + +void RecorderImpl::onVelocimeterFrame(OUT TrackingData::VelocimeterFrame& frame) +{ + mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); + mListener->onVelocimeterFrame(frame); +} + +void RecorderImpl::onControllerDiscoveryEventFrame(OUT TrackingData::ControllerDiscoveryEventFrame& frame) +{ + mListener->onControllerDiscoveryEventFrame(frame); +} + +void RecorderImpl::onControllerFrame(OUT TrackingData::ControllerFrame& frame) +{ + mListener->onControllerFrame(frame); +} + +void RecorderImpl::onControllerConnectedEventFrame(OUT TrackingData::ControllerConnectedEventFrame& frame) +{ + uint16_t actualSize = 0; + if (frame.controllerId != 1 && frame.controllerId != 2) + { + LOGE("Invalid controller id onControllerConnectedEventFrame %d", frame.controllerId); + return; + } + + int tableType = 0x100 + frame.controllerId; + uint8_t buffer[MAX_CONFIGURATION_SIZE] = { 0 }; + if (mDevice->ReadConfiguration(tableType, MAX_CONFIGURATION_SIZE, buffer, &actualSize) == Status::SUCCESS) + { + mWritingQueue.enqueue(std::make_shared(frame.controllerId, buffer, actualSize)); + } + else + { + LOGE("Failed to read physical info table from device"); + } + mListener->onControllerConnectedEventFrame(frame); +} + +void RecorderImpl::onControllerDisconnectedEventFrame(OUT TrackingData::ControllerDisconnectedEventFrame& frame) +{ + mListener->onControllerDisconnectedEventFrame(frame); +} + +void RecorderImpl::onRssiFrame(OUT TrackingData::RssiFrame& frame) +{ + mListener->onRssiFrame(frame); +} + +void RecorderImpl::onLocalizationDataEventFrame(OUT TrackingData::LocalizationDataFrame& frame) +{ + mListener->onLocalizationDataEventFrame(frame); +} + +void RecorderImpl::onFWUpdateEvent(OUT TrackingData::ControllerFWEventFrame& frame) +{ + mListener->onFWUpdateEvent(frame); +} + +void RecorderImpl::onStatusEvent(OUT TrackingData::StatusEventFrame& frame) +{ + mListener->onStatusEvent(frame); +} + +void RecorderImpl::onControllerLedEvent(OUT TrackingData::ControllerLedEventFrame& frame) +{ + mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); + mListener->onControllerLedEvent(frame); + +} diff --git a/third-party/libtm/libtm/src/RcSerializer/Recorder.h b/third-party/libtm/libtm/src/RcSerializer/Recorder.h new file mode 100644 index 0000000000..a791bf0e3e --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Recorder.h @@ -0,0 +1,68 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include +#include +#include +#include "TrackingDevice.h" +#include "latency_queue.h" +#include "concurrency.h" +#include "TrackingSerializer.h" + +namespace perc +{ + class RecorderImpl : public TrackingRecorder + { + public: + + RecorderImpl(TrackingDevice* device, Listener* listener, const char* file, bool stereoMode); + + virtual ~RecorderImpl(); + + virtual void onPoseFrame(OUT TrackingData::PoseFrame& pose) override; + + virtual void onVideoFrame(OUT TrackingData::VideoFrame& frame) override; + + virtual void onAccelerometerFrame(OUT TrackingData::AccelerometerFrame& frame) override; + + virtual void onGyroFrame(OUT TrackingData::GyroFrame& frame) override; + + virtual void onVelocimeterFrame(OUT TrackingData::VelocimeterFrame& frame) override; + + virtual void onControllerDiscoveryEventFrame(OUT TrackingData::ControllerDiscoveryEventFrame& frame) override; + + virtual void onControllerFrame(OUT TrackingData::ControllerFrame& frame) override; + + virtual void onControllerConnectedEventFrame(OUT TrackingData::ControllerConnectedEventFrame& frame) override; + + virtual void onControllerDisconnectedEventFrame(OUT TrackingData::ControllerDisconnectedEventFrame& frame) override; + + virtual void onRssiFrame(OUT TrackingData::RssiFrame& frame) override; + + virtual void onLocalizationDataEventFrame(OUT TrackingData::LocalizationDataFrame& frame) override; + + virtual void onFWUpdateEvent(OUT TrackingData::ControllerFWEventFrame& frame) override; + + virtual void onStatusEvent(OUT TrackingData::StatusEventFrame& frame) override; + + virtual void onControllerLedEvent(OUT TrackingData::ControllerLedEventFrame& frame) override; + + + private: + TrackingDevice * mDevice; + TrackingDevice::Listener * mListener; + latency_queue mLatencyQueue; + single_consumer_queue> mWritingQueue; + std::ofstream mOutputFile; + std::thread mWritingThread; + std::atomic mThreadIsAlive; + std::map mVideoFrames; + std::atomic mStereoMode; + }; + + +} \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/concurrency.h b/third-party/libtm/libtm/src/RcSerializer/concurrency.h new file mode 100644 index 0000000000..8c35e7e957 --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/concurrency.h @@ -0,0 +1,310 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2015 Intel Corporation. All Rights Reserved. + +#pragma once +#include +#include +#include +#include +#include +#include + +const int QUEUE_MAX_SIZE = 10; +// Simplest implementation of a blocking concurrent queue for thread messaging +template +class single_consumer_queue +{ + std::deque q; + std::mutex mutex; + std::condition_variable cv; // not empty signal + unsigned int cap; + bool accepting; + + // flush mechanism is required to abort wait on cv + // when need to stop + std::atomic need_to_flush; + std::atomic was_flushed; + std::condition_variable was_flushed_cv; + std::mutex was_flushed_mutex; +public: + explicit single_consumer_queue(unsigned int cap = QUEUE_MAX_SIZE) + : q(), mutex(), cv(), cap(cap), need_to_flush(false), was_flushed(false), accepting(true) + {} + + void enqueue(T&& item) + { + std::unique_lock lock(mutex); + if (accepting) + { + q.push_back(std::move(item)); + if (q.size() > cap && cap != 0) + { + printf("Droppping frame\n"); + q.pop_front(); + } + } + lock.unlock(); + cv.notify_one(); + } + + bool dequeue(T* item ,unsigned int timeout_ms = 5000) + { + std::unique_lock lock(mutex); + accepting = true; + was_flushed = false; + const auto ready = [this]() { return (q.size() > 0) || need_to_flush; }; + if (!ready() && !cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), ready)) + { + return false; + } + + if (q.size() <= 0) + { + return false; + } + *item = std::move(q.front()); + q.pop_front(); + return true; + } + + bool peek(T** item) + { + std::unique_lock lock(mutex); + + if (q.size() <= 0) + { + return false; + } + *item = &q.front(); + return true; + } + + bool try_dequeue(T* item) + { + std::unique_lock lock(mutex); + accepting = true; + if (q.size() > 0) + { + auto val = std::move(q.front()); + q.pop_front(); + *item = std::move(val); + return true; + } + return false; + } + + void clear() + { + std::unique_lock lock(mutex); + + accepting = false; + need_to_flush = true; + + while (q.size() > 0) + { + auto item = std::move(q.front()); + q.pop_front(); + } + cv.notify_all(); + } + + void start() + { + std::unique_lock lock(mutex); + need_to_flush = false; + accepting = true; + } + + size_t size() + { + return q.size(); + } +}; + + +class dispatcher +{ +public: + class cancellable_timer + { + public: + cancellable_timer(dispatcher* owner) + : _owner(owner) + {} + + bool try_sleep(int ms) + { + using namespace std::chrono; + + std::unique_lock lock(_owner->_was_stopped_mutex); + auto good = [&]() { return _owner->_was_stopped.load(); }; + return !(_owner->_was_stopped_cv.wait_for(lock, milliseconds(ms), good)); + } + + private: + dispatcher* _owner; + }; + + dispatcher(unsigned int cap) + : _queue(cap), + _was_stopped(true), + _was_flushed(false), + _is_alive(true) + { + _thread = std::thread([&]() + { + while (_is_alive) + { + std::function item; + + if (_queue.dequeue(&item)) + { + cancellable_timer time(this); + + try + { + item(time); + } + catch(...){} + } + +#ifndef ANDROID + std::unique_lock lock(_was_flushed_mutex); +#endif + _was_flushed = true; + _was_flushed_cv.notify_all(); +#ifndef ANDROID + lock.unlock(); +#endif + } + }); + } + + template + void invoke(T item) + { + if (!_was_stopped) + { + _queue.enqueue(std::move(item)); + } + } + + void start() + { + std::unique_lock lock(_was_stopped_mutex); + _was_stopped = false; + + _queue.start(); + } + + void stop() + { + { + std::unique_lock lock(_was_stopped_mutex); + _was_stopped = true; + _was_stopped_cv.notify_all(); + } + + _queue.clear(); + + { + std::unique_lock lock(_was_flushed_mutex); + _was_flushed = false; + } + + std::unique_lock lock_was_flushed(_was_flushed_mutex); + _was_flushed_cv.wait_for(lock_was_flushed, std::chrono::hours(999999), [&]() { return _was_flushed.load(); }); + + _queue.start(); + } + + ~dispatcher() + { + stop(); + _queue.clear(); + _is_alive = false; + _thread.join(); + } + + bool flush() + { + std::mutex m; + std::condition_variable cv; + bool invoked = false; + auto wait_sucess = std::make_shared(true); + invoke([&, wait_sucess](cancellable_timer t) + { + ///TODO: use _queue to flush, and implement properly + if (_was_stopped || !(*wait_sucess)) + return; + + { + std::lock_guard locker(m); + invoked = true; + } + cv.notify_one(); + }); + std::unique_lock locker(m); + *wait_sucess = cv.wait_for(locker, std::chrono::seconds(10), [&]() { return invoked || _was_stopped; }); + return *wait_sucess; + } +private: + friend cancellable_timer; + single_consumer_queue> _queue; + std::thread _thread; + + std::atomic _was_stopped; + std::condition_variable _was_stopped_cv; + std::mutex _was_stopped_mutex; + + std::atomic _was_flushed; + std::condition_variable _was_flushed_cv; + std::mutex _was_flushed_mutex; + + std::atomic _is_alive; +}; + +template> +class active_object +{ +public: + active_object(T operation) + : _operation(std::move(operation)), _dispatcher(1), _stopped(true) + { + } + + void start() + { + _stopped = false; + _dispatcher.start(); + + do_loop(); + } + + void stop() + { + _stopped = true; + _dispatcher.stop(); + } + + ~active_object() + { + stop(); + } +private: + void do_loop() + { + _dispatcher.invoke([this](dispatcher::cancellable_timer ct) + { + _operation(ct); + if (!_stopped) + { + do_loop(); + } + }); + } + + T _operation; + dispatcher _dispatcher; + std::atomic _stopped; +}; \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/latency_queue.h b/third-party/libtm/libtm/src/RcSerializer/latency_queue.h new file mode 100644 index 0000000000..92934158fc --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/latency_queue.h @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +#include "Packet.h" + +using namespace perc; + +class latency_queue +{ +public: + latency_queue(uint64_t latency, std::function)> pop_func) : + m_latency(latency), m_pop_func(pop_func), m_last_time(0) {} + + void push(uint64_t time, std::shared_ptr buf) + { + std::lock_guard lk(m_push_mutex); + // backward comaptibility - pass-through buffers with 0 timestamp + if (time == 0) { + m_pop_func(0, buf); + } + m_queue.push(queue_element_t(time, buf)); + if (time >= m_last_time) m_last_time = time; + while (!m_queue.empty()) { + queue_element_t top = m_queue.top(); + if (m_last_time - top.first < m_latency) { + break; + } + else { + m_queue.pop(); + m_pop_func(top.first, top.second); + } + } + } + + void drain() + { + std::lock_guard lk(m_push_mutex); + while (!m_queue.empty()) { + queue_element_t top = m_queue.top(); + m_queue.pop(); + m_pop_func(top.first, top.second); + } + } + +private: + uint64_t m_latency; + uint64_t m_last_time; + std::function)> m_pop_func; + std::mutex m_push_mutex; + typedef std::pair> queue_element_t; + struct element_cmp { + bool operator () (const queue_element_t &a, const queue_element_t &b) + { + return a.first > b.first; + } + }; + std::priority_queue, + element_cmp> m_queue; +}; \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/rc_packet.h b/third-party/libtm/libtm/src/RcSerializer/rc_packet.h new file mode 100644 index 0000000000..a5a6e217a4 --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/rc_packet.h @@ -0,0 +1,355 @@ +// Copyright (c) 2008-2012, Eagle Jones +// Copyright (c) 2012-2015 RealityCap, Inc +// All rights reserved. +// + +#ifndef __PACKET_H +#define __PACKET_H + +#include +#include +#include +#include + +#ifdef MYRIAD2 +// lives in target/host common area +#ifdef USE_TM2_PACKETS +#include "get_pose.h" +#endif +#endif + +#ifdef WIN32 +#pragma warning (push) +#pragma warning (disable : 4200) +#endif + +//WARNING: Do not change the order of this enum, or insert new packet types in the middle +//Only append new packet types after the last one previously defined. +typedef enum packet_type { + packet_none = 0, + packet_camera = 1, + packet_imu = 2, + packet_feature_track = 3, + packet_feature_select = 4, + packet_navsol = 5, + packet_feature_status = 6, + packet_filter_position = 7, + packet_filter_reconstruction = 8, + packet_feature_drop = 9, + packet_sift = 10, + packet_plot_info = 11, + packet_plot = 12, + packet_plot_drop = 13, + packet_recognition_group = 14, + packet_recognition_feature = 15, + packet_recognition_descriptor = 16, + packet_map_edge = 17, + packet_filter_current = 18, + packet_feature_prediction_variance = 19, + packet_accelerometer = 20, + packet_gyroscope = 21, + packet_filter_feature_id_visible = 22, + packet_filter_feature_id_association = 23, + packet_feature_intensity = 24, + packet_filter_control = 25, + packet_ground_truth = 26, + packet_core_motion = 27, + packet_image_with_depth = 28, + packet_image_raw = 29, + packet_diff_velocimeter = 30, + packet_thermometer = 31, + packet_stereo_raw = 40, + packet_image_stereo = 41, + packet_rc_pose = 42, + packet_calibration_json = 43, + packet_arrival_time = 44, + packet_velocimeter = 45, + packet_pose = 46, + packet_control = 47, + packet_calibration_bin = 48, + packet_exposure = 49, + packet_controller_physical_info = 50, + packet_led_intensity = 51, + packet_command_start = 100, + packet_command_stop = 101, +} packet_type; + +typedef struct packet_header_t { + uint32_t bytes; //size of packet including header + uint16_t type; //id of packet + uint16_t sensor_id; //id of sensor + uint64_t time; //time in microseconds +} packet_header_t; + +typedef struct packet_t { + packet_header_t header; + uint8_t data[]; +} packet_t; + +typedef struct { + packet_header_t header; + uint8_t data[]; +} packet_camera_t; + +typedef struct { + packet_header_t header; + uint64_t exposure_time_us; + uint16_t width, height; + uint16_t depth_width, depth_height; + uint8_t data[]; +} packet_image_with_depth_t; + +typedef struct { + packet_header_t header; + uint64_t exposure_time_us; + uint16_t width, height, stride; + uint16_t format; // enum { Y8, Z16_mm }; + uint8_t data[]; +} packet_image_raw_t; + +typedef struct { + packet_header_t header; + uint64_t exposure_time_us; + uint16_t width, height, stride1, stride2; + uint16_t format; // enum { Y8, Z16_mm }; + uint8_t data[]; // image2 starts at data + height*stride1 +} packet_stereo_raw_t; + +typedef struct { + packet_header_t header; + uint64_t exposure_time_us; + float gain; +} packet_exposure_t; + +typedef struct { + packet_header_t header; + packet_image_raw_t *frames[2]; +} packet_image_stereo_t; + +typedef struct { + packet_header_t header; + float a[3]; // m/s^2 + float w[3]; // rad/s +} packet_imu_t; + +typedef struct { + packet_header_t header; + float a[3]; // m/s^2 +} packet_accelerometer_t; + +typedef struct { + packet_header_t header; + float w[3]; // rad/s +} packet_gyroscope_t; + +typedef struct { + packet_header_t header; + float temperature_C; +} packet_thermometer_t; + +typedef struct { + packet_header_t header; + float v[3]; +} packet_velocimeter_t; + +typedef struct { + packet_header_t header; + float v[2]; // m/s in body x for sensor_id, sensor_id+1 +} packet_diff_velocimeter_t; + +typedef struct { + packet_header_t header; + float rotation_rate[3]; // rad/s + float gravity[3]; //m/s^2 +} packet_core_motion_t; + +typedef struct { + packet_header_t header; + struct feature_t { float x, y; } features[]; +} packet_feature_track_t; + +typedef struct { + packet_header_t header; + uint16_t indices[]; +} packet_feature_drop_t; + +typedef struct { + packet_header_t header; + uint8_t status[]; +} packet_feature_status_t; + +typedef struct { + packet_header_t header; + uint32_t led_id; + uint32_t intensity; +}packet_led_intensity_t; + +typedef struct { + packet_header_t header; + uint8_t intensity[]; +} packet_feature_intensity_t; + +typedef struct { + packet_header_t header; + float latitude, longitude; + float altitude; + float velocity[3]; + float orientation[3]; +} packet_navsol_t; + +typedef struct { + packet_header_t header; + float position[3]; + float orientation[3]; +} packet_filter_position_t; + +typedef struct { + packet_header_t header; + float points[][3]; +} packet_filter_reconstruction_t; + +typedef struct { + packet_header_t header; + uint64_t reference; + float T[3]; + float W[3]; + float points[][3]; +} packet_filter_current_t; + +typedef struct { + packet_header_t header; + float T[3]; + float W[3]; + uint64_t feature_id[]; +} packet_filter_feature_id_visible_t; + +typedef struct { + packet_header_t header; + uint64_t feature_id[]; +} packet_filter_feature_id_association_t; + +typedef struct packet_listener { + packet_t *latest; + int type; + bool isnew; +} packet_listener_t; + +typedef struct { + packet_header_t header; + float nominal; + const char identity[]; +} packet_plot_info_t; + +typedef struct { + packet_header_t header; + int count; + float data[]; +} packet_plot_t; + +typedef struct { + packet_header_t header; + uint64_t first, second; + float T[3], W[3]; + float T_var[3], W_var[3]; +} packet_map_edge_t; + +typedef struct { + packet_header_t header; + int64_t id; //signed to indicate add/drop + float W[3], W_var[3]; +} packet_recognition_group_t; + +typedef struct { + packet_header_t header; + uint64_t groupid; + uint64_t id; + float ix; + float iy; + float depth; + float x, y, z; + float variance; +} packet_recognition_feature_t; + +typedef struct { + packet_header_t header; + struct feature_covariance_t { float x, y, cx, cy, cxy; } covariance[]; +} packet_feature_prediction_variance_t; + +typedef struct { + packet_header_t header; + uint64_t groupid; + uint32_t id; + float color; + float x, y, z; + float variance; + uint32_t label; + uint8_t descriptor[128]; +} packet_recognition_descriptor_t; + +typedef struct { + packet_header_t header; + float T[3]; + float velocity[3]; // m/s + float rotation[4]; // axis [0:2] angle in rad [3] + float w[3]; // rad/s + float w_a[3]; // rad/s^2 +} packet_ground_truth_t; + +enum packet_plot_type { + packet_plot_var_T, + packet_plot_var_W, + packet_plot_var_a, + packet_plot_var_w, + packet_plot_inn_a, + packet_plot_inn_w, + packet_plot_meas_a, + packet_plot_meas_w, + packet_plot_unknown = 256 +}; + +#ifdef MYRIAD2 +#ifdef USE_TM2_PACKETS +typedef struct { + packet_header_t header; + sixDof_data data; +} packet_pose_t; +#endif +#endif + +typedef struct { + packet_header_t header; + char data[]; +} packet_calibration_json_t; + +typedef struct { + packet_header_t header; + uint8_t data[]; +} packet_calibration_bin_t; + +typedef struct { + packet_header_t header; + uint8_t data[]; +} packet_controller_physical_info_t; + +typedef struct { + packet_header_t header; // header::timestamp is packet arrival time (To algo), header::sensor_id is not used +} packet_arrival_time_t; + +typedef struct packet_control_t { + struct { + uint32_t bytes; //size of packet including header + uint16_t type; //id of packet + union { + uint16_t sensor_id; //id of sensor + uint16_t control_type; //id of control packet + }; + uint64_t time; //time in microseconds + } header; + uint8_t data[]; +} packet_control_t; + +#ifdef WIN32 +#pragma warning (pop) +#endif + +#endif \ No newline at end of file diff --git a/third-party/libtm/libtm/src/UsbPlugListener.cpp b/third-party/libtm/libtm/src/UsbPlugListener.cpp new file mode 100644 index 0000000000..fa6c72a931 --- /dev/null +++ b/third-party/libtm/libtm/src/UsbPlugListener.cpp @@ -0,0 +1,149 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#define LOG_TAG "UsbListener" +#define LOG_NDEBUG 0 /* Enable LOGV */ + +#include "UsbPlugListener.h" +#include +#include +#include "Manager.h" + +using namespace std::chrono; + +typedef enum { + USB_1_0 = 0x0100, // USB 1.0 (Low Speed 1.5 Mbps) + USB_1_1 = 0x0110, // USB 1.1 (Full Speed 12 Mbps) + USB_2_0 = 0x0200, // USB 2.0 (High Speed 480 Mbps) + USB_2_1 = 0x0210, // USB 2.1 (High Speed 480 Mbps) + USB_3_0 = 0x0300, // USB 3.0 (Super Speed 5.0 Gbps) + USB_3_1 = 0x0310, // USB 3.1 (Super Speed + 10.0 Gbps) +} USB_SPECIFICIATION_VERSION; + +perc::UsbPlugListener::UsbPlugListener(Owner& owner) : mOwner(owner), mMessage(0) +{ + // schedule the listener itself for every 100 milliseconds + mOwner.dispatcher().scheduleTimer(this, ENUMERATE_TIMEOUT_NSEC, mMessage); +} + +void perc::UsbPlugListener::onTimeout(uintptr_t timerId, const Message & msg) +{ + EnumerateDevices(); + + /* Reschedule the listener itself for every 100 milliseconds */ + mOwner.dispatcher().scheduleTimer(this, ENUMERATE_TIMEOUT_NSEC, mMessage); +} + +bool perc::UsbPlugListener::identifyDevice(libusb_device_descriptor* desc) +{ + if ((desc->idProduct == TM2_DEV_PID) && (desc->idVendor == TM2_DEV_VID) && (desc->bcdUSB >= USB_2_0)) + { + return true; + } + + return false; +} + +bool perc::UsbPlugListener::identifyUDFDevice(libusb_device_descriptor* desc) +{ + if ((desc->idVendor == TM2_UDF_DEV_VID) && (desc->idProduct == TM2_UDF_DEV_PID) && (desc->bcdUSB >= USB_2_0)) + { + return true; + } + + return false; +} + +perc::UsbPlugListener::~UsbPlugListener() +{ + // todo +} + +const char* perc::UsbPlugListener::usbSpeed(uint16_t bcdUSB) +{ + switch (bcdUSB) + { + case USB_1_0: return "USB 1.0 (Low Speed 1.5 Mbps)"; + case USB_1_1: return "USB 1.1 (Full Speed 12 Mbps)"; + case USB_2_0: return "USB 2.0 (High Speed 480 Mbps)"; + case USB_2_1: return "USB 2.1 (High Speed 480 Mbps)"; /* USB 3.0 device connected to USB 2.0 HUB */ + case USB_3_0: return "USB 3.0 (Super Speed 5.0 Gbps)"; + case USB_3_1: return "USB 3.1 (Super Speed+ 10.0 Gbps)"; + } + return "Unknown USB speed"; +} + + +void perc::UsbPlugListener::EnumerateDevices() +{ + libusb_device **list = NULL; + int rc = 0; + ssize_t count = 0; + + count = libusb_get_device_list(NULL, &list); + + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + + // mark all devices as not found to track removed devices + for (auto it = mDeviceToPortMap.begin(); it != mDeviceToPortMap.end(); it++) + { + it->second = false; + } + + for (ssize_t idx = 0; idx < count; ++idx) + { + libusb_device *device = list[idx]; + libusb_device_descriptor desc = { 0 }; + + rc = libusb_get_device_descriptor(device, &desc); + + LOGV("%d: VID 0x%04X, PID 0x%04X, bcdUSB 0x%x, iSerialNumber %d", idx, desc.idVendor, desc.idProduct, desc.bcdUSB, desc.iSerialNumber); + + if ((identifyDevice(&desc) == true) || (identifyUDFDevice(&desc) == true)) + { + std::lock_guard lk(mDeviceToPortMapLock); + DeviceToPortMap devicePort(device); + Status st = Status::SUCCESS; + + if (mDeviceToPortMap.find(devicePort) == mDeviceToPortMap.end()) + { + LOGD("Found USB device %s on port %d: VID 0x%04X, PID 0x%04X, %s",(desc.idVendor == TM2_UDF_DEV_VID)?"Movidius":"T250", devicePort.portChain[0], desc.idVendor, desc.idProduct, usbSpeed(desc.bcdUSB)); + st = mOwner.onAttach(device); + } + + // just mark the device as found in this list + if (st == Status::SUCCESS) + { + mDeviceToPortMap[devicePort] = true; + } + } + } + + // look for unplugged devices (==not found in the list) + std::list devicesToDelete; + for (auto item : mDeviceToPortMap) + { + if (item.second == false) // mark the device for deletion + { + devicesToDelete.push_back(item.first); + } + } + + for (auto devicePort : devicesToDelete) // ... and delete it + { + // send message to client handler ON_DETACH + std::lock_guard lock(mDeviceToPortMapLock); + mDeviceToPortMap.erase(devicePort); + mOwner.onDetach(devicePort.libusbDevice); + } + + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + auto duration = duration_cast(t2 - t1).count(); + + LOGV("Devices connected: %d. Time: %d micro-seconds", mDeviceToPortMap.size(), duration); + + // free the list + libusb_free_device_list(list, 1); +} diff --git a/third-party/libtm/libtm/src/UsbPlugListener.h b/third-party/libtm/libtm/src/UsbPlugListener.h new file mode 100644 index 0000000000..fe1a74ed84 --- /dev/null +++ b/third-party/libtm/libtm/src/UsbPlugListener.h @@ -0,0 +1,111 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include +#include +#include +#include "Dispatcher.h" +#include "Fsm.h" +#include "Device.h" +#include "TrackingCommon.h" +#include "TrackingData.h" +#include +#include +#include + +#define TM2_DEV_VID 0x8087 +#define TM2_DEV_PID 0x0AF3 +#define TM2_UDF_DEV_VID 0x03E7 +#define TM2_UDF_DEV_PID 0x2150 +#define ENUMERATE_TIMEOUT_NSEC 500000000 + + +namespace perc +{ + class UsbPlugListener : public EventHandler + { + public: + class Owner { + public: + virtual ~Owner() {}; + virtual Status onAttach(libusb_device*) = 0; + virtual void onDetach(libusb_device*) = 0; + virtual libusb_context* context() = 0; + virtual Dispatcher& dispatcher() = 0; + }; + + public: + UsbPlugListener(Owner& owner); + ~UsbPlugListener(); + + bool identifyDevice(libusb_device_descriptor* desc); + bool identifyUDFDevice(libusb_device_descriptor* desc); + + // [interface] EventHandler + virtual void onTimeout(uintptr_t timerId, const Message &msg); + static const char *usbSpeed(uint16_t bcdUSB); + + private: + void EnumerateDevices(); + Message mMessage; + Owner& mOwner; + class DeviceToPortMap + { + public: + DeviceToPortMap() : libusbDevice(NULL), portChainDepth(0), portChain{0} {}; + DeviceToPortMap(libusb_device* _libusbDevice) : portChain{ 0 } { + libusbDevice = _libusbDevice; + portChainDepth = libusb_get_port_numbers(libusbDevice, portChain, MAX_USB_TREE_DEPTH); + }; + + + libusb_device* libusbDevice; + uint32_t portChainDepth; + uint8_t portChain[MAX_USB_TREE_DEPTH]; + + bool operator<(const DeviceToPortMap &ref) const + { + if (this->libusbDevice < ref.libusbDevice) + { + return true; + } + else if (this->libusbDevice > ref.libusbDevice) + { + return false; + } + + /* this->libusbDevice == ref.libusbDevice */ + if (this->portChainDepth < ref.portChainDepth) + { + return true; + } + else if (this->portChainDepth > ref.portChainDepth) + { + return false; + } + + /* this->libusbDevice == ref.libusbDevice */ + /* this->portChainDepth == ref.portChainDepth */ + for (uint32_t i = 0; i < this->portChainDepth; i++) + { + if (this->portChain[i] < ref.portChain[i]) + { + return true; + } + else if (this->portChain[i] > ref.portChain[i]) + { + return false; + } + } + + return false; + } + + }; + std::map mDeviceToPortMap; + std::mutex mDeviceToPortMapLock; + }; +} diff --git a/third-party/libtm/libtm/src/Version.h.in b/third-party/libtm/libtm/src/Version.h.in new file mode 100644 index 0000000000..2333df2db7 --- /dev/null +++ b/third-party/libtm/libtm/src/Version.h.in @@ -0,0 +1,33 @@ +// INTEL CORPORATION PROPRIETARY INFORMATION +// This software is supplied under the terms of a license agreement or nondisclosure +// agreement with Intel Corporation and may not be copied or disclosed except in +// accordance with the terms of that agreement +// Copyright(c) 2017 Intel Corporation. All Rights Reserved. + +// Version.h file is an auto-generated file made by CMake from file Version.h.in. +// Changes to Version.h are likely to be overwritten and lost + +#pragma once +#define LIBTM_VERSION_MAJOR @LIBTM_VERSION_MAJOR@ +#define LIBTM_VERSION_MINOR @LIBTM_VERSION_MINOR@ +#define LIBTM_VERSION_PATCH @LIBTM_VERSION_PATCH@ +#define LIBTM_VERSION_BUILD @LIBTM_VERSION_BUILD@ + +#define LIBTM_API_VERSION_MAJOR @LIBTM_API_VERSION_MAJOR@ +#define LIBTM_API_VERSION_MINOR @LIBTM_API_VERSION_MINOR@ + +#define LIBTM_VERSION_BUILD_POS 0 +#define LIBTM_VERSION_BUILD_MSK 0xFFFFFFFF +#define LIBTM_VERSION_PATCH_POS 32 +#define LIBTM_VERSION_PATCH_MSK 0xFFFF +#define LIBTM_VERSION_MINOR_POS 48 +#define LIBTM_VERSION_MINOR_MSK 0xFF +#define LIBTM_VERSION_MAJOR_POS 56 +#define LIBTM_VERSION_MAJOR_MSK 0xFF + +#define LIBTM_VERSION (((uint64_t)(LIBTM_VERSION_MAJOR & LIBTM_VERSION_MAJOR_MSK) << LIBTM_VERSION_MAJOR_POS) | \ + ((uint64_t)(LIBTM_VERSION_MINOR & LIBTM_VERSION_MINOR_MSK) << LIBTM_VERSION_MINOR_POS) | \ + ((uint64_t)(LIBTM_VERSION_PATCH & LIBTM_VERSION_PATCH_MSK) << LIBTM_VERSION_PATCH_POS) | \ + ((uint64_t)(LIBTM_VERSION_BUILD & LIBTM_VERSION_BUILD_MSK) << LIBTM_VERSION_BUILD_POS)) + +#define LIBTM_BRANCH @GIT_BRANCH@ diff --git a/third-party/libtm/libtm/src/version.rc.in b/third-party/libtm/libtm/src/version.rc.in new file mode 100644 index 0000000000..53ea77fa81 --- /dev/null +++ b/third-party/libtm/libtm/src/version.rc.in @@ -0,0 +1,93 @@ +// Microsoft Visual C++ generated resource script. +// +#include "windows.h" +#include "fw.h" +#include "CentralAppFw.h" +#include "CentralBlFw.h" + +///////////////////////////////////////////////////////////////////////////// +// Hebrew (Israel) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_HEB) +LANGUAGE LANG_HEBREW, SUBLANG_DEFAULT + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#define FILE_VERSION "@LIBTM_VERSION_MAJOR@.@LIBTM_VERSION_MINOR@.@LIBTM_VERSION_PATCH@.@LIBTM_VERSION_BUILD@" +#define PRODUCT_VERSION FILE_VERSION ", FW " FW_VERSION ", Central APP " CENTRAL_APP_VERSION ", Central BL " CENTRAL_BL_VERSION " " + +VS_VERSION_INFO VERSIONINFO + FILEVERSION @LIBTM_VERSION_MAJOR@,@LIBTM_VERSION_MINOR@,@LIBTM_VERSION_PATCH@,0 + PRODUCTVERSION @LIBTM_VERSION_MAJOR@,@LIBTM_VERSION_MINOR@,@LIBTM_VERSION_PATCH@,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040004b0" + BEGIN + VALUE "CompanyName", "Intel(r)" + VALUE "FileDescription", "TM2 library" + VALUE "FileVersion", "@LIBTM_VERSION_MAJOR@.@LIBTM_VERSION_MINOR@.@LIBTM_VERSION_PATCH@.@LIBTM_VERSION_BUILD@" + VALUE "InternalName", "version.rc" + VALUE "LegalCopyright", "Copyright (C) 2017" + VALUE "OriginalFilename", "tm.dll" + VALUE "ProductName", "TM2 library" + VALUE "ProductVersion", PRODUCT_VERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x400, 1200 + END +END + +#endif // Hebrew (Israel) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/third-party/libtm/resources/CMakeLists.txt b/third-party/libtm/resources/CMakeLists.txt new file mode 100644 index 0000000000..ebfa614e15 --- /dev/null +++ b/third-party/libtm/resources/CMakeLists.txt @@ -0,0 +1,320 @@ +cmake_minimum_required(VERSION 2.8) +project(resources) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(FW_ARTIFACTORY_DIR "http://artifactoryperc01.iil.intel.com:8081/artifactory/libs-release-local/com/intel/realsense/target") +set(CENTRAL_APP_ARTIFACTORY_DIR "http://artifactoryperc01.iil.intel.com:8081/artifactory/libs-release-local/com/intel/realsense/central_app") +set(CENTRAL_BL_ARTIFACTORY_DIR "http://artifactoryperc01.iil.intel.com:8081/artifactory/libs-release-local/com/intel/realsense/central_bl") + +set(FW_OUTPUT_FILE "${LIBTM_SRC_DIR}/fw.h") +set(CENTRAL_APP_OUTPUT_FILE "${LIBTM_SRC_DIR}/CentralAppFw.h") +set(CENTRAL_BL_OUTPUT_FILE "${LIBTM_SRC_DIR}/CentralBlFw.h") + +# Convert HEX to DEC +function(from_hex HEX DEC) + string(SUBSTRING "${HEX}" 0 -1 HEX) + string(TOUPPER "${HEX}" HEX) + set(_res 0) + string(LENGTH "${HEX}" _strlen) + + while(_strlen GREATER 0) + math(EXPR _res "${_res} * 16") + string(SUBSTRING "${HEX}" 0 1 NIBBLE) + string(SUBSTRING "${HEX}" 1 -1 HEX) + if(NIBBLE STREQUAL "A") + math(EXPR _res "${_res} + 10") + elseif(NIBBLE STREQUAL "B") + math(EXPR _res "${_res} + 11") + elseif(NIBBLE STREQUAL "C") + math(EXPR _res "${_res} + 12") + elseif(NIBBLE STREQUAL "D") + math(EXPR _res "${_res} + 13") + elseif(NIBBLE STREQUAL "E") + math(EXPR _res "${_res} + 14") + elseif(NIBBLE STREQUAL "F") + math(EXPR _res "${_res} + 15") + else() + math(EXPR _res "${_res} + ${NIBBLE}") + endif() + + string(LENGTH "${HEX}" _strlen) + endwhile() + + set(${DEC} ${_res} PARENT_SCOPE) +endfunction() + +# Creates hex buffer from binary file - binary file must be 4 bytes aligned +function(readBinHeaderField input_bin_file offset limit output_hex_buffer) + #message("Reading BIN header from ${input_bin_file} offset ${offset} limit ${limit}") + + file(READ ${input_bin_file} buffer LIMIT ${limit} OFFSET ${offset} HEX) + string(TOUPPER "${buffer}" buffer) + string(REGEX REPLACE "([0-9A-F][0-9A-F])([0-9A-F][0-9A-F])([0-9A-F][0-9A-F])([0-9A-F][0-9A-F])" "\\4\\3\\2\\1" buffer ${buffer}) + from_hex("${buffer}" buffer) + + # Copy ready buffer to output buffer + set(${output_hex_buffer} "${buffer}" PARENT_SCOPE) +endfunction() + +# Creates hex buffer from binary file - binary file must be 4 bytes aligned +function(bin2h input_bin_file offset output_hex_buffer) + message("Creating HEX buffer from ${input_bin_file} offset ${offset}") + + # Read hex data from file + file(READ ${input_bin_file} buffer OFFSET ${offset} HEX) + + # Move all buffer to upper case + string(TOUPPER "${buffer}" buffer) + + # Convert every 4 bytes from AABBCCDD to 0xDDCCBBAA + string(REGEX REPLACE "([0-9A-F][0-9A-F])([0-9A-F][0-9A-F])([0-9A-F][0-9A-F])([0-9A-F][0-9A-F])" "0x\\4\\3\\2\\1," buffer ${buffer}) + + # Add new line to every 16 columns + string(REGEX REPLACE "(0x........,0x........,0x........,0x........,0x........,0x........,0x........,0x........,0x........,0x........,0x........,0x........,0x........,0x........,0x........,0x........,)" "\\1\\n" buffer ${buffer}) + + # Copy ready buffer to output buffer + set(${output_hex_buffer} "${buffer}" PARENT_SCOPE) +endfunction() + + +if(FW_VERSION) + message("--------------------------------------------------------------------------------------------------------------------------------------------------------------") + + if (FW_SOURCE MATCHES "Remote") + file(REMOVE ${LIBTM_RESOURCES_DIR}/target.mvcmd) + message("Downloading FW ${FW_VERSION} from '${FW_ARTIFACTORY_DIR}/${FW_VERSION}/target-${FW_VERSION}.mvcmd'") + file(DOWNLOAD "${FW_ARTIFACTORY_DIR}/${FW_VERSION}/target-${FW_VERSION}.mvcmd" "${LIBTM_RESOURCES_DIR}/target.mvcmd" TIMEOUT 60 STATUS status LOG log) + message("Downloading FW ${FW_VERSION} completed - status ${status}") + + list (FIND status "\"No error\"" _index) + if (${_index} EQUAL -1) + message(FATAL_ERROR "Download error ${FW_ARTIFACTORY_DIR}/${FW_VERSION}/target-${FW_VERSION}.mvcmd" ) + endif() + elseif (FW_SOURCE MATCHES "Local") + if(NOT EXISTS "${LIBTM_RESOURCES_DIR}/target.mvcmd") + message(FATAL_ERROR "File ${LIBTM_RESOURCES_DIR}/target.mvcmd is missing" ) + else() + message("Downloading FW ${FW_VERSION} skipped") + endif() + endif() + + if (FW_SOURCE MATCHES "Remote" OR FW_SOURCE MATCHES "Local") + file(REMOVE ${FW_OUTPUT_FILE}) + message("Converting FW version ${FW_VERSION} ${LIBTM_RESOURCES_DIR}/target.mvcmd to ${FW_OUTPUT_FILE}") + + # Create empty output file + file(WRITE ${FW_OUTPUT_FILE} "") + file(APPEND ${FW_OUTPUT_FILE} "/*******************************************************************************\n") + file(APPEND ${FW_OUTPUT_FILE} "INTEL CORPORATION PROPRIETARY INFORMATION\n") + file(APPEND ${FW_OUTPUT_FILE} "Copyright(c) 2017 Intel Corporation. All Rights Reserved.\n") + file(APPEND ${FW_OUTPUT_FILE} "*******************************************************************************/\n\n") + file(APPEND ${FW_OUTPUT_FILE} "#ifndef target_h\n") + file(APPEND ${FW_OUTPUT_FILE} "#define target_h\n\n") + file(APPEND ${FW_OUTPUT_FILE} "#define FW_VERSION \"${FW_VERSION}\"\n\n") + file(APPEND ${FW_OUTPUT_FILE} "static uint32_t target_hex [] = {\n") + + bin2h(${LIBTM_RESOURCES_DIR}/target.mvcmd 0 fw_ready_buffer) + + file(APPEND ${FW_OUTPUT_FILE} "${fw_ready_buffer}\n") + file(APPEND ${FW_OUTPUT_FILE} "};\n") + file(APPEND ${FW_OUTPUT_FILE} "#endif\n") + else() + if (NOT EXISTS "${FW_OUTPUT_FILE}") + message(FATAL_ERROR "File ${FW_OUTPUT_FILE} is missing" ) + else() + message("Already created ${FW_OUTPUT_FILE}") + endif() + endif() +endif(FW_VERSION) + + +if(CENTRAL_APP_VERSION) + message("--------------------------------------------------------------------------------------------------------------------------------------------------------------") + + if (FW_SOURCE MATCHES "Remote") + file(REMOVE ${LIBTM_RESOURCES_DIR}/central_app.bin) + message("Downloading Central App ${CENTRAL_APP_VERSION} from '${CENTRAL_APP_ARTIFACTORY_DIR}/${CENTRAL_APP_VERSION}/central_app-${CENTRAL_APP_VERSION}.bin'") + file(DOWNLOAD "${CENTRAL_APP_ARTIFACTORY_DIR}/${CENTRAL_APP_VERSION}/central_app-${CENTRAL_APP_VERSION}.bin" "${LIBTM_RESOURCES_DIR}/central_app.bin" TIMEOUT 60 STATUS status LOG log) + message("Downloading Central App ${CENTRAL_APP_VERSION} completed - status ${status}") + list (FIND status "\"No error\"" _index) + if (${_index} EQUAL -1) + message(FATAL_ERROR "Download error ${CENTRAL_APP_ARTIFACTORY_DIR}/${CENTRAL_APP_VERSION}/central_app-${CENTRAL_APP_VERSION}.bin" ) + endif() + elseif (FW_SOURCE MATCHES "Local") + if(NOT EXISTS "${LIBTM_RESOURCES_DIR}/central_app.bin") + message(FATAL_ERROR "File ${LIBTM_RESOURCES_DIR}/central_app.bin is missing" ) + else() + message("Downloading Central App ${CENTRAL_APP_VERSION} skipped") + endif() + endif() + + if (FW_SOURCE MATCHES "Remote" OR FW_SOURCE MATCHES "Local") + file(REMOVE ${CENTRAL_APP_OUTPUT_FILE}) + message("Converting Central App version ${CENTRAL_APP_VERSION} ${LIBTM_RESOURCES_DIR}/central_app.bin to ${CENTRAL_APP_OUTPUT_FILE}") + + STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+\\.[0-9]+" "\\1" central_app_major_version "${CENTRAL_APP_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" central_app_minor_version "${CENTRAL_APP_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" central_app_patch_version "${CENTRAL_APP_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" central_app_build_version "${CENTRAL_APP_VERSION}") + + # Central APP Header and data format: + # Header: + # uint32 header_size; + # uint32 data_size; + # uint32 file_format_version; // defines the format of both header and data + # uint32 version_size; + # uint8 version[] + # Data: + # uint8 data[]; + + # Read 1 byte of file_format_version and abort if not supported + readBinHeaderField(${LIBTM_RESOURCES_DIR}/central_app.bin 8 1 central_app_format_ver) + if (NOT ${central_app_format_ver} EQUAL 1) + message(FATAL_ERROR "Central FW File format (${central_app_format_ver}) is not supported" ) + endif() + + # Read 4 bytes of header_size + readBinHeaderField(${LIBTM_RESOURCES_DIR}/central_app.bin 0 4 central_app_header_size) + + # Read 4 bytes of data_size + readBinHeaderField(${LIBTM_RESOURCES_DIR}/central_app.bin 4 4 central_app_data_size) + + # Read 4 bytes of version_size + readBinHeaderField(${LIBTM_RESOURCES_DIR}/central_app.bin 12 4 central_app_version_size) + if (NOT ${central_app_version_size} EQUAL 3 AND NOT ${central_app_version_size} EQUAL 7) + message(FATAL_ERROR "Central FW version size (${central_app_version_size}) is not supported" ) + endif() + + # Read 1 byte of major,minor,patch versions + readBinHeaderField(${LIBTM_RESOURCES_DIR}/central_app.bin 16 1 central_app_version_major) + readBinHeaderField(${LIBTM_RESOURCES_DIR}/central_app.bin 17 1 central_app_version_minor) + readBinHeaderField(${LIBTM_RESOURCES_DIR}/central_app.bin 18 1 central_app_version_patch) + + # Check if need to read read 4 bytes of build version + if (${central_app_version_size} EQUAL 7) + readBinHeaderField(${LIBTM_RESOURCES_DIR}/central_app.bin 19 4 central_app_version_build) + endif() + + # Check if internal central FW version is compatible with file name + if (NOT ${central_app_version_major} EQUAL ${central_app_major_version} OR + NOT ${central_app_version_minor} EQUAL ${central_app_minor_version} OR + NOT ${central_app_version_patch} EQUAL ${central_app_patch_version} OR + NOT ${central_app_version_build} EQUAL ${central_app_build_version}) + message(FATAL_ERROR "Wrong Central FW version (${central_app_version_major}.${central_app_version_minor}.${central_app_version_patch}.${central_app_version_build}) VS (${central_app_major_version}.${central_app_minor_version}.${central_app_patch_version}.${central_app_build_version})") + endif() + + message("Central Header:") + message("- header_size = ${central_app_header_size}") + message("- data_size = ${central_app_data_size}") + message("- file_format_version = ${central_app_format_ver}") + message("- version_size = ${central_app_version_size}") + message("- version = ${central_app_version_major}.${central_app_version_minor}.${central_app_version_patch}.${central_app_version_build}") + + # Create empty output file + file(WRITE ${CENTRAL_APP_OUTPUT_FILE} "") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "/*******************************************************************************\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "INTEL CORPORATION PROPRIETARY INFORMATION\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "Copyright(c) 2017 Intel Corporation. All Rights Reserved.\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "*******************************************************************************/\n\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "#ifndef CentralAppFw_h\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "#define CentralAppFw_h\n\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "#define CENTRAL_APP_VERSION \"${CENTRAL_APP_VERSION}\"\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "#define CENTRAL_APP_SIZE ${central_app_data_size}\n\n") + + + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "namespace CentralAppFw {\n\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "const int Version[] = { ${central_app_major_version}, ${central_app_minor_version}, ${central_app_patch_version}, ${central_app_build_version} };\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "const uint32_t Buffer [] = {\n") + + bin2h(${LIBTM_RESOURCES_DIR}/central_app.bin ${central_app_header_size} central_app_ready_buffer) + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "${central_app_ready_buffer}\n") + + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "};\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "}\n") + file(APPEND ${CENTRAL_APP_OUTPUT_FILE} "#endif\n") + else() + if (NOT EXISTS "${CENTRAL_APP_OUTPUT_FILE}") + message(FATAL_ERROR "File ${CENTRAL_APP_OUTPUT_FILE} is missing" ) + else() + message("Already created ${CENTRAL_APP_OUTPUT_FILE}") + endif() + endif() + +endif(CENTRAL_APP_VERSION) + + +if(CENTRAL_BL_VERSION) + message("--------------------------------------------------------------------------------------------------------------------------------------------------------------") + + if (FW_SOURCE MATCHES "Remote") + file(REMOVE ${LIBTM_RESOURCES_DIR}/central_bl.bin) + message("Downloading Central BL ${CENTRAL_BL_VERSION} from '${CENTRAL_BL_ARTIFACTORY_DIR}/${CENTRAL_BL_VERSION}/central_bl-${CENTRAL_BL_VERSION}.bin'") + file(DOWNLOAD "${CENTRAL_BL_ARTIFACTORY_DIR}/${CENTRAL_BL_VERSION}/central_bl-${CENTRAL_BL_VERSION}.bin" "${LIBTM_RESOURCES_DIR}/central_bl.bin" TIMEOUT 60 STATUS status LOG log) + message("Downloading Central BL ${CENTRAL_BL_VERSION} completed - status ${status}") + list (FIND status "\"No error\"" _index) + if (${_index} EQUAL -1) + message(FATAL_ERROR "Download error ${CENTRAL_BL_ARTIFACTORY_DIR}/${CENTRAL_BL_VERSION}/central_bl-${CENTRAL_BL_VERSION}.bin" ) + endif() + elseif (FW_SOURCE MATCHES "Local") + if(NOT EXISTS "${LIBTM_RESOURCES_DIR}/central_bl.bin") + message(FATAL_ERROR "File ${LIBTM_RESOURCES_DIR}/central_bl.bin is missing" ) + else() + message("Downloading Central BL ${CENTRAL_BL_VERSION} skipped") + endif() + endif() + + if (FW_SOURCE MATCHES "Remote" OR FW_SOURCE MATCHES "Local") + file(REMOVE ${CENTRAL_BL_OUTPUT_FILE}) + message("Converting Central BL version ${CENTRAL_BL_VERSION} ${LIBTM_RESOURCES_DIR}/central_bl.bin to ${CENTRAL_BL_OUTPUT_FILE}") + + STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+\\.[0-9]+" "\\1" central_bl_major_version "${CENTRAL_BL_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" central_bl_minor_version "${CENTRAL_BL_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" central_bl_patch_version "${CENTRAL_BL_VERSION}") + STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" central_bl_build_version "${CENTRAL_BL_VERSION}") + + # Create empty output file + file(WRITE ${CENTRAL_BL_OUTPUT_FILE} "") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "/*******************************************************************************\n") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "INTEL CORPORATION PROPRIETARY INFORMATION\n") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "Copyright(c) 2017 Intel Corporation. All Rights Reserved.\n") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "*******************************************************************************/\n\n") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "#ifndef CentralBlFw_h\n") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "#define CentralBlFw_h\n\n") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "#define CENTRAL_BL_VERSION \"${CENTRAL_BL_VERSION}\"\n\n") + + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "namespace CentralBlFw {\n\n") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "const int Version[] = { ${central_bl_major_version}, ${central_bl_minor_version}, ${central_bl_patch_version}, ${central_bl_build_version} };\n") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "const uint32_t Buffer [] = {\n") + + bin2h(${LIBTM_RESOURCES_DIR}/central_bl.bin 3 central_bl_ready_buffer) + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "${central_bl_ready_buffer}\n") + + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "};\n") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "}\n") + file(APPEND ${CENTRAL_BL_OUTPUT_FILE} "#endif\n") + else() + if (NOT EXISTS "${CENTRAL_BL_OUTPUT_FILE}") + message(FATAL_ERROR "File ${CENTRAL_BL_OUTPUT_FILE} is missing" ) + else() + message("Already created ${CENTRAL_BL_OUTPUT_FILE}") + endif() + endif() + +endif(CENTRAL_BL_VERSION) + + +# Update versions.log & remote_versions.log file +set(FWVERSIONS "FW [${FW_VERSION}]\nCentral App [${CENTRAL_APP_VERSION}]\nCentral BL [${CENTRAL_BL_VERSION}]\n") +set(ALLVERSIONS "Host [${HOST_VERSION}]\nFW [${FW_VERSION}]\nCentral App [${CENTRAL_APP_VERSION}]\nCentral BL [${CENTRAL_BL_VERSION}]\n") + +if (FW_SOURCE MATCHES "Remote") + file(WRITE ${LIBTM_RESOURCES_DIR}/remote_versions.log ${FWVERSIONS}) + file(WRITE ${LIBTM_ROOT}/versions.log ${ALLVERSIONS}) +elseif (FW_SOURCE MATCHES "Local") + file(WRITE ${LIBTM_ROOT}/versions.log Host ${ALLVERSIONS}) +elseif (FW_SOURCE MATCHES "Internal") + file(WRITE ${LIBTM_ROOT}/versions.log ${ALLVERSIONS}) +else() + message(FATAL_ERROR "Bad FW source - ${FW_SOURCE}" ) +endif() diff --git a/third-party/libtm/versions.cmake b/third-party/libtm/versions.cmake new file mode 100644 index 0000000000..0e8383a0f0 --- /dev/null +++ b/third-party/libtm/versions.cmake @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 2.8) + +# All product versions are located here +# If FW source = Remote - FW versions are defined below +# If FW source = Local - FW versions are defined according to /resources/remote_versions.log +# If FW source = Internal - FW versions are defined according to fw.h, CentralAppFw.h, CentralBlFw.h +set(INTERNAL_HOST_VERSION "0.19.3.1132") +set(INTERNAL_FW_VERSION "0.0.18.3308") +set(INTERNAL_CENTRAL_APP_VERSION "2.0.19.271") +set(INTERNAL_CENTRAL_BL_VERSION "1.0.1.112") +set(INTERNAL_FW_SOURCE "Remote") + +option(HOST_VERSION "set HOST_VERSION to host version, set to 0 to use internal configuration (INTERNAL_HOST_VERSION)" 0) +if(HOST_VERSION) + set(HOST_VERSION_SOURCE "CMake cached variable") +else() + set(HOST_VERSION_SOURCE "Default from versions.cmake") + set (HOST_VERSION ${INTERNAL_HOST_VERSION}) +endif(HOST_VERSION) + +option(FW_VERSION "set FW_VERSION to fw version, set to 0 to use internal configuration (INTERNAL_FW_VERSION)" 0) +if(FW_VERSION) + set(FW_VERSION_SOURCE "CMake cached variable") +else() + set(FW_VERSION_SOURCE "Default from versions.cmake") + set (FW_VERSION ${INTERNAL_FW_VERSION}) +endif(FW_VERSION) + +option(CENTRAL_APP_VERSION "set CENTRAL_APP_VERSION to central app version, set to 0 to use internal configuration - INTERNAL_CENTRAL_APP_VERSION" 0) +if(CENTRAL_APP_VERSION) + set(CENTRAL_APP_VERSION_SOURCE "CMake cached variable") +else() + set(CENTRAL_APP_VERSION_SOURCE "Default from versions.cmake") + set (CENTRAL_APP_VERSION ${INTERNAL_CENTRAL_APP_VERSION}) +endif(CENTRAL_APP_VERSION) + +option(CENTRAL_BL_VERSION "set CENTRAL_BL_VERSION to central bl version, set to 0 to use internal configuration - INTERNAL_CENTRAL_BL_VERSION" 0) +if(CENTRAL_BL_VERSION) + set(CENTRAL_BL_VERSION_SOURCE "CMake cached variable") +else() + set(CENTRAL_BL_VERSION_SOURCE "Default from versions.cmake") + set (CENTRAL_BL_VERSION ${INTERNAL_CENTRAL_BL_VERSION}) +endif(CENTRAL_BL_VERSION) + +option(FW_SOURCE "set FW_SOURCE to FW source, set to 0 to use internal configuration - INTERNAL_FW_SOURCE" 0) +if(FW_SOURCE) +else() + set (FW_SOURCE ${INTERNAL_FW_SOURCE}) +endif(FW_SOURCE) + +message("----------------------------------------------------------------------------") +message("Product versions:") +message("- HOST ${HOST_VERSION} (${HOST_VERSION_SOURCE})") + +if (FW_SOURCE MATCHES "Remote") +# FW versions are already updated above + +elseif(FW_SOURCE MATCHES "Local" AND EXISTS "${LIBTM_RESOURCES_DIR}/remote_versions.log") + # Using local FW BIN files from resources folder, taking versions from remote_versions.log + + file(READ ${LIBTM_RESOURCES_DIR}/remote_versions.log buffer) + string(REGEX REPLACE ".*FW \\[([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)\\].*" "\\1" fw_version ${buffer}) + string(REGEX REPLACE ".*Central App \\[([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)\\].*" "\\1" central_app_version ${buffer}) + string(REGEX REPLACE ".*Central BL \\[([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)\\].*" "\\1" central_bl_version ${buffer}) + set(FW_VERSION ${fw_version}) + set(CENTRAL_APP_VERSION ${central_app_version}) + set(CENTRAL_BL_VERSION ${central_bl_version}) + +elseif (FW_SOURCE MATCHES "Internal" AND EXISTS "${LIBTM_SRC_DIR}/fw.h" AND EXISTS "${LIBTM_SRC_DIR}/CentralAppFw.h" AND EXISTS "${LIBTM_SRC_DIR}/CentralBlFw.h") + # Use internal FW header files, taking versions from header files + + file(READ ${LIBTM_SRC_DIR}/fw.h buffer) + string(REGEX REPLACE ".*FW_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+).*" "\\1" fw_version ${buffer}) + + file(READ ${LIBTM_SRC_DIR}/CentralAppFw.h buffer) + string(REGEX REPLACE ".*CENTRAL_APP_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+).*" "\\1" central_app_version ${buffer}) + + file(READ ${LIBTM_SRC_DIR}/CentralBlFw.h buffer) + string(REGEX REPLACE ".*CENTRAL_BL_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+).*" "\\1" central_bl_version ${buffer}) + + set(FW_VERSION ${fw_version}) + set(CENTRAL_APP_VERSION ${central_app_version}) + set(CENTRAL_BL_VERSION ${central_bl_version}) + +else() + message(FATAL_ERROR "Can't build using FW source = ${FW_SOURCE}" ) +endif() + +message("- ${FW_SOURCE} FW ${FW_VERSION} (${FW_VERSION_SOURCE})") +message("- ${FW_SOURCE} CENTRAL APP ${CENTRAL_APP_VERSION} (${CENTRAL_APP_VERSION_SOURCE})") +message("- ${FW_SOURCE} CENTRAL BL ${CENTRAL_BL_VERSION} (${CENTRAL_BL_VERSION_SOURCE})") diff --git a/third-party/win/libusb/include/libusb.h b/third-party/win/libusb/include/libusb.h new file mode 100644 index 0000000000..c562690f9a --- /dev/null +++ b/third-party/win/libusb/include/libusb.h @@ -0,0 +1,2008 @@ +/* + * Public libusb header file + * Copyright © 2001 Johannes Erdfelt + * Copyright © 2007-2008 Daniel Drake + * Copyright © 2012 Pete Batard + * Copyright © 2012 Nathan Hjelm + * For more information, please visit: http://libusb.info + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBUSB_H +#define LIBUSB_H + +#ifdef _MSC_VER +/* on MS environments, the inline keyword is available in C++ only */ +#if !defined(__cplusplus) +#define inline __inline +#endif +/* ssize_t is also not available (copy/paste from MinGW) */ +#ifndef _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED +#undef ssize_t +#ifdef _WIN64 + typedef __int64 ssize_t; +#else + typedef int ssize_t; +#endif /* _WIN64 */ +#endif /* _SSIZE_T_DEFINED */ +#endif /* _MSC_VER */ + +/* stdint.h is not available on older MSVC */ +#if defined(_MSC_VER) && (_MSC_VER < 1600) && (!defined(_STDINT)) && (!defined(_STDINT_H)) +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +#else +#include +#endif + +#if !defined(_WIN32_WCE) +#include +#endif + +#if defined(__linux) || defined(__APPLE__) || defined(__CYGWIN__) || defined(__HAIKU__) +#include +#endif + +#include +#include + +/* 'interface' might be defined as a macro on Windows, so we need to + * undefine it so as not to break the current libusb API, because + * libusb_config_descriptor has an 'interface' member + * As this can be problematic if you include windows.h after libusb.h + * in your sources, we force windows.h to be included first. */ +#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE) +#include +#if defined(interface) +#undef interface +#endif +#if !defined(__CYGWIN__) +#include +#endif +#endif + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) +#define LIBUSB_DEPRECATED_FOR(f) \ + __attribute__((deprecated("Use " #f " instead"))) +#else +#define LIBUSB_DEPRECATED_FOR(f) +#endif /* __GNUC__ */ + +/** \def LIBUSB_CALL + * \ingroup libusb_misc + * libusb's Windows calling convention. + * + * Under Windows, the selection of available compilers and configurations + * means that, unlike other platforms, there is not one true calling + * convention (calling convention: the manner in which parameters are + * passed to functions in the generated assembly code). + * + * Matching the Windows API itself, libusb uses the WINAPI convention (which + * translates to the stdcall convention) and guarantees that the + * library is compiled in this way. The public header file also includes + * appropriate annotations so that your own software will use the right + * convention, even if another convention is being used by default within + * your codebase. + * + * The one consideration that you must apply in your software is to mark + * all functions which you use as libusb callbacks with this LIBUSB_CALL + * annotation, so that they too get compiled for the correct calling + * convention. + * + * On non-Windows operating systems, this macro is defined as nothing. This + * means that you can apply it to your code without worrying about + * cross-platform compatibility. + */ +/* LIBUSB_CALL must be defined on both definition and declaration of libusb + * functions. You'd think that declaration would be enough, but cygwin will + * complain about conflicting types unless both are marked this way. + * The placement of this macro is important too; it must appear after the + * return type, before the function name. See internal documentation for + * API_EXPORTED. + */ +#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE) +#define LIBUSB_CALL WINAPI +#else +#define LIBUSB_CALL +#endif + +/** \def LIBUSB_API_VERSION + * \ingroup libusb_misc + * libusb's API version. + * + * Since version 1.0.13, to help with feature detection, libusb defines + * a LIBUSB_API_VERSION macro that gets increased every time there is a + * significant change to the API, such as the introduction of a new call, + * the definition of a new macro/enum member, or any other element that + * libusb applications may want to detect at compilation time. + * + * The macro is typically used in an application as follows: + * \code + * #if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01001234) + * // Use one of the newer features from the libusb API + * #endif + * \endcode + * + * Internally, LIBUSB_API_VERSION is defined as follows: + * (libusb major << 24) | (libusb minor << 16) | (16 bit incremental) + */ +#define LIBUSB_API_VERSION 0x01000105 + +/* The following is kept for compatibility, but will be deprecated in the future */ +#define LIBUSBX_API_VERSION LIBUSB_API_VERSION + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \ingroup libusb_misc + * Convert a 16-bit value from host-endian to little-endian format. On + * little endian systems, this function does nothing. On big endian systems, + * the bytes are swapped. + * \param x the host-endian value to convert + * \returns the value in little-endian byte order + */ +static inline uint16_t libusb_cpu_to_le16(const uint16_t x) +{ + union { + uint8_t b8[2]; + uint16_t b16; + } _tmp; + _tmp.b8[1] = (uint8_t) (x >> 8); + _tmp.b8[0] = (uint8_t) (x & 0xff); + return _tmp.b16; +} + +/** \def libusb_le16_to_cpu + * \ingroup libusb_misc + * Convert a 16-bit value from little-endian to host-endian format. On + * little endian systems, this function does nothing. On big endian systems, + * the bytes are swapped. + * \param x the little-endian value to convert + * \returns the value in host-endian byte order + */ +#define libusb_le16_to_cpu libusb_cpu_to_le16 + +/* standard USB stuff */ + +/** \ingroup libusb_desc + * Device and/or Interface Class codes */ +enum libusb_class_code { + /** In the context of a \ref libusb_device_descriptor "device descriptor", + * this bDeviceClass value indicates that each interface specifies its + * own class information and all interfaces operate independently. + */ + LIBUSB_CLASS_PER_INTERFACE = 0, + + /** Audio class */ + LIBUSB_CLASS_AUDIO = 1, + + /** Communications class */ + LIBUSB_CLASS_COMM = 2, + + /** Human Interface Device class */ + LIBUSB_CLASS_HID = 3, + + /** Physical */ + LIBUSB_CLASS_PHYSICAL = 5, + + /** Printer class */ + LIBUSB_CLASS_PRINTER = 7, + + /** Image class */ + LIBUSB_CLASS_PTP = 6, /* legacy name from libusb-0.1 usb.h */ + LIBUSB_CLASS_IMAGE = 6, + + /** Mass storage class */ + LIBUSB_CLASS_MASS_STORAGE = 8, + + /** Hub class */ + LIBUSB_CLASS_HUB = 9, + + /** Data class */ + LIBUSB_CLASS_DATA = 10, + + /** Smart Card */ + LIBUSB_CLASS_SMART_CARD = 0x0b, + + /** Content Security */ + LIBUSB_CLASS_CONTENT_SECURITY = 0x0d, + + /** Video */ + LIBUSB_CLASS_VIDEO = 0x0e, + + /** Personal Healthcare */ + LIBUSB_CLASS_PERSONAL_HEALTHCARE = 0x0f, + + /** Diagnostic Device */ + LIBUSB_CLASS_DIAGNOSTIC_DEVICE = 0xdc, + + /** Wireless class */ + LIBUSB_CLASS_WIRELESS = 0xe0, + + /** Application class */ + LIBUSB_CLASS_APPLICATION = 0xfe, + + /** Class is vendor-specific */ + LIBUSB_CLASS_VENDOR_SPEC = 0xff +}; + +/** \ingroup libusb_desc + * Descriptor types as defined by the USB specification. */ +enum libusb_descriptor_type { + /** Device descriptor. See libusb_device_descriptor. */ + LIBUSB_DT_DEVICE = 0x01, + + /** Configuration descriptor. See libusb_config_descriptor. */ + LIBUSB_DT_CONFIG = 0x02, + + /** String descriptor */ + LIBUSB_DT_STRING = 0x03, + + /** Interface descriptor. See libusb_interface_descriptor. */ + LIBUSB_DT_INTERFACE = 0x04, + + /** Endpoint descriptor. See libusb_endpoint_descriptor. */ + LIBUSB_DT_ENDPOINT = 0x05, + + /** BOS descriptor */ + LIBUSB_DT_BOS = 0x0f, + + /** Device Capability descriptor */ + LIBUSB_DT_DEVICE_CAPABILITY = 0x10, + + /** HID descriptor */ + LIBUSB_DT_HID = 0x21, + + /** HID report descriptor */ + LIBUSB_DT_REPORT = 0x22, + + /** Physical descriptor */ + LIBUSB_DT_PHYSICAL = 0x23, + + /** Hub descriptor */ + LIBUSB_DT_HUB = 0x29, + + /** SuperSpeed Hub descriptor */ + LIBUSB_DT_SUPERSPEED_HUB = 0x2a, + + /** SuperSpeed Endpoint Companion descriptor */ + LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30 +}; + +/* Descriptor sizes per descriptor type */ +#define LIBUSB_DT_DEVICE_SIZE 18 +#define LIBUSB_DT_CONFIG_SIZE 9 +#define LIBUSB_DT_INTERFACE_SIZE 9 +#define LIBUSB_DT_ENDPOINT_SIZE 7 +#define LIBUSB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ +#define LIBUSB_DT_HUB_NONVAR_SIZE 7 +#define LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE 6 +#define LIBUSB_DT_BOS_SIZE 5 +#define LIBUSB_DT_DEVICE_CAPABILITY_SIZE 3 + +/* BOS descriptor sizes */ +#define LIBUSB_BT_USB_2_0_EXTENSION_SIZE 7 +#define LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE 10 +#define LIBUSB_BT_CONTAINER_ID_SIZE 20 + +/* We unwrap the BOS => define its max size */ +#define LIBUSB_DT_BOS_MAX_SIZE ((LIBUSB_DT_BOS_SIZE) +\ + (LIBUSB_BT_USB_2_0_EXTENSION_SIZE) +\ + (LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE) +\ + (LIBUSB_BT_CONTAINER_ID_SIZE)) + +#define LIBUSB_ENDPOINT_ADDRESS_MASK 0x0f /* in bEndpointAddress */ +#define LIBUSB_ENDPOINT_DIR_MASK 0x80 + +/** \ingroup libusb_desc + * Endpoint direction. Values for bit 7 of the + * \ref libusb_endpoint_descriptor::bEndpointAddress "endpoint address" scheme. + */ +enum libusb_endpoint_direction { + /** In: device-to-host */ + LIBUSB_ENDPOINT_IN = 0x80, + + /** Out: host-to-device */ + LIBUSB_ENDPOINT_OUT = 0x00 +}; + +#define LIBUSB_TRANSFER_TYPE_MASK 0x03 /* in bmAttributes */ + +/** \ingroup libusb_desc + * Endpoint transfer type. Values for bits 0:1 of the + * \ref libusb_endpoint_descriptor::bmAttributes "endpoint attributes" field. + */ +enum libusb_transfer_type { + /** Control endpoint */ + LIBUSB_TRANSFER_TYPE_CONTROL = 0, + + /** Isochronous endpoint */ + LIBUSB_TRANSFER_TYPE_ISOCHRONOUS = 1, + + /** Bulk endpoint */ + LIBUSB_TRANSFER_TYPE_BULK = 2, + + /** Interrupt endpoint */ + LIBUSB_TRANSFER_TYPE_INTERRUPT = 3, + + /** Stream endpoint */ + LIBUSB_TRANSFER_TYPE_BULK_STREAM = 4, +}; + +/** \ingroup libusb_misc + * Standard requests, as defined in table 9-5 of the USB 3.0 specifications */ +enum libusb_standard_request { + /** Request status of the specific recipient */ + LIBUSB_REQUEST_GET_STATUS = 0x00, + + /** Clear or disable a specific feature */ + LIBUSB_REQUEST_CLEAR_FEATURE = 0x01, + + /* 0x02 is reserved */ + + /** Set or enable a specific feature */ + LIBUSB_REQUEST_SET_FEATURE = 0x03, + + /* 0x04 is reserved */ + + /** Set device address for all future accesses */ + LIBUSB_REQUEST_SET_ADDRESS = 0x05, + + /** Get the specified descriptor */ + LIBUSB_REQUEST_GET_DESCRIPTOR = 0x06, + + /** Used to update existing descriptors or add new descriptors */ + LIBUSB_REQUEST_SET_DESCRIPTOR = 0x07, + + /** Get the current device configuration value */ + LIBUSB_REQUEST_GET_CONFIGURATION = 0x08, + + /** Set device configuration */ + LIBUSB_REQUEST_SET_CONFIGURATION = 0x09, + + /** Return the selected alternate setting for the specified interface */ + LIBUSB_REQUEST_GET_INTERFACE = 0x0A, + + /** Select an alternate interface for the specified interface */ + LIBUSB_REQUEST_SET_INTERFACE = 0x0B, + + /** Set then report an endpoint's synchronization frame */ + LIBUSB_REQUEST_SYNCH_FRAME = 0x0C, + + /** Sets both the U1 and U2 Exit Latency */ + LIBUSB_REQUEST_SET_SEL = 0x30, + + /** Delay from the time a host transmits a packet to the time it is + * received by the device. */ + LIBUSB_SET_ISOCH_DELAY = 0x31, +}; + +/** \ingroup libusb_misc + * Request type bits of the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control + * transfers. */ +enum libusb_request_type { + /** Standard */ + LIBUSB_REQUEST_TYPE_STANDARD = (0x00 << 5), + + /** Class */ + LIBUSB_REQUEST_TYPE_CLASS = (0x01 << 5), + + /** Vendor */ + LIBUSB_REQUEST_TYPE_VENDOR = (0x02 << 5), + + /** Reserved */ + LIBUSB_REQUEST_TYPE_RESERVED = (0x03 << 5) +}; + +/** \ingroup libusb_misc + * Recipient bits of the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control + * transfers. Values 4 through 31 are reserved. */ +enum libusb_request_recipient { + /** Device */ + LIBUSB_RECIPIENT_DEVICE = 0x00, + + /** Interface */ + LIBUSB_RECIPIENT_INTERFACE = 0x01, + + /** Endpoint */ + LIBUSB_RECIPIENT_ENDPOINT = 0x02, + + /** Other */ + LIBUSB_RECIPIENT_OTHER = 0x03, +}; + +#define LIBUSB_ISO_SYNC_TYPE_MASK 0x0C + +/** \ingroup libusb_desc + * Synchronization type for isochronous endpoints. Values for bits 2:3 of the + * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in + * libusb_endpoint_descriptor. + */ +enum libusb_iso_sync_type { + /** No synchronization */ + LIBUSB_ISO_SYNC_TYPE_NONE = 0, + + /** Asynchronous */ + LIBUSB_ISO_SYNC_TYPE_ASYNC = 1, + + /** Adaptive */ + LIBUSB_ISO_SYNC_TYPE_ADAPTIVE = 2, + + /** Synchronous */ + LIBUSB_ISO_SYNC_TYPE_SYNC = 3 +}; + +#define LIBUSB_ISO_USAGE_TYPE_MASK 0x30 + +/** \ingroup libusb_desc + * Usage type for isochronous endpoints. Values for bits 4:5 of the + * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in + * libusb_endpoint_descriptor. + */ +enum libusb_iso_usage_type { + /** Data endpoint */ + LIBUSB_ISO_USAGE_TYPE_DATA = 0, + + /** Feedback endpoint */ + LIBUSB_ISO_USAGE_TYPE_FEEDBACK = 1, + + /** Implicit feedback Data endpoint */ + LIBUSB_ISO_USAGE_TYPE_IMPLICIT = 2, +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB device descriptor. This + * descriptor is documented in section 9.6.1 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_device_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE LIBUSB_DT_DEVICE in this + * context. */ + uint8_t bDescriptorType; + + /** USB specification release number in binary-coded decimal. A value of + * 0x0200 indicates USB 2.0, 0x0110 indicates USB 1.1, etc. */ + uint16_t bcdUSB; + + /** USB-IF class code for the device. See \ref libusb_class_code. */ + uint8_t bDeviceClass; + + /** USB-IF subclass code for the device, qualified by the bDeviceClass + * value */ + uint8_t bDeviceSubClass; + + /** USB-IF protocol code for the device, qualified by the bDeviceClass and + * bDeviceSubClass values */ + uint8_t bDeviceProtocol; + + /** Maximum packet size for endpoint 0 */ + uint8_t bMaxPacketSize0; + + /** USB-IF vendor ID */ + uint16_t idVendor; + + /** USB-IF product ID */ + uint16_t idProduct; + + /** Device release number in binary-coded decimal */ + uint16_t bcdDevice; + + /** Index of string descriptor describing manufacturer */ + uint8_t iManufacturer; + + /** Index of string descriptor describing product */ + uint8_t iProduct; + + /** Index of string descriptor containing device serial number */ + uint8_t iSerialNumber; + + /** Number of possible configurations */ + uint8_t bNumConfigurations; +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB endpoint descriptor. This + * descriptor is documented in section 9.6.6 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_endpoint_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_ENDPOINT LIBUSB_DT_ENDPOINT in + * this context. */ + uint8_t bDescriptorType; + + /** The address of the endpoint described by this descriptor. Bits 0:3 are + * the endpoint number. Bits 4:6 are reserved. Bit 7 indicates direction, + * see \ref libusb_endpoint_direction. + */ + uint8_t bEndpointAddress; + + /** Attributes which apply to the endpoint when it is configured using + * the bConfigurationValue. Bits 0:1 determine the transfer type and + * correspond to \ref libusb_transfer_type. Bits 2:3 are only used for + * isochronous endpoints and correspond to \ref libusb_iso_sync_type. + * Bits 4:5 are also only used for isochronous endpoints and correspond to + * \ref libusb_iso_usage_type. Bits 6:7 are reserved. + */ + uint8_t bmAttributes; + + /** Maximum packet size this endpoint is capable of sending/receiving. */ + uint16_t wMaxPacketSize; + + /** Interval for polling endpoint for data transfers. */ + uint8_t bInterval; + + /** For audio devices only: the rate at which synchronization feedback + * is provided. */ + uint8_t bRefresh; + + /** For audio devices only: the address if the synch endpoint */ + uint8_t bSynchAddress; + + /** Extra descriptors. If libusb encounters unknown endpoint descriptors, + * it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. */ + int extra_length; +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB interface descriptor. This + * descriptor is documented in section 9.6.5 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_interface_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_INTERFACE LIBUSB_DT_INTERFACE + * in this context. */ + uint8_t bDescriptorType; + + /** Number of this interface */ + uint8_t bInterfaceNumber; + + /** Value used to select this alternate setting for this interface */ + uint8_t bAlternateSetting; + + /** Number of endpoints used by this interface (excluding the control + * endpoint). */ + uint8_t bNumEndpoints; + + /** USB-IF class code for this interface. See \ref libusb_class_code. */ + uint8_t bInterfaceClass; + + /** USB-IF subclass code for this interface, qualified by the + * bInterfaceClass value */ + uint8_t bInterfaceSubClass; + + /** USB-IF protocol code for this interface, qualified by the + * bInterfaceClass and bInterfaceSubClass values */ + uint8_t bInterfaceProtocol; + + /** Index of string descriptor describing this interface */ + uint8_t iInterface; + + /** Array of endpoint descriptors. This length of this array is determined + * by the bNumEndpoints field. */ + const struct libusb_endpoint_descriptor *endpoint; + + /** Extra descriptors. If libusb encounters unknown interface descriptors, + * it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. */ + int extra_length; +}; + +/** \ingroup libusb_desc + * A collection of alternate settings for a particular USB interface. + */ +struct libusb_interface { + /** Array of interface descriptors. The length of this array is determined + * by the num_altsetting field. */ + const struct libusb_interface_descriptor *altsetting; + + /** The number of alternate settings that belong to this interface */ + int num_altsetting; +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB configuration descriptor. This + * descriptor is documented in section 9.6.3 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_config_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_CONFIG LIBUSB_DT_CONFIG + * in this context. */ + uint8_t bDescriptorType; + + /** Total length of data returned for this configuration */ + uint16_t wTotalLength; + + /** Number of interfaces supported by this configuration */ + uint8_t bNumInterfaces; + + /** Identifier value for this configuration */ + uint8_t bConfigurationValue; + + /** Index of string descriptor describing this configuration */ + uint8_t iConfiguration; + + /** Configuration characteristics */ + uint8_t bmAttributes; + + /** Maximum power consumption of the USB device from this bus in this + * configuration when the device is fully operation. Expressed in units + * of 2 mA when the device is operating in high-speed mode and in units + * of 8 mA when the device is operating in super-speed mode. */ + uint8_t MaxPower; + + /** Array of interfaces supported by this configuration. The length of + * this array is determined by the bNumInterfaces field. */ + const struct libusb_interface *interface; + + /** Extra descriptors. If libusb encounters unknown configuration + * descriptors, it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. */ + int extra_length; +}; + +/** \ingroup libusb_desc + * A structure representing the superspeed endpoint companion + * descriptor. This descriptor is documented in section 9.6.7 of + * the USB 3.0 specification. All multiple-byte fields are represented in + * host-endian format. + */ +struct libusb_ss_endpoint_companion_descriptor { + + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_SS_ENDPOINT_COMPANION in + * this context. */ + uint8_t bDescriptorType; + + + /** The maximum number of packets the endpoint can send or + * receive as part of a burst. */ + uint8_t bMaxBurst; + + /** In bulk EP: bits 4:0 represents the maximum number of + * streams the EP supports. In isochronous EP: bits 1:0 + * represents the Mult - a zero based value that determines + * the maximum number of packets within a service interval */ + uint8_t bmAttributes; + + /** The total number of bytes this EP will transfer every + * service interval. valid only for periodic EPs. */ + uint16_t wBytesPerInterval; +}; + +/** \ingroup libusb_desc + * A generic representation of a BOS Device Capability descriptor. It is + * advised to check bDevCapabilityType and call the matching + * libusb_get_*_descriptor function to get a structure fully matching the type. + */ +struct libusb_bos_dev_capability_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + /** Device Capability type */ + uint8_t bDevCapabilityType; + /** Device Capability data (bLength - 3 bytes) */ + uint8_t dev_capability_data +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + [] /* valid C99 code */ +#else + [0] /* non-standard, but usually working code */ +#endif + ; +}; + +/** \ingroup libusb_desc + * A structure representing the Binary Device Object Store (BOS) descriptor. + * This descriptor is documented in section 9.6.2 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_bos_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_BOS LIBUSB_DT_BOS + * in this context. */ + uint8_t bDescriptorType; + + /** Length of this descriptor and all of its sub descriptors */ + uint16_t wTotalLength; + + /** The number of separate device capability descriptors in + * the BOS */ + uint8_t bNumDeviceCaps; + + /** bNumDeviceCap Device Capability Descriptors */ + struct libusb_bos_dev_capability_descriptor *dev_capability +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + [] /* valid C99 code */ +#else + [0] /* non-standard, but usually working code */ +#endif + ; +}; + +/** \ingroup libusb_desc + * A structure representing the USB 2.0 Extension descriptor + * This descriptor is documented in section 9.6.2.1 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_usb_2_0_extension_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_capability_type::LIBUSB_BT_USB_2_0_EXTENSION + * LIBUSB_BT_USB_2_0_EXTENSION in this context. */ + uint8_t bDevCapabilityType; + + /** Bitmap encoding of supported device level features. + * A value of one in a bit location indicates a feature is + * supported; a value of zero indicates it is not supported. + * See \ref libusb_usb_2_0_extension_attributes. */ + uint32_t bmAttributes; +}; + +/** \ingroup libusb_desc + * A structure representing the SuperSpeed USB Device Capability descriptor + * This descriptor is documented in section 9.6.2.2 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_ss_usb_device_capability_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_capability_type::LIBUSB_BT_SS_USB_DEVICE_CAPABILITY + * LIBUSB_BT_SS_USB_DEVICE_CAPABILITY in this context. */ + uint8_t bDevCapabilityType; + + /** Bitmap encoding of supported device level features. + * A value of one in a bit location indicates a feature is + * supported; a value of zero indicates it is not supported. + * See \ref libusb_ss_usb_device_capability_attributes. */ + uint8_t bmAttributes; + + /** Bitmap encoding of the speed supported by this device when + * operating in SuperSpeed mode. See \ref libusb_supported_speed. */ + uint16_t wSpeedSupported; + + /** The lowest speed at which all the functionality supported + * by the device is available to the user. For example if the + * device supports all its functionality when connected at + * full speed and above then it sets this value to 1. */ + uint8_t bFunctionalitySupport; + + /** U1 Device Exit Latency. */ + uint8_t bU1DevExitLat; + + /** U2 Device Exit Latency. */ + uint16_t bU2DevExitLat; +}; + +/** \ingroup libusb_desc + * A structure representing the Container ID descriptor. + * This descriptor is documented in section 9.6.2.3 of the USB 3.0 specification. + * All multiple-byte fields, except UUIDs, are represented in host-endian format. + */ +struct libusb_container_id_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_capability_type::LIBUSB_BT_CONTAINER_ID + * LIBUSB_BT_CONTAINER_ID in this context. */ + uint8_t bDevCapabilityType; + + /** Reserved field */ + uint8_t bReserved; + + /** 128 bit UUID */ + uint8_t ContainerID[16]; +}; + +/** \ingroup libusb_asyncio + * Setup packet for control transfers. */ +struct libusb_control_setup { + /** Request type. Bits 0:4 determine recipient, see + * \ref libusb_request_recipient. Bits 5:6 determine type, see + * \ref libusb_request_type. Bit 7 determines data transfer direction, see + * \ref libusb_endpoint_direction. + */ + uint8_t bmRequestType; + + /** Request. If the type bits of bmRequestType are equal to + * \ref libusb_request_type::LIBUSB_REQUEST_TYPE_STANDARD + * "LIBUSB_REQUEST_TYPE_STANDARD" then this field refers to + * \ref libusb_standard_request. For other cases, use of this field is + * application-specific. */ + uint8_t bRequest; + + /** Value. Varies according to request */ + uint16_t wValue; + + /** Index. Varies according to request, typically used to pass an index + * or offset */ + uint16_t wIndex; + + /** Number of bytes to transfer */ + uint16_t wLength; +}; + +#define LIBUSB_CONTROL_SETUP_SIZE (sizeof(struct libusb_control_setup)) + +/* libusb */ + +struct libusb_context; +struct libusb_device; +struct libusb_device_handle; + +/** \ingroup libusb_lib + * Structure providing the version of the libusb runtime + */ +struct libusb_version { + /** Library major version. */ + const uint16_t major; + + /** Library minor version. */ + const uint16_t minor; + + /** Library micro version. */ + const uint16_t micro; + + /** Library nano version. */ + const uint16_t nano; + + /** Library release candidate suffix string, e.g. "-rc4". */ + const char *rc; + + /** For ABI compatibility only. */ + const char* describe; +}; + +/** \ingroup libusb_lib + * Structure representing a libusb session. The concept of individual libusb + * sessions allows for your program to use two libraries (or dynamically + * load two modules) which both independently use libusb. This will prevent + * interference between the individual libusb users - for example + * libusb_set_debug() will not affect the other user of the library, and + * libusb_exit() will not destroy resources that the other user is still + * using. + * + * Sessions are created by libusb_init() and destroyed through libusb_exit(). + * If your application is guaranteed to only ever include a single libusb + * user (i.e. you), you do not have to worry about contexts: pass NULL in + * every function call where a context is required. The default context + * will be used. + * + * For more information, see \ref libusb_contexts. + */ +typedef struct libusb_context libusb_context; + +/** \ingroup libusb_dev + * Structure representing a USB device detected on the system. This is an + * opaque type for which you are only ever provided with a pointer, usually + * originating from libusb_get_device_list(). + * + * Certain operations can be performed on a device, but in order to do any + * I/O you will have to first obtain a device handle using libusb_open(). + * + * Devices are reference counted with libusb_ref_device() and + * libusb_unref_device(), and are freed when the reference count reaches 0. + * New devices presented by libusb_get_device_list() have a reference count of + * 1, and libusb_free_device_list() can optionally decrease the reference count + * on all devices in the list. libusb_open() adds another reference which is + * later destroyed by libusb_close(). + */ +typedef struct libusb_device libusb_device; + + +/** \ingroup libusb_dev + * Structure representing a handle on a USB device. This is an opaque type for + * which you are only ever provided with a pointer, usually originating from + * libusb_open(). + * + * A device handle is used to perform I/O and other operations. When finished + * with a device handle, you should call libusb_close(). + */ +typedef struct libusb_device_handle libusb_device_handle; + +/** \ingroup libusb_dev + * Speed codes. Indicates the speed at which the device is operating. + */ +enum libusb_speed { + /** The OS doesn't report or know the device speed. */ + LIBUSB_SPEED_UNKNOWN = 0, + + /** The device is operating at low speed (1.5MBit/s). */ + LIBUSB_SPEED_LOW = 1, + + /** The device is operating at full speed (12MBit/s). */ + LIBUSB_SPEED_FULL = 2, + + /** The device is operating at high speed (480MBit/s). */ + LIBUSB_SPEED_HIGH = 3, + + /** The device is operating at super speed (5000MBit/s). */ + LIBUSB_SPEED_SUPER = 4, +}; + +/** \ingroup libusb_dev + * Supported speeds (wSpeedSupported) bitfield. Indicates what + * speeds the device supports. + */ +enum libusb_supported_speed { + /** Low speed operation supported (1.5MBit/s). */ + LIBUSB_LOW_SPEED_OPERATION = 1, + + /** Full speed operation supported (12MBit/s). */ + LIBUSB_FULL_SPEED_OPERATION = 2, + + /** High speed operation supported (480MBit/s). */ + LIBUSB_HIGH_SPEED_OPERATION = 4, + + /** Superspeed operation supported (5000MBit/s). */ + LIBUSB_SUPER_SPEED_OPERATION = 8, +}; + +/** \ingroup libusb_dev + * Masks for the bits of the + * \ref libusb_usb_2_0_extension_descriptor::bmAttributes "bmAttributes" field + * of the USB 2.0 Extension descriptor. + */ +enum libusb_usb_2_0_extension_attributes { + /** Supports Link Power Management (LPM) */ + LIBUSB_BM_LPM_SUPPORT = 2, +}; + +/** \ingroup libusb_dev + * Masks for the bits of the + * \ref libusb_ss_usb_device_capability_descriptor::bmAttributes "bmAttributes" field + * field of the SuperSpeed USB Device Capability descriptor. + */ +enum libusb_ss_usb_device_capability_attributes { + /** Supports Latency Tolerance Messages (LTM) */ + LIBUSB_BM_LTM_SUPPORT = 2, +}; + +/** \ingroup libusb_dev + * USB capability types + */ +enum libusb_bos_type { + /** Wireless USB device capability */ + LIBUSB_BT_WIRELESS_USB_DEVICE_CAPABILITY = 1, + + /** USB 2.0 extensions */ + LIBUSB_BT_USB_2_0_EXTENSION = 2, + + /** SuperSpeed USB device capability */ + LIBUSB_BT_SS_USB_DEVICE_CAPABILITY = 3, + + /** Container ID type */ + LIBUSB_BT_CONTAINER_ID = 4, +}; + +/** \ingroup libusb_misc + * Error codes. Most libusb functions return 0 on success or one of these + * codes on failure. + * You can call libusb_error_name() to retrieve a string representation of an + * error code or libusb_strerror() to get an end-user suitable description of + * an error code. + */ +enum libusb_error { + /** Success (no error) */ + LIBUSB_SUCCESS = 0, + + /** Input/output error */ + LIBUSB_ERROR_IO = -1, + + /** Invalid parameter */ + LIBUSB_ERROR_INVALID_PARAM = -2, + + /** Access denied (insufficient permissions) */ + LIBUSB_ERROR_ACCESS = -3, + + /** No such device (it may have been disconnected) */ + LIBUSB_ERROR_NO_DEVICE = -4, + + /** Entity not found */ + LIBUSB_ERROR_NOT_FOUND = -5, + + /** Resource busy */ + LIBUSB_ERROR_BUSY = -6, + + /** Operation timed out */ + LIBUSB_ERROR_TIMEOUT = -7, + + /** Overflow */ + LIBUSB_ERROR_OVERFLOW = -8, + + /** Pipe error */ + LIBUSB_ERROR_PIPE = -9, + + /** System call interrupted (perhaps due to signal) */ + LIBUSB_ERROR_INTERRUPTED = -10, + + /** Insufficient memory */ + LIBUSB_ERROR_NO_MEM = -11, + + /** Operation not supported or unimplemented on this platform */ + LIBUSB_ERROR_NOT_SUPPORTED = -12, + + /* NB: Remember to update LIBUSB_ERROR_COUNT below as well as the + message strings in strerror.c when adding new error codes here. */ + + /** Other error */ + LIBUSB_ERROR_OTHER = -99, +}; + +/* Total number of error codes in enum libusb_error */ +#define LIBUSB_ERROR_COUNT 14 + +/** \ingroup libusb_asyncio + * Transfer status codes */ +enum libusb_transfer_status { + /** Transfer completed without error. Note that this does not indicate + * that the entire amount of requested data was transferred. */ + LIBUSB_TRANSFER_COMPLETED, + + /** Transfer failed */ + LIBUSB_TRANSFER_ERROR, + + /** Transfer timed out */ + LIBUSB_TRANSFER_TIMED_OUT, + + /** Transfer was cancelled */ + LIBUSB_TRANSFER_CANCELLED, + + /** For bulk/interrupt endpoints: halt condition detected (endpoint + * stalled). For control endpoints: control request not supported. */ + LIBUSB_TRANSFER_STALL, + + /** Device was disconnected */ + LIBUSB_TRANSFER_NO_DEVICE, + + /** Device sent more data than requested */ + LIBUSB_TRANSFER_OVERFLOW, + + /* NB! Remember to update libusb_error_name() + when adding new status codes here. */ +}; + +/** \ingroup libusb_asyncio + * libusb_transfer.flags values */ +enum libusb_transfer_flags { + /** Report short frames as errors */ + LIBUSB_TRANSFER_SHORT_NOT_OK = 1<<0, + + /** Automatically free() transfer buffer during libusb_free_transfer(). + * Note that buffers allocated with libusb_dev_mem_alloc() should not + * be attempted freed in this way, since free() is not an appropriate + * way to release such memory. */ + LIBUSB_TRANSFER_FREE_BUFFER = 1<<1, + + /** Automatically call libusb_free_transfer() after callback returns. + * If this flag is set, it is illegal to call libusb_free_transfer() + * from your transfer callback, as this will result in a double-free + * when this flag is acted upon. */ + LIBUSB_TRANSFER_FREE_TRANSFER = 1<<2, + + /** Terminate transfers that are a multiple of the endpoint's + * wMaxPacketSize with an extra zero length packet. This is useful + * when a device protocol mandates that each logical request is + * terminated by an incomplete packet (i.e. the logical requests are + * not separated by other means). + * + * This flag only affects host-to-device transfers to bulk and interrupt + * endpoints. In other situations, it is ignored. + * + * This flag only affects transfers with a length that is a multiple of + * the endpoint's wMaxPacketSize. On transfers of other lengths, this + * flag has no effect. Therefore, if you are working with a device that + * needs a ZLP whenever the end of the logical request falls on a packet + * boundary, then it is sensible to set this flag on every + * transfer (you do not have to worry about only setting it on transfers + * that end on the boundary). + * + * This flag is currently only supported on Linux. + * On other systems, libusb_submit_transfer() will return + * LIBUSB_ERROR_NOT_SUPPORTED for every transfer where this flag is set. + * + * Available since libusb-1.0.9. + */ + LIBUSB_TRANSFER_ADD_ZERO_PACKET = 1 << 3, +}; + +/** \ingroup libusb_asyncio + * Isochronous packet descriptor. */ +struct libusb_iso_packet_descriptor { + /** Length of data to request in this packet */ + unsigned int length; + + /** Amount of data that was actually transferred */ + unsigned int actual_length; + + /** Status code for this packet */ + enum libusb_transfer_status status; +}; + +struct libusb_transfer; + +/** \ingroup libusb_asyncio + * Asynchronous transfer callback function type. When submitting asynchronous + * transfers, you pass a pointer to a callback function of this type via the + * \ref libusb_transfer::callback "callback" member of the libusb_transfer + * structure. libusb will call this function later, when the transfer has + * completed or failed. See \ref libusb_asyncio for more information. + * \param transfer The libusb_transfer struct the callback function is being + * notified about. + */ +typedef void (LIBUSB_CALL *libusb_transfer_cb_fn)(struct libusb_transfer *transfer); + +/** \ingroup libusb_asyncio + * The generic USB transfer structure. The user populates this structure and + * then submits it in order to request a transfer. After the transfer has + * completed, the library populates the transfer with the results and passes + * it back to the user. + */ +struct libusb_transfer { + /** Handle of the device that this transfer will be submitted to */ + libusb_device_handle *dev_handle; + + /** A bitwise OR combination of \ref libusb_transfer_flags. */ + uint8_t flags; + + /** Address of the endpoint where this transfer will be sent. */ + unsigned char endpoint; + + /** Type of the endpoint from \ref libusb_transfer_type */ + unsigned char type; + + /** Timeout for this transfer in milliseconds. A value of 0 indicates no + * timeout. */ + unsigned int timeout; + + /** The status of the transfer. Read-only, and only for use within + * transfer callback function. + * + * If this is an isochronous transfer, this field may read COMPLETED even + * if there were errors in the frames. Use the + * \ref libusb_iso_packet_descriptor::status "status" field in each packet + * to determine if errors occurred. */ + enum libusb_transfer_status status; + + /** Length of the data buffer */ + int length; + + /** Actual length of data that was transferred. Read-only, and only for + * use within transfer callback function. Not valid for isochronous + * endpoint transfers. */ + int actual_length; + + /** Callback function. This will be invoked when the transfer completes, + * fails, or is cancelled. */ + libusb_transfer_cb_fn callback; + + /** User context data to pass to the callback function. */ + void *user_data; + + /** Data buffer */ + unsigned char *buffer; + + /** Number of isochronous packets. Only used for I/O with isochronous + * endpoints. */ + int num_iso_packets; + + /** Isochronous packet descriptors, for isochronous transfers only. */ + struct libusb_iso_packet_descriptor iso_packet_desc +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + [] /* valid C99 code */ +#else + [0] /* non-standard, but usually working code */ +#endif + ; +}; + +/** \ingroup libusb_misc + * Capabilities supported by an instance of libusb on the current running + * platform. Test if the loaded library supports a given capability by calling + * \ref libusb_has_capability(). + */ +enum libusb_capability { + /** The libusb_has_capability() API is available. */ + LIBUSB_CAP_HAS_CAPABILITY = 0x0000, + /** Hotplug support is available on this platform. */ + LIBUSB_CAP_HAS_HOTPLUG = 0x0001, + /** The library can access HID devices without requiring user intervention. + * Note that before being able to actually access an HID device, you may + * still have to call additional libusb functions such as + * \ref libusb_detach_kernel_driver(). */ + LIBUSB_CAP_HAS_HID_ACCESS = 0x0100, + /** The library supports detaching of the default USB driver, using + * \ref libusb_detach_kernel_driver(), if one is set by the OS kernel */ + LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER = 0x0101 +}; + +/** \ingroup libusb_lib + * Log message levels. + * - LIBUSB_LOG_LEVEL_NONE (0) : no messages ever printed by the library (default) + * - LIBUSB_LOG_LEVEL_ERROR (1) : error messages are printed to stderr + * - LIBUSB_LOG_LEVEL_WARNING (2) : warning and error messages are printed to stderr + * - LIBUSB_LOG_LEVEL_INFO (3) : informational messages are printed to stdout, warning + * and error messages are printed to stderr + * - LIBUSB_LOG_LEVEL_DEBUG (4) : debug and informational messages are printed to stdout, + * warnings and errors to stderr + */ +enum libusb_log_level { + LIBUSB_LOG_LEVEL_NONE = 0, + LIBUSB_LOG_LEVEL_ERROR, + LIBUSB_LOG_LEVEL_WARNING, + LIBUSB_LOG_LEVEL_INFO, + LIBUSB_LOG_LEVEL_DEBUG, +}; + +int LIBUSB_CALL libusb_init(libusb_context **ctx); +void LIBUSB_CALL libusb_exit(libusb_context *ctx); +void LIBUSB_CALL libusb_set_debug(libusb_context *ctx, int level); +const struct libusb_version * LIBUSB_CALL libusb_get_version(void); +int LIBUSB_CALL libusb_has_capability(uint32_t capability); +const char * LIBUSB_CALL libusb_error_name(int errcode); +int LIBUSB_CALL libusb_setlocale(const char *locale); +const char * LIBUSB_CALL libusb_strerror(enum libusb_error errcode); + +ssize_t LIBUSB_CALL libusb_get_device_list(libusb_context *ctx, + libusb_device ***list); +void LIBUSB_CALL libusb_free_device_list(libusb_device **list, + int unref_devices); +libusb_device * LIBUSB_CALL libusb_ref_device(libusb_device *dev); +void LIBUSB_CALL libusb_unref_device(libusb_device *dev); + +int LIBUSB_CALL libusb_get_configuration(libusb_device_handle *dev, + int *config); +int LIBUSB_CALL libusb_get_device_descriptor(libusb_device *dev, + struct libusb_device_descriptor *desc); +int LIBUSB_CALL libusb_get_active_config_descriptor(libusb_device *dev, + struct libusb_config_descriptor **config); +int LIBUSB_CALL libusb_get_config_descriptor(libusb_device *dev, + uint8_t config_index, struct libusb_config_descriptor **config); +int LIBUSB_CALL libusb_get_config_descriptor_by_value(libusb_device *dev, + uint8_t bConfigurationValue, struct libusb_config_descriptor **config); +void LIBUSB_CALL libusb_free_config_descriptor( + struct libusb_config_descriptor *config); +int LIBUSB_CALL libusb_get_ss_endpoint_companion_descriptor( + struct libusb_context *ctx, + const struct libusb_endpoint_descriptor *endpoint, + struct libusb_ss_endpoint_companion_descriptor **ep_comp); +void LIBUSB_CALL libusb_free_ss_endpoint_companion_descriptor( + struct libusb_ss_endpoint_companion_descriptor *ep_comp); +int LIBUSB_CALL libusb_get_bos_descriptor(libusb_device_handle *dev_handle, + struct libusb_bos_descriptor **bos); +void LIBUSB_CALL libusb_free_bos_descriptor(struct libusb_bos_descriptor *bos); +int LIBUSB_CALL libusb_get_usb_2_0_extension_descriptor( + struct libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_usb_2_0_extension_descriptor **usb_2_0_extension); +void LIBUSB_CALL libusb_free_usb_2_0_extension_descriptor( + struct libusb_usb_2_0_extension_descriptor *usb_2_0_extension); +int LIBUSB_CALL libusb_get_ss_usb_device_capability_descriptor( + struct libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_ss_usb_device_capability_descriptor **ss_usb_device_cap); +void LIBUSB_CALL libusb_free_ss_usb_device_capability_descriptor( + struct libusb_ss_usb_device_capability_descriptor *ss_usb_device_cap); +int LIBUSB_CALL libusb_get_container_id_descriptor(struct libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_container_id_descriptor **container_id); +void LIBUSB_CALL libusb_free_container_id_descriptor( + struct libusb_container_id_descriptor *container_id); +uint8_t LIBUSB_CALL libusb_get_bus_number(libusb_device *dev); +uint8_t LIBUSB_CALL libusb_get_port_number(libusb_device *dev); +int LIBUSB_CALL libusb_get_port_numbers(libusb_device *dev, uint8_t* port_numbers, int port_numbers_len); +LIBUSB_DEPRECATED_FOR(libusb_get_port_numbers) +int LIBUSB_CALL libusb_get_port_path(libusb_context *ctx, libusb_device *dev, uint8_t* path, uint8_t path_length); +libusb_device * LIBUSB_CALL libusb_get_parent(libusb_device *dev); +uint8_t LIBUSB_CALL libusb_get_device_address(libusb_device *dev); +int LIBUSB_CALL libusb_get_device_speed(libusb_device *dev); +int LIBUSB_CALL libusb_get_max_packet_size(libusb_device *dev, + unsigned char endpoint); +int LIBUSB_CALL libusb_get_max_iso_packet_size(libusb_device *dev, + unsigned char endpoint); + +int LIBUSB_CALL libusb_open(libusb_device *dev, libusb_device_handle **dev_handle); +void LIBUSB_CALL libusb_close(libusb_device_handle *dev_handle); +libusb_device * LIBUSB_CALL libusb_get_device(libusb_device_handle *dev_handle); + +int LIBUSB_CALL libusb_set_configuration(libusb_device_handle *dev_handle, + int configuration); +int LIBUSB_CALL libusb_claim_interface(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_release_interface(libusb_device_handle *dev_handle, + int interface_number); + +libusb_device_handle * LIBUSB_CALL libusb_open_device_with_vid_pid( + libusb_context *ctx, uint16_t vendor_id, uint16_t product_id); + +int LIBUSB_CALL libusb_set_interface_alt_setting(libusb_device_handle *dev_handle, + int interface_number, int alternate_setting); +int LIBUSB_CALL libusb_clear_halt(libusb_device_handle *dev_handle, + unsigned char endpoint); +int LIBUSB_CALL libusb_reset_device(libusb_device_handle *dev_handle); + +int LIBUSB_CALL libusb_alloc_streams(libusb_device_handle *dev_handle, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints); +int LIBUSB_CALL libusb_free_streams(libusb_device_handle *dev_handle, + unsigned char *endpoints, int num_endpoints); + +unsigned char * LIBUSB_CALL libusb_dev_mem_alloc(libusb_device_handle *dev_handle, + size_t length); +int LIBUSB_CALL libusb_dev_mem_free(libusb_device_handle *dev_handle, + unsigned char *buffer, size_t length); + +int LIBUSB_CALL libusb_kernel_driver_active(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_detach_kernel_driver(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_attach_kernel_driver(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_set_auto_detach_kernel_driver( + libusb_device_handle *dev_handle, int enable); + +/* async I/O */ + +/** \ingroup libusb_asyncio + * Get the data section of a control transfer. This convenience function is here + * to remind you that the data does not start until 8 bytes into the actual + * buffer, as the setup packet comes first. + * + * Calling this function only makes sense from a transfer callback function, + * or situations where you have already allocated a suitably sized buffer at + * transfer->buffer. + * + * \param transfer a transfer + * \returns pointer to the first byte of the data section + */ +static inline unsigned char *libusb_control_transfer_get_data( + struct libusb_transfer *transfer) +{ + return transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; +} + +/** \ingroup libusb_asyncio + * Get the control setup packet of a control transfer. This convenience + * function is here to remind you that the control setup occupies the first + * 8 bytes of the transfer data buffer. + * + * Calling this function only makes sense from a transfer callback function, + * or situations where you have already allocated a suitably sized buffer at + * transfer->buffer. + * + * \param transfer a transfer + * \returns a casted pointer to the start of the transfer data buffer + */ +static inline struct libusb_control_setup *libusb_control_transfer_get_setup( + struct libusb_transfer *transfer) +{ + return (struct libusb_control_setup *)(void *) transfer->buffer; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the setup packet (first 8 bytes of the data + * buffer) for a control transfer. The wIndex, wValue and wLength values should + * be given in host-endian byte order. + * + * \param buffer buffer to output the setup packet into + * This pointer must be aligned to at least 2 bytes boundary. + * \param bmRequestType see the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field of + * \ref libusb_control_setup + * \param bRequest see the + * \ref libusb_control_setup::bRequest "bRequest" field of + * \ref libusb_control_setup + * \param wValue see the + * \ref libusb_control_setup::wValue "wValue" field of + * \ref libusb_control_setup + * \param wIndex see the + * \ref libusb_control_setup::wIndex "wIndex" field of + * \ref libusb_control_setup + * \param wLength see the + * \ref libusb_control_setup::wLength "wLength" field of + * \ref libusb_control_setup + */ +static inline void libusb_fill_control_setup(unsigned char *buffer, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + uint16_t wLength) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *) buffer; + setup->bmRequestType = bmRequestType; + setup->bRequest = bRequest; + setup->wValue = libusb_cpu_to_le16(wValue); + setup->wIndex = libusb_cpu_to_le16(wIndex); + setup->wLength = libusb_cpu_to_le16(wLength); +} + +struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer(int iso_packets); +int LIBUSB_CALL libusb_submit_transfer(struct libusb_transfer *transfer); +int LIBUSB_CALL libusb_cancel_transfer(struct libusb_transfer *transfer); +void LIBUSB_CALL libusb_free_transfer(struct libusb_transfer *transfer); +void LIBUSB_CALL libusb_transfer_set_stream_id( + struct libusb_transfer *transfer, uint32_t stream_id); +uint32_t LIBUSB_CALL libusb_transfer_get_stream_id( + struct libusb_transfer *transfer); + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a control transfer. + * + * If you pass a transfer buffer to this function, the first 8 bytes will + * be interpreted as a control setup packet, and the wLength field will be + * used to automatically populate the \ref libusb_transfer::length "length" + * field of the transfer. Therefore the recommended approach is: + * -# Allocate a suitably sized data buffer (including space for control setup) + * -# Call libusb_fill_control_setup() + * -# If this is a host-to-device transfer with a data stage, put the data + * in place after the setup packet + * -# Call this function + * -# Call libusb_submit_transfer() + * + * It is also legal to pass a NULL buffer to this function, in which case this + * function will not attempt to populate the length field. Remember that you + * must then populate the buffer and length fields later. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param buffer data buffer. If provided, this function will interpret the + * first 8 bytes as a setup packet and infer the transfer length from that. + * This pointer must be aligned to at least 2 bytes boundary. + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_control_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data, + unsigned int timeout) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *) buffer; + transfer->dev_handle = dev_handle; + transfer->endpoint = 0; + transfer->type = LIBUSB_TRANSFER_TYPE_CONTROL; + transfer->timeout = timeout; + transfer->buffer = buffer; + if (setup) + transfer->length = (int) (LIBUSB_CONTROL_SETUP_SIZE + + libusb_le16_to_cpu(setup->wLength)); + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a bulk transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_bulk_transfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *buffer, int length, libusb_transfer_cb_fn callback, + void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_BULK; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a bulk transfer using bulk streams. + * + * Since version 1.0.19, \ref LIBUSB_API_VERSION >= 0x01000103 + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param stream_id bulk stream id for this transfer + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_bulk_stream_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char endpoint, uint32_t stream_id, + unsigned char *buffer, int length, libusb_transfer_cb_fn callback, + void *user_data, unsigned int timeout) +{ + libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, + length, callback, user_data, timeout); + transfer->type = LIBUSB_TRANSFER_TYPE_BULK_STREAM; + libusb_transfer_set_stream_id(transfer, stream_id); +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for an interrupt transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_interrupt_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *buffer, int length, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for an isochronous transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param num_iso_packets the number of isochronous packets + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_iso_transfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *buffer, int length, int num_iso_packets, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->num_iso_packets = num_iso_packets; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Convenience function to set the length of all packets in an isochronous + * transfer, based on the num_iso_packets field in the transfer structure. + * + * \param transfer a transfer + * \param length the length to set in each isochronous packet descriptor + * \see libusb_get_max_packet_size() + */ +static inline void libusb_set_iso_packet_lengths( + struct libusb_transfer *transfer, unsigned int length) +{ + int i; + for (i = 0; i < transfer->num_iso_packets; i++) + transfer->iso_packet_desc[i].length = length; +} + +/** \ingroup libusb_asyncio + * Convenience function to locate the position of an isochronous packet + * within the buffer of an isochronous transfer. + * + * This is a thorough function which loops through all preceding packets, + * accumulating their lengths to find the position of the specified packet. + * Typically you will assign equal lengths to each packet in the transfer, + * and hence the above method is sub-optimal. You may wish to use + * libusb_get_iso_packet_buffer_simple() instead. + * + * \param transfer a transfer + * \param packet the packet to return the address of + * \returns the base address of the packet buffer inside the transfer buffer, + * or NULL if the packet does not exist. + * \see libusb_get_iso_packet_buffer_simple() + */ +static inline unsigned char *libusb_get_iso_packet_buffer( + struct libusb_transfer *transfer, unsigned int packet) +{ + int i; + size_t offset = 0; + int _packet; + + /* oops..slight bug in the API. packet is an unsigned int, but we use + * signed integers almost everywhere else. range-check and convert to + * signed to avoid compiler warnings. FIXME for libusb-2. */ + if (packet > INT_MAX) + return NULL; + _packet = (int) packet; + + if (_packet >= transfer->num_iso_packets) + return NULL; + + for (i = 0; i < _packet; i++) + offset += transfer->iso_packet_desc[i].length; + + return transfer->buffer + offset; +} + +/** \ingroup libusb_asyncio + * Convenience function to locate the position of an isochronous packet + * within the buffer of an isochronous transfer, for transfers where each + * packet is of identical size. + * + * This function relies on the assumption that every packet within the transfer + * is of identical size to the first packet. Calculating the location of + * the packet buffer is then just a simple calculation: + * buffer + (packet_size * packet) + * + * Do not use this function on transfers other than those that have identical + * packet lengths for each packet. + * + * \param transfer a transfer + * \param packet the packet to return the address of + * \returns the base address of the packet buffer inside the transfer buffer, + * or NULL if the packet does not exist. + * \see libusb_get_iso_packet_buffer() + */ +static inline unsigned char *libusb_get_iso_packet_buffer_simple( + struct libusb_transfer *transfer, unsigned int packet) +{ + int _packet; + + /* oops..slight bug in the API. packet is an unsigned int, but we use + * signed integers almost everywhere else. range-check and convert to + * signed to avoid compiler warnings. FIXME for libusb-2. */ + if (packet > INT_MAX) + return NULL; + _packet = (int) packet; + + if (_packet >= transfer->num_iso_packets) + return NULL; + + return transfer->buffer + ((int) transfer->iso_packet_desc[0].length * _packet); +} + +/* sync I/O */ + +int LIBUSB_CALL libusb_control_transfer(libusb_device_handle *dev_handle, + uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + unsigned char *data, uint16_t wLength, unsigned int timeout); + +int LIBUSB_CALL libusb_bulk_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *actual_length, unsigned int timeout); + +int LIBUSB_CALL libusb_interrupt_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *actual_length, unsigned int timeout); + +/** \ingroup libusb_desc + * Retrieve a descriptor from the default control pipe. + * This is a convenience function which formulates the appropriate control + * message to retrieve the descriptor. + * + * \param dev_handle a device handle + * \param desc_type the descriptor type, see \ref libusb_descriptor_type + * \param desc_index the index of the descriptor to retrieve + * \param data output buffer for descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + */ +static inline int libusb_get_descriptor(libusb_device_handle *dev_handle, + uint8_t desc_type, uint8_t desc_index, unsigned char *data, int length) +{ + return libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, (uint16_t) ((desc_type << 8) | desc_index), + 0, data, (uint16_t) length, 1000); +} + +/** \ingroup libusb_desc + * Retrieve a descriptor from a device. + * This is a convenience function which formulates the appropriate control + * message to retrieve the descriptor. The string returned is Unicode, as + * detailed in the USB specifications. + * + * \param dev_handle a device handle + * \param desc_index the index of the descriptor to retrieve + * \param langid the language ID for the string descriptor + * \param data output buffer for descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + * \see libusb_get_string_descriptor_ascii() + */ +static inline int libusb_get_string_descriptor(libusb_device_handle *dev_handle, + uint8_t desc_index, uint16_t langid, unsigned char *data, int length) +{ + return libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, (uint16_t)((LIBUSB_DT_STRING << 8) | desc_index), + langid, data, (uint16_t) length, 1000); +} + +int LIBUSB_CALL libusb_get_string_descriptor_ascii(libusb_device_handle *dev_handle, + uint8_t desc_index, unsigned char *data, int length); + +/* polling and timeouts */ + +int LIBUSB_CALL libusb_try_lock_events(libusb_context *ctx); +void LIBUSB_CALL libusb_lock_events(libusb_context *ctx); +void LIBUSB_CALL libusb_unlock_events(libusb_context *ctx); +int LIBUSB_CALL libusb_event_handling_ok(libusb_context *ctx); +int LIBUSB_CALL libusb_event_handler_active(libusb_context *ctx); +void LIBUSB_CALL libusb_interrupt_event_handler(libusb_context *ctx); +void LIBUSB_CALL libusb_lock_event_waiters(libusb_context *ctx); +void LIBUSB_CALL libusb_unlock_event_waiters(libusb_context *ctx); +int LIBUSB_CALL libusb_wait_for_event(libusb_context *ctx, struct timeval *tv); + +int LIBUSB_CALL libusb_handle_events_timeout(libusb_context *ctx, + struct timeval *tv); +int LIBUSB_CALL libusb_handle_events_timeout_completed(libusb_context *ctx, + struct timeval *tv, int *completed); +int LIBUSB_CALL libusb_handle_events(libusb_context *ctx); +int LIBUSB_CALL libusb_handle_events_completed(libusb_context *ctx, int *completed); +int LIBUSB_CALL libusb_handle_events_locked(libusb_context *ctx, + struct timeval *tv); +int LIBUSB_CALL libusb_pollfds_handle_timeouts(libusb_context *ctx); +int LIBUSB_CALL libusb_get_next_timeout(libusb_context *ctx, + struct timeval *tv); + +/** \ingroup libusb_poll + * File descriptor for polling + */ +struct libusb_pollfd { + /** Numeric file descriptor */ + int fd; + + /** Event flags to poll for from . POLLIN indicates that you + * should monitor this file descriptor for becoming ready to read from, + * and POLLOUT indicates that you should monitor this file descriptor for + * nonblocking write readiness. */ + short events; +}; + +/** \ingroup libusb_poll + * Callback function, invoked when a new file descriptor should be added + * to the set of file descriptors monitored for events. + * \param fd the new file descriptor + * \param events events to monitor for, see \ref libusb_pollfd for a + * description + * \param user_data User data pointer specified in + * libusb_set_pollfd_notifiers() call + * \see libusb_set_pollfd_notifiers() + */ +typedef void (LIBUSB_CALL *libusb_pollfd_added_cb)(int fd, short events, + void *user_data); + +/** \ingroup libusb_poll + * Callback function, invoked when a file descriptor should be removed from + * the set of file descriptors being monitored for events. After returning + * from this callback, do not use that file descriptor again. + * \param fd the file descriptor to stop monitoring + * \param user_data User data pointer specified in + * libusb_set_pollfd_notifiers() call + * \see libusb_set_pollfd_notifiers() + */ +typedef void (LIBUSB_CALL *libusb_pollfd_removed_cb)(int fd, void *user_data); + +const struct libusb_pollfd ** LIBUSB_CALL libusb_get_pollfds( + libusb_context *ctx); +void LIBUSB_CALL libusb_free_pollfds(const struct libusb_pollfd **pollfds); +void LIBUSB_CALL libusb_set_pollfd_notifiers(libusb_context *ctx, + libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb, + void *user_data); + +/** \ingroup libusb_hotplug + * Callback handle. + * + * Callbacks handles are generated by libusb_hotplug_register_callback() + * and can be used to deregister callbacks. Callback handles are unique + * per libusb_context and it is safe to call libusb_hotplug_deregister_callback() + * on an already deregisted callback. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * For more information, see \ref libusb_hotplug. + */ +typedef int libusb_hotplug_callback_handle; + +/** \ingroup libusb_hotplug + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * Flags for hotplug events */ +typedef enum { + /** Default value when not using any flags. */ + LIBUSB_HOTPLUG_NO_FLAGS = 0, + + /** Arm the callback and fire it for all matching currently attached devices. */ + LIBUSB_HOTPLUG_ENUMERATE = 1<<0, +} libusb_hotplug_flag; + +/** \ingroup libusb_hotplug + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * Hotplug events */ +typedef enum { + /** A device has been plugged in and is ready to use */ + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED = 0x01, + + /** A device has left and is no longer available. + * It is the user's responsibility to call libusb_close on any handle associated with a disconnected device. + * It is safe to call libusb_get_device_descriptor on a device that has left */ + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT = 0x02, +} libusb_hotplug_event; + +/** \ingroup libusb_hotplug + * Wildcard matching for hotplug events */ +#define LIBUSB_HOTPLUG_MATCH_ANY -1 + +/** \ingroup libusb_hotplug + * Hotplug callback function type. When requesting hotplug event notifications, + * you pass a pointer to a callback function of this type. + * + * This callback may be called by an internal event thread and as such it is + * recommended the callback do minimal processing before returning. + * + * libusb will call this function later, when a matching event had happened on + * a matching device. See \ref libusb_hotplug for more information. + * + * It is safe to call either libusb_hotplug_register_callback() or + * libusb_hotplug_deregister_callback() from within a callback function. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * \param ctx context of this notification + * \param device libusb_device this event occurred on + * \param event event that occurred + * \param user_data user data provided when this callback was registered + * \returns bool whether this callback is finished processing events. + * returning 1 will cause this callback to be deregistered + */ +typedef int (LIBUSB_CALL *libusb_hotplug_callback_fn)(libusb_context *ctx, + libusb_device *device, + libusb_hotplug_event event, + void *user_data); + +/** \ingroup libusb_hotplug + * Register a hotplug callback function + * + * Register a callback with the libusb_context. The callback will fire + * when a matching event occurs on a matching device. The callback is + * armed until either it is deregistered with libusb_hotplug_deregister_callback() + * or the supplied callback returns 1 to indicate it is finished processing events. + * + * If the \ref LIBUSB_HOTPLUG_ENUMERATE is passed the callback will be + * called with a \ref LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED for all devices + * already plugged into the machine. Note that libusb modifies its internal + * device list from a separate thread, while calling hotplug callbacks from + * libusb_handle_events(), so it is possible for a device to already be present + * on, or removed from, its internal device list, while the hotplug callbacks + * still need to be dispatched. This means that when using \ref + * LIBUSB_HOTPLUG_ENUMERATE, your callback may be called twice for the arrival + * of the same device, once from libusb_hotplug_register_callback() and once + * from libusb_handle_events(); and/or your callback may be called for the + * removal of a device for which an arrived call was never made. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * \param[in] ctx context to register this callback with + * \param[in] events bitwise or of events that will trigger this callback. See \ref + * libusb_hotplug_event + * \param[in] flags hotplug callback flags. See \ref libusb_hotplug_flag + * \param[in] vendor_id the vendor id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] product_id the product id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] dev_class the device class to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] cb_fn the function to be invoked on a matching event/device + * \param[in] user_data user data to pass to the callback function + * \param[out] callback_handle pointer to store the handle of the allocated callback (can be NULL) + * \returns LIBUSB_SUCCESS on success LIBUSB_ERROR code on failure + */ +int LIBUSB_CALL libusb_hotplug_register_callback(libusb_context *ctx, + libusb_hotplug_event events, + libusb_hotplug_flag flags, + int vendor_id, int product_id, + int dev_class, + libusb_hotplug_callback_fn cb_fn, + void *user_data, + libusb_hotplug_callback_handle *callback_handle); + +/** \ingroup libusb_hotplug + * Deregisters a hotplug callback. + * + * Deregister a callback from a libusb_context. This function is safe to call from within + * a hotplug callback. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * \param[in] ctx context this callback is registered with + * \param[in] callback_handle the handle of the callback to deregister + */ +void LIBUSB_CALL libusb_hotplug_deregister_callback(libusb_context *ctx, + libusb_hotplug_callback_handle callback_handle); + +#ifdef __cplusplus +} +#endif + +#endif From 42c74be2e10a42e8b86f2a9f79e863563e5fa329 Mon Sep 17 00:00:00 2001 From: belkinirena Date: Thu, 13 Sep 2018 14:19:02 +0300 Subject: [PATCH 04/25] Add libusb library (#28) --- third-party/win/libusb/bin/x64/libusb-1.0.lib | Bin 0 -> 1595450 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 third-party/win/libusb/bin/x64/libusb-1.0.lib diff --git a/third-party/win/libusb/bin/x64/libusb-1.0.lib b/third-party/win/libusb/bin/x64/libusb-1.0.lib new file mode 100644 index 0000000000000000000000000000000000000000..11c9088336cc79b358ecf647eaf923714894171f GIT binary patch literal 1595450 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%0D_%nu#Zb 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 N2%iTiWX3-#_ - * Copyright © 2007-2008 Daniel Drake - * Copyright © 2012 Pete Batard - * Copyright © 2012 Nathan Hjelm - * For more information, please visit: http://libusb.info - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#ifndef LIBUSB_H -#define LIBUSB_H - -#ifdef _MSC_VER -/* on MS environments, the inline keyword is available in C++ only */ -#if !defined(__cplusplus) -#define inline __inline -#endif -/* ssize_t is also not available (copy/paste from MinGW) */ -#ifndef _SSIZE_T_DEFINED -#define _SSIZE_T_DEFINED -#undef ssize_t -#ifdef _WIN64 - typedef __int64 ssize_t; -#else - typedef int ssize_t; -#endif /* _WIN64 */ -#endif /* _SSIZE_T_DEFINED */ -#endif /* _MSC_VER */ - -/* stdint.h is not available on older MSVC */ -#if defined(_MSC_VER) && (_MSC_VER < 1600) && (!defined(_STDINT)) && (!defined(_STDINT_H)) -typedef unsigned __int8 uint8_t; -typedef unsigned __int16 uint16_t; -typedef unsigned __int32 uint32_t; -#else -#include -#endif - -#if !defined(_WIN32_WCE) -#include -#endif - -#if defined(__linux) || defined(__APPLE__) || defined(__CYGWIN__) || defined(__HAIKU__) -#include -#endif - -#include -#include - -/* 'interface' might be defined as a macro on Windows, so we need to - * undefine it so as not to break the current libusb API, because - * libusb_config_descriptor has an 'interface' member - * As this can be problematic if you include windows.h after libusb.h - * in your sources, we force windows.h to be included first. */ -#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE) -#include -#if defined(interface) -#undef interface -#endif -#if !defined(__CYGWIN__) -#include -#endif -#endif - -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) -#define LIBUSB_DEPRECATED_FOR(f) \ - __attribute__((deprecated("Use " #f " instead"))) -#else -#define LIBUSB_DEPRECATED_FOR(f) -#endif /* __GNUC__ */ - -/** \def LIBUSB_CALL - * \ingroup libusb_misc - * libusb's Windows calling convention. - * - * Under Windows, the selection of available compilers and configurations - * means that, unlike other platforms, there is not one true calling - * convention (calling convention: the manner in which parameters are - * passed to functions in the generated assembly code). - * - * Matching the Windows API itself, libusb uses the WINAPI convention (which - * translates to the stdcall convention) and guarantees that the - * library is compiled in this way. The public header file also includes - * appropriate annotations so that your own software will use the right - * convention, even if another convention is being used by default within - * your codebase. - * - * The one consideration that you must apply in your software is to mark - * all functions which you use as libusb callbacks with this LIBUSB_CALL - * annotation, so that they too get compiled for the correct calling - * convention. - * - * On non-Windows operating systems, this macro is defined as nothing. This - * means that you can apply it to your code without worrying about - * cross-platform compatibility. - */ -/* LIBUSB_CALL must be defined on both definition and declaration of libusb - * functions. You'd think that declaration would be enough, but cygwin will - * complain about conflicting types unless both are marked this way. - * The placement of this macro is important too; it must appear after the - * return type, before the function name. See internal documentation for - * API_EXPORTED. - */ -#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE) -#define LIBUSB_CALL WINAPI -#else -#define LIBUSB_CALL -#endif - -/** \def LIBUSB_API_VERSION - * \ingroup libusb_misc - * libusb's API version. - * - * Since version 1.0.13, to help with feature detection, libusb defines - * a LIBUSB_API_VERSION macro that gets increased every time there is a - * significant change to the API, such as the introduction of a new call, - * the definition of a new macro/enum member, or any other element that - * libusb applications may want to detect at compilation time. - * - * The macro is typically used in an application as follows: - * \code - * #if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01001234) - * // Use one of the newer features from the libusb API - * #endif - * \endcode - * - * Internally, LIBUSB_API_VERSION is defined as follows: - * (libusb major << 24) | (libusb minor << 16) | (16 bit incremental) - */ -#define LIBUSB_API_VERSION 0x01000105 - -/* The following is kept for compatibility, but will be deprecated in the future */ -#define LIBUSBX_API_VERSION LIBUSB_API_VERSION - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * \ingroup libusb_misc - * Convert a 16-bit value from host-endian to little-endian format. On - * little endian systems, this function does nothing. On big endian systems, - * the bytes are swapped. - * \param x the host-endian value to convert - * \returns the value in little-endian byte order - */ -static inline uint16_t libusb_cpu_to_le16(const uint16_t x) -{ - union { - uint8_t b8[2]; - uint16_t b16; - } _tmp; - _tmp.b8[1] = (uint8_t) (x >> 8); - _tmp.b8[0] = (uint8_t) (x & 0xff); - return _tmp.b16; -} - -/** \def libusb_le16_to_cpu - * \ingroup libusb_misc - * Convert a 16-bit value from little-endian to host-endian format. On - * little endian systems, this function does nothing. On big endian systems, - * the bytes are swapped. - * \param x the little-endian value to convert - * \returns the value in host-endian byte order - */ -#define libusb_le16_to_cpu libusb_cpu_to_le16 - -/* standard USB stuff */ - -/** \ingroup libusb_desc - * Device and/or Interface Class codes */ -enum libusb_class_code { - /** In the context of a \ref libusb_device_descriptor "device descriptor", - * this bDeviceClass value indicates that each interface specifies its - * own class information and all interfaces operate independently. - */ - LIBUSB_CLASS_PER_INTERFACE = 0, - - /** Audio class */ - LIBUSB_CLASS_AUDIO = 1, - - /** Communications class */ - LIBUSB_CLASS_COMM = 2, - - /** Human Interface Device class */ - LIBUSB_CLASS_HID = 3, - - /** Physical */ - LIBUSB_CLASS_PHYSICAL = 5, - - /** Printer class */ - LIBUSB_CLASS_PRINTER = 7, - - /** Image class */ - LIBUSB_CLASS_PTP = 6, /* legacy name from libusb-0.1 usb.h */ - LIBUSB_CLASS_IMAGE = 6, - - /** Mass storage class */ - LIBUSB_CLASS_MASS_STORAGE = 8, - - /** Hub class */ - LIBUSB_CLASS_HUB = 9, - - /** Data class */ - LIBUSB_CLASS_DATA = 10, - - /** Smart Card */ - LIBUSB_CLASS_SMART_CARD = 0x0b, - - /** Content Security */ - LIBUSB_CLASS_CONTENT_SECURITY = 0x0d, - - /** Video */ - LIBUSB_CLASS_VIDEO = 0x0e, - - /** Personal Healthcare */ - LIBUSB_CLASS_PERSONAL_HEALTHCARE = 0x0f, - - /** Diagnostic Device */ - LIBUSB_CLASS_DIAGNOSTIC_DEVICE = 0xdc, - - /** Wireless class */ - LIBUSB_CLASS_WIRELESS = 0xe0, - - /** Application class */ - LIBUSB_CLASS_APPLICATION = 0xfe, - - /** Class is vendor-specific */ - LIBUSB_CLASS_VENDOR_SPEC = 0xff -}; - -/** \ingroup libusb_desc - * Descriptor types as defined by the USB specification. */ -enum libusb_descriptor_type { - /** Device descriptor. See libusb_device_descriptor. */ - LIBUSB_DT_DEVICE = 0x01, - - /** Configuration descriptor. See libusb_config_descriptor. */ - LIBUSB_DT_CONFIG = 0x02, - - /** String descriptor */ - LIBUSB_DT_STRING = 0x03, - - /** Interface descriptor. See libusb_interface_descriptor. */ - LIBUSB_DT_INTERFACE = 0x04, - - /** Endpoint descriptor. See libusb_endpoint_descriptor. */ - LIBUSB_DT_ENDPOINT = 0x05, - - /** BOS descriptor */ - LIBUSB_DT_BOS = 0x0f, - - /** Device Capability descriptor */ - LIBUSB_DT_DEVICE_CAPABILITY = 0x10, - - /** HID descriptor */ - LIBUSB_DT_HID = 0x21, - - /** HID report descriptor */ - LIBUSB_DT_REPORT = 0x22, - - /** Physical descriptor */ - LIBUSB_DT_PHYSICAL = 0x23, - - /** Hub descriptor */ - LIBUSB_DT_HUB = 0x29, - - /** SuperSpeed Hub descriptor */ - LIBUSB_DT_SUPERSPEED_HUB = 0x2a, - - /** SuperSpeed Endpoint Companion descriptor */ - LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30 -}; - -/* Descriptor sizes per descriptor type */ -#define LIBUSB_DT_DEVICE_SIZE 18 -#define LIBUSB_DT_CONFIG_SIZE 9 -#define LIBUSB_DT_INTERFACE_SIZE 9 -#define LIBUSB_DT_ENDPOINT_SIZE 7 -#define LIBUSB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ -#define LIBUSB_DT_HUB_NONVAR_SIZE 7 -#define LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE 6 -#define LIBUSB_DT_BOS_SIZE 5 -#define LIBUSB_DT_DEVICE_CAPABILITY_SIZE 3 - -/* BOS descriptor sizes */ -#define LIBUSB_BT_USB_2_0_EXTENSION_SIZE 7 -#define LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE 10 -#define LIBUSB_BT_CONTAINER_ID_SIZE 20 - -/* We unwrap the BOS => define its max size */ -#define LIBUSB_DT_BOS_MAX_SIZE ((LIBUSB_DT_BOS_SIZE) +\ - (LIBUSB_BT_USB_2_0_EXTENSION_SIZE) +\ - (LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE) +\ - (LIBUSB_BT_CONTAINER_ID_SIZE)) - -#define LIBUSB_ENDPOINT_ADDRESS_MASK 0x0f /* in bEndpointAddress */ -#define LIBUSB_ENDPOINT_DIR_MASK 0x80 - -/** \ingroup libusb_desc - * Endpoint direction. Values for bit 7 of the - * \ref libusb_endpoint_descriptor::bEndpointAddress "endpoint address" scheme. - */ -enum libusb_endpoint_direction { - /** In: device-to-host */ - LIBUSB_ENDPOINT_IN = 0x80, - - /** Out: host-to-device */ - LIBUSB_ENDPOINT_OUT = 0x00 -}; - -#define LIBUSB_TRANSFER_TYPE_MASK 0x03 /* in bmAttributes */ - -/** \ingroup libusb_desc - * Endpoint transfer type. Values for bits 0:1 of the - * \ref libusb_endpoint_descriptor::bmAttributes "endpoint attributes" field. - */ -enum libusb_transfer_type { - /** Control endpoint */ - LIBUSB_TRANSFER_TYPE_CONTROL = 0, - - /** Isochronous endpoint */ - LIBUSB_TRANSFER_TYPE_ISOCHRONOUS = 1, - - /** Bulk endpoint */ - LIBUSB_TRANSFER_TYPE_BULK = 2, - - /** Interrupt endpoint */ - LIBUSB_TRANSFER_TYPE_INTERRUPT = 3, - - /** Stream endpoint */ - LIBUSB_TRANSFER_TYPE_BULK_STREAM = 4, -}; - -/** \ingroup libusb_misc - * Standard requests, as defined in table 9-5 of the USB 3.0 specifications */ -enum libusb_standard_request { - /** Request status of the specific recipient */ - LIBUSB_REQUEST_GET_STATUS = 0x00, - - /** Clear or disable a specific feature */ - LIBUSB_REQUEST_CLEAR_FEATURE = 0x01, - - /* 0x02 is reserved */ - - /** Set or enable a specific feature */ - LIBUSB_REQUEST_SET_FEATURE = 0x03, - - /* 0x04 is reserved */ - - /** Set device address for all future accesses */ - LIBUSB_REQUEST_SET_ADDRESS = 0x05, - - /** Get the specified descriptor */ - LIBUSB_REQUEST_GET_DESCRIPTOR = 0x06, - - /** Used to update existing descriptors or add new descriptors */ - LIBUSB_REQUEST_SET_DESCRIPTOR = 0x07, - - /** Get the current device configuration value */ - LIBUSB_REQUEST_GET_CONFIGURATION = 0x08, - - /** Set device configuration */ - LIBUSB_REQUEST_SET_CONFIGURATION = 0x09, - - /** Return the selected alternate setting for the specified interface */ - LIBUSB_REQUEST_GET_INTERFACE = 0x0A, - - /** Select an alternate interface for the specified interface */ - LIBUSB_REQUEST_SET_INTERFACE = 0x0B, - - /** Set then report an endpoint's synchronization frame */ - LIBUSB_REQUEST_SYNCH_FRAME = 0x0C, - - /** Sets both the U1 and U2 Exit Latency */ - LIBUSB_REQUEST_SET_SEL = 0x30, - - /** Delay from the time a host transmits a packet to the time it is - * received by the device. */ - LIBUSB_SET_ISOCH_DELAY = 0x31, -}; - -/** \ingroup libusb_misc - * Request type bits of the - * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control - * transfers. */ -enum libusb_request_type { - /** Standard */ - LIBUSB_REQUEST_TYPE_STANDARD = (0x00 << 5), - - /** Class */ - LIBUSB_REQUEST_TYPE_CLASS = (0x01 << 5), - - /** Vendor */ - LIBUSB_REQUEST_TYPE_VENDOR = (0x02 << 5), - - /** Reserved */ - LIBUSB_REQUEST_TYPE_RESERVED = (0x03 << 5) -}; - -/** \ingroup libusb_misc - * Recipient bits of the - * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control - * transfers. Values 4 through 31 are reserved. */ -enum libusb_request_recipient { - /** Device */ - LIBUSB_RECIPIENT_DEVICE = 0x00, - - /** Interface */ - LIBUSB_RECIPIENT_INTERFACE = 0x01, - - /** Endpoint */ - LIBUSB_RECIPIENT_ENDPOINT = 0x02, - - /** Other */ - LIBUSB_RECIPIENT_OTHER = 0x03, -}; - -#define LIBUSB_ISO_SYNC_TYPE_MASK 0x0C - -/** \ingroup libusb_desc - * Synchronization type for isochronous endpoints. Values for bits 2:3 of the - * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in - * libusb_endpoint_descriptor. - */ -enum libusb_iso_sync_type { - /** No synchronization */ - LIBUSB_ISO_SYNC_TYPE_NONE = 0, - - /** Asynchronous */ - LIBUSB_ISO_SYNC_TYPE_ASYNC = 1, - - /** Adaptive */ - LIBUSB_ISO_SYNC_TYPE_ADAPTIVE = 2, - - /** Synchronous */ - LIBUSB_ISO_SYNC_TYPE_SYNC = 3 -}; - -#define LIBUSB_ISO_USAGE_TYPE_MASK 0x30 - -/** \ingroup libusb_desc - * Usage type for isochronous endpoints. Values for bits 4:5 of the - * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in - * libusb_endpoint_descriptor. - */ -enum libusb_iso_usage_type { - /** Data endpoint */ - LIBUSB_ISO_USAGE_TYPE_DATA = 0, - - /** Feedback endpoint */ - LIBUSB_ISO_USAGE_TYPE_FEEDBACK = 1, - - /** Implicit feedback Data endpoint */ - LIBUSB_ISO_USAGE_TYPE_IMPLICIT = 2, -}; - -/** \ingroup libusb_desc - * A structure representing the standard USB device descriptor. This - * descriptor is documented in section 9.6.1 of the USB 3.0 specification. - * All multiple-byte fields are represented in host-endian format. - */ -struct libusb_device_descriptor { - /** Size of this descriptor (in bytes) */ - uint8_t bLength; - - /** Descriptor type. Will have value - * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE LIBUSB_DT_DEVICE in this - * context. */ - uint8_t bDescriptorType; - - /** USB specification release number in binary-coded decimal. A value of - * 0x0200 indicates USB 2.0, 0x0110 indicates USB 1.1, etc. */ - uint16_t bcdUSB; - - /** USB-IF class code for the device. See \ref libusb_class_code. */ - uint8_t bDeviceClass; - - /** USB-IF subclass code for the device, qualified by the bDeviceClass - * value */ - uint8_t bDeviceSubClass; - - /** USB-IF protocol code for the device, qualified by the bDeviceClass and - * bDeviceSubClass values */ - uint8_t bDeviceProtocol; - - /** Maximum packet size for endpoint 0 */ - uint8_t bMaxPacketSize0; - - /** USB-IF vendor ID */ - uint16_t idVendor; - - /** USB-IF product ID */ - uint16_t idProduct; - - /** Device release number in binary-coded decimal */ - uint16_t bcdDevice; - - /** Index of string descriptor describing manufacturer */ - uint8_t iManufacturer; - - /** Index of string descriptor describing product */ - uint8_t iProduct; - - /** Index of string descriptor containing device serial number */ - uint8_t iSerialNumber; - - /** Number of possible configurations */ - uint8_t bNumConfigurations; -}; - -/** \ingroup libusb_desc - * A structure representing the standard USB endpoint descriptor. This - * descriptor is documented in section 9.6.6 of the USB 3.0 specification. - * All multiple-byte fields are represented in host-endian format. - */ -struct libusb_endpoint_descriptor { - /** Size of this descriptor (in bytes) */ - uint8_t bLength; - - /** Descriptor type. Will have value - * \ref libusb_descriptor_type::LIBUSB_DT_ENDPOINT LIBUSB_DT_ENDPOINT in - * this context. */ - uint8_t bDescriptorType; - - /** The address of the endpoint described by this descriptor. Bits 0:3 are - * the endpoint number. Bits 4:6 are reserved. Bit 7 indicates direction, - * see \ref libusb_endpoint_direction. - */ - uint8_t bEndpointAddress; - - /** Attributes which apply to the endpoint when it is configured using - * the bConfigurationValue. Bits 0:1 determine the transfer type and - * correspond to \ref libusb_transfer_type. Bits 2:3 are only used for - * isochronous endpoints and correspond to \ref libusb_iso_sync_type. - * Bits 4:5 are also only used for isochronous endpoints and correspond to - * \ref libusb_iso_usage_type. Bits 6:7 are reserved. - */ - uint8_t bmAttributes; - - /** Maximum packet size this endpoint is capable of sending/receiving. */ - uint16_t wMaxPacketSize; - - /** Interval for polling endpoint for data transfers. */ - uint8_t bInterval; - - /** For audio devices only: the rate at which synchronization feedback - * is provided. */ - uint8_t bRefresh; - - /** For audio devices only: the address if the synch endpoint */ - uint8_t bSynchAddress; - - /** Extra descriptors. If libusb encounters unknown endpoint descriptors, - * it will store them here, should you wish to parse them. */ - const unsigned char *extra; - - /** Length of the extra descriptors, in bytes. */ - int extra_length; -}; - -/** \ingroup libusb_desc - * A structure representing the standard USB interface descriptor. This - * descriptor is documented in section 9.6.5 of the USB 3.0 specification. - * All multiple-byte fields are represented in host-endian format. - */ -struct libusb_interface_descriptor { - /** Size of this descriptor (in bytes) */ - uint8_t bLength; - - /** Descriptor type. Will have value - * \ref libusb_descriptor_type::LIBUSB_DT_INTERFACE LIBUSB_DT_INTERFACE - * in this context. */ - uint8_t bDescriptorType; - - /** Number of this interface */ - uint8_t bInterfaceNumber; - - /** Value used to select this alternate setting for this interface */ - uint8_t bAlternateSetting; - - /** Number of endpoints used by this interface (excluding the control - * endpoint). */ - uint8_t bNumEndpoints; - - /** USB-IF class code for this interface. See \ref libusb_class_code. */ - uint8_t bInterfaceClass; - - /** USB-IF subclass code for this interface, qualified by the - * bInterfaceClass value */ - uint8_t bInterfaceSubClass; - - /** USB-IF protocol code for this interface, qualified by the - * bInterfaceClass and bInterfaceSubClass values */ - uint8_t bInterfaceProtocol; - - /** Index of string descriptor describing this interface */ - uint8_t iInterface; - - /** Array of endpoint descriptors. This length of this array is determined - * by the bNumEndpoints field. */ - const struct libusb_endpoint_descriptor *endpoint; - - /** Extra descriptors. If libusb encounters unknown interface descriptors, - * it will store them here, should you wish to parse them. */ - const unsigned char *extra; - - /** Length of the extra descriptors, in bytes. */ - int extra_length; -}; - -/** \ingroup libusb_desc - * A collection of alternate settings for a particular USB interface. - */ -struct libusb_interface { - /** Array of interface descriptors. The length of this array is determined - * by the num_altsetting field. */ - const struct libusb_interface_descriptor *altsetting; - - /** The number of alternate settings that belong to this interface */ - int num_altsetting; -}; - -/** \ingroup libusb_desc - * A structure representing the standard USB configuration descriptor. This - * descriptor is documented in section 9.6.3 of the USB 3.0 specification. - * All multiple-byte fields are represented in host-endian format. - */ -struct libusb_config_descriptor { - /** Size of this descriptor (in bytes) */ - uint8_t bLength; - - /** Descriptor type. Will have value - * \ref libusb_descriptor_type::LIBUSB_DT_CONFIG LIBUSB_DT_CONFIG - * in this context. */ - uint8_t bDescriptorType; - - /** Total length of data returned for this configuration */ - uint16_t wTotalLength; - - /** Number of interfaces supported by this configuration */ - uint8_t bNumInterfaces; - - /** Identifier value for this configuration */ - uint8_t bConfigurationValue; - - /** Index of string descriptor describing this configuration */ - uint8_t iConfiguration; - - /** Configuration characteristics */ - uint8_t bmAttributes; - - /** Maximum power consumption of the USB device from this bus in this - * configuration when the device is fully operation. Expressed in units - * of 2 mA when the device is operating in high-speed mode and in units - * of 8 mA when the device is operating in super-speed mode. */ - uint8_t MaxPower; - - /** Array of interfaces supported by this configuration. The length of - * this array is determined by the bNumInterfaces field. */ - const struct libusb_interface *interface; - - /** Extra descriptors. If libusb encounters unknown configuration - * descriptors, it will store them here, should you wish to parse them. */ - const unsigned char *extra; - - /** Length of the extra descriptors, in bytes. */ - int extra_length; -}; - -/** \ingroup libusb_desc - * A structure representing the superspeed endpoint companion - * descriptor. This descriptor is documented in section 9.6.7 of - * the USB 3.0 specification. All multiple-byte fields are represented in - * host-endian format. - */ -struct libusb_ss_endpoint_companion_descriptor { - - /** Size of this descriptor (in bytes) */ - uint8_t bLength; - - /** Descriptor type. Will have value - * \ref libusb_descriptor_type::LIBUSB_DT_SS_ENDPOINT_COMPANION in - * this context. */ - uint8_t bDescriptorType; - - - /** The maximum number of packets the endpoint can send or - * receive as part of a burst. */ - uint8_t bMaxBurst; - - /** In bulk EP: bits 4:0 represents the maximum number of - * streams the EP supports. In isochronous EP: bits 1:0 - * represents the Mult - a zero based value that determines - * the maximum number of packets within a service interval */ - uint8_t bmAttributes; - - /** The total number of bytes this EP will transfer every - * service interval. valid only for periodic EPs. */ - uint16_t wBytesPerInterval; -}; - -/** \ingroup libusb_desc - * A generic representation of a BOS Device Capability descriptor. It is - * advised to check bDevCapabilityType and call the matching - * libusb_get_*_descriptor function to get a structure fully matching the type. - */ -struct libusb_bos_dev_capability_descriptor { - /** Size of this descriptor (in bytes) */ - uint8_t bLength; - /** Descriptor type. Will have value - * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY - * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ - uint8_t bDescriptorType; - /** Device Capability type */ - uint8_t bDevCapabilityType; - /** Device Capability data (bLength - 3 bytes) */ - uint8_t dev_capability_data -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) - [] /* valid C99 code */ -#else - [0] /* non-standard, but usually working code */ -#endif - ; -}; - -/** \ingroup libusb_desc - * A structure representing the Binary Device Object Store (BOS) descriptor. - * This descriptor is documented in section 9.6.2 of the USB 3.0 specification. - * All multiple-byte fields are represented in host-endian format. - */ -struct libusb_bos_descriptor { - /** Size of this descriptor (in bytes) */ - uint8_t bLength; - - /** Descriptor type. Will have value - * \ref libusb_descriptor_type::LIBUSB_DT_BOS LIBUSB_DT_BOS - * in this context. */ - uint8_t bDescriptorType; - - /** Length of this descriptor and all of its sub descriptors */ - uint16_t wTotalLength; - - /** The number of separate device capability descriptors in - * the BOS */ - uint8_t bNumDeviceCaps; - - /** bNumDeviceCap Device Capability Descriptors */ - struct libusb_bos_dev_capability_descriptor *dev_capability -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) - [] /* valid C99 code */ -#else - [0] /* non-standard, but usually working code */ -#endif - ; -}; - -/** \ingroup libusb_desc - * A structure representing the USB 2.0 Extension descriptor - * This descriptor is documented in section 9.6.2.1 of the USB 3.0 specification. - * All multiple-byte fields are represented in host-endian format. - */ -struct libusb_usb_2_0_extension_descriptor { - /** Size of this descriptor (in bytes) */ - uint8_t bLength; - - /** Descriptor type. Will have value - * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY - * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ - uint8_t bDescriptorType; - - /** Capability type. Will have value - * \ref libusb_capability_type::LIBUSB_BT_USB_2_0_EXTENSION - * LIBUSB_BT_USB_2_0_EXTENSION in this context. */ - uint8_t bDevCapabilityType; - - /** Bitmap encoding of supported device level features. - * A value of one in a bit location indicates a feature is - * supported; a value of zero indicates it is not supported. - * See \ref libusb_usb_2_0_extension_attributes. */ - uint32_t bmAttributes; -}; - -/** \ingroup libusb_desc - * A structure representing the SuperSpeed USB Device Capability descriptor - * This descriptor is documented in section 9.6.2.2 of the USB 3.0 specification. - * All multiple-byte fields are represented in host-endian format. - */ -struct libusb_ss_usb_device_capability_descriptor { - /** Size of this descriptor (in bytes) */ - uint8_t bLength; - - /** Descriptor type. Will have value - * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY - * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ - uint8_t bDescriptorType; - - /** Capability type. Will have value - * \ref libusb_capability_type::LIBUSB_BT_SS_USB_DEVICE_CAPABILITY - * LIBUSB_BT_SS_USB_DEVICE_CAPABILITY in this context. */ - uint8_t bDevCapabilityType; - - /** Bitmap encoding of supported device level features. - * A value of one in a bit location indicates a feature is - * supported; a value of zero indicates it is not supported. - * See \ref libusb_ss_usb_device_capability_attributes. */ - uint8_t bmAttributes; - - /** Bitmap encoding of the speed supported by this device when - * operating in SuperSpeed mode. See \ref libusb_supported_speed. */ - uint16_t wSpeedSupported; - - /** The lowest speed at which all the functionality supported - * by the device is available to the user. For example if the - * device supports all its functionality when connected at - * full speed and above then it sets this value to 1. */ - uint8_t bFunctionalitySupport; - - /** U1 Device Exit Latency. */ - uint8_t bU1DevExitLat; - - /** U2 Device Exit Latency. */ - uint16_t bU2DevExitLat; -}; - -/** \ingroup libusb_desc - * A structure representing the Container ID descriptor. - * This descriptor is documented in section 9.6.2.3 of the USB 3.0 specification. - * All multiple-byte fields, except UUIDs, are represented in host-endian format. - */ -struct libusb_container_id_descriptor { - /** Size of this descriptor (in bytes) */ - uint8_t bLength; - - /** Descriptor type. Will have value - * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY - * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ - uint8_t bDescriptorType; - - /** Capability type. Will have value - * \ref libusb_capability_type::LIBUSB_BT_CONTAINER_ID - * LIBUSB_BT_CONTAINER_ID in this context. */ - uint8_t bDevCapabilityType; - - /** Reserved field */ - uint8_t bReserved; - - /** 128 bit UUID */ - uint8_t ContainerID[16]; -}; - -/** \ingroup libusb_asyncio - * Setup packet for control transfers. */ -struct libusb_control_setup { - /** Request type. Bits 0:4 determine recipient, see - * \ref libusb_request_recipient. Bits 5:6 determine type, see - * \ref libusb_request_type. Bit 7 determines data transfer direction, see - * \ref libusb_endpoint_direction. - */ - uint8_t bmRequestType; - - /** Request. If the type bits of bmRequestType are equal to - * \ref libusb_request_type::LIBUSB_REQUEST_TYPE_STANDARD - * "LIBUSB_REQUEST_TYPE_STANDARD" then this field refers to - * \ref libusb_standard_request. For other cases, use of this field is - * application-specific. */ - uint8_t bRequest; - - /** Value. Varies according to request */ - uint16_t wValue; - - /** Index. Varies according to request, typically used to pass an index - * or offset */ - uint16_t wIndex; - - /** Number of bytes to transfer */ - uint16_t wLength; -}; - -#define LIBUSB_CONTROL_SETUP_SIZE (sizeof(struct libusb_control_setup)) - -/* libusb */ - -struct libusb_context; -struct libusb_device; -struct libusb_device_handle; - -/** \ingroup libusb_lib - * Structure providing the version of the libusb runtime - */ -struct libusb_version { - /** Library major version. */ - const uint16_t major; - - /** Library minor version. */ - const uint16_t minor; - - /** Library micro version. */ - const uint16_t micro; - - /** Library nano version. */ - const uint16_t nano; - - /** Library release candidate suffix string, e.g. "-rc4". */ - const char *rc; - - /** For ABI compatibility only. */ - const char* describe; -}; - -/** \ingroup libusb_lib - * Structure representing a libusb session. The concept of individual libusb - * sessions allows for your program to use two libraries (or dynamically - * load two modules) which both independently use libusb. This will prevent - * interference between the individual libusb users - for example - * libusb_set_debug() will not affect the other user of the library, and - * libusb_exit() will not destroy resources that the other user is still - * using. - * - * Sessions are created by libusb_init() and destroyed through libusb_exit(). - * If your application is guaranteed to only ever include a single libusb - * user (i.e. you), you do not have to worry about contexts: pass NULL in - * every function call where a context is required. The default context - * will be used. - * - * For more information, see \ref libusb_contexts. - */ -typedef struct libusb_context libusb_context; - -/** \ingroup libusb_dev - * Structure representing a USB device detected on the system. This is an - * opaque type for which you are only ever provided with a pointer, usually - * originating from libusb_get_device_list(). - * - * Certain operations can be performed on a device, but in order to do any - * I/O you will have to first obtain a device handle using libusb_open(). - * - * Devices are reference counted with libusb_ref_device() and - * libusb_unref_device(), and are freed when the reference count reaches 0. - * New devices presented by libusb_get_device_list() have a reference count of - * 1, and libusb_free_device_list() can optionally decrease the reference count - * on all devices in the list. libusb_open() adds another reference which is - * later destroyed by libusb_close(). - */ -typedef struct libusb_device libusb_device; - - -/** \ingroup libusb_dev - * Structure representing a handle on a USB device. This is an opaque type for - * which you are only ever provided with a pointer, usually originating from - * libusb_open(). - * - * A device handle is used to perform I/O and other operations. When finished - * with a device handle, you should call libusb_close(). - */ -typedef struct libusb_device_handle libusb_device_handle; - -/** \ingroup libusb_dev - * Speed codes. Indicates the speed at which the device is operating. - */ -enum libusb_speed { - /** The OS doesn't report or know the device speed. */ - LIBUSB_SPEED_UNKNOWN = 0, - - /** The device is operating at low speed (1.5MBit/s). */ - LIBUSB_SPEED_LOW = 1, - - /** The device is operating at full speed (12MBit/s). */ - LIBUSB_SPEED_FULL = 2, - - /** The device is operating at high speed (480MBit/s). */ - LIBUSB_SPEED_HIGH = 3, - - /** The device is operating at super speed (5000MBit/s). */ - LIBUSB_SPEED_SUPER = 4, -}; - -/** \ingroup libusb_dev - * Supported speeds (wSpeedSupported) bitfield. Indicates what - * speeds the device supports. - */ -enum libusb_supported_speed { - /** Low speed operation supported (1.5MBit/s). */ - LIBUSB_LOW_SPEED_OPERATION = 1, - - /** Full speed operation supported (12MBit/s). */ - LIBUSB_FULL_SPEED_OPERATION = 2, - - /** High speed operation supported (480MBit/s). */ - LIBUSB_HIGH_SPEED_OPERATION = 4, - - /** Superspeed operation supported (5000MBit/s). */ - LIBUSB_SUPER_SPEED_OPERATION = 8, -}; - -/** \ingroup libusb_dev - * Masks for the bits of the - * \ref libusb_usb_2_0_extension_descriptor::bmAttributes "bmAttributes" field - * of the USB 2.0 Extension descriptor. - */ -enum libusb_usb_2_0_extension_attributes { - /** Supports Link Power Management (LPM) */ - LIBUSB_BM_LPM_SUPPORT = 2, -}; - -/** \ingroup libusb_dev - * Masks for the bits of the - * \ref libusb_ss_usb_device_capability_descriptor::bmAttributes "bmAttributes" field - * field of the SuperSpeed USB Device Capability descriptor. - */ -enum libusb_ss_usb_device_capability_attributes { - /** Supports Latency Tolerance Messages (LTM) */ - LIBUSB_BM_LTM_SUPPORT = 2, -}; - -/** \ingroup libusb_dev - * USB capability types - */ -enum libusb_bos_type { - /** Wireless USB device capability */ - LIBUSB_BT_WIRELESS_USB_DEVICE_CAPABILITY = 1, - - /** USB 2.0 extensions */ - LIBUSB_BT_USB_2_0_EXTENSION = 2, - - /** SuperSpeed USB device capability */ - LIBUSB_BT_SS_USB_DEVICE_CAPABILITY = 3, - - /** Container ID type */ - LIBUSB_BT_CONTAINER_ID = 4, -}; - -/** \ingroup libusb_misc - * Error codes. Most libusb functions return 0 on success or one of these - * codes on failure. - * You can call libusb_error_name() to retrieve a string representation of an - * error code or libusb_strerror() to get an end-user suitable description of - * an error code. - */ -enum libusb_error { - /** Success (no error) */ - LIBUSB_SUCCESS = 0, - - /** Input/output error */ - LIBUSB_ERROR_IO = -1, - - /** Invalid parameter */ - LIBUSB_ERROR_INVALID_PARAM = -2, - - /** Access denied (insufficient permissions) */ - LIBUSB_ERROR_ACCESS = -3, - - /** No such device (it may have been disconnected) */ - LIBUSB_ERROR_NO_DEVICE = -4, - - /** Entity not found */ - LIBUSB_ERROR_NOT_FOUND = -5, - - /** Resource busy */ - LIBUSB_ERROR_BUSY = -6, - - /** Operation timed out */ - LIBUSB_ERROR_TIMEOUT = -7, - - /** Overflow */ - LIBUSB_ERROR_OVERFLOW = -8, - - /** Pipe error */ - LIBUSB_ERROR_PIPE = -9, - - /** System call interrupted (perhaps due to signal) */ - LIBUSB_ERROR_INTERRUPTED = -10, - - /** Insufficient memory */ - LIBUSB_ERROR_NO_MEM = -11, - - /** Operation not supported or unimplemented on this platform */ - LIBUSB_ERROR_NOT_SUPPORTED = -12, - - /* NB: Remember to update LIBUSB_ERROR_COUNT below as well as the - message strings in strerror.c when adding new error codes here. */ - - /** Other error */ - LIBUSB_ERROR_OTHER = -99, -}; - -/* Total number of error codes in enum libusb_error */ -#define LIBUSB_ERROR_COUNT 14 - -/** \ingroup libusb_asyncio - * Transfer status codes */ -enum libusb_transfer_status { - /** Transfer completed without error. Note that this does not indicate - * that the entire amount of requested data was transferred. */ - LIBUSB_TRANSFER_COMPLETED, - - /** Transfer failed */ - LIBUSB_TRANSFER_ERROR, - - /** Transfer timed out */ - LIBUSB_TRANSFER_TIMED_OUT, - - /** Transfer was cancelled */ - LIBUSB_TRANSFER_CANCELLED, - - /** For bulk/interrupt endpoints: halt condition detected (endpoint - * stalled). For control endpoints: control request not supported. */ - LIBUSB_TRANSFER_STALL, - - /** Device was disconnected */ - LIBUSB_TRANSFER_NO_DEVICE, - - /** Device sent more data than requested */ - LIBUSB_TRANSFER_OVERFLOW, - - /* NB! Remember to update libusb_error_name() - when adding new status codes here. */ -}; - -/** \ingroup libusb_asyncio - * libusb_transfer.flags values */ -enum libusb_transfer_flags { - /** Report short frames as errors */ - LIBUSB_TRANSFER_SHORT_NOT_OK = 1<<0, - - /** Automatically free() transfer buffer during libusb_free_transfer(). - * Note that buffers allocated with libusb_dev_mem_alloc() should not - * be attempted freed in this way, since free() is not an appropriate - * way to release such memory. */ - LIBUSB_TRANSFER_FREE_BUFFER = 1<<1, - - /** Automatically call libusb_free_transfer() after callback returns. - * If this flag is set, it is illegal to call libusb_free_transfer() - * from your transfer callback, as this will result in a double-free - * when this flag is acted upon. */ - LIBUSB_TRANSFER_FREE_TRANSFER = 1<<2, - - /** Terminate transfers that are a multiple of the endpoint's - * wMaxPacketSize with an extra zero length packet. This is useful - * when a device protocol mandates that each logical request is - * terminated by an incomplete packet (i.e. the logical requests are - * not separated by other means). - * - * This flag only affects host-to-device transfers to bulk and interrupt - * endpoints. In other situations, it is ignored. - * - * This flag only affects transfers with a length that is a multiple of - * the endpoint's wMaxPacketSize. On transfers of other lengths, this - * flag has no effect. Therefore, if you are working with a device that - * needs a ZLP whenever the end of the logical request falls on a packet - * boundary, then it is sensible to set this flag on every - * transfer (you do not have to worry about only setting it on transfers - * that end on the boundary). - * - * This flag is currently only supported on Linux. - * On other systems, libusb_submit_transfer() will return - * LIBUSB_ERROR_NOT_SUPPORTED for every transfer where this flag is set. - * - * Available since libusb-1.0.9. - */ - LIBUSB_TRANSFER_ADD_ZERO_PACKET = 1 << 3, -}; - -/** \ingroup libusb_asyncio - * Isochronous packet descriptor. */ -struct libusb_iso_packet_descriptor { - /** Length of data to request in this packet */ - unsigned int length; - - /** Amount of data that was actually transferred */ - unsigned int actual_length; - - /** Status code for this packet */ - enum libusb_transfer_status status; -}; - -struct libusb_transfer; - -/** \ingroup libusb_asyncio - * Asynchronous transfer callback function type. When submitting asynchronous - * transfers, you pass a pointer to a callback function of this type via the - * \ref libusb_transfer::callback "callback" member of the libusb_transfer - * structure. libusb will call this function later, when the transfer has - * completed or failed. See \ref libusb_asyncio for more information. - * \param transfer The libusb_transfer struct the callback function is being - * notified about. - */ -typedef void (LIBUSB_CALL *libusb_transfer_cb_fn)(struct libusb_transfer *transfer); - -/** \ingroup libusb_asyncio - * The generic USB transfer structure. The user populates this structure and - * then submits it in order to request a transfer. After the transfer has - * completed, the library populates the transfer with the results and passes - * it back to the user. - */ -struct libusb_transfer { - /** Handle of the device that this transfer will be submitted to */ - libusb_device_handle *dev_handle; - - /** A bitwise OR combination of \ref libusb_transfer_flags. */ - uint8_t flags; - - /** Address of the endpoint where this transfer will be sent. */ - unsigned char endpoint; - - /** Type of the endpoint from \ref libusb_transfer_type */ - unsigned char type; - - /** Timeout for this transfer in milliseconds. A value of 0 indicates no - * timeout. */ - unsigned int timeout; - - /** The status of the transfer. Read-only, and only for use within - * transfer callback function. - * - * If this is an isochronous transfer, this field may read COMPLETED even - * if there were errors in the frames. Use the - * \ref libusb_iso_packet_descriptor::status "status" field in each packet - * to determine if errors occurred. */ - enum libusb_transfer_status status; - - /** Length of the data buffer */ - int length; - - /** Actual length of data that was transferred. Read-only, and only for - * use within transfer callback function. Not valid for isochronous - * endpoint transfers. */ - int actual_length; - - /** Callback function. This will be invoked when the transfer completes, - * fails, or is cancelled. */ - libusb_transfer_cb_fn callback; - - /** User context data to pass to the callback function. */ - void *user_data; - - /** Data buffer */ - unsigned char *buffer; - - /** Number of isochronous packets. Only used for I/O with isochronous - * endpoints. */ - int num_iso_packets; - - /** Isochronous packet descriptors, for isochronous transfers only. */ - struct libusb_iso_packet_descriptor iso_packet_desc -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) - [] /* valid C99 code */ -#else - [0] /* non-standard, but usually working code */ -#endif - ; -}; - -/** \ingroup libusb_misc - * Capabilities supported by an instance of libusb on the current running - * platform. Test if the loaded library supports a given capability by calling - * \ref libusb_has_capability(). - */ -enum libusb_capability { - /** The libusb_has_capability() API is available. */ - LIBUSB_CAP_HAS_CAPABILITY = 0x0000, - /** Hotplug support is available on this platform. */ - LIBUSB_CAP_HAS_HOTPLUG = 0x0001, - /** The library can access HID devices without requiring user intervention. - * Note that before being able to actually access an HID device, you may - * still have to call additional libusb functions such as - * \ref libusb_detach_kernel_driver(). */ - LIBUSB_CAP_HAS_HID_ACCESS = 0x0100, - /** The library supports detaching of the default USB driver, using - * \ref libusb_detach_kernel_driver(), if one is set by the OS kernel */ - LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER = 0x0101 -}; - -/** \ingroup libusb_lib - * Log message levels. - * - LIBUSB_LOG_LEVEL_NONE (0) : no messages ever printed by the library (default) - * - LIBUSB_LOG_LEVEL_ERROR (1) : error messages are printed to stderr - * - LIBUSB_LOG_LEVEL_WARNING (2) : warning and error messages are printed to stderr - * - LIBUSB_LOG_LEVEL_INFO (3) : informational messages are printed to stdout, warning - * and error messages are printed to stderr - * - LIBUSB_LOG_LEVEL_DEBUG (4) : debug and informational messages are printed to stdout, - * warnings and errors to stderr - */ -enum libusb_log_level { - LIBUSB_LOG_LEVEL_NONE = 0, - LIBUSB_LOG_LEVEL_ERROR, - LIBUSB_LOG_LEVEL_WARNING, - LIBUSB_LOG_LEVEL_INFO, - LIBUSB_LOG_LEVEL_DEBUG, -}; - -int LIBUSB_CALL libusb_init(libusb_context **ctx); -void LIBUSB_CALL libusb_exit(libusb_context *ctx); -void LIBUSB_CALL libusb_set_debug(libusb_context *ctx, int level); -const struct libusb_version * LIBUSB_CALL libusb_get_version(void); -int LIBUSB_CALL libusb_has_capability(uint32_t capability); -const char * LIBUSB_CALL libusb_error_name(int errcode); -int LIBUSB_CALL libusb_setlocale(const char *locale); -const char * LIBUSB_CALL libusb_strerror(enum libusb_error errcode); - -ssize_t LIBUSB_CALL libusb_get_device_list(libusb_context *ctx, - libusb_device ***list); -void LIBUSB_CALL libusb_free_device_list(libusb_device **list, - int unref_devices); -libusb_device * LIBUSB_CALL libusb_ref_device(libusb_device *dev); -void LIBUSB_CALL libusb_unref_device(libusb_device *dev); - -int LIBUSB_CALL libusb_get_configuration(libusb_device_handle *dev, - int *config); -int LIBUSB_CALL libusb_get_device_descriptor(libusb_device *dev, - struct libusb_device_descriptor *desc); -int LIBUSB_CALL libusb_get_active_config_descriptor(libusb_device *dev, - struct libusb_config_descriptor **config); -int LIBUSB_CALL libusb_get_config_descriptor(libusb_device *dev, - uint8_t config_index, struct libusb_config_descriptor **config); -int LIBUSB_CALL libusb_get_config_descriptor_by_value(libusb_device *dev, - uint8_t bConfigurationValue, struct libusb_config_descriptor **config); -void LIBUSB_CALL libusb_free_config_descriptor( - struct libusb_config_descriptor *config); -int LIBUSB_CALL libusb_get_ss_endpoint_companion_descriptor( - struct libusb_context *ctx, - const struct libusb_endpoint_descriptor *endpoint, - struct libusb_ss_endpoint_companion_descriptor **ep_comp); -void LIBUSB_CALL libusb_free_ss_endpoint_companion_descriptor( - struct libusb_ss_endpoint_companion_descriptor *ep_comp); -int LIBUSB_CALL libusb_get_bos_descriptor(libusb_device_handle *dev_handle, - struct libusb_bos_descriptor **bos); -void LIBUSB_CALL libusb_free_bos_descriptor(struct libusb_bos_descriptor *bos); -int LIBUSB_CALL libusb_get_usb_2_0_extension_descriptor( - struct libusb_context *ctx, - struct libusb_bos_dev_capability_descriptor *dev_cap, - struct libusb_usb_2_0_extension_descriptor **usb_2_0_extension); -void LIBUSB_CALL libusb_free_usb_2_0_extension_descriptor( - struct libusb_usb_2_0_extension_descriptor *usb_2_0_extension); -int LIBUSB_CALL libusb_get_ss_usb_device_capability_descriptor( - struct libusb_context *ctx, - struct libusb_bos_dev_capability_descriptor *dev_cap, - struct libusb_ss_usb_device_capability_descriptor **ss_usb_device_cap); -void LIBUSB_CALL libusb_free_ss_usb_device_capability_descriptor( - struct libusb_ss_usb_device_capability_descriptor *ss_usb_device_cap); -int LIBUSB_CALL libusb_get_container_id_descriptor(struct libusb_context *ctx, - struct libusb_bos_dev_capability_descriptor *dev_cap, - struct libusb_container_id_descriptor **container_id); -void LIBUSB_CALL libusb_free_container_id_descriptor( - struct libusb_container_id_descriptor *container_id); -uint8_t LIBUSB_CALL libusb_get_bus_number(libusb_device *dev); -uint8_t LIBUSB_CALL libusb_get_port_number(libusb_device *dev); -int LIBUSB_CALL libusb_get_port_numbers(libusb_device *dev, uint8_t* port_numbers, int port_numbers_len); -LIBUSB_DEPRECATED_FOR(libusb_get_port_numbers) -int LIBUSB_CALL libusb_get_port_path(libusb_context *ctx, libusb_device *dev, uint8_t* path, uint8_t path_length); -libusb_device * LIBUSB_CALL libusb_get_parent(libusb_device *dev); -uint8_t LIBUSB_CALL libusb_get_device_address(libusb_device *dev); -int LIBUSB_CALL libusb_get_device_speed(libusb_device *dev); -int LIBUSB_CALL libusb_get_max_packet_size(libusb_device *dev, - unsigned char endpoint); -int LIBUSB_CALL libusb_get_max_iso_packet_size(libusb_device *dev, - unsigned char endpoint); - -int LIBUSB_CALL libusb_open(libusb_device *dev, libusb_device_handle **dev_handle); -void LIBUSB_CALL libusb_close(libusb_device_handle *dev_handle); -libusb_device * LIBUSB_CALL libusb_get_device(libusb_device_handle *dev_handle); - -int LIBUSB_CALL libusb_set_configuration(libusb_device_handle *dev_handle, - int configuration); -int LIBUSB_CALL libusb_claim_interface(libusb_device_handle *dev_handle, - int interface_number); -int LIBUSB_CALL libusb_release_interface(libusb_device_handle *dev_handle, - int interface_number); - -libusb_device_handle * LIBUSB_CALL libusb_open_device_with_vid_pid( - libusb_context *ctx, uint16_t vendor_id, uint16_t product_id); - -int LIBUSB_CALL libusb_set_interface_alt_setting(libusb_device_handle *dev_handle, - int interface_number, int alternate_setting); -int LIBUSB_CALL libusb_clear_halt(libusb_device_handle *dev_handle, - unsigned char endpoint); -int LIBUSB_CALL libusb_reset_device(libusb_device_handle *dev_handle); - -int LIBUSB_CALL libusb_alloc_streams(libusb_device_handle *dev_handle, - uint32_t num_streams, unsigned char *endpoints, int num_endpoints); -int LIBUSB_CALL libusb_free_streams(libusb_device_handle *dev_handle, - unsigned char *endpoints, int num_endpoints); - -unsigned char * LIBUSB_CALL libusb_dev_mem_alloc(libusb_device_handle *dev_handle, - size_t length); -int LIBUSB_CALL libusb_dev_mem_free(libusb_device_handle *dev_handle, - unsigned char *buffer, size_t length); - -int LIBUSB_CALL libusb_kernel_driver_active(libusb_device_handle *dev_handle, - int interface_number); -int LIBUSB_CALL libusb_detach_kernel_driver(libusb_device_handle *dev_handle, - int interface_number); -int LIBUSB_CALL libusb_attach_kernel_driver(libusb_device_handle *dev_handle, - int interface_number); -int LIBUSB_CALL libusb_set_auto_detach_kernel_driver( - libusb_device_handle *dev_handle, int enable); - -/* async I/O */ - -/** \ingroup libusb_asyncio - * Get the data section of a control transfer. This convenience function is here - * to remind you that the data does not start until 8 bytes into the actual - * buffer, as the setup packet comes first. - * - * Calling this function only makes sense from a transfer callback function, - * or situations where you have already allocated a suitably sized buffer at - * transfer->buffer. - * - * \param transfer a transfer - * \returns pointer to the first byte of the data section - */ -static inline unsigned char *libusb_control_transfer_get_data( - struct libusb_transfer *transfer) -{ - return transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; -} - -/** \ingroup libusb_asyncio - * Get the control setup packet of a control transfer. This convenience - * function is here to remind you that the control setup occupies the first - * 8 bytes of the transfer data buffer. - * - * Calling this function only makes sense from a transfer callback function, - * or situations where you have already allocated a suitably sized buffer at - * transfer->buffer. - * - * \param transfer a transfer - * \returns a casted pointer to the start of the transfer data buffer - */ -static inline struct libusb_control_setup *libusb_control_transfer_get_setup( - struct libusb_transfer *transfer) -{ - return (struct libusb_control_setup *)(void *) transfer->buffer; -} - -/** \ingroup libusb_asyncio - * Helper function to populate the setup packet (first 8 bytes of the data - * buffer) for a control transfer. The wIndex, wValue and wLength values should - * be given in host-endian byte order. - * - * \param buffer buffer to output the setup packet into - * This pointer must be aligned to at least 2 bytes boundary. - * \param bmRequestType see the - * \ref libusb_control_setup::bmRequestType "bmRequestType" field of - * \ref libusb_control_setup - * \param bRequest see the - * \ref libusb_control_setup::bRequest "bRequest" field of - * \ref libusb_control_setup - * \param wValue see the - * \ref libusb_control_setup::wValue "wValue" field of - * \ref libusb_control_setup - * \param wIndex see the - * \ref libusb_control_setup::wIndex "wIndex" field of - * \ref libusb_control_setup - * \param wLength see the - * \ref libusb_control_setup::wLength "wLength" field of - * \ref libusb_control_setup - */ -static inline void libusb_fill_control_setup(unsigned char *buffer, - uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, - uint16_t wLength) -{ - struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *) buffer; - setup->bmRequestType = bmRequestType; - setup->bRequest = bRequest; - setup->wValue = libusb_cpu_to_le16(wValue); - setup->wIndex = libusb_cpu_to_le16(wIndex); - setup->wLength = libusb_cpu_to_le16(wLength); -} - -struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer(int iso_packets); -int LIBUSB_CALL libusb_submit_transfer(struct libusb_transfer *transfer); -int LIBUSB_CALL libusb_cancel_transfer(struct libusb_transfer *transfer); -void LIBUSB_CALL libusb_free_transfer(struct libusb_transfer *transfer); -void LIBUSB_CALL libusb_transfer_set_stream_id( - struct libusb_transfer *transfer, uint32_t stream_id); -uint32_t LIBUSB_CALL libusb_transfer_get_stream_id( - struct libusb_transfer *transfer); - -/** \ingroup libusb_asyncio - * Helper function to populate the required \ref libusb_transfer fields - * for a control transfer. - * - * If you pass a transfer buffer to this function, the first 8 bytes will - * be interpreted as a control setup packet, and the wLength field will be - * used to automatically populate the \ref libusb_transfer::length "length" - * field of the transfer. Therefore the recommended approach is: - * -# Allocate a suitably sized data buffer (including space for control setup) - * -# Call libusb_fill_control_setup() - * -# If this is a host-to-device transfer with a data stage, put the data - * in place after the setup packet - * -# Call this function - * -# Call libusb_submit_transfer() - * - * It is also legal to pass a NULL buffer to this function, in which case this - * function will not attempt to populate the length field. Remember that you - * must then populate the buffer and length fields later. - * - * \param transfer the transfer to populate - * \param dev_handle handle of the device that will handle the transfer - * \param buffer data buffer. If provided, this function will interpret the - * first 8 bytes as a setup packet and infer the transfer length from that. - * This pointer must be aligned to at least 2 bytes boundary. - * \param callback callback function to be invoked on transfer completion - * \param user_data user data to pass to callback function - * \param timeout timeout for the transfer in milliseconds - */ -static inline void libusb_fill_control_transfer( - struct libusb_transfer *transfer, libusb_device_handle *dev_handle, - unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data, - unsigned int timeout) -{ - struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *) buffer; - transfer->dev_handle = dev_handle; - transfer->endpoint = 0; - transfer->type = LIBUSB_TRANSFER_TYPE_CONTROL; - transfer->timeout = timeout; - transfer->buffer = buffer; - if (setup) - transfer->length = (int) (LIBUSB_CONTROL_SETUP_SIZE - + libusb_le16_to_cpu(setup->wLength)); - transfer->user_data = user_data; - transfer->callback = callback; -} - -/** \ingroup libusb_asyncio - * Helper function to populate the required \ref libusb_transfer fields - * for a bulk transfer. - * - * \param transfer the transfer to populate - * \param dev_handle handle of the device that will handle the transfer - * \param endpoint address of the endpoint where this transfer will be sent - * \param buffer data buffer - * \param length length of data buffer - * \param callback callback function to be invoked on transfer completion - * \param user_data user data to pass to callback function - * \param timeout timeout for the transfer in milliseconds - */ -static inline void libusb_fill_bulk_transfer(struct libusb_transfer *transfer, - libusb_device_handle *dev_handle, unsigned char endpoint, - unsigned char *buffer, int length, libusb_transfer_cb_fn callback, - void *user_data, unsigned int timeout) -{ - transfer->dev_handle = dev_handle; - transfer->endpoint = endpoint; - transfer->type = LIBUSB_TRANSFER_TYPE_BULK; - transfer->timeout = timeout; - transfer->buffer = buffer; - transfer->length = length; - transfer->user_data = user_data; - transfer->callback = callback; -} - -/** \ingroup libusb_asyncio - * Helper function to populate the required \ref libusb_transfer fields - * for a bulk transfer using bulk streams. - * - * Since version 1.0.19, \ref LIBUSB_API_VERSION >= 0x01000103 - * - * \param transfer the transfer to populate - * \param dev_handle handle of the device that will handle the transfer - * \param endpoint address of the endpoint where this transfer will be sent - * \param stream_id bulk stream id for this transfer - * \param buffer data buffer - * \param length length of data buffer - * \param callback callback function to be invoked on transfer completion - * \param user_data user data to pass to callback function - * \param timeout timeout for the transfer in milliseconds - */ -static inline void libusb_fill_bulk_stream_transfer( - struct libusb_transfer *transfer, libusb_device_handle *dev_handle, - unsigned char endpoint, uint32_t stream_id, - unsigned char *buffer, int length, libusb_transfer_cb_fn callback, - void *user_data, unsigned int timeout) -{ - libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, - length, callback, user_data, timeout); - transfer->type = LIBUSB_TRANSFER_TYPE_BULK_STREAM; - libusb_transfer_set_stream_id(transfer, stream_id); -} - -/** \ingroup libusb_asyncio - * Helper function to populate the required \ref libusb_transfer fields - * for an interrupt transfer. - * - * \param transfer the transfer to populate - * \param dev_handle handle of the device that will handle the transfer - * \param endpoint address of the endpoint where this transfer will be sent - * \param buffer data buffer - * \param length length of data buffer - * \param callback callback function to be invoked on transfer completion - * \param user_data user data to pass to callback function - * \param timeout timeout for the transfer in milliseconds - */ -static inline void libusb_fill_interrupt_transfer( - struct libusb_transfer *transfer, libusb_device_handle *dev_handle, - unsigned char endpoint, unsigned char *buffer, int length, - libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) -{ - transfer->dev_handle = dev_handle; - transfer->endpoint = endpoint; - transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; - transfer->timeout = timeout; - transfer->buffer = buffer; - transfer->length = length; - transfer->user_data = user_data; - transfer->callback = callback; -} - -/** \ingroup libusb_asyncio - * Helper function to populate the required \ref libusb_transfer fields - * for an isochronous transfer. - * - * \param transfer the transfer to populate - * \param dev_handle handle of the device that will handle the transfer - * \param endpoint address of the endpoint where this transfer will be sent - * \param buffer data buffer - * \param length length of data buffer - * \param num_iso_packets the number of isochronous packets - * \param callback callback function to be invoked on transfer completion - * \param user_data user data to pass to callback function - * \param timeout timeout for the transfer in milliseconds - */ -static inline void libusb_fill_iso_transfer(struct libusb_transfer *transfer, - libusb_device_handle *dev_handle, unsigned char endpoint, - unsigned char *buffer, int length, int num_iso_packets, - libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) -{ - transfer->dev_handle = dev_handle; - transfer->endpoint = endpoint; - transfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; - transfer->timeout = timeout; - transfer->buffer = buffer; - transfer->length = length; - transfer->num_iso_packets = num_iso_packets; - transfer->user_data = user_data; - transfer->callback = callback; -} - -/** \ingroup libusb_asyncio - * Convenience function to set the length of all packets in an isochronous - * transfer, based on the num_iso_packets field in the transfer structure. - * - * \param transfer a transfer - * \param length the length to set in each isochronous packet descriptor - * \see libusb_get_max_packet_size() - */ -static inline void libusb_set_iso_packet_lengths( - struct libusb_transfer *transfer, unsigned int length) -{ - int i; - for (i = 0; i < transfer->num_iso_packets; i++) - transfer->iso_packet_desc[i].length = length; -} - -/** \ingroup libusb_asyncio - * Convenience function to locate the position of an isochronous packet - * within the buffer of an isochronous transfer. - * - * This is a thorough function which loops through all preceding packets, - * accumulating their lengths to find the position of the specified packet. - * Typically you will assign equal lengths to each packet in the transfer, - * and hence the above method is sub-optimal. You may wish to use - * libusb_get_iso_packet_buffer_simple() instead. - * - * \param transfer a transfer - * \param packet the packet to return the address of - * \returns the base address of the packet buffer inside the transfer buffer, - * or NULL if the packet does not exist. - * \see libusb_get_iso_packet_buffer_simple() - */ -static inline unsigned char *libusb_get_iso_packet_buffer( - struct libusb_transfer *transfer, unsigned int packet) -{ - int i; - size_t offset = 0; - int _packet; - - /* oops..slight bug in the API. packet is an unsigned int, but we use - * signed integers almost everywhere else. range-check and convert to - * signed to avoid compiler warnings. FIXME for libusb-2. */ - if (packet > INT_MAX) - return NULL; - _packet = (int) packet; - - if (_packet >= transfer->num_iso_packets) - return NULL; - - for (i = 0; i < _packet; i++) - offset += transfer->iso_packet_desc[i].length; - - return transfer->buffer + offset; -} - -/** \ingroup libusb_asyncio - * Convenience function to locate the position of an isochronous packet - * within the buffer of an isochronous transfer, for transfers where each - * packet is of identical size. - * - * This function relies on the assumption that every packet within the transfer - * is of identical size to the first packet. Calculating the location of - * the packet buffer is then just a simple calculation: - * buffer + (packet_size * packet) - * - * Do not use this function on transfers other than those that have identical - * packet lengths for each packet. - * - * \param transfer a transfer - * \param packet the packet to return the address of - * \returns the base address of the packet buffer inside the transfer buffer, - * or NULL if the packet does not exist. - * \see libusb_get_iso_packet_buffer() - */ -static inline unsigned char *libusb_get_iso_packet_buffer_simple( - struct libusb_transfer *transfer, unsigned int packet) -{ - int _packet; - - /* oops..slight bug in the API. packet is an unsigned int, but we use - * signed integers almost everywhere else. range-check and convert to - * signed to avoid compiler warnings. FIXME for libusb-2. */ - if (packet > INT_MAX) - return NULL; - _packet = (int) packet; - - if (_packet >= transfer->num_iso_packets) - return NULL; - - return transfer->buffer + ((int) transfer->iso_packet_desc[0].length * _packet); -} - -/* sync I/O */ - -int LIBUSB_CALL libusb_control_transfer(libusb_device_handle *dev_handle, - uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, - unsigned char *data, uint16_t wLength, unsigned int timeout); - -int LIBUSB_CALL libusb_bulk_transfer(libusb_device_handle *dev_handle, - unsigned char endpoint, unsigned char *data, int length, - int *actual_length, unsigned int timeout); - -int LIBUSB_CALL libusb_interrupt_transfer(libusb_device_handle *dev_handle, - unsigned char endpoint, unsigned char *data, int length, - int *actual_length, unsigned int timeout); - -/** \ingroup libusb_desc - * Retrieve a descriptor from the default control pipe. - * This is a convenience function which formulates the appropriate control - * message to retrieve the descriptor. - * - * \param dev_handle a device handle - * \param desc_type the descriptor type, see \ref libusb_descriptor_type - * \param desc_index the index of the descriptor to retrieve - * \param data output buffer for descriptor - * \param length size of data buffer - * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure - */ -static inline int libusb_get_descriptor(libusb_device_handle *dev_handle, - uint8_t desc_type, uint8_t desc_index, unsigned char *data, int length) -{ - return libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN, - LIBUSB_REQUEST_GET_DESCRIPTOR, (uint16_t) ((desc_type << 8) | desc_index), - 0, data, (uint16_t) length, 1000); -} - -/** \ingroup libusb_desc - * Retrieve a descriptor from a device. - * This is a convenience function which formulates the appropriate control - * message to retrieve the descriptor. The string returned is Unicode, as - * detailed in the USB specifications. - * - * \param dev_handle a device handle - * \param desc_index the index of the descriptor to retrieve - * \param langid the language ID for the string descriptor - * \param data output buffer for descriptor - * \param length size of data buffer - * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure - * \see libusb_get_string_descriptor_ascii() - */ -static inline int libusb_get_string_descriptor(libusb_device_handle *dev_handle, - uint8_t desc_index, uint16_t langid, unsigned char *data, int length) -{ - return libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN, - LIBUSB_REQUEST_GET_DESCRIPTOR, (uint16_t)((LIBUSB_DT_STRING << 8) | desc_index), - langid, data, (uint16_t) length, 1000); -} - -int LIBUSB_CALL libusb_get_string_descriptor_ascii(libusb_device_handle *dev_handle, - uint8_t desc_index, unsigned char *data, int length); - -/* polling and timeouts */ - -int LIBUSB_CALL libusb_try_lock_events(libusb_context *ctx); -void LIBUSB_CALL libusb_lock_events(libusb_context *ctx); -void LIBUSB_CALL libusb_unlock_events(libusb_context *ctx); -int LIBUSB_CALL libusb_event_handling_ok(libusb_context *ctx); -int LIBUSB_CALL libusb_event_handler_active(libusb_context *ctx); -void LIBUSB_CALL libusb_interrupt_event_handler(libusb_context *ctx); -void LIBUSB_CALL libusb_lock_event_waiters(libusb_context *ctx); -void LIBUSB_CALL libusb_unlock_event_waiters(libusb_context *ctx); -int LIBUSB_CALL libusb_wait_for_event(libusb_context *ctx, struct timeval *tv); - -int LIBUSB_CALL libusb_handle_events_timeout(libusb_context *ctx, - struct timeval *tv); -int LIBUSB_CALL libusb_handle_events_timeout_completed(libusb_context *ctx, - struct timeval *tv, int *completed); -int LIBUSB_CALL libusb_handle_events(libusb_context *ctx); -int LIBUSB_CALL libusb_handle_events_completed(libusb_context *ctx, int *completed); -int LIBUSB_CALL libusb_handle_events_locked(libusb_context *ctx, - struct timeval *tv); -int LIBUSB_CALL libusb_pollfds_handle_timeouts(libusb_context *ctx); -int LIBUSB_CALL libusb_get_next_timeout(libusb_context *ctx, - struct timeval *tv); - -/** \ingroup libusb_poll - * File descriptor for polling - */ -struct libusb_pollfd { - /** Numeric file descriptor */ - int fd; - - /** Event flags to poll for from . POLLIN indicates that you - * should monitor this file descriptor for becoming ready to read from, - * and POLLOUT indicates that you should monitor this file descriptor for - * nonblocking write readiness. */ - short events; -}; - -/** \ingroup libusb_poll - * Callback function, invoked when a new file descriptor should be added - * to the set of file descriptors monitored for events. - * \param fd the new file descriptor - * \param events events to monitor for, see \ref libusb_pollfd for a - * description - * \param user_data User data pointer specified in - * libusb_set_pollfd_notifiers() call - * \see libusb_set_pollfd_notifiers() - */ -typedef void (LIBUSB_CALL *libusb_pollfd_added_cb)(int fd, short events, - void *user_data); - -/** \ingroup libusb_poll - * Callback function, invoked when a file descriptor should be removed from - * the set of file descriptors being monitored for events. After returning - * from this callback, do not use that file descriptor again. - * \param fd the file descriptor to stop monitoring - * \param user_data User data pointer specified in - * libusb_set_pollfd_notifiers() call - * \see libusb_set_pollfd_notifiers() - */ -typedef void (LIBUSB_CALL *libusb_pollfd_removed_cb)(int fd, void *user_data); - -const struct libusb_pollfd ** LIBUSB_CALL libusb_get_pollfds( - libusb_context *ctx); -void LIBUSB_CALL libusb_free_pollfds(const struct libusb_pollfd **pollfds); -void LIBUSB_CALL libusb_set_pollfd_notifiers(libusb_context *ctx, - libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb, - void *user_data); - -/** \ingroup libusb_hotplug - * Callback handle. - * - * Callbacks handles are generated by libusb_hotplug_register_callback() - * and can be used to deregister callbacks. Callback handles are unique - * per libusb_context and it is safe to call libusb_hotplug_deregister_callback() - * on an already deregisted callback. - * - * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 - * - * For more information, see \ref libusb_hotplug. - */ -typedef int libusb_hotplug_callback_handle; - -/** \ingroup libusb_hotplug - * - * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 - * - * Flags for hotplug events */ -typedef enum { - /** Default value when not using any flags. */ - LIBUSB_HOTPLUG_NO_FLAGS = 0, - - /** Arm the callback and fire it for all matching currently attached devices. */ - LIBUSB_HOTPLUG_ENUMERATE = 1<<0, -} libusb_hotplug_flag; - -/** \ingroup libusb_hotplug - * - * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 - * - * Hotplug events */ -typedef enum { - /** A device has been plugged in and is ready to use */ - LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED = 0x01, - - /** A device has left and is no longer available. - * It is the user's responsibility to call libusb_close on any handle associated with a disconnected device. - * It is safe to call libusb_get_device_descriptor on a device that has left */ - LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT = 0x02, -} libusb_hotplug_event; - -/** \ingroup libusb_hotplug - * Wildcard matching for hotplug events */ -#define LIBUSB_HOTPLUG_MATCH_ANY -1 - -/** \ingroup libusb_hotplug - * Hotplug callback function type. When requesting hotplug event notifications, - * you pass a pointer to a callback function of this type. - * - * This callback may be called by an internal event thread and as such it is - * recommended the callback do minimal processing before returning. - * - * libusb will call this function later, when a matching event had happened on - * a matching device. See \ref libusb_hotplug for more information. - * - * It is safe to call either libusb_hotplug_register_callback() or - * libusb_hotplug_deregister_callback() from within a callback function. - * - * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 - * - * \param ctx context of this notification - * \param device libusb_device this event occurred on - * \param event event that occurred - * \param user_data user data provided when this callback was registered - * \returns bool whether this callback is finished processing events. - * returning 1 will cause this callback to be deregistered - */ -typedef int (LIBUSB_CALL *libusb_hotplug_callback_fn)(libusb_context *ctx, - libusb_device *device, - libusb_hotplug_event event, - void *user_data); - -/** \ingroup libusb_hotplug - * Register a hotplug callback function - * - * Register a callback with the libusb_context. The callback will fire - * when a matching event occurs on a matching device. The callback is - * armed until either it is deregistered with libusb_hotplug_deregister_callback() - * or the supplied callback returns 1 to indicate it is finished processing events. - * - * If the \ref LIBUSB_HOTPLUG_ENUMERATE is passed the callback will be - * called with a \ref LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED for all devices - * already plugged into the machine. Note that libusb modifies its internal - * device list from a separate thread, while calling hotplug callbacks from - * libusb_handle_events(), so it is possible for a device to already be present - * on, or removed from, its internal device list, while the hotplug callbacks - * still need to be dispatched. This means that when using \ref - * LIBUSB_HOTPLUG_ENUMERATE, your callback may be called twice for the arrival - * of the same device, once from libusb_hotplug_register_callback() and once - * from libusb_handle_events(); and/or your callback may be called for the - * removal of a device for which an arrived call was never made. - * - * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 - * - * \param[in] ctx context to register this callback with - * \param[in] events bitwise or of events that will trigger this callback. See \ref - * libusb_hotplug_event - * \param[in] flags hotplug callback flags. See \ref libusb_hotplug_flag - * \param[in] vendor_id the vendor id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY - * \param[in] product_id the product id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY - * \param[in] dev_class the device class to match or \ref LIBUSB_HOTPLUG_MATCH_ANY - * \param[in] cb_fn the function to be invoked on a matching event/device - * \param[in] user_data user data to pass to the callback function - * \param[out] callback_handle pointer to store the handle of the allocated callback (can be NULL) - * \returns LIBUSB_SUCCESS on success LIBUSB_ERROR code on failure - */ -int LIBUSB_CALL libusb_hotplug_register_callback(libusb_context *ctx, - libusb_hotplug_event events, - libusb_hotplug_flag flags, - int vendor_id, int product_id, - int dev_class, - libusb_hotplug_callback_fn cb_fn, - void *user_data, - libusb_hotplug_callback_handle *callback_handle); - -/** \ingroup libusb_hotplug - * Deregisters a hotplug callback. - * - * Deregister a callback from a libusb_context. This function is safe to call from within - * a hotplug callback. - * - * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 - * - * \param[in] ctx context this callback is registered with - * \param[in] callback_handle the handle of the callback to deregister - */ -void LIBUSB_CALL libusb_hotplug_deregister_callback(libusb_context *ctx, - libusb_hotplug_callback_handle callback_handle); - -#ifdef __cplusplus -} -#endif - -#endif From 246d016210b0629cf173ad7fe76ecb5d3912fec2 Mon Sep 17 00:00:00 2001 From: Belkin Date: Wed, 10 Oct 2018 11:02:47 +0300 Subject: [PATCH 16/25] Move libtm library from third party to src directory --- CMakeLists.txt | 6 ++++-- {third-party => src/tm2}/libtm/.gitignore | 0 {third-party => src/tm2}/libtm/CMakeLists.txt | 0 {third-party => src/tm2}/libtm/cmake/linux.cmake | 0 {third-party => src/tm2}/libtm/cmake/os.cmake | 0 {third-party => src/tm2}/libtm/cmake/windows.cmake | 0 {third-party => src/tm2}/libtm/infra/config.cmake | 0 {third-party => src/tm2}/libtm/infra/include/Dispatcher.h | 0 {third-party => src/tm2}/libtm/infra/include/EmbeddedList.h | 0 {third-party => src/tm2}/libtm/infra/include/Event.h | 0 {third-party => src/tm2}/libtm/infra/include/EventHandler.h | 0 {third-party => src/tm2}/libtm/infra/include/Fence.h | 0 {third-party => src/tm2}/libtm/infra/include/Fsm.h | 0 {third-party => src/tm2}/libtm/infra/include/Log.h | 0 {third-party => src/tm2}/libtm/infra/include/Poller.h | 0 {third-party => src/tm2}/libtm/infra/include/Semaphore.h | 0 {third-party => src/tm2}/libtm/infra/include/Utils.h | 0 {third-party => src/tm2}/libtm/infra/src/Dispatcher.cpp | 0 {third-party => src/tm2}/libtm/infra/src/Event_lin.cpp | 0 {third-party => src/tm2}/libtm/infra/src/Event_win.cpp | 0 {third-party => src/tm2}/libtm/infra/src/Fsm.cpp | 0 {third-party => src/tm2}/libtm/infra/src/Log.cpp | 0 {third-party => src/tm2}/libtm/infra/src/Poller_lin.cpp | 0 {third-party => src/tm2}/libtm/infra/src/Poller_win.cpp | 0 {third-party => src/tm2}/libtm/infra/src/Semaphore_lin.cpp | 0 {third-party => src/tm2}/libtm/infra/src/Semaphore_win.cpp | 0 {third-party => src/tm2}/libtm/infra/src/Utils.cpp | 0 {third-party => src/tm2}/libtm/libtm/CMakeLists.txt | 0 .../tm2}/libtm/libtm/include/TrackingCommon.h | 0 {third-party => src/tm2}/libtm/libtm/include/TrackingData.h | 0 .../tm2}/libtm/libtm/include/TrackingDevice.h | 0 .../tm2}/libtm/libtm/include/TrackingManager.h | 0 .../tm2}/libtm/libtm/include/TrackingSerializer.h | 0 {third-party => src/tm2}/libtm/libtm/src/.bdsignore | 0 {third-party => src/tm2}/libtm/libtm/src/CMakeLists.txt | 2 ++ {third-party => src/tm2}/libtm/libtm/src/Common.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/Common.h | 0 {third-party => src/tm2}/libtm/libtm/src/CompleteTask.h | 0 {third-party => src/tm2}/libtm/libtm/src/Device.cpp | 1 + {third-party => src/tm2}/libtm/libtm/src/Device.h | 0 {third-party => src/tm2}/libtm/libtm/src/Loader.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/Loader.h | 0 {third-party => src/tm2}/libtm/libtm/src/Main.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/Manager.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/Manager.h | 0 {third-party => src/tm2}/libtm/libtm/src/Message.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/Message.h | 0 {third-party => src/tm2}/libtm/libtm/src/Protocol.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/Protocol.h | 0 .../tm2}/libtm/libtm/src/RcSerializer/Packet.cpp | 0 .../tm2}/libtm/libtm/src/RcSerializer/Packet.h | 0 .../tm2}/libtm/libtm/src/RcSerializer/Player.cpp | 0 .../tm2}/libtm/libtm/src/RcSerializer/Player.h | 0 .../tm2}/libtm/libtm/src/RcSerializer/Recorder.cpp | 0 .../tm2}/libtm/libtm/src/RcSerializer/Recorder.h | 0 .../tm2}/libtm/libtm/src/RcSerializer/concurrency.h | 0 .../tm2}/libtm/libtm/src/RcSerializer/latency_queue.h | 0 .../tm2}/libtm/libtm/src/RcSerializer/rc_packet.h | 0 .../tm2}/libtm/libtm/src/UsbPlugListener.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/UsbPlugListener.h | 0 {third-party => src/tm2}/libtm/libtm/src/Version.h.in | 0 .../tm2}/libtm/libtm/src/infra/CMakeLists.txt | 0 .../tm2}/libtm/libtm/src/infra/Dispatcher.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Dispatcher.h | 0 .../tm2}/libtm/libtm/src/infra/EmbeddedList.h | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Event.h | 0 .../tm2}/libtm/libtm/src/infra/EventHandler.h | 0 .../tm2}/libtm/libtm/src/infra/Event_lin.cpp | 0 .../tm2}/libtm/libtm/src/infra/Event_win.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Fence.h | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Fsm.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Fsm.h | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Log.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Log.h | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Poller.h | 0 .../tm2}/libtm/libtm/src/infra/Poller_lin.cpp | 0 .../tm2}/libtm/libtm/src/infra/Poller_win.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Semaphore.h | 0 .../tm2}/libtm/libtm/src/infra/Semaphore_lin.cpp | 0 .../tm2}/libtm/libtm/src/infra/Semaphore_win.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Utils.cpp | 0 {third-party => src/tm2}/libtm/libtm/src/infra/Utils.h | 0 {third-party => src/tm2}/libtm/libtm/src/version.rc.in | 0 {third-party => src/tm2}/libtm/resources/CMakeLists.txt | 0 {third-party => src/tm2}/libtm/tools/CMakeLists.txt | 0 .../tm2}/libtm/tools/libtm_util/CMakeLists.txt | 0 .../tm2}/libtm/tools/libtm_util/libtm_util.cpp | 0 {third-party => src/tm2}/libtm/versions.cmake | 0 88 files changed, 7 insertions(+), 2 deletions(-) rename {third-party => src/tm2}/libtm/.gitignore (100%) rename {third-party => src/tm2}/libtm/CMakeLists.txt (100%) rename {third-party => src/tm2}/libtm/cmake/linux.cmake (100%) rename {third-party => src/tm2}/libtm/cmake/os.cmake (100%) rename {third-party => src/tm2}/libtm/cmake/windows.cmake (100%) rename {third-party => src/tm2}/libtm/infra/config.cmake (100%) rename {third-party => src/tm2}/libtm/infra/include/Dispatcher.h (100%) rename {third-party => src/tm2}/libtm/infra/include/EmbeddedList.h (100%) rename {third-party => src/tm2}/libtm/infra/include/Event.h (100%) rename {third-party => src/tm2}/libtm/infra/include/EventHandler.h (100%) rename {third-party => src/tm2}/libtm/infra/include/Fence.h (100%) rename {third-party => src/tm2}/libtm/infra/include/Fsm.h (100%) rename {third-party => src/tm2}/libtm/infra/include/Log.h (100%) rename {third-party => src/tm2}/libtm/infra/include/Poller.h (100%) rename {third-party => src/tm2}/libtm/infra/include/Semaphore.h (100%) rename {third-party => src/tm2}/libtm/infra/include/Utils.h (100%) rename {third-party => src/tm2}/libtm/infra/src/Dispatcher.cpp (100%) rename {third-party => src/tm2}/libtm/infra/src/Event_lin.cpp (100%) rename {third-party => src/tm2}/libtm/infra/src/Event_win.cpp (100%) rename {third-party => src/tm2}/libtm/infra/src/Fsm.cpp (100%) rename {third-party => src/tm2}/libtm/infra/src/Log.cpp (100%) rename {third-party => src/tm2}/libtm/infra/src/Poller_lin.cpp (100%) rename {third-party => src/tm2}/libtm/infra/src/Poller_win.cpp (100%) rename {third-party => src/tm2}/libtm/infra/src/Semaphore_lin.cpp (100%) rename {third-party => src/tm2}/libtm/infra/src/Semaphore_win.cpp (100%) rename {third-party => src/tm2}/libtm/infra/src/Utils.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/CMakeLists.txt (100%) rename {third-party => src/tm2}/libtm/libtm/include/TrackingCommon.h (100%) rename {third-party => src/tm2}/libtm/libtm/include/TrackingData.h (100%) rename {third-party => src/tm2}/libtm/libtm/include/TrackingDevice.h (100%) rename {third-party => src/tm2}/libtm/libtm/include/TrackingManager.h (100%) rename {third-party => src/tm2}/libtm/libtm/include/TrackingSerializer.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/.bdsignore (100%) rename {third-party => src/tm2}/libtm/libtm/src/CMakeLists.txt (98%) rename {third-party => src/tm2}/libtm/libtm/src/Common.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/Common.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/CompleteTask.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/Device.cpp (99%) rename {third-party => src/tm2}/libtm/libtm/src/Device.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/Loader.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/Loader.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/Main.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/Manager.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/Manager.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/Message.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/Message.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/Protocol.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/Protocol.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/RcSerializer/Packet.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/RcSerializer/Packet.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/RcSerializer/Player.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/RcSerializer/Player.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/RcSerializer/Recorder.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/RcSerializer/Recorder.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/RcSerializer/concurrency.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/RcSerializer/latency_queue.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/RcSerializer/rc_packet.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/UsbPlugListener.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/UsbPlugListener.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/Version.h.in (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/CMakeLists.txt (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Dispatcher.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Dispatcher.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/EmbeddedList.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Event.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/EventHandler.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Event_lin.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Event_win.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Fence.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Fsm.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Fsm.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Log.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Log.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Poller.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Poller_lin.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Poller_win.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Semaphore.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Semaphore_lin.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Semaphore_win.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Utils.cpp (100%) rename {third-party => src/tm2}/libtm/libtm/src/infra/Utils.h (100%) rename {third-party => src/tm2}/libtm/libtm/src/version.rc.in (100%) rename {third-party => src/tm2}/libtm/resources/CMakeLists.txt (100%) rename {third-party => src/tm2}/libtm/tools/CMakeLists.txt (100%) rename {third-party => src/tm2}/libtm/tools/libtm_util/CMakeLists.txt (100%) rename {third-party => src/tm2}/libtm/tools/libtm_util/libtm_util.cpp (100%) rename {third-party => src/tm2}/libtm/versions.cmake (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 260e96c79a..a719904dc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -827,7 +827,7 @@ add_subdirectory(third-party/realsense-file) if (BUILD_WITH_TM2) message(STATUS "Building with TM2") - add_subdirectory(third-party/libtm) + add_subdirectory(src/tm2/libtm) if(WIN32) source_group("Source Files\\Devices\\Tracking" FILES @@ -860,6 +860,8 @@ if (BUILD_WITH_TM2) src/tm2/tm-device.cpp src/tm2/tm-info.cpp ) + + add_dependencies(tm libusb) endif() if(LRS_TRY_USE_AVX) @@ -888,7 +890,7 @@ endif() if(BUILD_WITH_TM2) target_compile_definitions(realsense2 PRIVATE WITH_TRACKING=1 BUILD_STATIC=1) target_link_libraries(realsense2 PRIVATE tm ${CMAKE_THREAD_LIBS_INIT} ${TRACKING_DEVICE_LIBS}) - target_include_directories(realsense2 PRIVATE third-party/libtm/libtm/include) + target_include_directories(realsense2 PRIVATE src/tm2/libtm/libtm/include) endif() set_target_properties(realsense2 PROPERTIES VERSION ${REALSENSE_VERSION_STRING} SOVERSION ${REALSENSE_VERSION_MAJOR}) diff --git a/third-party/libtm/.gitignore b/src/tm2/libtm/.gitignore similarity index 100% rename from third-party/libtm/.gitignore rename to src/tm2/libtm/.gitignore diff --git a/third-party/libtm/CMakeLists.txt b/src/tm2/libtm/CMakeLists.txt similarity index 100% rename from third-party/libtm/CMakeLists.txt rename to src/tm2/libtm/CMakeLists.txt diff --git a/third-party/libtm/cmake/linux.cmake b/src/tm2/libtm/cmake/linux.cmake similarity index 100% rename from third-party/libtm/cmake/linux.cmake rename to src/tm2/libtm/cmake/linux.cmake diff --git a/third-party/libtm/cmake/os.cmake b/src/tm2/libtm/cmake/os.cmake similarity index 100% rename from third-party/libtm/cmake/os.cmake rename to src/tm2/libtm/cmake/os.cmake diff --git a/third-party/libtm/cmake/windows.cmake b/src/tm2/libtm/cmake/windows.cmake similarity index 100% rename from third-party/libtm/cmake/windows.cmake rename to src/tm2/libtm/cmake/windows.cmake diff --git a/third-party/libtm/infra/config.cmake b/src/tm2/libtm/infra/config.cmake similarity index 100% rename from third-party/libtm/infra/config.cmake rename to src/tm2/libtm/infra/config.cmake diff --git a/third-party/libtm/infra/include/Dispatcher.h b/src/tm2/libtm/infra/include/Dispatcher.h similarity index 100% rename from third-party/libtm/infra/include/Dispatcher.h rename to src/tm2/libtm/infra/include/Dispatcher.h diff --git a/third-party/libtm/infra/include/EmbeddedList.h b/src/tm2/libtm/infra/include/EmbeddedList.h similarity index 100% rename from third-party/libtm/infra/include/EmbeddedList.h rename to src/tm2/libtm/infra/include/EmbeddedList.h diff --git a/third-party/libtm/infra/include/Event.h b/src/tm2/libtm/infra/include/Event.h similarity index 100% rename from third-party/libtm/infra/include/Event.h rename to src/tm2/libtm/infra/include/Event.h diff --git a/third-party/libtm/infra/include/EventHandler.h b/src/tm2/libtm/infra/include/EventHandler.h similarity index 100% rename from third-party/libtm/infra/include/EventHandler.h rename to src/tm2/libtm/infra/include/EventHandler.h diff --git a/third-party/libtm/infra/include/Fence.h b/src/tm2/libtm/infra/include/Fence.h similarity index 100% rename from third-party/libtm/infra/include/Fence.h rename to src/tm2/libtm/infra/include/Fence.h diff --git a/third-party/libtm/infra/include/Fsm.h b/src/tm2/libtm/infra/include/Fsm.h similarity index 100% rename from third-party/libtm/infra/include/Fsm.h rename to src/tm2/libtm/infra/include/Fsm.h diff --git a/third-party/libtm/infra/include/Log.h b/src/tm2/libtm/infra/include/Log.h similarity index 100% rename from third-party/libtm/infra/include/Log.h rename to src/tm2/libtm/infra/include/Log.h diff --git a/third-party/libtm/infra/include/Poller.h b/src/tm2/libtm/infra/include/Poller.h similarity index 100% rename from third-party/libtm/infra/include/Poller.h rename to src/tm2/libtm/infra/include/Poller.h diff --git a/third-party/libtm/infra/include/Semaphore.h b/src/tm2/libtm/infra/include/Semaphore.h similarity index 100% rename from third-party/libtm/infra/include/Semaphore.h rename to src/tm2/libtm/infra/include/Semaphore.h diff --git a/third-party/libtm/infra/include/Utils.h b/src/tm2/libtm/infra/include/Utils.h similarity index 100% rename from third-party/libtm/infra/include/Utils.h rename to src/tm2/libtm/infra/include/Utils.h diff --git a/third-party/libtm/infra/src/Dispatcher.cpp b/src/tm2/libtm/infra/src/Dispatcher.cpp similarity index 100% rename from third-party/libtm/infra/src/Dispatcher.cpp rename to src/tm2/libtm/infra/src/Dispatcher.cpp diff --git a/third-party/libtm/infra/src/Event_lin.cpp b/src/tm2/libtm/infra/src/Event_lin.cpp similarity index 100% rename from third-party/libtm/infra/src/Event_lin.cpp rename to src/tm2/libtm/infra/src/Event_lin.cpp diff --git a/third-party/libtm/infra/src/Event_win.cpp b/src/tm2/libtm/infra/src/Event_win.cpp similarity index 100% rename from third-party/libtm/infra/src/Event_win.cpp rename to src/tm2/libtm/infra/src/Event_win.cpp diff --git a/third-party/libtm/infra/src/Fsm.cpp b/src/tm2/libtm/infra/src/Fsm.cpp similarity index 100% rename from third-party/libtm/infra/src/Fsm.cpp rename to src/tm2/libtm/infra/src/Fsm.cpp diff --git a/third-party/libtm/infra/src/Log.cpp b/src/tm2/libtm/infra/src/Log.cpp similarity index 100% rename from third-party/libtm/infra/src/Log.cpp rename to src/tm2/libtm/infra/src/Log.cpp diff --git a/third-party/libtm/infra/src/Poller_lin.cpp b/src/tm2/libtm/infra/src/Poller_lin.cpp similarity index 100% rename from third-party/libtm/infra/src/Poller_lin.cpp rename to src/tm2/libtm/infra/src/Poller_lin.cpp diff --git a/third-party/libtm/infra/src/Poller_win.cpp b/src/tm2/libtm/infra/src/Poller_win.cpp similarity index 100% rename from third-party/libtm/infra/src/Poller_win.cpp rename to src/tm2/libtm/infra/src/Poller_win.cpp diff --git a/third-party/libtm/infra/src/Semaphore_lin.cpp b/src/tm2/libtm/infra/src/Semaphore_lin.cpp similarity index 100% rename from third-party/libtm/infra/src/Semaphore_lin.cpp rename to src/tm2/libtm/infra/src/Semaphore_lin.cpp diff --git a/third-party/libtm/infra/src/Semaphore_win.cpp b/src/tm2/libtm/infra/src/Semaphore_win.cpp similarity index 100% rename from third-party/libtm/infra/src/Semaphore_win.cpp rename to src/tm2/libtm/infra/src/Semaphore_win.cpp diff --git a/third-party/libtm/infra/src/Utils.cpp b/src/tm2/libtm/infra/src/Utils.cpp similarity index 100% rename from third-party/libtm/infra/src/Utils.cpp rename to src/tm2/libtm/infra/src/Utils.cpp diff --git a/third-party/libtm/libtm/CMakeLists.txt b/src/tm2/libtm/libtm/CMakeLists.txt similarity index 100% rename from third-party/libtm/libtm/CMakeLists.txt rename to src/tm2/libtm/libtm/CMakeLists.txt diff --git a/third-party/libtm/libtm/include/TrackingCommon.h b/src/tm2/libtm/libtm/include/TrackingCommon.h similarity index 100% rename from third-party/libtm/libtm/include/TrackingCommon.h rename to src/tm2/libtm/libtm/include/TrackingCommon.h diff --git a/third-party/libtm/libtm/include/TrackingData.h b/src/tm2/libtm/libtm/include/TrackingData.h similarity index 100% rename from third-party/libtm/libtm/include/TrackingData.h rename to src/tm2/libtm/libtm/include/TrackingData.h diff --git a/third-party/libtm/libtm/include/TrackingDevice.h b/src/tm2/libtm/libtm/include/TrackingDevice.h similarity index 100% rename from third-party/libtm/libtm/include/TrackingDevice.h rename to src/tm2/libtm/libtm/include/TrackingDevice.h diff --git a/third-party/libtm/libtm/include/TrackingManager.h b/src/tm2/libtm/libtm/include/TrackingManager.h similarity index 100% rename from third-party/libtm/libtm/include/TrackingManager.h rename to src/tm2/libtm/libtm/include/TrackingManager.h diff --git a/third-party/libtm/libtm/include/TrackingSerializer.h b/src/tm2/libtm/libtm/include/TrackingSerializer.h similarity index 100% rename from third-party/libtm/libtm/include/TrackingSerializer.h rename to src/tm2/libtm/libtm/include/TrackingSerializer.h diff --git a/third-party/libtm/libtm/src/.bdsignore b/src/tm2/libtm/libtm/src/.bdsignore similarity index 100% rename from third-party/libtm/libtm/src/.bdsignore rename to src/tm2/libtm/libtm/src/.bdsignore diff --git a/third-party/libtm/libtm/src/CMakeLists.txt b/src/tm2/libtm/libtm/src/CMakeLists.txt similarity index 98% rename from third-party/libtm/libtm/src/CMakeLists.txt rename to src/tm2/libtm/libtm/src/CMakeLists.txt index 89c311e21f..476706a175 100644 --- a/third-party/libtm/libtm/src/CMakeLists.txt +++ b/src/tm2/libtm/libtm/src/CMakeLists.txt @@ -6,6 +6,7 @@ project(tm) include_directories(${LIBTM_INCLUDE_DIR} ${INFRA_INCLUDE_DIR} ${LIBUSB_LOCAL_INCLUDE_PATH} + ${CMAKE_SOURCE_DIR}/src ) #Source Files @@ -84,3 +85,4 @@ install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) + diff --git a/third-party/libtm/libtm/src/Common.cpp b/src/tm2/libtm/libtm/src/Common.cpp similarity index 100% rename from third-party/libtm/libtm/src/Common.cpp rename to src/tm2/libtm/libtm/src/Common.cpp diff --git a/third-party/libtm/libtm/src/Common.h b/src/tm2/libtm/libtm/src/Common.h similarity index 100% rename from third-party/libtm/libtm/src/Common.h rename to src/tm2/libtm/libtm/src/Common.h diff --git a/third-party/libtm/libtm/src/CompleteTask.h b/src/tm2/libtm/libtm/src/CompleteTask.h similarity index 100% rename from third-party/libtm/libtm/src/CompleteTask.h rename to src/tm2/libtm/libtm/src/CompleteTask.h diff --git a/third-party/libtm/libtm/src/Device.cpp b/src/tm2/libtm/libtm/src/Device.cpp similarity index 99% rename from third-party/libtm/libtm/src/Device.cpp rename to src/tm2/libtm/libtm/src/Device.cpp index 9f08b63620..7243032593 100644 --- a/third-party/libtm/libtm/src/Device.cpp +++ b/src/tm2/libtm/libtm/src/Device.cpp @@ -22,6 +22,7 @@ Copyright(c) 2017 Intel Corporation. All Rights Reserved. #include "TrackingData.h" #include "CentralAppFw.h" #include "CentralBlFw.h" +//#include "types.h" #define CHUNK_SIZE 512 #define BUFFER_SIZE 1024 diff --git a/third-party/libtm/libtm/src/Device.h b/src/tm2/libtm/libtm/src/Device.h similarity index 100% rename from third-party/libtm/libtm/src/Device.h rename to src/tm2/libtm/libtm/src/Device.h diff --git a/third-party/libtm/libtm/src/Loader.cpp b/src/tm2/libtm/libtm/src/Loader.cpp similarity index 100% rename from third-party/libtm/libtm/src/Loader.cpp rename to src/tm2/libtm/libtm/src/Loader.cpp diff --git a/third-party/libtm/libtm/src/Loader.h b/src/tm2/libtm/libtm/src/Loader.h similarity index 100% rename from third-party/libtm/libtm/src/Loader.h rename to src/tm2/libtm/libtm/src/Loader.h diff --git a/third-party/libtm/libtm/src/Main.cpp b/src/tm2/libtm/libtm/src/Main.cpp similarity index 100% rename from third-party/libtm/libtm/src/Main.cpp rename to src/tm2/libtm/libtm/src/Main.cpp diff --git a/third-party/libtm/libtm/src/Manager.cpp b/src/tm2/libtm/libtm/src/Manager.cpp similarity index 100% rename from third-party/libtm/libtm/src/Manager.cpp rename to src/tm2/libtm/libtm/src/Manager.cpp diff --git a/third-party/libtm/libtm/src/Manager.h b/src/tm2/libtm/libtm/src/Manager.h similarity index 100% rename from third-party/libtm/libtm/src/Manager.h rename to src/tm2/libtm/libtm/src/Manager.h diff --git a/third-party/libtm/libtm/src/Message.cpp b/src/tm2/libtm/libtm/src/Message.cpp similarity index 100% rename from third-party/libtm/libtm/src/Message.cpp rename to src/tm2/libtm/libtm/src/Message.cpp diff --git a/third-party/libtm/libtm/src/Message.h b/src/tm2/libtm/libtm/src/Message.h similarity index 100% rename from third-party/libtm/libtm/src/Message.h rename to src/tm2/libtm/libtm/src/Message.h diff --git a/third-party/libtm/libtm/src/Protocol.cpp b/src/tm2/libtm/libtm/src/Protocol.cpp similarity index 100% rename from third-party/libtm/libtm/src/Protocol.cpp rename to src/tm2/libtm/libtm/src/Protocol.cpp diff --git a/third-party/libtm/libtm/src/Protocol.h b/src/tm2/libtm/libtm/src/Protocol.h similarity index 100% rename from third-party/libtm/libtm/src/Protocol.h rename to src/tm2/libtm/libtm/src/Protocol.h diff --git a/third-party/libtm/libtm/src/RcSerializer/Packet.cpp b/src/tm2/libtm/libtm/src/RcSerializer/Packet.cpp similarity index 100% rename from third-party/libtm/libtm/src/RcSerializer/Packet.cpp rename to src/tm2/libtm/libtm/src/RcSerializer/Packet.cpp diff --git a/third-party/libtm/libtm/src/RcSerializer/Packet.h b/src/tm2/libtm/libtm/src/RcSerializer/Packet.h similarity index 100% rename from third-party/libtm/libtm/src/RcSerializer/Packet.h rename to src/tm2/libtm/libtm/src/RcSerializer/Packet.h diff --git a/third-party/libtm/libtm/src/RcSerializer/Player.cpp b/src/tm2/libtm/libtm/src/RcSerializer/Player.cpp similarity index 100% rename from third-party/libtm/libtm/src/RcSerializer/Player.cpp rename to src/tm2/libtm/libtm/src/RcSerializer/Player.cpp diff --git a/third-party/libtm/libtm/src/RcSerializer/Player.h b/src/tm2/libtm/libtm/src/RcSerializer/Player.h similarity index 100% rename from third-party/libtm/libtm/src/RcSerializer/Player.h rename to src/tm2/libtm/libtm/src/RcSerializer/Player.h diff --git a/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp b/src/tm2/libtm/libtm/src/RcSerializer/Recorder.cpp similarity index 100% rename from third-party/libtm/libtm/src/RcSerializer/Recorder.cpp rename to src/tm2/libtm/libtm/src/RcSerializer/Recorder.cpp diff --git a/third-party/libtm/libtm/src/RcSerializer/Recorder.h b/src/tm2/libtm/libtm/src/RcSerializer/Recorder.h similarity index 100% rename from third-party/libtm/libtm/src/RcSerializer/Recorder.h rename to src/tm2/libtm/libtm/src/RcSerializer/Recorder.h diff --git a/third-party/libtm/libtm/src/RcSerializer/concurrency.h b/src/tm2/libtm/libtm/src/RcSerializer/concurrency.h similarity index 100% rename from third-party/libtm/libtm/src/RcSerializer/concurrency.h rename to src/tm2/libtm/libtm/src/RcSerializer/concurrency.h diff --git a/third-party/libtm/libtm/src/RcSerializer/latency_queue.h b/src/tm2/libtm/libtm/src/RcSerializer/latency_queue.h similarity index 100% rename from third-party/libtm/libtm/src/RcSerializer/latency_queue.h rename to src/tm2/libtm/libtm/src/RcSerializer/latency_queue.h diff --git a/third-party/libtm/libtm/src/RcSerializer/rc_packet.h b/src/tm2/libtm/libtm/src/RcSerializer/rc_packet.h similarity index 100% rename from third-party/libtm/libtm/src/RcSerializer/rc_packet.h rename to src/tm2/libtm/libtm/src/RcSerializer/rc_packet.h diff --git a/third-party/libtm/libtm/src/UsbPlugListener.cpp b/src/tm2/libtm/libtm/src/UsbPlugListener.cpp similarity index 100% rename from third-party/libtm/libtm/src/UsbPlugListener.cpp rename to src/tm2/libtm/libtm/src/UsbPlugListener.cpp diff --git a/third-party/libtm/libtm/src/UsbPlugListener.h b/src/tm2/libtm/libtm/src/UsbPlugListener.h similarity index 100% rename from third-party/libtm/libtm/src/UsbPlugListener.h rename to src/tm2/libtm/libtm/src/UsbPlugListener.h diff --git a/third-party/libtm/libtm/src/Version.h.in b/src/tm2/libtm/libtm/src/Version.h.in similarity index 100% rename from third-party/libtm/libtm/src/Version.h.in rename to src/tm2/libtm/libtm/src/Version.h.in diff --git a/third-party/libtm/libtm/src/infra/CMakeLists.txt b/src/tm2/libtm/libtm/src/infra/CMakeLists.txt similarity index 100% rename from third-party/libtm/libtm/src/infra/CMakeLists.txt rename to src/tm2/libtm/libtm/src/infra/CMakeLists.txt diff --git a/third-party/libtm/libtm/src/infra/Dispatcher.cpp b/src/tm2/libtm/libtm/src/infra/Dispatcher.cpp similarity index 100% rename from third-party/libtm/libtm/src/infra/Dispatcher.cpp rename to src/tm2/libtm/libtm/src/infra/Dispatcher.cpp diff --git a/third-party/libtm/libtm/src/infra/Dispatcher.h b/src/tm2/libtm/libtm/src/infra/Dispatcher.h similarity index 100% rename from third-party/libtm/libtm/src/infra/Dispatcher.h rename to src/tm2/libtm/libtm/src/infra/Dispatcher.h diff --git a/third-party/libtm/libtm/src/infra/EmbeddedList.h b/src/tm2/libtm/libtm/src/infra/EmbeddedList.h similarity index 100% rename from third-party/libtm/libtm/src/infra/EmbeddedList.h rename to src/tm2/libtm/libtm/src/infra/EmbeddedList.h diff --git a/third-party/libtm/libtm/src/infra/Event.h b/src/tm2/libtm/libtm/src/infra/Event.h similarity index 100% rename from third-party/libtm/libtm/src/infra/Event.h rename to src/tm2/libtm/libtm/src/infra/Event.h diff --git a/third-party/libtm/libtm/src/infra/EventHandler.h b/src/tm2/libtm/libtm/src/infra/EventHandler.h similarity index 100% rename from third-party/libtm/libtm/src/infra/EventHandler.h rename to src/tm2/libtm/libtm/src/infra/EventHandler.h diff --git a/third-party/libtm/libtm/src/infra/Event_lin.cpp b/src/tm2/libtm/libtm/src/infra/Event_lin.cpp similarity index 100% rename from third-party/libtm/libtm/src/infra/Event_lin.cpp rename to src/tm2/libtm/libtm/src/infra/Event_lin.cpp diff --git a/third-party/libtm/libtm/src/infra/Event_win.cpp b/src/tm2/libtm/libtm/src/infra/Event_win.cpp similarity index 100% rename from third-party/libtm/libtm/src/infra/Event_win.cpp rename to src/tm2/libtm/libtm/src/infra/Event_win.cpp diff --git a/third-party/libtm/libtm/src/infra/Fence.h b/src/tm2/libtm/libtm/src/infra/Fence.h similarity index 100% rename from third-party/libtm/libtm/src/infra/Fence.h rename to src/tm2/libtm/libtm/src/infra/Fence.h diff --git a/third-party/libtm/libtm/src/infra/Fsm.cpp b/src/tm2/libtm/libtm/src/infra/Fsm.cpp similarity index 100% rename from third-party/libtm/libtm/src/infra/Fsm.cpp rename to src/tm2/libtm/libtm/src/infra/Fsm.cpp diff --git a/third-party/libtm/libtm/src/infra/Fsm.h b/src/tm2/libtm/libtm/src/infra/Fsm.h similarity index 100% rename from third-party/libtm/libtm/src/infra/Fsm.h rename to src/tm2/libtm/libtm/src/infra/Fsm.h diff --git a/third-party/libtm/libtm/src/infra/Log.cpp b/src/tm2/libtm/libtm/src/infra/Log.cpp similarity index 100% rename from third-party/libtm/libtm/src/infra/Log.cpp rename to src/tm2/libtm/libtm/src/infra/Log.cpp diff --git a/third-party/libtm/libtm/src/infra/Log.h b/src/tm2/libtm/libtm/src/infra/Log.h similarity index 100% rename from third-party/libtm/libtm/src/infra/Log.h rename to src/tm2/libtm/libtm/src/infra/Log.h diff --git a/third-party/libtm/libtm/src/infra/Poller.h b/src/tm2/libtm/libtm/src/infra/Poller.h similarity index 100% rename from third-party/libtm/libtm/src/infra/Poller.h rename to src/tm2/libtm/libtm/src/infra/Poller.h diff --git a/third-party/libtm/libtm/src/infra/Poller_lin.cpp b/src/tm2/libtm/libtm/src/infra/Poller_lin.cpp similarity index 100% rename from third-party/libtm/libtm/src/infra/Poller_lin.cpp rename to src/tm2/libtm/libtm/src/infra/Poller_lin.cpp diff --git a/third-party/libtm/libtm/src/infra/Poller_win.cpp b/src/tm2/libtm/libtm/src/infra/Poller_win.cpp similarity index 100% rename from third-party/libtm/libtm/src/infra/Poller_win.cpp rename to src/tm2/libtm/libtm/src/infra/Poller_win.cpp diff --git a/third-party/libtm/libtm/src/infra/Semaphore.h b/src/tm2/libtm/libtm/src/infra/Semaphore.h similarity index 100% rename from third-party/libtm/libtm/src/infra/Semaphore.h rename to src/tm2/libtm/libtm/src/infra/Semaphore.h diff --git a/third-party/libtm/libtm/src/infra/Semaphore_lin.cpp b/src/tm2/libtm/libtm/src/infra/Semaphore_lin.cpp similarity index 100% rename from third-party/libtm/libtm/src/infra/Semaphore_lin.cpp rename to src/tm2/libtm/libtm/src/infra/Semaphore_lin.cpp diff --git a/third-party/libtm/libtm/src/infra/Semaphore_win.cpp b/src/tm2/libtm/libtm/src/infra/Semaphore_win.cpp similarity index 100% rename from third-party/libtm/libtm/src/infra/Semaphore_win.cpp rename to src/tm2/libtm/libtm/src/infra/Semaphore_win.cpp diff --git a/third-party/libtm/libtm/src/infra/Utils.cpp b/src/tm2/libtm/libtm/src/infra/Utils.cpp similarity index 100% rename from third-party/libtm/libtm/src/infra/Utils.cpp rename to src/tm2/libtm/libtm/src/infra/Utils.cpp diff --git a/third-party/libtm/libtm/src/infra/Utils.h b/src/tm2/libtm/libtm/src/infra/Utils.h similarity index 100% rename from third-party/libtm/libtm/src/infra/Utils.h rename to src/tm2/libtm/libtm/src/infra/Utils.h diff --git a/third-party/libtm/libtm/src/version.rc.in b/src/tm2/libtm/libtm/src/version.rc.in similarity index 100% rename from third-party/libtm/libtm/src/version.rc.in rename to src/tm2/libtm/libtm/src/version.rc.in diff --git a/third-party/libtm/resources/CMakeLists.txt b/src/tm2/libtm/resources/CMakeLists.txt similarity index 100% rename from third-party/libtm/resources/CMakeLists.txt rename to src/tm2/libtm/resources/CMakeLists.txt diff --git a/third-party/libtm/tools/CMakeLists.txt b/src/tm2/libtm/tools/CMakeLists.txt similarity index 100% rename from third-party/libtm/tools/CMakeLists.txt rename to src/tm2/libtm/tools/CMakeLists.txt diff --git a/third-party/libtm/tools/libtm_util/CMakeLists.txt b/src/tm2/libtm/tools/libtm_util/CMakeLists.txt similarity index 100% rename from third-party/libtm/tools/libtm_util/CMakeLists.txt rename to src/tm2/libtm/tools/libtm_util/CMakeLists.txt diff --git a/third-party/libtm/tools/libtm_util/libtm_util.cpp b/src/tm2/libtm/tools/libtm_util/libtm_util.cpp similarity index 100% rename from third-party/libtm/tools/libtm_util/libtm_util.cpp rename to src/tm2/libtm/tools/libtm_util/libtm_util.cpp diff --git a/third-party/libtm/versions.cmake b/src/tm2/libtm/versions.cmake similarity index 100% rename from third-party/libtm/versions.cmake rename to src/tm2/libtm/versions.cmake From 64e9f375d657b34bb522617b02232155423cc880 Mon Sep 17 00:00:00 2001 From: Belkin Date: Wed, 10 Oct 2018 14:47:29 +0300 Subject: [PATCH 17/25] Remove duplicate source code --- src/tm2/libtm/infra/config.cmake | 28 -- src/tm2/libtm/infra/include/Dispatcher.h | 253 ------------- src/tm2/libtm/infra/include/EmbeddedList.h | 187 ---------- src/tm2/libtm/infra/include/Event.h | 32 -- src/tm2/libtm/infra/include/EventHandler.h | 62 ---- src/tm2/libtm/infra/include/Fence.h | 39 -- src/tm2/libtm/infra/include/Fsm.h | 334 ----------------- src/tm2/libtm/infra/include/Log.h | 134 ------- src/tm2/libtm/infra/include/Poller.h | 62 ---- src/tm2/libtm/infra/include/Semaphore.h | 40 -- src/tm2/libtm/infra/include/Utils.h | 86 ----- src/tm2/libtm/infra/src/Dispatcher.cpp | 411 --------------------- src/tm2/libtm/infra/src/Event_lin.cpp | 35 -- src/tm2/libtm/infra/src/Event_win.cpp | 37 -- src/tm2/libtm/infra/src/Fsm.cpp | 394 -------------------- src/tm2/libtm/infra/src/Log.cpp | 351 ------------------ src/tm2/libtm/infra/src/Poller_lin.cpp | 112 ------ src/tm2/libtm/infra/src/Poller_win.cpp | 154 -------- src/tm2/libtm/infra/src/Semaphore_lin.cpp | 49 --- src/tm2/libtm/infra/src/Semaphore_win.cpp | 63 ---- src/tm2/libtm/infra/src/Utils.cpp | 163 -------- 21 files changed, 3026 deletions(-) delete mode 100644 src/tm2/libtm/infra/config.cmake delete mode 100644 src/tm2/libtm/infra/include/Dispatcher.h delete mode 100644 src/tm2/libtm/infra/include/EmbeddedList.h delete mode 100644 src/tm2/libtm/infra/include/Event.h delete mode 100644 src/tm2/libtm/infra/include/EventHandler.h delete mode 100644 src/tm2/libtm/infra/include/Fence.h delete mode 100644 src/tm2/libtm/infra/include/Fsm.h delete mode 100644 src/tm2/libtm/infra/include/Log.h delete mode 100644 src/tm2/libtm/infra/include/Poller.h delete mode 100644 src/tm2/libtm/infra/include/Semaphore.h delete mode 100644 src/tm2/libtm/infra/include/Utils.h delete mode 100644 src/tm2/libtm/infra/src/Dispatcher.cpp delete mode 100644 src/tm2/libtm/infra/src/Event_lin.cpp delete mode 100644 src/tm2/libtm/infra/src/Event_win.cpp delete mode 100644 src/tm2/libtm/infra/src/Fsm.cpp delete mode 100644 src/tm2/libtm/infra/src/Log.cpp delete mode 100644 src/tm2/libtm/infra/src/Poller_lin.cpp delete mode 100644 src/tm2/libtm/infra/src/Poller_win.cpp delete mode 100644 src/tm2/libtm/infra/src/Semaphore_lin.cpp delete mode 100644 src/tm2/libtm/infra/src/Semaphore_win.cpp delete mode 100644 src/tm2/libtm/infra/src/Utils.cpp diff --git a/src/tm2/libtm/infra/config.cmake b/src/tm2/libtm/infra/config.cmake deleted file mode 100644 index cc8888468d..0000000000 --- a/src/tm2/libtm/infra/config.cmake +++ /dev/null @@ -1,28 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -set(INFRA_HEADER_DIR ${CMAKE_CURRENT_LIST_DIR}/include) -set(INFRA_SRC_DIR ${CMAKE_CURRENT_LIST_DIR}/src) - -#Header Files -set(HEADER_FILES_INFRA - ${CMAKE_CURRENT_LIST_DIR}/include/Utils.h - ${CMAKE_CURRENT_LIST_DIR}/include/Log.h - ${CMAKE_CURRENT_LIST_DIR}/include/Dispatcher.h - ${CMAKE_CURRENT_LIST_DIR}/include/Fsm.h - ${CMAKE_CURRENT_LIST_DIR}/include/Event.h - ${CMAKE_CURRENT_LIST_DIR}/include/Fence.h - ${CMAKE_CURRENT_LIST_DIR}/include/EventHandler.h - ${CMAKE_CURRENT_LIST_DIR}/include/Semaphore.h - ${CMAKE_CURRENT_LIST_DIR}/include/Poller.h -) - -#Source Files -set(SOURCE_FILES_INFRA - ${CMAKE_CURRENT_LIST_DIR}/src/Log.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/Poller_${OS}.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/Dispatcher.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/Fsm.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/Utils.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/Semaphore_${OS}.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/Event_${OS}.cpp -) \ No newline at end of file diff --git a/src/tm2/libtm/infra/include/Dispatcher.h b/src/tm2/libtm/infra/include/Dispatcher.h deleted file mode 100644 index 4446e3c803..0000000000 --- a/src/tm2/libtm/infra/include/Dispatcher.h +++ /dev/null @@ -1,253 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once - -#include "Utils.h" -#include "Event.h" -#include "Fence.h" -#include "EmbeddedList.h" -#include "EventHandler.h" -#include "Poller.h" - -namespace perc { -// ---------------------------------------------------------------------------- -/// -/// @class Dispatcher -/// -/// @brief Provides a event demultiplexing mechanism for messages, timers -/// and file descriptors events. -/// -// ---------------------------------------------------------------------------- -class Dispatcher -{ -public: - Dispatcher(); - ~Dispatcher(); - - // Message notification mechanism - enum - { - PRIORITY_IDLE = 0, // Lowest priority - PRIORITY_NORMAL, - PRIORITY_HIGH, - PRIORITY_MAX, - }; - - // = Post/Send message - // - // User can call to this function from any running context. - // Call to these functions will trigger callback. - // Post message to via the Dispatcher's notification mechanism, don't wait for execution. - // This method will internally copy message (using copy constructor) and free after dispatching. - template - int postMessage(EventHandler *, const T&, int priority = Dispatcher::PRIORITY_IDLE); - - // Send message to via the Dispatcher's notification mechanism and blocking wait for execution. - // User SHOULD call to this function from different running context not one. - // This function waits to the message processing by client and returns after returns - // from callback. - // parameter is transferred by reference to callback - user may indicate return parameters if needed. - // Don't call to this function in the same running context. - template - int sendMessage(EventHandler *, const T&, int priority = Dispatcher::PRIORITY_IDLE); - - // = Register/remove handler for I/O and OS events. - // - // A handler can be associated with multiple handles. - // A handle cannot be associated with multiple handlers. - // User can call to this function from any running context. - // Call to these functions will trigger callback, - // according to parameter - int registerHandler(EventHandler *, Handle fd, unsigned long mask = Poller::READ_MASK, void *act = 0); - - // Register handler for Dispatcher exit processing, callback should be called - // during Disptcher deactivation. - int registerHandler(EventHandler *); - - // Remove handle from dispatcher and it's associated handler. - // User SHOULD call these functions from dispatcher context only (from callbacks)!!! - void removeHandle(Handle fd); - - // Remove all messages (and release them), handles and timers (cancel them) associated with this handler - // according to mask parameters. - // - // User SHOULD call these functions from dispatcher context only (from callbacks)!!! - enum - { - MESSAGES_MASK = (1<<0), - HANDLES_MASK = (1<<1), - TIMERS_MASK = (1<<2), - EXIT_MASK = (1<<3), - ALL_MASK = MESSAGES_MASK | HANDLES_MASK | TIMERS_MASK | EXIT_MASK, - }; - int removeHandler(EventHandler *, unsigned int mask = Dispatcher::ALL_MASK); - - // = Timers. - // - // @params: delayMs Time interval in nanoseconds after which the timer will expire. - // @return or 0 in case of error. - template - uintptr_t scheduleTimer (EventHandler *, nsecs_t delay, const T&, int priority = Dispatcher::PRIORITY_IDLE); - - // Cancel timer associated with this . - // User SHOULD call these functions from dispatcher context only (from callbacks)!!! - void cancelTimer (uintptr_t timerId); - - // Main dispatch function - // - // Returns: - // >0 - the total number of timers, I/Oevents and messages that were dispatched in single call - // 0 - if the elapsed without dispatching any handlers - // -1 - if an error occurs - int handleEvents(nsecs_t timeout = INFINITE); - void endEventsLoop(); - -protected: - - // holder of messages base class - class Holder : public EmbeddedListElement - { - public: - Holder(EventHandler *h) : Handler(h) {} - virtual ~Holder() {} - virtual void complete() {} - EventHandler *Handler; - }; - -private: - std::thread::id mThreadId; - Event mEvent; - Poller mPoller; - bool mExitPending = false; - - int putMessage(Holder *, int priority); - uintptr_t putTimer (EventHandler *, nsecs_t delay, Message *, int priority); - int processMessages(); - int processEvents(Poller::event &); - int processTimers(); - int processExit(); - - /** - * @brief This function calculates the next wake-up time according to timers queue or user timeout - * - * @param[in] timeout - poll timeout in nsec. - * @return Next wake up time in msec. - */ - int calculatePollTimeout(nsecs_t); - bool wakeup() { return mEvent.signal(); } - - class HolderPost : public Holder - { - public: - HolderPost(EventHandler *h, Message *m) : Holder(h), Msg(m) {} - virtual ~HolderPost() { delete Msg; } - virtual void complete() { Handler->onMessage(*Msg); } - Message *Msg; - - /* Disallow these operations */ - HolderPost &operator=(const HolderPost &); - HolderPost(const HolderPost &); - }; - - class HolderSend : public Holder - { - public: - HolderSend(EventHandler *h, const Message &m, Fence &w) : - Holder(h), Msg(m), Waiter(w) {} - virtual ~HolderSend() { Waiter.notify(); } - virtual void complete() { Handler->onMessage(Msg); } - const Message &Msg; - Fence &Waiter; // sync wait lock handle - }; - EmbeddedList m_Messages[PRIORITY_MAX]; - std::mutex m_MessagesGuard; - - // I/O HANDLERS - struct HandlerHolder - { - EventHandler *Handler; - Poller::event Event; - HandlerHolder() : Handler(NULL) {} - HandlerHolder(EventHandler *handler, Handle fd, unsigned long mask, void *act) : - Handler(handler), Event(fd, mask, act) {} - - }; - std::unordered_map m_Handlers; - std::mutex m_HandlersGuard; - - // TIMERS - class HolderTimer : public HolderPost - { - public: - HolderTimer(EventHandler *h, Message *m, nsecs_t d) : HolderPost(h, m) - { Uptime = systemTime() + d; } - virtual void complete() { Uptime = 0; Handler->onTimeout((uintptr_t)this, *Msg); } - nsecs_t Uptime; - }; - EmbeddedList m_Timers; - std::mutex m_TimersGuard; - - // HANDLERS - EmbeddedList m_Holders; - std::mutex m_HoldersGuard; - - // Disallow these operations. - Dispatcher &operator=(const Dispatcher &); - Dispatcher(const Dispatcher &); -}; - - -// ---------------------------------------------------------------------------- -// Template functions MUST be implemented in header file with minimal code size -template -int Dispatcher::postMessage(EventHandler *handler, const T &msg, int priority) -{ - T::IS_DERIVED_FROM_Message; - if (!handler) return -1; - - T *m = new T(msg); // use copy constructor to create message clone - if (!m) return -ENOMEM; - - Holder *holder = new HolderPost(handler, m); - if (!holder) - { - delete m; - return -ENOMEM; - } - return putMessage(holder, priority); -} - -// ---------------------------------------------------------------------------- -template -int Dispatcher::sendMessage(EventHandler *handler, const T &msg, int priority) -{ - T::IS_DERIVED_FROM_Message; - ASSERT(mThreadId != std::this_thread::get_id()); - // use message reference - Fence f; - Holder *holder = new HolderSend(handler, msg, f); - if (!holder ) - return -ENOMEM; - - if (putMessage(holder, priority) < 0) - return -1; - - // wake for message completion or reset by dispatcher - f.wait(); - return 0; -} - -// ---------------------------------------------------------------------------- -template -uintptr_t Dispatcher::scheduleTimer (EventHandler *handler, nsecs_t delay, const T &msg, int priority) -{ - T::IS_DERIVED_FROM_Message; - return putTimer(handler, delay, new T(msg), priority); -} - - -// ---------------------------------------------------------------------------- -} // namespace perc diff --git a/src/tm2/libtm/infra/include/EmbeddedList.h b/src/tm2/libtm/infra/include/EmbeddedList.h deleted file mode 100644 index 5e18777097..0000000000 --- a/src/tm2/libtm/infra/include/EmbeddedList.h +++ /dev/null @@ -1,187 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once - - -namespace perc { -// ---------------------------------------------------------------------------- -/// -/// @class EmbeddedList -/// -/// @brief Doubly linked list. -/// -/// @note This implementation of a doubly linked list does not require -/// use of dynamically allocated memory. Instead, each class -/// that is a potential list element must inherite from a class -/// . All of the list functions operate on these -/// embedded element. -/// -// ---------------------------------------------------------------------------- -class EmbeddedListElement -{ -public: - EmbeddedListElement *Next () const {return m_EmbeddedListElementNext; } - void Next (EmbeddedListElement *Element) { m_EmbeddedListElementNext = Element; } - EmbeddedListElement *Prev () const {return m_EmbeddedListElementPrev; } - void Prev (EmbeddedListElement *Element) { m_EmbeddedListElementPrev = Element; } -private: - EmbeddedListElement *m_EmbeddedListElementNext; - EmbeddedListElement *m_EmbeddedListElementPrev; -}; - -class EmbeddedList -{ -public: - - EmbeddedList () : Head (0), Tail (0), m_Size(0) {} - - // Check Head - EmbeddedListElement *GetHead () - { - return Head; - } - - // Inserts an element onto the top of the list - void AddHead (EmbeddedListElement *Element) - { - //ASSERT (Element); - Element->Prev (0); - Element->Next (Head); - - // if first element, the tail also points to this element - if (!Head) - Tail = Element; - // more than one - else - // poin to the new head - Head->Prev (Element); - Head = Element; - m_Size++; - } - - // Takes a element off from the top of the list - EmbeddedListElement *RemoveHead () - { - EmbeddedListElement *element = Head; - if (element) - { - // if only one element - if (Tail == Head) - Tail = 0; - else - element->Next ()->Prev (0); - Head = element->Next (); - m_Size--; - } - return element; - } - - // Inserts a element onto the end of the list - void AddTail (EmbeddedListElement *Element) - { - //ASSERT (Element); - Element->Prev (Tail); - Element->Next (0); - - // if first element, the tail also points to this element - if (!Tail) - Head = Element; - // more than one - else - // poin to the new tail - Tail->Next (Element); - Tail = Element; - m_Size++; - } - - // Takes a element off from the end of the list - EmbeddedListElement *RemoveTail () - { - EmbeddedListElement *element = Tail; - if (element) - { - // if only one element - if (Tail == Head) - Head = 0; - else - element->Prev ()->Next (0); - Tail = element->Prev (); - m_Size--; - } - return element; - } - - // Inserts a element onto the end of the list - void AddBefore (EmbeddedListElement *ElementCurr, EmbeddedListElement *ElementNew) - { - //ASSERT (ElementCurr && ElementNew); - if (ElementCurr == Head) - AddHead (ElementNew); - else - { - ElementNew->Prev (ElementCurr->Prev ()); - ElementCurr->Prev (ElementNew); - ElementNew->Next (ElementCurr); - ElementNew->Prev ()->Next (ElementNew); - m_Size++; - } - } - - // Takes a element off from the list - int Remove (EmbeddedListElement *Element) - { - //ASSERT (Element); - if (Element == Head) - RemoveHead (); - else if (Element == Tail) - RemoveTail (); - else - { - Element->Next ()->Prev (Element->Prev ()); - Element->Prev ()->Next (Element->Next ()); - m_Size--; - } - return 0; - } - - /// @class Iterator - class Iterator - { - const EmbeddedList &m_List; - EmbeddedListElement *m_CurrentNode; - public: - Iterator (const EmbeddedList &List) : m_List (List) { Reset (); } - - // Reset the Iterator so that GetNext will give you the first list element - void Reset () - { - m_CurrentNode = m_List.Head; - } - - // Return the next list element (i.e. advance iterator and return next list element) - EmbeddedListElement *Next () - { - // move the iterator one step forward - if (m_CurrentNode) - m_CurrentNode = m_CurrentNode->Next (); - return m_CurrentNode; - } - - // Return the current list element - EmbeddedListElement *Current () const { return m_CurrentNode; } - }; - - int Size() const { return m_Size; } - -private: - EmbeddedListElement *Head; - EmbeddedListElement *Tail; - int m_Size; -}; - - -// ---------------------------------------------------------------------------- -} // namespace perc diff --git a/src/tm2/libtm/infra/include/Event.h b/src/tm2/libtm/infra/include/Event.h deleted file mode 100644 index e033457900..0000000000 --- a/src/tm2/libtm/infra/include/Event.h +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once -#include "TrackingCommon.h" -#include "Log.h" - -namespace perc -{ - class Event - { - public: - Event(); - - inline Handle handle() { return mEvent; } - - int signal(); - - int reset(); - - ~Event(); - - private: - Handle mEvent; - - void operator= (const Event &) = delete; - Event(const Event &) = delete; - }; - -} // namespace perc diff --git a/src/tm2/libtm/infra/include/EventHandler.h b/src/tm2/libtm/infra/include/EventHandler.h deleted file mode 100644 index 096cccb607..0000000000 --- a/src/tm2/libtm/infra/include/EventHandler.h +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once -#include "TrackingCommon.h" - -namespace perc { -// ---------------------------------------------------------------------------- -/// -/// @class Message -/// -/// @brief Declare a base class for all messages that can be posted to Dispatcher. -/// User MUST manage allocation policy if needed by defining copy constructor -// and appropriate destructor. and its derives will be copied internally and -/// SHOULD be freed after dispatching callback by Dispatcher. -/// -// ---------------------------------------------------------------------------- -class Message -{ -public: - Message(int type, int param = 0) : Type(type), Param(param), Result(-1) {} - virtual ~Message() {} - int Type; - int Param; - mutable int Result; - - // replace mechanism that generate compilation error - // if derived class was not inherited from class - enum { IS_DERIVED_FROM_Message = true }; -}; - -// ---------------------------------------------------------------------------- -/// -/// @class EventHandler -/// -/// @brief This base class defines the interface for receiving the -/// results of sync and asynchronous operations. -/// Subclasses of this class will fill in appropriate methods. -/// @note -/// -// ---------------------------------------------------------------------------- -class EventHandler -{ -public: - virtual ~EventHandler() {} - - // = Completion callbacks - virtual void onMessage(const Message &) {} - virtual void onEvent(Handle fd, unsigned long mask, void *act) {} - virtual void onTimeout(uintptr_t timerId, const Message &) {} - virtual void onExit() {} - -protected: - // Hide the constructor - EventHandler() {} -}; - - -// ---------------------------------------------------------------------------- -} // namespace perc diff --git a/src/tm2/libtm/infra/include/Fence.h b/src/tm2/libtm/infra/include/Fence.h deleted file mode 100644 index 06401a9ab8..0000000000 --- a/src/tm2/libtm/infra/include/Fence.h +++ /dev/null @@ -1,39 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once -#include -#include - - -namespace perc { - -class Fence -{ -public: - Fence(){} - - inline void notify() { - std::unique_lock l(m); - fired = true; - cv.notify_one(); - } - - inline void wait() { - std::unique_lock l(m); - cv.wait(l, [this]{ return fired; }); - fired = false; - } - -private: - std::mutex m; - std::condition_variable cv; - bool fired = false; - // Prevent assignment and initialization. - void operator= (const Fence &); - Fence (const Fence &); -}; - -} // namespace perc diff --git a/src/tm2/libtm/infra/include/Fsm.h b/src/tm2/libtm/infra/include/Fsm.h deleted file mode 100644 index 3ea0c6b43f..0000000000 --- a/src/tm2/libtm/infra/include/Fsm.h +++ /dev/null @@ -1,334 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once -#include "Log.h" -#include "Dispatcher.h" - - -namespace perc { -// -[Defines]------------------------------------------------------------------ -/// C++ helpers macros that transform static FSM functions interface to class methods. -// Put these macros into header file (protected/private) section. -#define ACTION(Class, State, Event) Class::sAction_s ## State ## _e ## Event -#define GUARD(Class, State, Event) Class::sGuard_s ## State ## _e ## Event -#define ENTRY(Class, State) Class::sEntry_s ## State -#define EXIT(Class, State) Class::sExit_s ## State - -#define DECLARE_FSM_ACTION(State, Event) \ - static void sAction_s##State##_e##Event(Fsm *, const Message &); \ - void Action_s##State##_e##Event(const Message &) - -#define DECLARE_FSM_GUARD(State, Event) \ - static bool sGuard_s##State##_e##Event(Fsm *, const Message &); \ - bool Guard_s##State##_e##Event(const Message &) - -#define DECLARE_FSM_STATE_ENTRY(State) \ - static void sEntry_s##State(Fsm *); \ - void Entry_s##State() - -#define DECLARE_FSM_STATE_EXIT(State) \ - static void sExit_s##State(Fsm *); \ - void Exit_s##State() - -// Put these macros into source file and define body. -#define DEFINE_FSM_ACTION(Class, State, Event, Msg) \ - void ACTION(Class, State, Event)(Fsm *pContext, const Message &Msg) \ - { Class* pThis = reinterpret_cast (pContext->owner()); pThis->Action_s ## State ## _e ## Event(Msg); } \ - void Class::Action_s ## State ## _e ## Event(const Message &Msg) - -#define DEFINE_FSM_GUARD(Class, State, Event, Msg) \ - bool GUARD(Class, State, Event)(Fsm *pContext, const Message &Msg) \ - { Class* pThis = reinterpret_cast (pContext->owner()); return pThis->Guard_s##State##_e##Event(Msg); } \ - bool Class::Guard_s##State##_e##Event(const Message &Msg) - -#define DEFINE_FSM_STATE_ENTRY(Class, State) \ - void ENTRY(Class, State)(Fsm *pContext) \ - { Class* pThis = reinterpret_cast (pContext->owner()); pThis->Entry_s##State(); } \ - void Class::Entry_s##State() - -#define DEFINE_FSM_STATE_EXIT(Class, State) \ - void EXIT(Class, State)(Fsm *pContext) \ - { Class* pThis = reinterpret_cast (pContext->owner()); pThis->Exit_s##State(); } \ - void Class::Exit_s##State() - - -// -[FSM Event]---------------------------------------------------------------- -/// -/// Event types definitions. -/// -/// @note: event types - this is a user defined type exclude timeout event type. -/// User must define application specific events like this (offset from USER_DEFINED): -/// - #define EVENT_TYPE_0 (0 + FSM_EVENT_USER_DEFINED) -/// - #define EVENT_TYPE_1 (1 + FSM_EVENT_USER_DEFINED) -/// - ... -/// -#define FSM_EVENT_NONE ((char)-1) -#define FSM_EVENT_TIMEOUT 0 -#define FSM_EVENT_USER_DEFINED 1 - - -// -[FSM Transition]----------------------------------------------------------- -/// -/// Internal FSM transition type definitions. -/// -/// @note: Only for Infra::Fsm internal use. -/// -#define FSM_TRANSITION_NONE_NAME "NONE" -#define FSM_TRANSITION_NONE ((char)-1) -#define FSM_TRANSITION 0 -#define FSM_TRANSITION_AFTER 1 -#define FSM_TRANSITION_INTERNAL 2 -#define FSM_TRANSITION_INTERNAL_AFTER 3 -#define FSM_TRANSITION_UNCONDITIONAL 4 - -// Define max timeout of after transition. -#define FSM_TRANSITION_AFTER_MAX_TIMEOUT ((unsigned long)~0) - -// Guard and Action function prototypes. -class Fsm; -class FsmEvent; -typedef bool (*FsmTransitionGuard) (Fsm *, const Message &); -typedef void (*FsmTransitionAction)(Fsm *, const Message &); - - -// -[Infra:FSM State]---------------------------------------------------------- -/// -/// FSM state type definitions. -/// -/// @note: !!! WARNING - Forbidden defining state with typeid - (-1) -/// User must define application specific states like this(offset from USER_DEFINED): -/// - #define STATE_0 (0 + FSM_STATE_USER_DEFINED) -/// - #define STATE_1 (1 + FSM_STATE_USER_DEFINED) -/// - ... -/// -#define FSM_STATE_FINAL_NAME "FINAL" -#define FSM_STATE_FINAL ((char)-1) ///< !!! Don't use this define for user defined states -#define FSM_STATE_SAME 0 -#define FSM_STATE_USER_DEFINED 1 - -// State entry/exit functions prototypes. -typedef void (*FsmStateEntry)(Fsm *pFsmContext); -typedef void (*FsmStateExit) (Fsm *pFsmContext); - - -// -[FSM Definition]----------------------------------------------------------- -/// Transition definition -typedef struct FsmTransition_T -{ - const char *Name; - char Type; - char EventType; - char NewState; - FsmTransitionGuard Guard; - FsmTransitionAction Action; - unsigned long TimeOut; -} FsmTransition; - -/// Default NONE transition definition. -extern const FsmTransition s_FsmTransitionNone; - - -/// State definition -typedef struct FsmState_T -{ - const char *Name; - char Type; - FsmStateEntry Entry; - FsmStateExit Exit; - FsmTransition *TransitionList; -} FsmState; - -/// Final state default definitions -extern const FsmState s_FsmStateFinal; - - -/// Internal use: FSM loggings definition macros -# define FSM_NAME(_name) _name, - -/// Transition list definition macros -#define TRANSITION_INTERNAL(_event, _guard, _action) \ - { FSM_NAME (#_event) FSM_TRANSITION_INTERNAL, _event, FSM_STATE_SAME, _guard, _action, FSM_TRANSITION_AFTER_MAX_TIMEOUT }, - -#define TRANSITION_INTERNAL_AFTER(_guard, _action, _timeout) \ - { FSM_NAME ("INTERNAL_AFTER") FSM_TRANSITION_INTERNAL_AFTER, FSM_EVENT_TIMEOUT, FSM_STATE_SAME, _guard, _action, _timeout }, - -#define TRANSITION(_event, _guard, _action, _new_state_type) \ - { FSM_NAME (#_event) FSM_TRANSITION, _event, _new_state_type, _guard, _action, FSM_TRANSITION_AFTER_MAX_TIMEOUT }, - -#define TRANSITION_AFTER(_guard, _action, _new_state_type, _timeout) \ - { FSM_NAME ("AFTER") FSM_TRANSITION_AFTER, FSM_EVENT_TIMEOUT, _new_state_type, _guard, _action, _timeout }, - - -/// State definition macros -#define DECLARE_FSM_STATE(_state) \ - static const FsmState s_Fsm##_s##_state; \ - static const FsmTransition s_Fsm##_s##_state##_##TransitionList[] - -#define DEFINE_FSM_STATE_BEGIN(Class, _state) \ - const FsmTransition Class::s_Fsm##_s##_state##_##TransitionList[] = { - -#define DEFINE_FSM_STATE_END(Class, _state, _entry, _exit) \ - { FSM_NAME (FSM_TRANSITION_NONE_NAME) FSM_TRANSITION_NONE, FSM_EVENT_NONE, FSM_STATE_FINAL, 0, 0, FSM_TRANSITION_AFTER_MAX_TIMEOUT } }; \ - const FsmState Class::s_Fsm##_s##_state = \ - { FSM_NAME (#_state) _state, _entry, _exit, (FsmTransition *)Class::s_Fsm##_s##_state##_##TransitionList }; - - -/// Macros for defining FSM. -#define FSM(_fsm) s_Fsm##_##_fsm - -#define DEFINE_FSM_BEGIN(Class, _fsm) \ - const FsmState * const Class::FSM(_fsm)[] = { - -#define STATE(Class, _state) \ - &Class::s_Fsm##_s##_state, - -#define DEFINE_FSM_END() \ - &s_FsmStateFinal \ - }; - -#define DECLARE_FSM(_fsm) \ - static const FsmState * const FSM(_fsm)[]; - - -// ---------------------------------------------------------------------------- -/// -/// @class Fsm -/// -/// @brief FSM context class. -/// -// ---------------------------------------------------------------------------- -/// FSM engine handle event return codes. -#define FSM_CONTEXT_STATUS_ERROR (-1) -#define FSM_CONTEXT_STATUS_OK 0 -#define FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND 1 -#define FSM_CONTEXT_STATUS_UNCOND_TRANSITION_NOT_FOUND 2 -#define FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED 3 -#define FSM_CONTEXT_STATUS_STATE_FINAL 4 -#define FSM_CONTEXT_STATUS_STATE_NOT_FOUND 5 - -#define FSM_CONTEXT_DEFAULT_NAME "Fsm" -#define FSM_CONTEXT_TIMER_LIST_LEN 8 - -class Fsm : public EventHandler -{ -public: - - Fsm (); - virtual ~Fsm (); - - /// Explicit init function. - int init (const FsmState * const *pFsm, - void *Owner = 0, - Dispatcher *pDispatcher = 0, - const char *Name = FSM_CONTEXT_DEFAULT_NAME); - - /// - /// FSM context main function - handle user events and run defined state machine. - /// Before calling this function user MUST call Init() function. - /// User MUST call this function from well defined (single) context. - /// - /// @param pEvent Event to proceed. - /// - /// @return See Infra FSM engine handle event return codes. - /// In case of successful event handling function returns - FSM_CONTEXT_STATUS_OK. - /// In another case function returns error code that user MUST check and process. - /// - /// @note May enter recursively !!! - /// - int fireEvent(const Message &); - - int done(); - - // = Get/Set methods - // - /// Get User param method. - void *owner() const { return m_Owner; } - void owner(void *pOwner) { m_Owner = pOwner; } - - // = Self event mechanism - // - // Call to function will enable recursive message processing without - // main dispatcher loop. User SHOULD call once to this function in function, - // and transitions only. - void selfEvent(const Message &selfEvent) - { - ASSERT(!m_SelfEvent); - m_SelfEvent = new Message(selfEvent); - ASSERT(m_SelfEvent); - } - - /// Utility - int getCurrentState (void) const - { - if (m_pFsm) - return m_pFsm[m_CurrStateId]->Type; - return FSM_STATE_FINAL; - } - - static const char *statusName (int Status); - -protected: - - // = Interface: EventHandler - // - void onMessage(const Message &msg) { fireEvent (msg); } - void onTimeout(uintptr_t timerId, const Message &msg) { fireEvent (msg); } - - // Get the dispatcher associated with this handler - Dispatcher *dispatcher() const { return m_Dispatcher; } - -private: - /// FSM context parameters. - const FsmState * const *m_pFsm; - void *m_Owner; - Dispatcher *m_Dispatcher; - int m_CurrStateId; - const char *m_Name; - - /// Self event - Message *m_SelfEvent; - void resetSelfEvent() { m_SelfEvent = 0; } - int processSelfEvent() - { - int ret = FSM_CONTEXT_STATUS_OK; - if (m_SelfEvent) - { - Message *savedSelfEvent = m_SelfEvent; - ret = fireEvent (*m_SelfEvent); - delete savedSelfEvent; - } - return ret; - } - - // = Internal functions - // - int InitNewState(int StateType); - int DoneCurrState(); - static const char *TransitionType(int Transition); - void logRetCode (int retCode, const FsmState *state, const Message &); - - /// - /// This function returns id of transition that wait for this event type in OUT parameter. - /// Checks guard function of transition. In case that we reached last state's defined transition - /// this function returns error - INFRA_FSM_TRANSITION_NONE. - /// - int FindTransition(int /*OUT*/ *, const Message &); - - /// Init after transition list: schedule timer via engine object - int ScheduleAfterTransitions(); - - /// Cancel all timers and reset after transition list - int CancelAfterTransitions(); - - bool CallTransitionGuard(const FsmTransition *, const Message &); - void CallTransitionAction(const FsmTransition *, const Message &); - void CallStateEntry(); - void CallStateExit(); -}; - - -// ---------------------------------------------------------------------------- -} // namespace perc diff --git a/src/tm2/libtm/infra/include/Log.h b/src/tm2/libtm/infra/include/Log.h deleted file mode 100644 index cde77a2042..0000000000 --- a/src/tm2/libtm/infra/include/Log.h +++ /dev/null @@ -1,134 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once -#include -#include -#include -#include - -// Log priority values, in ascending priority order. -// Note: This definition is using the same priority order as android log subsystem. -typedef enum LogPriority { - LOG_UNKNOWN = 0, - LOG_DEFAULT, - LOG_VERBOSE, - LOG_DEBUG, - LOG_TRACE, - LOG_INFO, - LOG_WARN, - LOG_ERROR, - LOG_FATAL, - LOG_SILENT, -#define LOG_PRI2MASK(_pri) (1 << _pri) -} LogPriority; - -enum LogSource { - LogSourceHost = 0x0000, /**< Host Log Configuration */ - LogSourceFW = 0x0001, /**< FW Log Configuration */ - LogSourceMax = 0x0002, -}; - -// Normally we strip LOGV (VERBOSE messages) from release builds. -// You can modify this (for example with "#define LOG_NDEBUG 0" at the top of your source file) to change that behavior. -#ifndef NDEBUG -#define NDEBUG -#endif - -#ifndef LOG_NDEBUG -#ifdef NDEBUG -#define LOG_NDEBUG 1 -#else -#define LOG_NDEBUG 0 -#endif -#endif - -#ifndef LOG_TAG -#define LOG_TAG NULL -#endif - -// Macro to send a verbose log message using the current LOG_TAG. -#ifndef LOGV -#if LOG_NDEBUG -#define LOGV(...) ((void)0) -#else -#define LOGV(...) ((void)LOG(NULL, LOG_VERBOSE, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif -#endif - -#ifndef DEVICELOGV -#if LOG_NDEBUG -#define DEVICELOGV(...) ((void)0) -#else -#define DEVICELOGV(...) ((void)LOG(this, LOG_VERBOSE, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif -#endif - -// Macros to send a priority log message using the current LOG_TAG. -#ifndef LOGD -#define LOGD(...) ((void)LOG(NULL, LOG_DEBUG, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef LOGT -#define LOGT(...) ((void)LOG(NULL, LOG_TRACE, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef LOGI -#define LOGI(...) ((void)LOG(NULL, LOG_INFO, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef LOGW -#define LOGW(...) ((void)LOG(NULL, LOG_WARN, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef LOGE -#define LOGE(...) ((void)LOG(NULL, LOG_ERROR, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef LOGF -#define LOGF(...) ((void)LOG(NULL, LOG_FATAL, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef DEVICELOGD -#define DEVICELOGD(...) ((void)LOG(this, LOG_DEBUG, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef DEVICELOGT -#define DEVICELOGT(...) ((void)LOG(this, LOG_TRACE, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef DEVICELOGI -#define DEVICELOGI(...) ((void)LOG(this, LOG_INFO, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef DEVICELOGW -#define DEVICELOGW(...) ((void)LOG(this, LOG_WARN, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef DEVICELOGE -#define DEVICELOGE(...) ((void)LOG(this, LOG_ERROR, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef DEVICELOGF -#define DEVICELOGF(...) ((void)LOG(this, LOG_FATAL, LOG_TAG, __LINE__, __VA_ARGS__)) -#endif - -#ifndef LOG -#define LOG(_deviceId, _prio, _tag, _line, ...) \ - __perc_Log_print(_deviceId, _prio, _tag, _line, __VA_ARGS__) -#endif - -#define ASSERT assert -#define UNUSED(_var) (void)(_var) - -// platform depended print declaration -void __perc_Log_write(int prio, const char *tag, const char *text); -void __perc_Log_print(void* deviceId, int prio, const char *tag, int line, const char *fmt, ...); -int __perc_Log_print_header(char * buf, int bufSize, int prio, const char *tag, const char* deviceId); -void __perc_Log_Save(void* deviceId, int prio, int payloadLen, char* payload); -void __perc_Log_Set_Configuration(uint8_t source, uint8_t outputMode, uint8_t verbosity, uint8_t rolloverMode); -void __perc_Log_Get_Configuration(uint8_t source, uint8_t* outputMode, uint8_t* verbosityMask, uint8_t* rolloverMode); -void __perc_Log_Get_Log(void* log); - diff --git a/src/tm2/libtm/infra/include/Poller.h b/src/tm2/libtm/infra/include/Poller.h deleted file mode 100644 index 7190c35952..0000000000 --- a/src/tm2/libtm/infra/include/Poller.h +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once -#include -#include -#include -#include -#include "TrackingCommon.h" - -namespace perc { - - class Poller - { - public: - - struct event - { - Handle handle; - unsigned long mask; - void *act; - event() : - handle(ILLEGAL_HANDLE), mask(Poller::NULL_MASK), act(0) {} - event(Handle h, unsigned long m, void *a) : - handle(h), mask(m), act(a) {} - }; - - Poller(); - ~Poller(); - bool add(const Poller::event &); - bool remove(Handle); - - // POLLING LOOP - // - // Returns: - // >0 - the total number of events - // 0 - if the elapsed without dispatching any handle - // -1 - if an error occurs - int poll(Poller::event &, unsigned long timeoutMs); - - public: - - static const int NULL_MASK; - static const int READ_MASK; - static const int WRITE_MASK; - static const int EXCEPT_MASK; - static const int ALL_MASK; - - - private: - - struct CheshireCat; - std::unique_ptr mData; - - // Prevent assignment and initialization. - void operator= (const Poller &) = delete; - Poller(const Poller &) = delete; - }; - -} // namespace perc diff --git a/src/tm2/libtm/infra/include/Semaphore.h b/src/tm2/libtm/infra/include/Semaphore.h deleted file mode 100644 index 371b352527..0000000000 --- a/src/tm2/libtm/infra/include/Semaphore.h +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once -#include "Utils.h" -#include -/* -* Wrapper class for pthread semaphore implementation. -*/ - - -namespace perc -{ - - class Semaphore - { - public: - Semaphore(unsigned int initValue = 0); - - int get(nsecs_t timeoutNs = -1); - int put(); - - ~Semaphore(); - - private: - - struct CheshireCat; - - std::unique_ptr mData; - - // = Prevent assignment and initialization. - void operator= (const Semaphore &) = delete; - Semaphore(const Semaphore &) = delete; - - }; - - -} // namespace perc diff --git a/src/tm2/libtm/infra/include/Utils.h b/src/tm2/libtm/infra/include/Utils.h deleted file mode 100644 index 94039f2e39..0000000000 --- a/src/tm2/libtm/infra/include/Utils.h +++ /dev/null @@ -1,86 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once - -#include -#include "Log.h" - -#ifdef _WIN32 -#include -#else -#include -#define INFINITE (-1) -#endif - -class HostLocalTime { -public: - HostLocalTime() : year(0), month(0), dayOfWeek(0), day(0), hour(0), minute(0), second(0), milliseconds(0) {} - uint16_t year; - uint16_t month; - uint16_t dayOfWeek; - uint16_t day; - uint16_t hour; - uint16_t minute; - uint16_t second; - uint16_t milliseconds; -}; - -HostLocalTime getLocalTime(); - -#ifdef __cplusplus -extern "C" { -#endif - typedef int64_t nsecs_t; - - inline nsecs_t s2ns(nsecs_t sec) - { - return (sec < 0) ? INFINITE : sec * 1000000000; - } - - inline nsecs_t ms2ns(nsecs_t ms) - { - return (ms < 0) ? INFINITE : ms * 1000000; - } - - inline int ns2s(nsecs_t ns) - { - return (ns < 0) ? INFINITE : (int)(ns / 1000000000); - } - - inline int ns2ms(nsecs_t ns) - { - return (ns < 0) ? INFINITE : (int)(ns / 1000000); - } - - nsecs_t systemTime(); - - uint64_t bytesSwap(uint64_t val); - - uint32_t getThreadId(void); - - inline int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime) - { - int timeoutDelayMsec = 0; - - if (timeoutTime > referenceTime) - { - timeoutDelayMsec = (int)(((uint64_t)(timeoutTime - referenceTime) + 999999LL) / 1000000LL); - } - - return timeoutDelayMsec; - } - - bool setProcessPriorityToRealtime(); - - namespace perc - { - void copy(void* dst, void const* src, size_t size); - size_t stringLength(const char* string, size_t maxSize); - } - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/src/tm2/libtm/infra/src/Dispatcher.cpp b/src/tm2/libtm/infra/src/Dispatcher.cpp deleted file mode 100644 index 6bd083fefa..0000000000 --- a/src/tm2/libtm/infra/src/Dispatcher.cpp +++ /dev/null @@ -1,411 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#define LOG_TAG "infra/Dispatcher" -#define LOG_NDEBUG 1 // controls LOGV only -#include "Log.h" -#include "Dispatcher.h" - - -namespace perc { -// ---------------------------------------------------------------------------- - Dispatcher::Dispatcher() : mThreadId(), m_Handlers(), m_HandlersGuard(), m_MessagesGuard(), m_Timers(), m_TimersGuard(), m_HoldersGuard(), m_Holders() -{ - bool ret = mPoller.add(Poller::event(mEvent.handle(), Poller::READ_MASK, this)); - if (ret != true) - { - ASSERT(ret); - } -} - -// ---------------------------------------------------------------------------- -Dispatcher::~Dispatcher() -{ - processExit(); - { - std::lock_guard guard(m_MessagesGuard); - for (int i = 0; i < PRIORITY_MAX; i++) { - Holder *holder = (Holder *)m_Messages[i].GetHead(); - while (holder) { - do { - m_Messages[i].RemoveHead(); - delete holder; - } while ((holder = (Holder *)m_Messages[i].GetHead()) != 0); - } - } - } - { - std::lock_guard guard(m_HandlersGuard); - m_Handlers.clear(); - } - { - std::lock_guard guard(m_TimersGuard); - Holder *holder = (Holder *)m_Timers.GetHead(); - while (holder) { - do { - m_Timers.RemoveHead(); - delete holder; - } while ((holder = (Holder *)m_Timers.GetHead()) != 0); - } - } - mPoller.remove(mEvent.handle()); -} - -// ---------------------------------------------------------------------------- -// DISPATCHER LOOP -// -// Returns: -// >0 - the total number of timers, I/Oevents and messages that were dispatched in single call -// 0 - if the elapsed without dispatching any handlers -// -1 - if an error occurs -int Dispatcher::handleEvents(nsecs_t timeout) -{ - if (mExitPending) { - LOGV("handleEvents(): processExit"); - processExit(); - return -1; - } - int ret = 0; - mThreadId = std::this_thread::get_id(); - LOGV("handleEvents(): Poller::poll()"); - Poller::event event; - int n = mPoller.poll(event, calculatePollTimeout(timeout)); - LOGV("handleEvents(): Poller::poll() ret %d", n); - if (n > 0) { - // process message queues: complete number of messages from message list that was signaled by event - if (event.handle == mEvent.handle()) { - LOGV("handleEvents(): processMessages"); - ret += processMessages(); - } - else { - // process file descriptor - LOGV("handleEvents(): processEvent fd %d, revents %x", event.handle, event.mask); - ret += processEvents(event); - } - } - else if (n == -1) { - LOGE("handleEvents(): Poller::poll() ret %d", n); - return -1; - } - // process timers - ret += processTimers(); - return ret; -} - -// ---------------------------------------------------------------------------- -void Dispatcher::endEventsLoop() -{ -// TRACE(""); - mExitPending = true; - wakeup(); -} - -// ---------------------------------------------------------------------------- -int Dispatcher::registerHandler(EventHandler *handler, Handle fd, unsigned long mask, void *act) -{ - if (mExitPending) return -1; - ASSERT(handler); - ASSERT(mThreadId == std::this_thread::get_id()); - int res = -1; - HandlerHolder holder(handler, fd, mask, act); - if (mPoller.add(holder.Event)) { - std::lock_guard guard(m_HandlersGuard); - if (m_Handlers.count(fd) == 0) { - // adding new one - m_Handlers.emplace(fd, holder); - } - else { - // modifying old one - m_Handlers[fd] = holder; - } - res = 0; - } - return res; -} - -// ---------------------------------------------------------------------------- -int Dispatcher::registerHandler(EventHandler *handler) -{ - if (mExitPending) return -1; - ASSERT(handler); - std::lock_guard guard(m_HoldersGuard); - EmbeddedList::Iterator it(m_Holders); - Holder *h = (Holder *)it.Current(); - // check if handler exists in a list - while(h) { - if (h->Handler == handler) - return -1; - h = (Holder *)it.Next(); - } - Holder *holder = new Holder(handler); - if (!holder) return -1; - m_Holders.AddTail(holder); - return 0; -} - -// ---------------------------------------------------------------------------- -void Dispatcher::removeHandle(Handle fd) -{ - ASSERT(mThreadId == std::this_thread::get_id()); - mPoller.remove(fd); - std::lock_guard guard(m_HandlersGuard); - if (m_Handlers.count(fd) > 0) { - m_Handlers.erase(fd); - } -} - -// ---------------------------------------------------------------------------- -void Dispatcher::cancelTimer (uintptr_t timerId) -{ - ASSERT(timerId); - ASSERT(mThreadId == std::this_thread::get_id()); - std::lock_guard guard(m_TimersGuard); - HolderTimer *holder = (HolderTimer *)timerId; - // check if trying to cancel timer from callback - if (holder->Uptime) { - m_Timers.Remove(holder); - delete holder; - } -} - -// ---------------------------------------------------------------------------- -int Dispatcher::removeHandler(EventHandler *handler, unsigned int mask) -{ - ASSERT(handler); - ASSERT(mThreadId == std::this_thread::get_id()); - int removedHandlers = 0; - - if (mask & MESSAGES_MASK) { - std::lock_guard guard(m_MessagesGuard); - for (int i = 0; i < PRIORITY_MAX; i++) { - EmbeddedList::Iterator it(m_Messages[i]); - Holder *holder = (Holder *)it.Current(); - while (holder) { - Holder *curr = holder; - holder = (Holder *)it.Next(); - if (curr->Handler == handler) { - m_Messages[i].Remove(curr); - delete curr; - removedHandlers++; - } - } - } - } - if (mask & HANDLES_MASK) { - std::lock_guard guard(m_HandlersGuard); - for (auto pair : m_Handlers) { - const HandlerHolder &holder = pair.second; - if (holder.Handler == handler) { - Handle fd = pair.first; - mPoller.remove(fd); - m_Handlers.erase(fd); - removedHandlers++; - } - } - } - if (mask & TIMERS_MASK) { - std::lock_guard guard(m_TimersGuard); - EmbeddedList::Iterator it(m_Timers); - Holder *holder = (Holder *)it.Current(); - while (holder) { - Holder *curr = holder; - holder = (Holder *)it.Next(); - if (curr->Handler == handler) { - m_Timers.Remove(curr); - delete curr; - removedHandlers++; - } - } - } - if (mask & EXIT_MASK) { - std::lock_guard guard(m_HoldersGuard); - EmbeddedList::Iterator it(m_Holders); - Holder *holder = (Holder *)it.Current(); - while (holder) { - Holder *curr = holder; - holder = (Holder *)it.Next(); - if (curr->Handler == handler) { - m_Holders.Remove(curr); - delete curr; - removedHandlers++; - break; - } - } - } - - return removedHandlers; -} - -// ---------------------------------------------------------------------------- -int Dispatcher::putMessage(Holder *holder, int priority) -{ - if (mExitPending) return -1; - ASSERT(holder); - if (priority >= PRIORITY_MAX) priority = PRIORITY_MAX - 1; - if (priority < 0) priority = 0; - std::lock_guard guard(m_MessagesGuard); - m_Messages[priority].AddTail(holder); - // wake up running loop - if (!wakeup()) { - m_Messages[priority].Remove(holder); - delete holder; - return -1; - } - return 0; -} - -// ---------------------------------------------------------------------------- -uintptr_t Dispatcher::putTimer(EventHandler *handler, nsecs_t delay, Message *msg, int priority) -{ - // sanity check - if ((mExitPending == true) || (msg == NULL)) - { - return 0; - } - - ASSERT(handler && delay != 0); - // TODO: optimization: implement free list with local allocator - HolderTimer *holder = new HolderTimer(handler, msg, delay); - if (!holder) - { - delete msg; - return 0; - } - std::lock_guard guard(m_TimersGuard); - EmbeddedList::Iterator it(m_Timers); - HolderTimer *curr = (HolderTimer *)it.Current(); - if (!curr) { - m_Timers.AddHead(holder); - } - else { - do { - if (holder->Uptime < curr->Uptime) { - m_Timers.AddBefore(curr, holder); - break; - } - } while ((curr = (HolderTimer *)it.Next()) != 0); - if (!curr) { - m_Timers.AddTail(holder); - } - } - // reschedule timers by wake up running loop if was performed from another context - if (mThreadId != std::this_thread::get_id()) - wakeup(); - return (uintptr_t)holder; -} - -// ---------------------------------------------------------------------------- -int Dispatcher::processMessages() -{ - mEvent.reset(); - // normalize wake-up events counter and real messages number before callback - int cnt = 0; - for (int i = 0; i < PRIORITY_MAX; i++) { - cnt += m_Messages[i].Size(); - } - int cntMsgs = cnt; - while (cnt) { - int priority = 0; - for (int i = (PRIORITY_MAX - 1); i >= 0; i--) { - if (m_Messages[i].Size()) { - priority = i; - break; - } - } - m_MessagesGuard.lock(); - Holder *holder = (Holder *)m_Messages[priority].RemoveHead(); - m_MessagesGuard.unlock(); - if (holder) { - holder->complete(); - delete holder; - } - else { - break; - } - cnt--; - } - return cntMsgs; -} - -// ---------------------------------------------------------------------------- -int Dispatcher::processEvents(Poller::event &event) -{ - m_HandlersGuard.lock(); - auto it = m_Handlers.find(event.handle); - if (it != m_Handlers.end()) { - const HandlerHolder &holder = it->second; - m_HandlersGuard.unlock(); - holder.Handler->onEvent(holder.Event.handle, event.mask, holder.Event.act); - return 1; - } - else { - mPoller.remove(event.handle); - m_HandlersGuard.unlock(); - } - return 0; -} - -// ---------------------------------------------------------------------------- -int Dispatcher::processTimers() -{ - int cnt = 0; - HolderTimer* timer; - m_TimersGuard.lock(); - timer = (HolderTimer *)m_Timers.GetHead(); - if (timer) { - // work with head only, user can add new timers in a callback - do { - nsecs_t now = systemTime(); - if (timer->Uptime <= now) { - m_Timers.RemoveHead (); - m_TimersGuard.unlock(); - timer->complete(); - delete timer; - m_TimersGuard.lock(); - cnt++; - } - else { - break; - } - } while ((timer = (HolderTimer *)m_Timers.GetHead()) != 0); - } - m_TimersGuard.unlock(); - return cnt; -} - -// ---------------------------------------------------------------------------- -int Dispatcher::processExit() -{ - m_HoldersGuard.lock(); - Holder *holder; - while (holder = (Holder *)m_Holders.RemoveHead()) { - m_HoldersGuard.unlock(); - holder->Handler->onExit(); - delete holder; - m_HoldersGuard.lock(); - } - m_HoldersGuard.unlock(); - return 0; -} - -// ---------------------------------------------------------------------------- -int Dispatcher::calculatePollTimeout(nsecs_t timeout) -{ - // calculate next wake-up time according to timers queue or user timeout - std::lock_guard guard(m_TimersGuard); - HolderTimer *timer = (HolderTimer *)m_Timers.GetHead(); - if (timer) { - int t1 = toMillisecondTimeoutDelay(systemTime(), timer->Uptime); - int t2 = ns2ms(timeout); - return ((unsigned long)t2 == INFINITE)? t1 : (t1 > t2 ? t2 : t1); - } - - // No timer in timer queue, returning timeout input in msec - return ns2ms(timeout); -} - - -// ---------------------------------------------------------------------------- -} // namespace perc diff --git a/src/tm2/libtm/infra/src/Event_lin.cpp b/src/tm2/libtm/infra/src/Event_lin.cpp deleted file mode 100644 index 30071e4394..0000000000 --- a/src/tm2/libtm/infra/src/Event_lin.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#include "Event.h" -#include -# include - -perc::Event::Event() -{ - // LINUX: NONBLOCK - mEvent = ::eventfd(0, EFD_NONBLOCK); - - ASSERT(mEvent != ILLEGAL_HANDLE); -} - -perc::Event::~Event() -{ - ::close(mEvent); -} - -int perc::Event::reset() -{ - ASSERT(mEvent != ILLEGAL_HANDLE); - - eventfd_t cnt; - return !::eventfd_read(mEvent, &cnt); -} - -int perc::Event::signal() { - ASSERT(mEvent != ILLEGAL_HANDLE); - - return !::eventfd_write(mEvent, 1); -} \ No newline at end of file diff --git a/src/tm2/libtm/infra/src/Event_win.cpp b/src/tm2/libtm/infra/src/Event_win.cpp deleted file mode 100644 index b9f832751c..0000000000 --- a/src/tm2/libtm/infra/src/Event_win.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#include "Event.h" - -perc::Event::Event() -{ - // WINDOWS: manual reset event - mEvent = CreateEvent( - NULL, // default security attributes - TRUE, // manual-reset event - FALSE, // initial state is nonsignaled - NULL // Unnamed event - ); - - ASSERT(mEvent != ILLEGAL_HANDLE); -} - -perc::Event::~Event() -{ - CloseHandle(mEvent); -} - -int perc::Event::reset() -{ - ASSERT(mEvent != ILLEGAL_HANDLE); - return ResetEvent(mEvent); -} - -int perc::Event::signal() -{ - ASSERT(mEvent != ILLEGAL_HANDLE); - - return SetEvent(mEvent); -} \ No newline at end of file diff --git a/src/tm2/libtm/infra/src/Fsm.cpp b/src/tm2/libtm/infra/src/Fsm.cpp deleted file mode 100644 index 4bd281cca6..0000000000 --- a/src/tm2/libtm/infra/src/Fsm.cpp +++ /dev/null @@ -1,394 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ -#define LOG_TAG "Fsm" - -#include "Fsm.h" - -namespace perc { -// -[statics]------------------------------------------------------------------ -// Default NONE transition definition. -const FsmTransition s_FsmTransitionNone = -{ - FSM_TRANSITION_NONE_NAME, - FSM_TRANSITION_NONE, - FSM_EVENT_NONE, - FSM_STATE_FINAL, - 0, - 0, - FSM_TRANSITION_AFTER_MAX_TIMEOUT -}; - -// Default FINAL state definition. -const FsmState s_FsmStateFinal = -{ - FSM_STATE_FINAL_NAME, - FSM_STATE_FINAL, - 0, - 0, - (FsmTransition *)&s_FsmTransitionNone -}; - - -// ---------------------------------------------------------------------------- -Fsm::Fsm() : - m_pFsm(0), - m_CurrStateId(FSM_STATE_FINAL), - m_Owner(NULL), - m_Dispatcher(NULL), - m_Name(""), - m_SelfEvent(NULL) -{ -} - -// ---------------------------------------------------------------------------- -Fsm::~Fsm() -{ - done(); -} - -// ---------------------------------------------------------------------------- -int -Fsm::init (const FsmState * const *pFsm, - void *Owner, - Dispatcher *pDispatcher, - const char *Name) -{ - // User MUST define FSM definition!!! - ASSERT (pFsm); - - // Save context parameters - m_pFsm = pFsm; - m_Owner = Owner; - m_Dispatcher = pDispatcher; - m_Name = Name; - - // Check if Dispatcher was set - if (!dispatcher ()) - { - LOGW("engine not found, can't schedule after transitions!"); - } - - // Init Initial state and call to initial state entry function. - // Initial state always first into the states list! - resetSelfEvent(); - int ret_code = InitNewState (m_pFsm[0]->Type); - if (FSM_CONTEXT_STATUS_OK != ret_code) - { - ASSERT(!m_SelfEvent); - logRetCode(ret_code, m_pFsm[m_CurrStateId], Message(FSM_EVENT_NONE)); - return ret_code; - } - // Process self event if exist - return processSelfEvent(); -} - -// ---------------------------------------------------------------------------- -int -Fsm::done () -{ - if (!m_pFsm) return FSM_CONTEXT_STATUS_ERROR; - - CancelAfterTransitions (); - - // Reset FSM context parameters. - m_pFsm = 0; - m_Owner = 0; - m_Dispatcher = 0; - - return FSM_CONTEXT_STATUS_OK; -} - -// ---------------------------------------------------------------------------- -int -Fsm::fireEvent (const Message &pEvent) -{ - ASSERT (m_pFsm); - - resetSelfEvent(); - - // Find transition that wait for this event - const FsmState *state = m_pFsm[m_CurrStateId]; - int transition_id = FSM_TRANSITION_NONE; - int ret_code = FSM_CONTEXT_STATUS_ERROR; - const FsmTransition *transition = 0; - - if ((ret_code = FindTransition (&transition_id, pEvent)) != FSM_CONTEXT_STATUS_OK) - goto exit_; - else - transition = &state->TransitionList[transition_id]; - - ASSERT (transition); - - // Check if transition internal - in this case only call to transition action and return - if (transition->NewState == FSM_STATE_SAME) - { - CallTransitionAction (transition, pEvent); - ret_code = FSM_CONTEXT_STATUS_OK; - goto exit_; - } - - // Transition is not internal - we must to do some work... - // Call to state exit function. - DoneCurrState (); - - // Call to transition action. - CallTransitionAction (transition, pEvent); - - // Go to new state, that may be final... - ret_code = InitNewState (transition->NewState); - -exit_: - if (FSM_CONTEXT_STATUS_OK != ret_code) - { - ASSERT(!m_SelfEvent); - logRetCode(ret_code, state, pEvent); - return ret_code; - } - // Process self event if exist - return processSelfEvent(); -} - -// -[Private functions]-------------------------------------------------------- -int -Fsm::InitNewState (int StateType) -{ - // Check if new state - final state - if (StateType == FSM_STATE_FINAL) - return FSM_CONTEXT_STATUS_STATE_FINAL; - - // Find new state definition - for (int state_id = 0; m_pFsm[state_id]->Type != FSM_STATE_FINAL; state_id++) - { - if (m_pFsm[state_id]->Type == StateType) - { - // Remember new state index. - m_CurrStateId = state_id; - - CallStateEntry (); - - // Schedule After transitions timers via engine object. - ScheduleAfterTransitions (); - - return FSM_CONTEXT_STATUS_OK; - } - } - return FSM_CONTEXT_STATUS_STATE_NOT_FOUND; -} - -// ---------------------------------------------------------------------------- -int -Fsm::DoneCurrState () -{ - // Cancel after transitions timers - CancelAfterTransitions (); - - // Call to old state exit function - CallStateExit (); - - return FSM_CONTEXT_STATUS_OK; -} - -// ---------------------------------------------------------------------------- -int -Fsm::FindTransition (int *pTransitionId, const Message &pEvent) -{ - const FsmTransition *transition_list = m_pFsm[m_CurrStateId]->TransitionList; - int ret_code = FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND; - - // = Find transition that wait for this event type - // - if (pEvent.Type == FSM_EVENT_TIMEOUT) - { - int tid = pEvent.Param; - - ASSERT (transition_list[tid].Type == FSM_TRANSITION_AFTER || - transition_list[tid].Type == FSM_TRANSITION_INTERNAL_AFTER); - - // Check transition Guard function - if (CallTransitionGuard (&transition_list[tid], pEvent)) - { - // Save found transition - *pTransitionId = tid; - ret_code = FSM_CONTEXT_STATUS_OK; - } - else - ret_code = FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED; - } - else - { - for (int i = 0; transition_list[i].Type != FSM_TRANSITION_NONE; i++) - { - // Check if this transition wait for current event. - if (transition_list[i].EventType == pEvent.Type) - { - ret_code = FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED; - - if (CallTransitionGuard (&transition_list[i], pEvent)) - { - // Save found transition - *pTransitionId = i; - return FSM_CONTEXT_STATUS_OK; - } - } - } - } - return ret_code; -} - -// ---------------------------------------------------------------------------- -int -Fsm::ScheduleAfterTransitions () -{ - if (!dispatcher ()) return -1; - - const FsmState *state = m_pFsm[m_CurrStateId]; - const FsmTransition *transition_list = state->TransitionList; - - // Find after transitions and schedule timers for them. - for (int tid = 0; transition_list[tid].Type != FSM_TRANSITION_NONE; tid++) - { - // Check if this after transition - if (transition_list[tid].TimeOut != FSM_TRANSITION_AFTER_MAX_TIMEOUT) - { - uintptr_t timerId = dispatcher ()->scheduleTimer(this, ms2ns(transition_list[tid].TimeOut), Message(FSM_EVENT_TIMEOUT, tid)); - if (!timerId) - { - LOGE("[%s]:invalid timer id, can't schedule more!", state->Name); - } - } - } - return 0; -} - -// ---------------------------------------------------------------------------- -int Fsm::CancelAfterTransitions() -{ - if (!dispatcher()) return -1; - - // Cancel timer for all after transitions that was scheduled. - dispatcher()->removeHandler(this, Dispatcher::TIMERS_MASK); - return 0; -} - -// ---------------------------------------------------------------------------- -bool Fsm::CallTransitionGuard(const FsmTransition *pTransition, const Message &pEvent) -{ - bool ret = true; - - if (pTransition->Guard) - ret = pTransition->Guard(this, pEvent); - - LOGV("[%s]:%s[%s]:guard%s %d", - m_pFsm[m_CurrStateId]->Name, - TransitionType (pTransition->Type), - pTransition->Name, - pTransition->Guard ? "()": "(none)", - ret); - return ret; -} - -// ---------------------------------------------------------------------------- -void Fsm::CallTransitionAction(const FsmTransition *pTransition, const Message &pEvent) -{ - LOGV("[%s]:%s[%s]:action%s", - m_pFsm[m_CurrStateId]->Name, - TransitionType (pTransition->Type), - pTransition->Name, - pTransition->Action ? "()": "(none)"); - if (pTransition->Action) - pTransition->Action(this, pEvent); -} - -// ---------------------------------------------------------------------------- -void Fsm::CallStateEntry() -{ - const FsmState *state = m_pFsm[m_CurrStateId]; - LOGV("[%s]:entry%s", - state->Name, - state->Entry ? "()": "(none)"); - if (state->Entry) - state->Entry(this); -} - -// ---------------------------------------------------------------------------- -void Fsm::CallStateExit() -{ - const FsmState *state = m_pFsm[m_CurrStateId]; - LOGV("[%s]:exit%s", - state->Name, - state->Exit ? "()": "(none)"); - if (state->Exit) - state->Exit(this); -} - -// ---------------------------------------------------------------------------- -// A utility static method that translates FSM status to a string. -const char* -Fsm::statusName (int Status) -{ - switch (Status) - { - case FSM_CONTEXT_STATUS_ERROR: return "Error"; - case FSM_CONTEXT_STATUS_OK: return "OK"; - case FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND: return "Transition not found"; - case FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED: return "Event not handled"; - case FSM_CONTEXT_STATUS_STATE_FINAL: return "State final"; - case FSM_CONTEXT_STATUS_STATE_NOT_FOUND: return "State not found"; - } - return "Unknown status"; -} - -// ---------------------------------------------------------------------------- -// A utility static method that translates FSM transition to a string. -const char* -Fsm::TransitionType (int Transition) -{ - switch (Transition) - { - case FSM_TRANSITION_NONE: return "TN"; - case FSM_TRANSITION: return "T"; - case FSM_TRANSITION_AFTER: return "TA"; - case FSM_TRANSITION_INTERNAL: return "TI"; - case FSM_TRANSITION_INTERNAL_AFTER: return "TIA"; - } - return "T?"; -} - -// ---------------------------------------------------------------------------- -// Debug print of FSM status -void -Fsm::logRetCode (int retCode, const FsmState *state, const Message &pEvent) -{ - if (FSM_CONTEXT_STATUS_STATE_FINAL == retCode) - { - LOGD("final state reached"); - } - else - { - if (FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED == retCode) - { - LOGW("[%s]:event[%d] not handled", state->Name, pEvent.Type); - } - else if (FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND == retCode) - { - LOGW("[%s]:no appropriate transition for this event[%d]", - state->Name, pEvent.Type); - } - else if (FSM_CONTEXT_STATUS_STATE_NOT_FOUND == retCode) - { - LOGW("[%s]:no appropriate state found for this event[%d]", - state->Name, pEvent.Type); - } - else - { - LOGE("[%s]:undefined status error - %d, event[%d]", - state->Name, retCode, pEvent.Type); - } - } -} - - -// ---------------------------------------------------------------------------- -} // namespace perc diff --git a/src/tm2/libtm/infra/src/Log.cpp b/src/tm2/libtm/infra/src/Log.cpp deleted file mode 100644 index 47113020e7..0000000000 --- a/src/tm2/libtm/infra/src/Log.cpp +++ /dev/null @@ -1,351 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#include "Log.h" -#include "Utils.h" -#include -#include -#include -#include -#include "../include/TrackingData.h" - -#ifdef _WIN32 -#include -#include -#include -#include -#endif - -#ifdef __linux__ -#include -#include -#include -#include -#include -#include -#include -#define gettid() syscall(SYS_gettid) -#endif - -using namespace perc; - -/* Define TIME_OF_DAY_LOG_HEADER to enable time of day (HH:MM:SS.mmm) log */ -/* Undefine TIME_OF_DAY_LOG_HEADER to enable epoch time log */ -#define TIME_OF_DAY_LOG_HEADER - -#define MAX_LOG_CONTAINERS 2 - -// Default priority mask - ALL enabled -static int s_PriorityMask = /*LOG_PRI2MASK(LOG_VERBOSE) | */ -LOG_PRI2MASK(LOG_DEBUG) | -LOG_PRI2MASK(LOG_TRACE) | -LOG_PRI2MASK(LOG_INFO) | -LOG_PRI2MASK(LOG_WARN) | -LOG_PRI2MASK(LOG_ERROR); - -class AdvancedLog : public TrackingData::Log { -public: - AdvancedLog() : rolledOver(0) {}; - - uint8_t rolledOver; -}; - - -class LogConfiguration { -public: - uint32_t outputMode; - uint8_t verbosityMask; - uint8_t rolloverMode; -} ; - -class logManager { -public: - logManager() : activeContainer(0) - { - loadTimestamp = systemTime(); - configuration[LogSourceHost].verbosityMask = s_PriorityMask; - configuration[LogSourceHost].rolloverMode = true; - configuration[LogSourceHost].outputMode = LogOutputModeScreen; - }; - - nsecs_t loadTimestamp; - - std::mutex configurationMutex; - LogConfiguration configuration[LogSourceMax]; - - std::atomic activeContainer; - std::mutex logContainerMutex[MAX_LOG_CONTAINERS]; - AdvancedLog logContainer[MAX_LOG_CONTAINERS]; -}; - -logManager gLogManager; - -FILE *gStream = NULL; - -const char* logPrioritySign[] = { "U" ,/* LOG_UNKNOWN */ -"T" ,/* LOG_DEFAULT */ -"V" ,/* LOG_VERBOSE */ -"D" ,/* LOG_DEBUG */ -"T" ,/* LOG_TRACE */ -"I" ,/* LOG_INFO */ -"W" ,/* LOG_WARN */ -"E" ,/* LOG_ERROR */ -"F" ,/* LOG_FATAL */ -"S" ,/* LOG_SILENT */ }; - -const LogVerbosityLevel prio2verbosity[] = {None, None, Verbose, Debug, Trace, Info, Warning, Error, Error, None}; - -// LOG_FATAL is always enabled by default -#define isPriorityEnabled(prio) (LOG_FATAL == prio || (gLogManager.configuration[LogSourceHost].verbosityMask & LOG_PRI2MASK(prio))) - -void __perc_Log_write(int prio, const char *tag, const char *text) -{ - if (!isPriorityEnabled(prio)) - return; - - fprintf(stdout, "%s", text); - -} - - void __perc_Log_Save(void* deviceId, int prio, const char *tag, int line, int payloadLen, char* payload) -{ - std::lock_guard guardLogContainer(gLogManager.logContainerMutex[gLogManager.activeContainer]); - AdvancedLog* logContainer = &gLogManager.logContainer[gLogManager.activeContainer]; - uint32_t entries = logContainer->entries; - - if ((gLogManager.configuration[LogSourceHost].rolloverMode == 0) && (logContainer->rolledOver == 1)) - { - printf("rolled over - stopped saving prints on container %d, entries = %d...\n", (uint32_t)gLogManager.activeContainer, entries); - return; - } - - logContainer->entry[entries].timeStamp = systemTime() - gLogManager.loadTimestamp; - - HostLocalTime localTime = getLocalTime(); - - logContainer->entry[entries].localTimeStamp.year = localTime.year; - logContainer->entry[entries].localTimeStamp.month = localTime.month; - logContainer->entry[entries].localTimeStamp.dayOfWeek = localTime.dayOfWeek; - logContainer->entry[entries].localTimeStamp.day = localTime.day; - logContainer->entry[entries].localTimeStamp.hour = localTime.hour; - logContainer->entry[entries].localTimeStamp.minute = localTime.minute; - logContainer->entry[entries].localTimeStamp.second = localTime.second; - logContainer->entry[entries].localTimeStamp.milliseconds = localTime.milliseconds; - - logContainer->entry[entries].lineNumber = line; - - if (tag != NULL) - { - perc::copy(&logContainer->entry[entries].moduleID, tag, MAX_LOG_BUFFER_MODULE_SIZE); - } - - logContainer->entry[entries].verbosity = prio2verbosity[prio]; - logContainer->entry[entries].deviceID = (uint64_t)deviceId; - logContainer->entry[entries].threadID = getThreadId(); - - snprintf(logContainer->entry[entries].payload, payloadLen + 1, "%s", payload); - logContainer->entry[entries].payloadSize = payloadLen; - - if (entries == (MAX_LOG_BUFFER_ENTRIES-1)) - { - printf("entries %d, setting rollover = 1...\n", logContainer->entries); - logContainer->rolledOver = 1; - } - - logContainer->entries = (entries + 1) % MAX_LOG_BUFFER_ENTRIES; - -} - - -#ifdef __linux__ -int __perc_Log_print_header(char * buf, int bufSize, int prio, const char *tag, const char* deviceId) -{ - struct timeval tv; - int headerLen; - static struct timeval loadTime = { 0 }; - - gettimeofday(&tv, NULL); - -#ifdef TIME_OF_DAY_LOG_HEADER - - // Round to nearest millisec - int millisec = lrint(tv.tv_usec / 1000.0); - if (millisec >= 1000) - { - millisec -= 1000; - tv.tv_sec++; - } - - struct tm* tm_info = localtime(&tv.tv_sec); // Transform time to ASCII - int timeLen = 8; // Length of adding HH:MM:SS - strftime(buf, timeLen + 2, "%H:%M:%S", tm_info); // Adding HH:MM:SS/0 to buffer - headerLen = snprintf(buf + timeLen, bufSize - timeLen, ".%03d [%lu] [%s] %s%s: ", millisec, gettid(), logPrioritySign[prio], tag, deviceId) + timeLen; - -#else - if (loadTime.tv_sec == 0) - { - loadTime = tv; - } - - unsigned long currentTime = ((tv.tv_sec * 1000000) + tv.tv_usec) - ((loadTime.tv_sec * 1000000) + loadTime.tv_usec); - headerLen = snprintf(buf, bufSize, "%014lu [%lu] [%s] %s: ", currentTime, gettid(), logPrioritySign[prio], tag); - -#endif //TIME_OF_DAY_LOG_HEADER - - return headerLen; -} -#elif _WIN32 -int __perc_Log_print_header(char * buf, int bufSize, int prio, const char *tag, const char* deviceId) -{ - int headerLen = 0; - -#ifdef TIME_OF_DAY_LOG_HEADER - - SYSTEMTIME lt; - GetLocalTime(<); - - headerLen = snprintf(buf, bufSize, "%02d:%02d:%02d:%03d [%06lu] [%s] %s%s: ", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds, GetCurrentThreadId(), logPrioritySign[prio], tag, deviceId); - -#else - - static unsigned long long loadTime = 0; - FILETIME currentFileTime; - GetSystemTimeAsFileTime(¤tFileTime); - unsigned long long currentTime = (((ULONGLONG)currentFileTime.dwHighDateTime << 32) | (ULONGLONG)currentFileTime.dwLowDateTime) / 10000; - - if (loadTime == 0) - { - loadTime = currentTime; - } - - headerLen = snprintf(buf, bufSize, "%011llu [%06lu] [%s] %s: ", currentTime - loadTime, GetCurrentThreadId(), logPrioritySign[prio], tag); - -#endif // TIME_OF_DAY_LOG_HEADER - - return headerLen; - -} -#endif - -void __perc_Log_print(void* deviceId, int prio, const char *tag, int line, const char *fmt, ...) -{ - if (isPriorityEnabled(prio)) { - uint8_t outputMode; - uint8_t verbosity; - uint8_t rolloverMode; - int headerLen = 0; - int payloadLen = 0; - - va_list ap; - va_start(ap, fmt); - - __perc_Log_Get_Configuration(LogSourceHost, &outputMode, &verbosity, &rolloverMode); - - char buf[BUFSIZ * 4]; - - if (outputMode == LogOutputModeScreen) - { - char deviceBuf[30] = { 0 }; - if (deviceId != NULL) - { - short device = ((uintptr_t)deviceId & 0xFFFF); - snprintf(deviceBuf, sizeof(deviceBuf), "-%04hX", device); - } - - headerLen = __perc_Log_print_header(buf, sizeof(buf), prio, tag, deviceBuf); - - ASSERT((size_t)headerLen < sizeof(buf)); - payloadLen = vsnprintf(buf + headerLen, sizeof(buf) - headerLen, fmt, ap); - fprintf(stdout, "%s\n", buf); - } - else - { - payloadLen = vsnprintf(buf, sizeof(buf), fmt, ap); - __perc_Log_Save(deviceId, prio, tag, line, payloadLen, buf); - } - - va_end(ap); - } -} - -void __perc_Log_Set_Configuration(uint8_t source, uint8_t outputMode, uint8_t verbosity, uint8_t rolloverMode) -{ - std::lock_guard guardLogConfiguration(gLogManager.configurationMutex); - - gLogManager.configuration[source].outputMode = outputMode; - gLogManager.configuration[source].rolloverMode = rolloverMode; - - uint8_t verbosityMask = 0; - switch (verbosity) - { - case Trace: - verbosityMask |= LOG_PRI2MASK(LOG_TRACE); - case Verbose: - verbosityMask |= LOG_PRI2MASK(LOG_VERBOSE); - case Debug: - verbosityMask |= LOG_PRI2MASK(LOG_DEBUG); - case Warning: - verbosityMask |= LOG_PRI2MASK(LOG_WARN); - case Info: - verbosityMask |= LOG_PRI2MASK(LOG_INFO); - case Error: - verbosityMask |= LOG_PRI2MASK(LOG_ERROR); - case None: - verbosityMask |= 0; - } - - gLogManager.configuration[source].verbosityMask = verbosityMask; -} - -void __perc_Log_Get_Configuration(uint8_t source, uint8_t* outputMode, uint8_t* verbosity, uint8_t* rolloverMode) -{ - std::lock_guard guardLogConfiguration(gLogManager.configurationMutex); - - *outputMode = gLogManager.configuration[source].outputMode; - *verbosity = gLogManager.configuration[source].verbosityMask; - *rolloverMode = gLogManager.configuration[source].rolloverMode; -} - - -void __perc_Log_Get_Log(void* log) -{ - TrackingData::Log* logOutput = (TrackingData::Log*)log; - uint32_t entryIndex = 0; - - uint8_t activeContainer = gLogManager.activeContainer; - - gLogManager.activeContainer ^= 1; - - { - std::lock_guard guardLogContainer(gLogManager.logContainerMutex[activeContainer]); - AdvancedLog* logContainer = &gLogManager.logContainer[activeContainer]; - - if (logContainer->rolledOver == 1) - { - for (uint32_t i = logContainer->entries; i < MAX_LOG_BUFFER_ENTRIES; i++, entryIndex++) - { - logOutput->entry[entryIndex] = logContainer->entry[i]; - } - - logOutput->entries = MAX_LOG_BUFFER_ENTRIES; - } - else - { - logOutput->entries = logContainer->entries; - } - - for (uint32_t i = 0; i < logContainer->entries; i++, entryIndex++) - { - logOutput->entry[entryIndex] = logContainer->entry[i]; - } - - logOutput->maxEntries = MAX_LOG_BUFFER_ENTRIES; - - logContainer->entries = 0; - logContainer->rolledOver = 0; - - //printf("got log on container %d - %d entries (new container %d)\n", activeContainer, logOutput->entries, (uint32_t)gLogManager.activeContainer); - } -} diff --git a/src/tm2/libtm/infra/src/Poller_lin.cpp b/src/tm2/libtm/infra/src/Poller_lin.cpp deleted file mode 100644 index 90050cf34e..0000000000 --- a/src/tm2/libtm/infra/src/Poller_lin.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#define LOG_TAG "infra/Poller" -#define LOG_NDEBUG 1 // controls LOGV only -#include "Log.h" -#include "Utils.h" -#include "Poller.h" -#include "TrackingCommon.h" -#include -#include - -namespace perc -{ - - struct Poller::CheshireCat - { - std::unordered_map mEvents; - std::mutex mEventsGuard; - int mEpoll; - }; - - const int Poller::READ_MASK = EPOLLIN | EPOLLRDNORM; - const int Poller::NULL_MASK = 0; - const int Poller::WRITE_MASK = EPOLLOUT | EPOLLWRNORM; - const int Poller::EXCEPT_MASK = EPOLLPRI | EPOLLERR | EPOLLHUP | EPOLLRDHUP; - const int Poller::ALL_MASK = READ_MASK | WRITE_MASK | EXCEPT_MASK; - - Poller::Poller() : mData(new CheshireCat()) { - mData->mEpoll = ::epoll_create(1); - ASSERT(mData->mEpoll != -1); - } - - Poller::~Poller() - { - ::close(mData->mEpoll); - } - - bool Poller::add(const Poller::event &evt) - { - int res = -1; - if (evt.handle != -1) { - struct epoll_event e; - e.events = evt.mask; - e.data.fd = evt.handle; - // synchronized section - std::lock_guard guard(mData->mEventsGuard); - if (mData->mEvents.count(evt.handle) == 0) { - // adding new one - res = ::epoll_ctl(mData->mEpoll, EPOLL_CTL_ADD, evt.handle, &e); - if (!res) - mData->mEvents.emplace(evt.handle, evt); - } - else { - // modifying old one - res = ::epoll_ctl(mData->mEpoll, EPOLL_CTL_MOD, evt.handle, &e); - if (!res) - mData->mEvents[evt.handle] = evt; - } - } - return res == 0; - } - - bool Poller::remove(Handle handle) - { - if (handle != -1) { - //ASSERT(::pthread_equal(tid, ::pthread_self())); - std::lock_guard guard(mData->mEventsGuard); - if (mData->mEvents.count(handle) > 0) { - int res = ::epoll_ctl(mData->mEpoll, EPOLL_CTL_DEL, handle, 0); - mData->mEvents.erase(handle); - return res == 0; - } - } - return false; - } - - int Poller::poll(Poller::event &evt, unsigned long timeoutMs) - { - struct epoll_event e; - int timeoutAbs = (int)timeoutMs == INFINITE ? INFINITE : ns2ms(systemTime()) + (int)timeoutMs; - again: - int n = ::epoll_wait(mData->mEpoll, &e, 1, (int)timeoutMs); - if (n > 0) { - std::lock_guard guard(mData->mEventsGuard); - LOGV("poll: e.data.fd %d", e.data.fd); - if (mData->mEvents.count(e.data.fd) > 0) { - evt = mData->mEvents[e.data.fd]; - } - else { - ::epoll_ctl(mData->mEpoll, EPOLL_CTL_DEL, e.data.fd, 0); - int cur = ns2ms(systemTime()); - if ((int)timeoutMs != INFINITE) { - if (cur < (int)timeoutAbs) - timeoutMs = timeoutAbs - cur; - else - return 0; - } - goto again; - } - } - else if (n == -1) { - // TODO: add logs or clear errors - LOGE("poll: epoll_wait error %d", errno); - } - // timeout - return n; - } - -} // namespace perc diff --git a/src/tm2/libtm/infra/src/Poller_win.cpp b/src/tm2/libtm/infra/src/Poller_win.cpp deleted file mode 100644 index e15d5505e7..0000000000 --- a/src/tm2/libtm/infra/src/Poller_win.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#define LOG_TAG "infra/Poller" -#define LOG_NDEBUG 1 // controls LOGV only -#include "Log.h" -#include "Utils.h" -#include "Poller.h" -#include "TrackingCommon.h" - - -namespace perc -{ - - struct Poller::CheshireCat - { - std::unordered_map mEvents; - std::mutex mEventsGuard; - - std::mutex mAddRemoveMutex; - std::vector mHandles; - HANDLE mAddRemoveDoneEvent; - }; - - const int Poller::READ_MASK = 0; - const int Poller::NULL_MASK = 0; - const int Poller::WRITE_MASK = 0; - const int Poller::EXCEPT_MASK = 0; - const int Poller::ALL_MASK = 0; - - - Poller::Poller() : mData(new CheshireCat()) - { - mData->mAddRemoveDoneEvent = CreateEvent( - NULL, // default security attributes - TRUE, // manual-reset event - FALSE, // initial state is nonsignaled - NULL // Unnamed event - ); - ASSERT(mData->mAddRemoveDoneEvent != ILLEGAL_HANDLE); - - mData->mHandles.push_back(mData->mAddRemoveDoneEvent); - } - - Poller::~Poller() - { - CloseHandle(mData->mAddRemoveDoneEvent); - } - - bool Poller::add(const Poller::event &evt) - { - // Take Add remove/mutex - std::lock_guard guardAddRemove(mData->mAddRemoveMutex); - - // Notify poll thread to exit and release mEventsGuard - SetEvent(mData->mAddRemoveDoneEvent); - - // Take mEventsGuard - wait for poll thread to release the mutex - std::lock_guard guardEvents(mData->mEventsGuard); - - ResetEvent(mData->mAddRemoveDoneEvent); - - //check if this handle already exists - auto it = std::find(mData->mHandles.begin(), mData->mHandles.end(), evt.handle); - - // we are already at maximum events - if (mData->mHandles.size() >= MAXIMUM_WAIT_OBJECTS && it == mData->mHandles.end()) - return false; - - if (it == mData->mHandles.end()) - mData->mHandles.push_back(evt.handle); - - // Add or modify event in events map - mData->mEvents[evt.handle] = evt; - - return true; - } - - bool Poller::remove(Handle handle) - { - // Take Add remove/mutex - std::lock_guard guardAddRemove(mData->mAddRemoveMutex); - - // Notify poll thread to exit and release mEventsGuard - SetEvent(mData->mAddRemoveDoneEvent); - - // Take mEventsGuard - wait for poll thread to release the mutex - std::lock_guard guardEvents(mData->mEventsGuard); - - ResetEvent(mData->mAddRemoveDoneEvent); - - //check if this handle already exists - auto it = std::find(mData->mHandles.begin(), mData->mHandles.end(), handle); - - if (it == mData->mHandles.end()) - return false; - - // Remove the handle from vector - mData->mHandles.erase(it); - - // remove the evt from events map - mData->mEvents.erase(handle); - - return true; - } - - int Poller::poll(Poller::event &evt, unsigned long timeoutMs) - { - int timeoutAbs = (timeoutMs == INFINITE)? INFINITE : ns2ms(systemTime()) + (int)timeoutMs; - - while (true) - { - int exitReason; - { - // Take events guard, to wait safely on vector of handles - std::lock_guard guardEvents(mData->mEventsGuard); - - exitReason = WaitForMultipleObjects((DWORD)mData->mHandles.size(), - mData->mHandles.data(), - FALSE, - timeoutMs); - - int eventIndex = exitReason - WAIT_OBJECT_0; - if ((exitReason < 0) || ((unsigned int)eventIndex >= mData->mHandles.size())) - { - return 0; - } - else if (eventIndex != 0) // zero is special case - it is our private event - { - evt = mData->mEvents[mData->mHandles[eventIndex]]; - return 1; - } - } // here we release the events guard, so Add/remove function may continue - - // WAIT_OBJECT_0 means add/remove function called, lets wait for it completion by just waiting for the lock - { - std::lock_guard guardAddRemove(mData->mAddRemoveMutex); - - // we are going to a second iteration - update timeout - if (timeoutMs != INFINITE) - { - auto nowMs = ns2ms(systemTime()); - if (nowMs >= timeoutAbs) - timeoutMs = 0; - else - timeoutMs = timeoutAbs - nowMs; - } - } - } - return 0; - } -} // namespace perc diff --git a/src/tm2/libtm/infra/src/Semaphore_lin.cpp b/src/tm2/libtm/infra/src/Semaphore_lin.cpp deleted file mode 100644 index 7fdfc94353..0000000000 --- a/src/tm2/libtm/infra/src/Semaphore_lin.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#include "Semaphore.h" -#include "Utils.h" -#include - -namespace perc -{ - - struct Semaphore::CheshireCat - { - ::sem_t m_Semaphore; - }; - - int Semaphore::get(nsecs_t timeoutNs) - { - if (timeoutNs == -1) - return ::sem_wait(&mData->m_Semaphore); - if (timeoutNs == 0) - return ::sem_trywait(&mData->m_Semaphore); - nsecs_t nsecsTime = systemTime() + timeoutNs; - struct timespec absTimeout; - absTimeout.tv_sec = ns2s(nsecsTime); - absTimeout.tv_nsec = nsecsTime % 1000000000; - return ::sem_timedwait(&mData->m_Semaphore, &absTimeout); - } - - - Semaphore::Semaphore(unsigned int initValue) : mData(new CheshireCat()) - { - ::sem_init(&mData->m_Semaphore, 0, initValue); - } - - Semaphore::~Semaphore() - { - ::sem_destroy(&mData->m_Semaphore); - } - - int Semaphore::put() - { - return ::sem_post(&mData->m_Semaphore); - } - - - -} // namespace perc diff --git a/src/tm2/libtm/infra/src/Semaphore_win.cpp b/src/tm2/libtm/infra/src/Semaphore_win.cpp deleted file mode 100644 index 5fac93e5a5..0000000000 --- a/src/tm2/libtm/infra/src/Semaphore_win.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#include "Semaphore.h" -#include "Utils.h" -#include -#include - -#define MAX_SEMAPHORE_COUNT 128 - -namespace perc { - - struct Semaphore::CheshireCat - { - HANDLE m_Semaphore; - }; - - int Semaphore::get(nsecs_t timeoutNs) - { - DWORD timeoutMs; - - timeoutMs = ns2ms(timeoutNs); - - auto ret = WaitForSingleObject(mData->m_Semaphore, timeoutMs); - - if (ret == WAIT_OBJECT_0) // well this is useless , since WAIT_OBJECT_0 is equal to 0, but at least this is clear; - return 0; - - return -1; - } - - - Semaphore::Semaphore(unsigned int initValue) : mData(new CheshireCat()) - { - mData->m_Semaphore = CreateSemaphore( - NULL, // default security attributes - initValue, // initial count - MAX_SEMAPHORE_COUNT, // maximum count - NULL); - - assert(mData->m_Semaphore != NULL); - } - - Semaphore::~Semaphore() - { - CloseHandle(mData->m_Semaphore); - } - - int Semaphore::put() - { - int ret = ReleaseSemaphore( - mData->m_Semaphore, // handle to semaphore - 1, // increase count by one - NULL); // not interested in previous count - - return !ret; // return zero on success; same as in linux - } - - - -} // namespace perc diff --git a/src/tm2/libtm/infra/src/Utils.cpp b/src/tm2/libtm/infra/src/Utils.cpp deleted file mode 100644 index 0713f5c5a0..0000000000 --- a/src/tm2/libtm/infra/src/Utils.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ -#define LOG_TAG "Utils" -#include "Utils.h" -#include -#include - -#ifdef __linux__ -#include -#include -#include -#include -#include -#include -#define gettid() syscall(SYS_gettid) -#else -#include -#include -#endif - -nsecs_t systemTime() -{ -#ifdef _WIN32 - /* - auto start = std::chrono::high_resolution_clock::now(); - std::chrono::time_point time_point_ns(start); - return time_point_ns.time_since_epoch().count();*/ - LARGE_INTEGER StartingTime; - LARGE_INTEGER Frequency; - - QueryPerformanceFrequency(&Frequency); - QueryPerformanceCounter(&StartingTime); - - StartingTime.QuadPart *= 1000000LL; // convert to microseconds - StartingTime.QuadPart /= Frequency.QuadPart; - StartingTime.QuadPart *= 1000; // Convert to nano seconds - - return StartingTime.QuadPart; - -#else - struct timespec ts; - ts.tv_sec = ts.tv_nsec = 0; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ((nsecs_t)(ts.tv_sec)) * 1000000000LL + ts.tv_nsec; -#endif -} - - -HostLocalTime getLocalTime() -{ - HostLocalTime localTime; - -#ifdef _WIN32 - SYSTEMTIME lt; - GetLocalTime(<); - - localTime.year = lt.wYear; - localTime.month = lt.wMonth; - localTime.dayOfWeek = lt.wDayOfWeek; - localTime.day = lt.wDay; - localTime.hour = lt.wHour; - localTime.minute = lt.wMinute; - localTime.second = lt.wSecond; - localTime.milliseconds = lt.wMilliseconds; -#else - struct timeval tv; - gettimeofday(&tv, NULL); - - // Round to nearest millisec - int millisec = lrint(tv.tv_usec / 1000.0); - if (millisec >= 1000) - { - millisec -= 1000; - tv.tv_sec++; - } - - struct tm* tm_info = localtime(&tv.tv_sec); // Transform time to ASCII - - localTime.year = tm_info->tm_year; - localTime.month = tm_info->tm_mon; - localTime.dayOfWeek = tm_info->tm_wday; - localTime.day = tm_info->tm_mday; - localTime.hour = tm_info->tm_hour; - localTime.minute = tm_info->tm_min; - localTime.second = tm_info->tm_sec; - localTime.milliseconds = millisec; -#endif - - return localTime; -} - - -uint64_t bytesSwap(uint64_t val) -{ -#ifdef _WIN32 - return _byteswap_uint64(val); -#else - return htobe64(val); -#endif -} - -uint32_t getThreadId(void) -{ -#ifdef _WIN32 - return GetCurrentThreadId(); -#else - return gettid(); -#endif -} - - -void perc::copy(void* dst, void const* src, size_t size) -{ -#ifdef _WIN32 - memcpy_s(dst, size, src, size); -#else - auto from = reinterpret_cast(src); - std::copy(from, from + size, reinterpret_cast(dst)); -#endif -} - - -size_t perc::stringLength(const char* string, size_t maxSize) -{ -#ifdef _WIN32 - return strnlen_s(string, maxSize); -#else - return strnlen(string, maxSize); -#endif -} - -bool setProcessPriorityToRealtime() -{ -#ifdef _WIN32 - HANDLE pid = GetCurrentProcess(); - if (!SetPriorityClass(pid, REALTIME_PRIORITY_CLASS)) - { - LOGE("Error: Failed to set process priority to runtime"); - return false; - } - // Display priority class - DWORD dwPriClass = GetPriorityClass(pid); - LOGD("Setting process priority to 0x%X", dwPriClass); -#else - const int MAX_NICENESS = -20; - - id_t pid = getpid(); - int ret = setpriority(PRIO_PROCESS, pid, MAX_NICENESS); - if (ret == -1) - { - LOGE("Error: Failed to set process priority - to set priority rerun with sudo\n"); - return true; - } - // Display priority class - ret = getpriority(PRIO_PROCESS, pid); - LOGD("Setting process priority to 0x%X", ret); -#endif - - return true; -} - From 8c639334d73a5f68510ee0312b4a2c1668929ca4 Mon Sep 17 00:00:00 2001 From: Belkin Date: Wed, 10 Oct 2018 16:23:00 +0300 Subject: [PATCH 18/25] Disable libtm logs by default --- src/tm2/libtm/libtm/src/Manager.cpp | 7 +++++++ src/tm2/libtm/tools/libtm_util/libtm_util.cpp | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/tm2/libtm/libtm/src/Manager.cpp b/src/tm2/libtm/libtm/src/Manager.cpp index 7b17b4f31b..bb076b741b 100644 --- a/src/tm2/libtm/libtm/src/Manager.cpp +++ b/src/tm2/libtm/libtm/src/Manager.cpp @@ -66,6 +66,13 @@ Manager::Manager(Listener* lis, void* param) : mDispatcher(new Dispatcher()), mL { throw std::runtime_error("Failed to init manager"); } + + + TrackingData::LogControl logControl(LogVerbosityLevel::None, + LogOutputMode::LogOutputModeBuffer, + true); + + setHostLogControl(logControl); } diff --git a/src/tm2/libtm/tools/libtm_util/libtm_util.cpp b/src/tm2/libtm/tools/libtm_util/libtm_util.cpp index 32ef645ac8..d0723bbf6f 100644 --- a/src/tm2/libtm/tools/libtm_util/libtm_util.cpp +++ b/src/tm2/libtm/tools/libtm_util/libtm_util.cpp @@ -3692,6 +3692,14 @@ int main(int argc, char *argv[]) gManagerInstance.get()->setHostLogControl(logControl); } + else //default logger settings + { + TrackingData::LogControl logControl(LogVerbosityLevel::Debug, + LogOutputMode::LogOutputModeScreen, + true); + + gManagerInstance.get()->setHostLogControl(logControl); + } string lookDeviceString("Looking for device..."); uint32_t deviceWaitSec = WAIT_FOR_DEVICE_SEC * 3; From 679e301d4102f6b213f2516da10e16811a3bd242 Mon Sep 17 00:00:00 2001 From: Belkin Date: Thu, 11 Oct 2018 14:03:40 +0300 Subject: [PATCH 19/25] Move libtm to third party directory --- CMakeLists.txt | 4 ++-- {src/tm2 => third-party}/libtm/.gitignore | 0 {src/tm2 => third-party}/libtm/CMakeLists.txt | 0 {src/tm2 => third-party}/libtm/cmake/linux.cmake | 0 {src/tm2 => third-party}/libtm/cmake/os.cmake | 0 {src/tm2 => third-party}/libtm/cmake/windows.cmake | 0 {src/tm2 => third-party}/libtm/libtm/CMakeLists.txt | 0 {src/tm2 => third-party}/libtm/libtm/include/TrackingCommon.h | 0 {src/tm2 => third-party}/libtm/libtm/include/TrackingData.h | 0 {src/tm2 => third-party}/libtm/libtm/include/TrackingDevice.h | 0 .../tm2 => third-party}/libtm/libtm/include/TrackingManager.h | 0 .../libtm/libtm/include/TrackingSerializer.h | 0 {src/tm2 => third-party}/libtm/libtm/src/.bdsignore | 0 {src/tm2 => third-party}/libtm/libtm/src/CMakeLists.txt | 0 {src/tm2 => third-party}/libtm/libtm/src/Common.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/Common.h | 0 {src/tm2 => third-party}/libtm/libtm/src/CompleteTask.h | 0 {src/tm2 => third-party}/libtm/libtm/src/Device.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/Device.h | 0 {src/tm2 => third-party}/libtm/libtm/src/Loader.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/Loader.h | 0 {src/tm2 => third-party}/libtm/libtm/src/Main.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/Manager.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/Manager.h | 0 {src/tm2 => third-party}/libtm/libtm/src/Message.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/Message.h | 0 {src/tm2 => third-party}/libtm/libtm/src/Protocol.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/Protocol.h | 0 .../libtm/libtm/src/RcSerializer/Packet.cpp | 0 .../tm2 => third-party}/libtm/libtm/src/RcSerializer/Packet.h | 0 .../libtm/libtm/src/RcSerializer/Player.cpp | 0 .../tm2 => third-party}/libtm/libtm/src/RcSerializer/Player.h | 0 .../libtm/libtm/src/RcSerializer/Recorder.cpp | 0 .../libtm/libtm/src/RcSerializer/Recorder.h | 0 .../libtm/libtm/src/RcSerializer/concurrency.h | 0 .../libtm/libtm/src/RcSerializer/latency_queue.h | 0 .../libtm/libtm/src/RcSerializer/rc_packet.h | 0 {src/tm2 => third-party}/libtm/libtm/src/UsbPlugListener.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/UsbPlugListener.h | 0 {src/tm2 => third-party}/libtm/libtm/src/Version.h.in | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/CMakeLists.txt | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Dispatcher.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Dispatcher.h | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/EmbeddedList.h | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Event.h | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/EventHandler.h | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Event_lin.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Event_win.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Fence.h | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Fsm.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Fsm.h | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Log.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Log.h | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Poller.h | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Poller_lin.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Poller_win.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Semaphore.h | 0 .../libtm/libtm/src/infra/Semaphore_lin.cpp | 0 .../libtm/libtm/src/infra/Semaphore_win.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Utils.cpp | 0 {src/tm2 => third-party}/libtm/libtm/src/infra/Utils.h | 0 {src/tm2 => third-party}/libtm/libtm/src/version.rc.in | 0 {src/tm2 => third-party}/libtm/resources/CMakeLists.txt | 0 {src/tm2 => third-party}/libtm/tools/CMakeLists.txt | 0 .../tm2 => third-party}/libtm/tools/libtm_util/CMakeLists.txt | 0 .../tm2 => third-party}/libtm/tools/libtm_util/libtm_util.cpp | 0 {src/tm2 => third-party}/libtm/versions.cmake | 0 67 files changed, 2 insertions(+), 2 deletions(-) rename {src/tm2 => third-party}/libtm/.gitignore (100%) rename {src/tm2 => third-party}/libtm/CMakeLists.txt (100%) rename {src/tm2 => third-party}/libtm/cmake/linux.cmake (100%) rename {src/tm2 => third-party}/libtm/cmake/os.cmake (100%) rename {src/tm2 => third-party}/libtm/cmake/windows.cmake (100%) rename {src/tm2 => third-party}/libtm/libtm/CMakeLists.txt (100%) rename {src/tm2 => third-party}/libtm/libtm/include/TrackingCommon.h (100%) rename {src/tm2 => third-party}/libtm/libtm/include/TrackingData.h (100%) rename {src/tm2 => third-party}/libtm/libtm/include/TrackingDevice.h (100%) rename {src/tm2 => third-party}/libtm/libtm/include/TrackingManager.h (100%) rename {src/tm2 => third-party}/libtm/libtm/include/TrackingSerializer.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/.bdsignore (100%) rename {src/tm2 => third-party}/libtm/libtm/src/CMakeLists.txt (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Common.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Common.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/CompleteTask.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Device.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Device.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Loader.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Loader.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Main.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Manager.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Manager.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Message.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Message.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Protocol.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Protocol.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/RcSerializer/Packet.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/RcSerializer/Packet.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/RcSerializer/Player.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/RcSerializer/Player.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/RcSerializer/Recorder.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/RcSerializer/Recorder.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/RcSerializer/concurrency.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/RcSerializer/latency_queue.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/RcSerializer/rc_packet.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/UsbPlugListener.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/UsbPlugListener.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/Version.h.in (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/CMakeLists.txt (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Dispatcher.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Dispatcher.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/EmbeddedList.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Event.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/EventHandler.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Event_lin.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Event_win.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Fence.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Fsm.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Fsm.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Log.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Log.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Poller.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Poller_lin.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Poller_win.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Semaphore.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Semaphore_lin.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Semaphore_win.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Utils.cpp (100%) rename {src/tm2 => third-party}/libtm/libtm/src/infra/Utils.h (100%) rename {src/tm2 => third-party}/libtm/libtm/src/version.rc.in (100%) rename {src/tm2 => third-party}/libtm/resources/CMakeLists.txt (100%) rename {src/tm2 => third-party}/libtm/tools/CMakeLists.txt (100%) rename {src/tm2 => third-party}/libtm/tools/libtm_util/CMakeLists.txt (100%) rename {src/tm2 => third-party}/libtm/tools/libtm_util/libtm_util.cpp (100%) rename {src/tm2 => third-party}/libtm/versions.cmake (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index a719904dc3..a79b1d722f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -827,7 +827,7 @@ add_subdirectory(third-party/realsense-file) if (BUILD_WITH_TM2) message(STATUS "Building with TM2") - add_subdirectory(src/tm2/libtm) + add_subdirectory(third-party/libtm) if(WIN32) source_group("Source Files\\Devices\\Tracking" FILES @@ -890,7 +890,7 @@ endif() if(BUILD_WITH_TM2) target_compile_definitions(realsense2 PRIVATE WITH_TRACKING=1 BUILD_STATIC=1) target_link_libraries(realsense2 PRIVATE tm ${CMAKE_THREAD_LIBS_INIT} ${TRACKING_DEVICE_LIBS}) - target_include_directories(realsense2 PRIVATE src/tm2/libtm/libtm/include) + target_include_directories(realsense2 PRIVATE third-party/libtm/libtm/include) endif() set_target_properties(realsense2 PROPERTIES VERSION ${REALSENSE_VERSION_STRING} SOVERSION ${REALSENSE_VERSION_MAJOR}) diff --git a/src/tm2/libtm/.gitignore b/third-party/libtm/.gitignore similarity index 100% rename from src/tm2/libtm/.gitignore rename to third-party/libtm/.gitignore diff --git a/src/tm2/libtm/CMakeLists.txt b/third-party/libtm/CMakeLists.txt similarity index 100% rename from src/tm2/libtm/CMakeLists.txt rename to third-party/libtm/CMakeLists.txt diff --git a/src/tm2/libtm/cmake/linux.cmake b/third-party/libtm/cmake/linux.cmake similarity index 100% rename from src/tm2/libtm/cmake/linux.cmake rename to third-party/libtm/cmake/linux.cmake diff --git a/src/tm2/libtm/cmake/os.cmake b/third-party/libtm/cmake/os.cmake similarity index 100% rename from src/tm2/libtm/cmake/os.cmake rename to third-party/libtm/cmake/os.cmake diff --git a/src/tm2/libtm/cmake/windows.cmake b/third-party/libtm/cmake/windows.cmake similarity index 100% rename from src/tm2/libtm/cmake/windows.cmake rename to third-party/libtm/cmake/windows.cmake diff --git a/src/tm2/libtm/libtm/CMakeLists.txt b/third-party/libtm/libtm/CMakeLists.txt similarity index 100% rename from src/tm2/libtm/libtm/CMakeLists.txt rename to third-party/libtm/libtm/CMakeLists.txt diff --git a/src/tm2/libtm/libtm/include/TrackingCommon.h b/third-party/libtm/libtm/include/TrackingCommon.h similarity index 100% rename from src/tm2/libtm/libtm/include/TrackingCommon.h rename to third-party/libtm/libtm/include/TrackingCommon.h diff --git a/src/tm2/libtm/libtm/include/TrackingData.h b/third-party/libtm/libtm/include/TrackingData.h similarity index 100% rename from src/tm2/libtm/libtm/include/TrackingData.h rename to third-party/libtm/libtm/include/TrackingData.h diff --git a/src/tm2/libtm/libtm/include/TrackingDevice.h b/third-party/libtm/libtm/include/TrackingDevice.h similarity index 100% rename from src/tm2/libtm/libtm/include/TrackingDevice.h rename to third-party/libtm/libtm/include/TrackingDevice.h diff --git a/src/tm2/libtm/libtm/include/TrackingManager.h b/third-party/libtm/libtm/include/TrackingManager.h similarity index 100% rename from src/tm2/libtm/libtm/include/TrackingManager.h rename to third-party/libtm/libtm/include/TrackingManager.h diff --git a/src/tm2/libtm/libtm/include/TrackingSerializer.h b/third-party/libtm/libtm/include/TrackingSerializer.h similarity index 100% rename from src/tm2/libtm/libtm/include/TrackingSerializer.h rename to third-party/libtm/libtm/include/TrackingSerializer.h diff --git a/src/tm2/libtm/libtm/src/.bdsignore b/third-party/libtm/libtm/src/.bdsignore similarity index 100% rename from src/tm2/libtm/libtm/src/.bdsignore rename to third-party/libtm/libtm/src/.bdsignore diff --git a/src/tm2/libtm/libtm/src/CMakeLists.txt b/third-party/libtm/libtm/src/CMakeLists.txt similarity index 100% rename from src/tm2/libtm/libtm/src/CMakeLists.txt rename to third-party/libtm/libtm/src/CMakeLists.txt diff --git a/src/tm2/libtm/libtm/src/Common.cpp b/third-party/libtm/libtm/src/Common.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/Common.cpp rename to third-party/libtm/libtm/src/Common.cpp diff --git a/src/tm2/libtm/libtm/src/Common.h b/third-party/libtm/libtm/src/Common.h similarity index 100% rename from src/tm2/libtm/libtm/src/Common.h rename to third-party/libtm/libtm/src/Common.h diff --git a/src/tm2/libtm/libtm/src/CompleteTask.h b/third-party/libtm/libtm/src/CompleteTask.h similarity index 100% rename from src/tm2/libtm/libtm/src/CompleteTask.h rename to third-party/libtm/libtm/src/CompleteTask.h diff --git a/src/tm2/libtm/libtm/src/Device.cpp b/third-party/libtm/libtm/src/Device.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/Device.cpp rename to third-party/libtm/libtm/src/Device.cpp diff --git a/src/tm2/libtm/libtm/src/Device.h b/third-party/libtm/libtm/src/Device.h similarity index 100% rename from src/tm2/libtm/libtm/src/Device.h rename to third-party/libtm/libtm/src/Device.h diff --git a/src/tm2/libtm/libtm/src/Loader.cpp b/third-party/libtm/libtm/src/Loader.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/Loader.cpp rename to third-party/libtm/libtm/src/Loader.cpp diff --git a/src/tm2/libtm/libtm/src/Loader.h b/third-party/libtm/libtm/src/Loader.h similarity index 100% rename from src/tm2/libtm/libtm/src/Loader.h rename to third-party/libtm/libtm/src/Loader.h diff --git a/src/tm2/libtm/libtm/src/Main.cpp b/third-party/libtm/libtm/src/Main.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/Main.cpp rename to third-party/libtm/libtm/src/Main.cpp diff --git a/src/tm2/libtm/libtm/src/Manager.cpp b/third-party/libtm/libtm/src/Manager.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/Manager.cpp rename to third-party/libtm/libtm/src/Manager.cpp diff --git a/src/tm2/libtm/libtm/src/Manager.h b/third-party/libtm/libtm/src/Manager.h similarity index 100% rename from src/tm2/libtm/libtm/src/Manager.h rename to third-party/libtm/libtm/src/Manager.h diff --git a/src/tm2/libtm/libtm/src/Message.cpp b/third-party/libtm/libtm/src/Message.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/Message.cpp rename to third-party/libtm/libtm/src/Message.cpp diff --git a/src/tm2/libtm/libtm/src/Message.h b/third-party/libtm/libtm/src/Message.h similarity index 100% rename from src/tm2/libtm/libtm/src/Message.h rename to third-party/libtm/libtm/src/Message.h diff --git a/src/tm2/libtm/libtm/src/Protocol.cpp b/third-party/libtm/libtm/src/Protocol.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/Protocol.cpp rename to third-party/libtm/libtm/src/Protocol.cpp diff --git a/src/tm2/libtm/libtm/src/Protocol.h b/third-party/libtm/libtm/src/Protocol.h similarity index 100% rename from src/tm2/libtm/libtm/src/Protocol.h rename to third-party/libtm/libtm/src/Protocol.h diff --git a/src/tm2/libtm/libtm/src/RcSerializer/Packet.cpp b/third-party/libtm/libtm/src/RcSerializer/Packet.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/RcSerializer/Packet.cpp rename to third-party/libtm/libtm/src/RcSerializer/Packet.cpp diff --git a/src/tm2/libtm/libtm/src/RcSerializer/Packet.h b/third-party/libtm/libtm/src/RcSerializer/Packet.h similarity index 100% rename from src/tm2/libtm/libtm/src/RcSerializer/Packet.h rename to third-party/libtm/libtm/src/RcSerializer/Packet.h diff --git a/src/tm2/libtm/libtm/src/RcSerializer/Player.cpp b/third-party/libtm/libtm/src/RcSerializer/Player.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/RcSerializer/Player.cpp rename to third-party/libtm/libtm/src/RcSerializer/Player.cpp diff --git a/src/tm2/libtm/libtm/src/RcSerializer/Player.h b/third-party/libtm/libtm/src/RcSerializer/Player.h similarity index 100% rename from src/tm2/libtm/libtm/src/RcSerializer/Player.h rename to third-party/libtm/libtm/src/RcSerializer/Player.h diff --git a/src/tm2/libtm/libtm/src/RcSerializer/Recorder.cpp b/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/RcSerializer/Recorder.cpp rename to third-party/libtm/libtm/src/RcSerializer/Recorder.cpp diff --git a/src/tm2/libtm/libtm/src/RcSerializer/Recorder.h b/third-party/libtm/libtm/src/RcSerializer/Recorder.h similarity index 100% rename from src/tm2/libtm/libtm/src/RcSerializer/Recorder.h rename to third-party/libtm/libtm/src/RcSerializer/Recorder.h diff --git a/src/tm2/libtm/libtm/src/RcSerializer/concurrency.h b/third-party/libtm/libtm/src/RcSerializer/concurrency.h similarity index 100% rename from src/tm2/libtm/libtm/src/RcSerializer/concurrency.h rename to third-party/libtm/libtm/src/RcSerializer/concurrency.h diff --git a/src/tm2/libtm/libtm/src/RcSerializer/latency_queue.h b/third-party/libtm/libtm/src/RcSerializer/latency_queue.h similarity index 100% rename from src/tm2/libtm/libtm/src/RcSerializer/latency_queue.h rename to third-party/libtm/libtm/src/RcSerializer/latency_queue.h diff --git a/src/tm2/libtm/libtm/src/RcSerializer/rc_packet.h b/third-party/libtm/libtm/src/RcSerializer/rc_packet.h similarity index 100% rename from src/tm2/libtm/libtm/src/RcSerializer/rc_packet.h rename to third-party/libtm/libtm/src/RcSerializer/rc_packet.h diff --git a/src/tm2/libtm/libtm/src/UsbPlugListener.cpp b/third-party/libtm/libtm/src/UsbPlugListener.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/UsbPlugListener.cpp rename to third-party/libtm/libtm/src/UsbPlugListener.cpp diff --git a/src/tm2/libtm/libtm/src/UsbPlugListener.h b/third-party/libtm/libtm/src/UsbPlugListener.h similarity index 100% rename from src/tm2/libtm/libtm/src/UsbPlugListener.h rename to third-party/libtm/libtm/src/UsbPlugListener.h diff --git a/src/tm2/libtm/libtm/src/Version.h.in b/third-party/libtm/libtm/src/Version.h.in similarity index 100% rename from src/tm2/libtm/libtm/src/Version.h.in rename to third-party/libtm/libtm/src/Version.h.in diff --git a/src/tm2/libtm/libtm/src/infra/CMakeLists.txt b/third-party/libtm/libtm/src/infra/CMakeLists.txt similarity index 100% rename from src/tm2/libtm/libtm/src/infra/CMakeLists.txt rename to third-party/libtm/libtm/src/infra/CMakeLists.txt diff --git a/src/tm2/libtm/libtm/src/infra/Dispatcher.cpp b/third-party/libtm/libtm/src/infra/Dispatcher.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Dispatcher.cpp rename to third-party/libtm/libtm/src/infra/Dispatcher.cpp diff --git a/src/tm2/libtm/libtm/src/infra/Dispatcher.h b/third-party/libtm/libtm/src/infra/Dispatcher.h similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Dispatcher.h rename to third-party/libtm/libtm/src/infra/Dispatcher.h diff --git a/src/tm2/libtm/libtm/src/infra/EmbeddedList.h b/third-party/libtm/libtm/src/infra/EmbeddedList.h similarity index 100% rename from src/tm2/libtm/libtm/src/infra/EmbeddedList.h rename to third-party/libtm/libtm/src/infra/EmbeddedList.h diff --git a/src/tm2/libtm/libtm/src/infra/Event.h b/third-party/libtm/libtm/src/infra/Event.h similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Event.h rename to third-party/libtm/libtm/src/infra/Event.h diff --git a/src/tm2/libtm/libtm/src/infra/EventHandler.h b/third-party/libtm/libtm/src/infra/EventHandler.h similarity index 100% rename from src/tm2/libtm/libtm/src/infra/EventHandler.h rename to third-party/libtm/libtm/src/infra/EventHandler.h diff --git a/src/tm2/libtm/libtm/src/infra/Event_lin.cpp b/third-party/libtm/libtm/src/infra/Event_lin.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Event_lin.cpp rename to third-party/libtm/libtm/src/infra/Event_lin.cpp diff --git a/src/tm2/libtm/libtm/src/infra/Event_win.cpp b/third-party/libtm/libtm/src/infra/Event_win.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Event_win.cpp rename to third-party/libtm/libtm/src/infra/Event_win.cpp diff --git a/src/tm2/libtm/libtm/src/infra/Fence.h b/third-party/libtm/libtm/src/infra/Fence.h similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Fence.h rename to third-party/libtm/libtm/src/infra/Fence.h diff --git a/src/tm2/libtm/libtm/src/infra/Fsm.cpp b/third-party/libtm/libtm/src/infra/Fsm.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Fsm.cpp rename to third-party/libtm/libtm/src/infra/Fsm.cpp diff --git a/src/tm2/libtm/libtm/src/infra/Fsm.h b/third-party/libtm/libtm/src/infra/Fsm.h similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Fsm.h rename to third-party/libtm/libtm/src/infra/Fsm.h diff --git a/src/tm2/libtm/libtm/src/infra/Log.cpp b/third-party/libtm/libtm/src/infra/Log.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Log.cpp rename to third-party/libtm/libtm/src/infra/Log.cpp diff --git a/src/tm2/libtm/libtm/src/infra/Log.h b/third-party/libtm/libtm/src/infra/Log.h similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Log.h rename to third-party/libtm/libtm/src/infra/Log.h diff --git a/src/tm2/libtm/libtm/src/infra/Poller.h b/third-party/libtm/libtm/src/infra/Poller.h similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Poller.h rename to third-party/libtm/libtm/src/infra/Poller.h diff --git a/src/tm2/libtm/libtm/src/infra/Poller_lin.cpp b/third-party/libtm/libtm/src/infra/Poller_lin.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Poller_lin.cpp rename to third-party/libtm/libtm/src/infra/Poller_lin.cpp diff --git a/src/tm2/libtm/libtm/src/infra/Poller_win.cpp b/third-party/libtm/libtm/src/infra/Poller_win.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Poller_win.cpp rename to third-party/libtm/libtm/src/infra/Poller_win.cpp diff --git a/src/tm2/libtm/libtm/src/infra/Semaphore.h b/third-party/libtm/libtm/src/infra/Semaphore.h similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Semaphore.h rename to third-party/libtm/libtm/src/infra/Semaphore.h diff --git a/src/tm2/libtm/libtm/src/infra/Semaphore_lin.cpp b/third-party/libtm/libtm/src/infra/Semaphore_lin.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Semaphore_lin.cpp rename to third-party/libtm/libtm/src/infra/Semaphore_lin.cpp diff --git a/src/tm2/libtm/libtm/src/infra/Semaphore_win.cpp b/third-party/libtm/libtm/src/infra/Semaphore_win.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Semaphore_win.cpp rename to third-party/libtm/libtm/src/infra/Semaphore_win.cpp diff --git a/src/tm2/libtm/libtm/src/infra/Utils.cpp b/third-party/libtm/libtm/src/infra/Utils.cpp similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Utils.cpp rename to third-party/libtm/libtm/src/infra/Utils.cpp diff --git a/src/tm2/libtm/libtm/src/infra/Utils.h b/third-party/libtm/libtm/src/infra/Utils.h similarity index 100% rename from src/tm2/libtm/libtm/src/infra/Utils.h rename to third-party/libtm/libtm/src/infra/Utils.h diff --git a/src/tm2/libtm/libtm/src/version.rc.in b/third-party/libtm/libtm/src/version.rc.in similarity index 100% rename from src/tm2/libtm/libtm/src/version.rc.in rename to third-party/libtm/libtm/src/version.rc.in diff --git a/src/tm2/libtm/resources/CMakeLists.txt b/third-party/libtm/resources/CMakeLists.txt similarity index 100% rename from src/tm2/libtm/resources/CMakeLists.txt rename to third-party/libtm/resources/CMakeLists.txt diff --git a/src/tm2/libtm/tools/CMakeLists.txt b/third-party/libtm/tools/CMakeLists.txt similarity index 100% rename from src/tm2/libtm/tools/CMakeLists.txt rename to third-party/libtm/tools/CMakeLists.txt diff --git a/src/tm2/libtm/tools/libtm_util/CMakeLists.txt b/third-party/libtm/tools/libtm_util/CMakeLists.txt similarity index 100% rename from src/tm2/libtm/tools/libtm_util/CMakeLists.txt rename to third-party/libtm/tools/libtm_util/CMakeLists.txt diff --git a/src/tm2/libtm/tools/libtm_util/libtm_util.cpp b/third-party/libtm/tools/libtm_util/libtm_util.cpp similarity index 100% rename from src/tm2/libtm/tools/libtm_util/libtm_util.cpp rename to third-party/libtm/tools/libtm_util/libtm_util.cpp diff --git a/src/tm2/libtm/versions.cmake b/third-party/libtm/versions.cmake similarity index 100% rename from src/tm2/libtm/versions.cmake rename to third-party/libtm/versions.cmake From 1aabdedfa076146a815c38964f9f9ea5562786a5 Mon Sep 17 00:00:00 2001 From: Belkin Date: Mon, 15 Oct 2018 10:20:39 +0300 Subject: [PATCH 20/25] Fix linux compilation issues --- CMakeLists.txt | 4 +--- third-party/libtm/CMakeLists.txt | 2 -- third-party/libtm/cmake/linux.cmake | 8 -------- third-party/libtm/cmake/os.cmake | 5 ----- third-party/libtm/cmake/windows.cmake | 11 ----------- third-party/libtm/libtm/src/CMakeLists.txt | 10 +++++----- 6 files changed, 6 insertions(+), 34 deletions(-) delete mode 100644 third-party/libtm/cmake/linux.cmake delete mode 100644 third-party/libtm/cmake/os.cmake delete mode 100644 third-party/libtm/cmake/windows.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index a79b1d722f..0c2ed5ebe8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -785,8 +785,7 @@ if(HWM_OVER_XU) add_definitions(-DHWM_OVER_XU) endif() -if (NOT USE_SYSTEM_LIBUSB OR BUILD_WITH_TM2) - if(BUILD_WITH_TM2 OR FORCE_LIBUVC) +if ((NOT USE_SYSTEM_LIBUSB AND FORCE_LIBUVC) OR (BUILD_WITH_TM2 AND WIN32)) include(ExternalProject) ExternalProject_Add( @@ -818,7 +817,6 @@ if (NOT USE_SYSTEM_LIBUSB OR BUILD_WITH_TM2) set(LIBUSB1_LIBRARIES ${LIBUSB1_LIBRARIES} log) endif() - endif() endif() add_subdirectory(third-party/realsense-file) diff --git a/third-party/libtm/CMakeLists.txt b/third-party/libtm/CMakeLists.txt index b3f23ddd6c..b03bcd4465 100644 --- a/third-party/libtm/CMakeLists.txt +++ b/third-party/libtm/CMakeLists.txt @@ -3,8 +3,6 @@ project(libtm) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -include(cmake/os.cmake) - set(LIBTM_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") set(LIBTM_INCLUDE_DIR "${LIBTM_ROOT}/libtm/include") set(INFRA_INCLUDE_DIR "${LIBTM_ROOT}/libtm/src/infra") diff --git a/third-party/libtm/cmake/linux.cmake b/third-party/libtm/cmake/linux.cmake deleted file mode 100644 index 32d1ffbfde..0000000000 --- a/third-party/libtm/cmake/linux.cmake +++ /dev/null @@ -1,8 +0,0 @@ -set(OS "lin") - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") - -include_directories("/usr/include/libusb-1.0") -set(OS_SPECIFIC_LIBS "pthread") -set(LIBUSB_LIB "usb-1.0") diff --git a/third-party/libtm/cmake/os.cmake b/third-party/libtm/cmake/os.cmake deleted file mode 100644 index 72965fc1d0..0000000000 --- a/third-party/libtm/cmake/os.cmake +++ /dev/null @@ -1,5 +0,0 @@ -IF(WIN32) - include(cmake/windows.cmake) -ELSE() - include(cmake/linux.cmake) -ENDIF(WIN32) diff --git a/third-party/libtm/cmake/windows.cmake b/third-party/libtm/cmake/windows.cmake deleted file mode 100644 index c59879c6dc..0000000000 --- a/third-party/libtm/cmake/windows.cmake +++ /dev/null @@ -1,11 +0,0 @@ -set(OS "win") - -set(WINDOWS_LIBUSB_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third-party/win/libusb/include") - -message("Platform is set to ${PLATFORM}") - -set(WINDOWS_LIBUSB_LIB_DIR "${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/x64") - -include_directories("${WINDOWS_LIBUSB_INCLUDE_DIR}") - -set(LIBUSB_LIB "${WINDOWS_LIBUSB_LIB_DIR}/libusb-1.0.lib") diff --git a/third-party/libtm/libtm/src/CMakeLists.txt b/third-party/libtm/libtm/src/CMakeLists.txt index 476706a175..686039aa33 100644 --- a/third-party/libtm/libtm/src/CMakeLists.txt +++ b/third-party/libtm/libtm/src/CMakeLists.txt @@ -3,11 +3,11 @@ cmake_policy(SET CMP0015 NEW) project(tm) -include_directories(${LIBTM_INCLUDE_DIR} - ${INFRA_INCLUDE_DIR} - ${LIBUSB_LOCAL_INCLUDE_PATH} - ${CMAKE_SOURCE_DIR}/src -) +include_directories(${LIBUSB_LOCAL_INCLUDE_PATH}) + +IF(WIN32) + include_directories(${LIBUSB_LOCAL_INCLUDE_PATH}/libtm) +ENDIF(WIN32) #Source Files set(SOURCE_FILES From 2df97b3048658a90411296e7ff9df0cd2bd7a6d3 Mon Sep 17 00:00:00 2001 From: Belkin Date: Mon, 15 Oct 2018 10:43:07 +0300 Subject: [PATCH 21/25] 1. Fix cmake error 2. Remove tabs --- third-party/libtm/libtm/src/CMakeLists.txt | 9 +- third-party/libtm/libtm/src/Device.cpp | 6 +- third-party/libtm/libtm/src/Manager.cpp | 16 ++-- third-party/libtm/libtm/src/Message.h | 2 +- .../libtm/libtm/src/RcSerializer/rc_packet.h | 88 +++++++++--------- .../libtm/libtm/src/infra/EventHandler.h | 2 +- .../libtm/libtm/src/infra/Event_win.cpp | 2 +- .../libtm/libtm/src/infra/Poller_win.cpp | 4 +- .../libtm/libtm/src/infra/Semaphore_win.cpp | 90 +++++++++---------- 9 files changed, 111 insertions(+), 108 deletions(-) diff --git a/third-party/libtm/libtm/src/CMakeLists.txt b/third-party/libtm/libtm/src/CMakeLists.txt index 686039aa33..7a525fe33f 100644 --- a/third-party/libtm/libtm/src/CMakeLists.txt +++ b/third-party/libtm/libtm/src/CMakeLists.txt @@ -6,7 +6,10 @@ project(tm) include_directories(${LIBUSB_LOCAL_INCLUDE_PATH}) IF(WIN32) + set(OS "win") include_directories(${LIBUSB_LOCAL_INCLUDE_PATH}/libtm) +ELSE() + set(OS "lin") ENDIF(WIN32) #Source Files @@ -58,7 +61,7 @@ set(SOURCE_FILES #Add versioning to DLL IF(WIN32) -SET(SOURCE_FILES "${SOURCE_FILES};version.rc") + set(SOURCE_FILES "${SOURCE_FILES};version.rc") ENDIF(WIN32) #Building Library @@ -70,8 +73,8 @@ add_library(${PROJECT_NAME} ${SDK_LIB_TYPE} ${SOURCE_FILES}) #LINK_LIBRARIES target_link_libraries(${PROJECT_NAME} -${OS_SPECIFIC_LIBS} -${LIBUSB1_LIBRARIES} + ${OS_SPECIFIC_LIBS} + ${LIBUSB1_LIBRARIES} ) set_target_properties(${PROJECT_NAME} PROPERTIES VERSION "${LIBVERSION}" SOVERSION "${LIBTM_VERSION_MAJOR}") diff --git a/third-party/libtm/libtm/src/Device.cpp b/third-party/libtm/libtm/src/Device.cpp index 7243032593..17734218b8 100644 --- a/third-party/libtm/libtm/src/Device.cpp +++ b/third-party/libtm/libtm/src/Device.cpp @@ -2494,7 +2494,7 @@ namespace perc { { if (!mHasBluetooth) { - DEVICELOGE("cannot CentralLoadFW, there is no bluetooth in the device"); + DEVICELOGE("cannot CentralLoadFW, there is no bluetooth in the device"); return Status::NO_BLUETOOTH; } uint32_t addressSize = offsetof(message_fw_update_request, bNumFiles); @@ -2528,7 +2528,7 @@ namespace perc { { if(!mHasBluetooth) { - DEVICELOGE("cannot CentralFWUpdate, there is no bluetooth in the device"); + DEVICELOGE("cannot CentralFWUpdate, there is no bluetooth in the device"); return Status::NO_BLUETOOTH; } @@ -2571,7 +2571,7 @@ namespace perc { { if (!mHasBluetooth) { - DEVICELOGE("cannot ControllerFWUpdate, there is no bluetooth in the device"); + DEVICELOGE("cannot ControllerFWUpdate, there is no bluetooth in the device"); return Status::NO_BLUETOOTH; } if (fw.imageSize == 0) diff --git a/third-party/libtm/libtm/src/Manager.cpp b/third-party/libtm/libtm/src/Manager.cpp index bb076b741b..d6200ca726 100644 --- a/third-party/libtm/libtm/src/Manager.cpp +++ b/third-party/libtm/libtm/src/Manager.cpp @@ -201,14 +201,14 @@ Status Manager::getHostLog(OUT TrackingData::Log* log) // -[impl]--------------------------------------------------------------------- Status Manager::processMessage(const Message &msg, int prio) { - msg.Result = toUnderlying(Status::COMMON_ERROR); + msg.Result = toUnderlying(Status::COMMON_ERROR); - int ret = mDispatcher->sendMessage(&mFsm, msg, prio); - if (ret < 0) { - LOGE("mDispatcher->sendMessage ret %d", ret); - return Status::COMMON_ERROR; - } - return (Status)msg.Result; + int ret = mDispatcher->sendMessage(&mFsm, msg, prio); + if (ret < 0) { + LOGE("mDispatcher->sendMessage ret %d", ret); + return Status::COMMON_ERROR; + } + return (Status)msg.Result; } @@ -306,7 +306,7 @@ Status Manager::loadBufferToDevice(libusb_device * device, unsigned char * buffe //W\A for pipe error bug rc = libusb_bulk_transfer(devHandle, EP_OUT, buffer, 0, &bytesWritten, TIMEOUT_MS); - // status check ignored according to Movidius sample code + // status check ignored according to Movidius sample code //if (rc != 0 || 0 != bytesWritten) //{ // LOGE("Error while loading image - libusb_bulk_transfer failed. LIBUSB_ERROR_CODE: %d (%s)", rc, libusb_error_name(rc)); diff --git a/third-party/libtm/libtm/src/Message.h b/third-party/libtm/libtm/src/Message.h index 51256fcaf3..0b81d402fb 100644 --- a/third-party/libtm/libtm/src/Message.h +++ b/third-party/libtm/libtm/src/Message.h @@ -1460,7 +1460,7 @@ namespace perc * Start of all Interrupt messages. */ typedef struct { - uint32_t dwLength; /**< Message length in bytes */ + uint32_t dwLength; /**< Message length in bytes */ uint16_t wMessageID; /**< ID of message */ } interrupt_message_header; diff --git a/third-party/libtm/libtm/src/RcSerializer/rc_packet.h b/third-party/libtm/libtm/src/RcSerializer/rc_packet.h index a5a6e217a4..24d930074f 100644 --- a/third-party/libtm/libtm/src/RcSerializer/rc_packet.h +++ b/third-party/libtm/libtm/src/RcSerializer/rc_packet.h @@ -26,50 +26,50 @@ //WARNING: Do not change the order of this enum, or insert new packet types in the middle //Only append new packet types after the last one previously defined. typedef enum packet_type { - packet_none = 0, - packet_camera = 1, - packet_imu = 2, - packet_feature_track = 3, - packet_feature_select = 4, - packet_navsol = 5, - packet_feature_status = 6, - packet_filter_position = 7, - packet_filter_reconstruction = 8, - packet_feature_drop = 9, - packet_sift = 10, - packet_plot_info = 11, - packet_plot = 12, - packet_plot_drop = 13, - packet_recognition_group = 14, - packet_recognition_feature = 15, - packet_recognition_descriptor = 16, - packet_map_edge = 17, - packet_filter_current = 18, - packet_feature_prediction_variance = 19, - packet_accelerometer = 20, - packet_gyroscope = 21, - packet_filter_feature_id_visible = 22, - packet_filter_feature_id_association = 23, - packet_feature_intensity = 24, - packet_filter_control = 25, - packet_ground_truth = 26, - packet_core_motion = 27, - packet_image_with_depth = 28, - packet_image_raw = 29, - packet_diff_velocimeter = 30, - packet_thermometer = 31, - packet_stereo_raw = 40, - packet_image_stereo = 41, - packet_rc_pose = 42, - packet_calibration_json = 43, - packet_arrival_time = 44, - packet_velocimeter = 45, - packet_pose = 46, - packet_control = 47, - packet_calibration_bin = 48, - packet_exposure = 49, - packet_controller_physical_info = 50, - packet_led_intensity = 51, + packet_none = 0, + packet_camera = 1, + packet_imu = 2, + packet_feature_track = 3, + packet_feature_select = 4, + packet_navsol = 5, + packet_feature_status = 6, + packet_filter_position = 7, + packet_filter_reconstruction = 8, + packet_feature_drop = 9, + packet_sift = 10, + packet_plot_info = 11, + packet_plot = 12, + packet_plot_drop = 13, + packet_recognition_group = 14, + packet_recognition_feature = 15, + packet_recognition_descriptor = 16, + packet_map_edge = 17, + packet_filter_current = 18, + packet_feature_prediction_variance = 19, + packet_accelerometer = 20, + packet_gyroscope = 21, + packet_filter_feature_id_visible = 22, + packet_filter_feature_id_association = 23, + packet_feature_intensity = 24, + packet_filter_control = 25, + packet_ground_truth = 26, + packet_core_motion = 27, + packet_image_with_depth = 28, + packet_image_raw = 29, + packet_diff_velocimeter = 30, + packet_thermometer = 31, + packet_stereo_raw = 40, + packet_image_stereo = 41, + packet_rc_pose = 42, + packet_calibration_json = 43, + packet_arrival_time = 44, + packet_velocimeter = 45, + packet_pose = 46, + packet_control = 47, + packet_calibration_bin = 48, + packet_exposure = 49, + packet_controller_physical_info = 50, + packet_led_intensity = 51, packet_command_start = 100, packet_command_stop = 101, } packet_type; diff --git a/third-party/libtm/libtm/src/infra/EventHandler.h b/third-party/libtm/libtm/src/infra/EventHandler.h index 096cccb607..3b05a9b069 100644 --- a/third-party/libtm/libtm/src/infra/EventHandler.h +++ b/third-party/libtm/libtm/src/infra/EventHandler.h @@ -44,7 +44,7 @@ class Message class EventHandler { public: - virtual ~EventHandler() {} + virtual ~EventHandler() {} // = Completion callbacks virtual void onMessage(const Message &) {} diff --git a/third-party/libtm/libtm/src/infra/Event_win.cpp b/third-party/libtm/libtm/src/infra/Event_win.cpp index b9f832751c..7a32e24bd2 100644 --- a/third-party/libtm/libtm/src/infra/Event_win.cpp +++ b/third-party/libtm/libtm/src/infra/Event_win.cpp @@ -12,7 +12,7 @@ perc::Event::Event() NULL, // default security attributes TRUE, // manual-reset event FALSE, // initial state is nonsignaled - NULL // Unnamed event + NULL // Unnamed event ); ASSERT(mEvent != ILLEGAL_HANDLE); diff --git a/third-party/libtm/libtm/src/infra/Poller_win.cpp b/third-party/libtm/libtm/src/infra/Poller_win.cpp index 1b5f0670c4..fbae16029a 100644 --- a/third-party/libtm/libtm/src/infra/Poller_win.cpp +++ b/third-party/libtm/libtm/src/infra/Poller_win.cpp @@ -37,7 +37,7 @@ namespace perc NULL, // default security attributes TRUE, // manual-reset event FALSE, // initial state is nonsignaled - NULL // Unnamed event + NULL // Unnamed event ); ASSERT(mData->mAddRemoveDoneEvent != ILLEGAL_HANDLE); @@ -132,7 +132,7 @@ namespace perc evt = mData->mEvents[mData->mHandles[eventIndex]]; return 1; } - } // here we release the events guard, so Add/remove function may continue + } // here we release the events guard, so Add/remove function may continue // WAIT_OBJECT_0 means add/remove function called, lets wait for it completion by just waiting for the lock { diff --git a/third-party/libtm/libtm/src/infra/Semaphore_win.cpp b/third-party/libtm/libtm/src/infra/Semaphore_win.cpp index 5fac93e5a5..7065004eaa 100644 --- a/third-party/libtm/libtm/src/infra/Semaphore_win.cpp +++ b/third-party/libtm/libtm/src/infra/Semaphore_win.cpp @@ -12,51 +12,51 @@ Copyright(c) 2017 Intel Corporation. All Rights Reserved. namespace perc { - struct Semaphore::CheshireCat - { - HANDLE m_Semaphore; - }; - - int Semaphore::get(nsecs_t timeoutNs) - { - DWORD timeoutMs; - - timeoutMs = ns2ms(timeoutNs); - - auto ret = WaitForSingleObject(mData->m_Semaphore, timeoutMs); - - if (ret == WAIT_OBJECT_0) // well this is useless , since WAIT_OBJECT_0 is equal to 0, but at least this is clear; - return 0; - - return -1; - } - - - Semaphore::Semaphore(unsigned int initValue) : mData(new CheshireCat()) - { - mData->m_Semaphore = CreateSemaphore( - NULL, // default security attributes - initValue, // initial count - MAX_SEMAPHORE_COUNT, // maximum count - NULL); - - assert(mData->m_Semaphore != NULL); - } - - Semaphore::~Semaphore() - { - CloseHandle(mData->m_Semaphore); - } - - int Semaphore::put() - { - int ret = ReleaseSemaphore( - mData->m_Semaphore, // handle to semaphore - 1, // increase count by one - NULL); // not interested in previous count - - return !ret; // return zero on success; same as in linux - } + struct Semaphore::CheshireCat + { + HANDLE m_Semaphore; + }; + + int Semaphore::get(nsecs_t timeoutNs) + { + DWORD timeoutMs; + + timeoutMs = ns2ms(timeoutNs); + + auto ret = WaitForSingleObject(mData->m_Semaphore, timeoutMs); + + if (ret == WAIT_OBJECT_0) // well this is useless , since WAIT_OBJECT_0 is equal to 0, but at least this is clear; + return 0; + + return -1; + } + + + Semaphore::Semaphore(unsigned int initValue) : mData(new CheshireCat()) + { + mData->m_Semaphore = CreateSemaphore( + NULL, // default security attributes + initValue, // initial count + MAX_SEMAPHORE_COUNT, // maximum count + NULL); + + assert(mData->m_Semaphore != NULL); + } + + Semaphore::~Semaphore() + { + CloseHandle(mData->m_Semaphore); + } + + int Semaphore::put() + { + int ret = ReleaseSemaphore( + mData->m_Semaphore, // handle to semaphore + 1, // increase count by one + NULL); // not interested in previous count + + return !ret; // return zero on success; same as in linux + } From ac272a04357b1a86af44534d534b17462d7b0937 Mon Sep 17 00:00:00 2001 From: Belkin Date: Mon, 15 Oct 2018 11:17:01 +0300 Subject: [PATCH 22/25] Remove infra cmake file --- .../libtm/libtm/src/infra/CMakeLists.txt | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 third-party/libtm/libtm/src/infra/CMakeLists.txt diff --git a/third-party/libtm/libtm/src/infra/CMakeLists.txt b/third-party/libtm/libtm/src/infra/CMakeLists.txt deleted file mode 100644 index e72e5d8c42..0000000000 --- a/third-party/libtm/libtm/src/infra/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -cmake_minimum_required(VERSION 2.8) -project(infra) - -#Source Files -set(SOURCE_FILES - Log.h - Log.cpp - Event.h - Fence.h - EventHandler.h - Poller.h - Poller_${OS}.cpp - Dispatcher.h - Dispatcher.cpp - Fsm.h - Fsm.cpp - Utils.h - Utils.cpp - Semaphore.h - Semaphore_${OS}.cpp - Event_${OS}.cpp -) - - -#Building Library -set(SDK_LIB_TYPE "STATIC") -MESSAGE("Building project ${PROJECT_NAME} as ${SDK_LIB_TYPE} library") -add_library(${PROJECT_NAME} ${SDK_LIB_TYPE} ${SOURCE_FILES}) - - From 40e4452ab4ecd38f2188b381a3b46a086aa2c0b4 Mon Sep 17 00:00:00 2001 From: Belkin Date: Mon, 15 Oct 2018 12:04:08 +0300 Subject: [PATCH 23/25] Add include directories to libtm --- third-party/libtm/libtm/src/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/third-party/libtm/libtm/src/CMakeLists.txt b/third-party/libtm/libtm/src/CMakeLists.txt index 7a525fe33f..fab07fdece 100644 --- a/third-party/libtm/libtm/src/CMakeLists.txt +++ b/third-party/libtm/libtm/src/CMakeLists.txt @@ -3,7 +3,10 @@ cmake_policy(SET CMP0015 NEW) project(tm) -include_directories(${LIBUSB_LOCAL_INCLUDE_PATH}) +include_directories(${LIBUSB_LOCAL_INCLUDE_PATH} + ${LIBTM_INCLUDE_DIR} + ${INFRA_INCLUDE_DIR} +) IF(WIN32) set(OS "win") From 0649fdfb96f64f3efc473eb35214f867a3b79ba8 Mon Sep 17 00:00:00 2001 From: Belkin Date: Mon, 15 Oct 2018 12:08:21 +0300 Subject: [PATCH 24/25] Fix compilation error --- third-party/libtm/libtm/src/CMakeLists.txt | 2 +- third-party/libtm/libtm/src/Device.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/third-party/libtm/libtm/src/CMakeLists.txt b/third-party/libtm/libtm/src/CMakeLists.txt index fab07fdece..c464d329cd 100644 --- a/third-party/libtm/libtm/src/CMakeLists.txt +++ b/third-party/libtm/libtm/src/CMakeLists.txt @@ -10,7 +10,7 @@ include_directories(${LIBUSB_LOCAL_INCLUDE_PATH} IF(WIN32) set(OS "win") - include_directories(${LIBUSB_LOCAL_INCLUDE_PATH}/libtm) + include_directories(${LIBUSB_LOCAL_INCLUDE_PATH}/libusb) ELSE() set(OS "lin") ENDIF(WIN32) diff --git a/third-party/libtm/libtm/src/Device.h b/third-party/libtm/libtm/src/Device.h index d0e81e8bb3..dc5bb85846 100644 --- a/third-party/libtm/libtm/src/Device.h +++ b/third-party/libtm/libtm/src/Device.h @@ -22,7 +22,7 @@ Copyright(c) 2017 Intel Corporation. All Rights Reserved. #pragma warning (push) #pragma warning (disable : 4200) #endif -#include "libusb/libusb.h" +#include "libusb.h" #ifdef _WIN32 #pragma warning (pop) #endif From 7c430e888eca39740b0ec5dc6a1110ac60629609 Mon Sep 17 00:00:00 2001 From: Belkin Date: Mon, 15 Oct 2018 13:52:07 +0300 Subject: [PATCH 25/25] Fix cmake warning --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c2ed5ebe8..fb80903993 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -841,6 +841,7 @@ if (BUILD_WITH_TM2) src/tm2/tm-conversions.h src/tm2/controller_event_serializer.h ) + add_dependencies(tm libusb) endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMake) @@ -859,7 +860,6 @@ if (BUILD_WITH_TM2) src/tm2/tm-info.cpp ) - add_dependencies(tm libusb) endif() if(LRS_TRY_USE_AVX)

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 N2%iTiWX3-#_ Date: Sun, 16 Sep 2018 11:23:41 +0300 Subject: [PATCH 05/25] Add libtm source code to librealsense solution --- third-party/libtm/CMakeLists.txt | 69 +++- third-party/libtm/infra/config.cmake | 28 ++ .../infra => infra/include}/Dispatcher.h | 0 .../infra => infra/include}/EmbeddedList.h | 0 .../{libtm/infra => infra/include}/Event.h | 0 .../infra => infra/include}/EventHandler.h | 0 .../{libtm/infra => infra/include}/Fence.h | 0 .../{libtm/infra => infra/include}/Fsm.h | 0 .../{libtm/infra => infra/include}/Log.h | 0 .../{libtm/infra => infra/include}/Poller.h | 0 .../infra => infra/include}/Semaphore.h | 0 .../{libtm/infra => infra/include}/Utils.h | 0 .../{libtm/infra => infra/src}/Dispatcher.cpp | 0 .../{libtm/infra => infra/src}/Event_lin.cpp | 0 .../{libtm/infra => infra/src}/Event_win.cpp | 0 .../libtm/{libtm/infra => infra/src}/Fsm.cpp | 0 .../libtm/{libtm/infra => infra/src}/Log.cpp | 0 .../{libtm/infra => infra/src}/Poller_lin.cpp | 0 .../{libtm/infra => infra/src}/Poller_win.cpp | 0 .../infra => infra/src}/Semaphore_lin.cpp | 0 .../infra => infra/src}/Semaphore_win.cpp | 0 .../{libtm/infra => infra/src}/Utils.cpp | 0 third-party/libtm/libtm/CMakeLists.txt | 37 -- third-party/libtm/libtm/config.cmake | 24 ++ .../libtm/libtm/include/TrackingSerializer.h | 47 --- third-party/libtm/libtm/infra/Android.mk | 56 --- third-party/libtm/libtm/infra/CMakeLists.txt | 38 -- third-party/libtm/libtm/src/CMakeLists.txt | 67 --- .../libtm/libtm/src/RcSerializer/Packet.cpp | 373 ----------------- .../libtm/libtm/src/RcSerializer/Packet.h | 185 --------- .../libtm/libtm/src/RcSerializer/Player.cpp | 390 ------------------ .../libtm/libtm/src/RcSerializer/Player.h | 50 --- .../libtm/libtm/src/RcSerializer/Recorder.cpp | 249 ----------- .../libtm/libtm/src/RcSerializer/Recorder.h | 68 --- .../libtm/src/RcSerializer/concurrency.h | 310 -------------- .../libtm/src/RcSerializer/latency_queue.h | 62 --- .../libtm/libtm/src/RcSerializer/rc_packet.h | 355 ---------------- 37 files changed, 112 insertions(+), 2296 deletions(-) create mode 100644 third-party/libtm/infra/config.cmake rename third-party/libtm/{libtm/infra => infra/include}/Dispatcher.h (100%) rename third-party/libtm/{libtm/infra => infra/include}/EmbeddedList.h (100%) rename third-party/libtm/{libtm/infra => infra/include}/Event.h (100%) rename third-party/libtm/{libtm/infra => infra/include}/EventHandler.h (100%) rename third-party/libtm/{libtm/infra => infra/include}/Fence.h (100%) rename third-party/libtm/{libtm/infra => infra/include}/Fsm.h (100%) rename third-party/libtm/{libtm/infra => infra/include}/Log.h (100%) rename third-party/libtm/{libtm/infra => infra/include}/Poller.h (100%) rename third-party/libtm/{libtm/infra => infra/include}/Semaphore.h (100%) rename third-party/libtm/{libtm/infra => infra/include}/Utils.h (100%) rename third-party/libtm/{libtm/infra => infra/src}/Dispatcher.cpp (100%) rename third-party/libtm/{libtm/infra => infra/src}/Event_lin.cpp (100%) rename third-party/libtm/{libtm/infra => infra/src}/Event_win.cpp (100%) rename third-party/libtm/{libtm/infra => infra/src}/Fsm.cpp (100%) rename third-party/libtm/{libtm/infra => infra/src}/Log.cpp (100%) rename third-party/libtm/{libtm/infra => infra/src}/Poller_lin.cpp (100%) rename third-party/libtm/{libtm/infra => infra/src}/Poller_win.cpp (100%) rename third-party/libtm/{libtm/infra => infra/src}/Semaphore_lin.cpp (100%) rename third-party/libtm/{libtm/infra => infra/src}/Semaphore_win.cpp (100%) rename third-party/libtm/{libtm/infra => infra/src}/Utils.cpp (100%) delete mode 100644 third-party/libtm/libtm/CMakeLists.txt create mode 100644 third-party/libtm/libtm/config.cmake delete mode 100644 third-party/libtm/libtm/include/TrackingSerializer.h delete mode 100644 third-party/libtm/libtm/infra/Android.mk delete mode 100644 third-party/libtm/libtm/infra/CMakeLists.txt delete mode 100644 third-party/libtm/libtm/src/CMakeLists.txt delete mode 100644 third-party/libtm/libtm/src/RcSerializer/Packet.cpp delete mode 100644 third-party/libtm/libtm/src/RcSerializer/Packet.h delete mode 100644 third-party/libtm/libtm/src/RcSerializer/Player.cpp delete mode 100644 third-party/libtm/libtm/src/RcSerializer/Player.h delete mode 100644 third-party/libtm/libtm/src/RcSerializer/Recorder.cpp delete mode 100644 third-party/libtm/libtm/src/RcSerializer/Recorder.h delete mode 100644 third-party/libtm/libtm/src/RcSerializer/concurrency.h delete mode 100644 third-party/libtm/libtm/src/RcSerializer/latency_queue.h delete mode 100644 third-party/libtm/libtm/src/RcSerializer/rc_packet.h diff --git a/third-party/libtm/CMakeLists.txt b/third-party/libtm/CMakeLists.txt index 349585d45e..c6d2286efb 100644 --- a/third-party/libtm/CMakeLists.txt +++ b/third-party/libtm/CMakeLists.txt @@ -1,16 +1,13 @@ cmake_minimum_required(VERSION 2.8) -project(libtm) +cmake_policy(SET CMP0015 NEW) -include(cmake/os.cmake) +project(tm) -set(LIBTM_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") -set(LIBTM_INCLUDE_DIR "${LIBTM_ROOT}/libtm/include") -set(LIBTM_INFRA_DIR "${LIBTM_ROOT}/libtm/infra") -set(LIBTM_SRC_DIR "${LIBTM_ROOT}/libtm/src") -set(LIBTM_RESOURCES_DIR "${LIBTM_ROOT}/resources") +include(cmake/os.cmake) -message("${LIBTM_INCLUDE_DIR}") include(versions.cmake) +include(libtm/config.cmake) +include(infra/config.cmake) # Build resources (FW, Central, Controller binaries) add_subdirectory(resources) @@ -18,7 +15,61 @@ add_subdirectory(resources) set(LIB_TYPE "STATIC") add_definitions(-DBUILD_STATIC) -add_subdirectory(libtm) +STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+\\.[0-9]+" "\\1" LIBTM_VERSION_MAJOR "${HOST_VERSION}") +STRING(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" LIBTM_VERSION_MINOR "${HOST_VERSION}") +STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" LIBTM_VERSION_PATCH "${HOST_VERSION}") +STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" LIBTM_VERSION_BUILD "${HOST_VERSION}") + +set(LIBTM_API_VERSION_MAJOR 10) # Major part of the device supported interface API version, updated upon an incompatible API change +set(LIBTM_API_VERSION_MINOR 0) # Minor part of the device supported interface API version, updated upon a backwards-compatible change + +set(LIBVERSION ${LIBTM_VERSION_MAJOR}.${LIBTM_VERSION_MINOR}.${LIBTM_VERSION_PATCH}) + +# Retrieve Git branch name +execute_process(COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE) + +MESSAGE("--------------------------------------------------------------------------------------------------------------------------------------------------------------") +MESSAGE("Building ${PROJECT_NAME} project on ${OS}, LIBTM version [${LIBTM_VERSION_MAJOR}.${LIBTM_VERSION_MINOR}.${LIBTM_VERSION_PATCH}.${LIBTM_VERSION_BUILD}], API version [${LIBTM_API_VERSION_MAJOR}.${LIBTM_API_VERSION_MINOR}], branch [${GIT_BRANCH}], FW [${FW_VERSION}], Central APP [${CENTRAL_APP_VERSION}], Central BL [${CENTRAL_BL_VERSION}]") + +# Configure version file according to libtm version definitions and branch name above +MESSAGE("Creating version file ${PROJECT_SOURCE_DIR}/src/Version.h") +configure_file("${PROJECT_SOURCE_DIR}/libtm/src/Version.h.in" "${PROJECT_SOURCE_DIR}/libtm/src/Version.h") +configure_file("${PROJECT_SOURCE_DIR}/libtm/src/version.rc.in" "${PROJECT_SOURCE_DIR}/libtm/src/version.rc") + +include_directories( +${INFRA_HEADER_DIR} +${LIBTM_HEADER_DIR} +) + +add_library(${PROJECT_NAME} ${LIB_TYPE} +${HEADER_FILES_INFRA} +${SOURCE_FILES_INFRA} +${HEADER_FILES_LIBTM} +${SOURCE_FILES_LIBTM} +) + +#LINK_LIBRARIES +target_link_libraries(${PROJECT_NAME} +${OS_SPECIFIC_LIBS} +${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/x64/libusb-1.0.lib +) + +set_target_properties(${PROJECT_NAME} PROPERTIES VERSION "${LIBVERSION}" SOVERSION "${LIBTM_VERSION_MAJOR}") +set_target_properties (${PROJECT_NAME} PROPERTIES FOLDER Library) + +#install(TARGETS ${PROJECT_NAME} DESTINATION lib) + +set(CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/../cmake/realsense2") + +install(TARGETS ${PROJECT_NAME} + EXPORT realsense2Targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) message("----------------------------------------------------------------------------") message("CMake Done") diff --git a/third-party/libtm/infra/config.cmake b/third-party/libtm/infra/config.cmake new file mode 100644 index 0000000000..cc8888468d --- /dev/null +++ b/third-party/libtm/infra/config.cmake @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 2.8) + +set(INFRA_HEADER_DIR ${CMAKE_CURRENT_LIST_DIR}/include) +set(INFRA_SRC_DIR ${CMAKE_CURRENT_LIST_DIR}/src) + +#Header Files +set(HEADER_FILES_INFRA + ${CMAKE_CURRENT_LIST_DIR}/include/Utils.h + ${CMAKE_CURRENT_LIST_DIR}/include/Log.h + ${CMAKE_CURRENT_LIST_DIR}/include/Dispatcher.h + ${CMAKE_CURRENT_LIST_DIR}/include/Fsm.h + ${CMAKE_CURRENT_LIST_DIR}/include/Event.h + ${CMAKE_CURRENT_LIST_DIR}/include/Fence.h + ${CMAKE_CURRENT_LIST_DIR}/include/EventHandler.h + ${CMAKE_CURRENT_LIST_DIR}/include/Semaphore.h + ${CMAKE_CURRENT_LIST_DIR}/include/Poller.h +) + +#Source Files +set(SOURCE_FILES_INFRA + ${CMAKE_CURRENT_LIST_DIR}/src/Log.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Poller_${OS}.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Dispatcher.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Fsm.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Utils.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Semaphore_${OS}.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Event_${OS}.cpp +) \ No newline at end of file diff --git a/third-party/libtm/libtm/infra/Dispatcher.h b/third-party/libtm/infra/include/Dispatcher.h similarity index 100% rename from third-party/libtm/libtm/infra/Dispatcher.h rename to third-party/libtm/infra/include/Dispatcher.h diff --git a/third-party/libtm/libtm/infra/EmbeddedList.h b/third-party/libtm/infra/include/EmbeddedList.h similarity index 100% rename from third-party/libtm/libtm/infra/EmbeddedList.h rename to third-party/libtm/infra/include/EmbeddedList.h diff --git a/third-party/libtm/libtm/infra/Event.h b/third-party/libtm/infra/include/Event.h similarity index 100% rename from third-party/libtm/libtm/infra/Event.h rename to third-party/libtm/infra/include/Event.h diff --git a/third-party/libtm/libtm/infra/EventHandler.h b/third-party/libtm/infra/include/EventHandler.h similarity index 100% rename from third-party/libtm/libtm/infra/EventHandler.h rename to third-party/libtm/infra/include/EventHandler.h diff --git a/third-party/libtm/libtm/infra/Fence.h b/third-party/libtm/infra/include/Fence.h similarity index 100% rename from third-party/libtm/libtm/infra/Fence.h rename to third-party/libtm/infra/include/Fence.h diff --git a/third-party/libtm/libtm/infra/Fsm.h b/third-party/libtm/infra/include/Fsm.h similarity index 100% rename from third-party/libtm/libtm/infra/Fsm.h rename to third-party/libtm/infra/include/Fsm.h diff --git a/third-party/libtm/libtm/infra/Log.h b/third-party/libtm/infra/include/Log.h similarity index 100% rename from third-party/libtm/libtm/infra/Log.h rename to third-party/libtm/infra/include/Log.h diff --git a/third-party/libtm/libtm/infra/Poller.h b/third-party/libtm/infra/include/Poller.h similarity index 100% rename from third-party/libtm/libtm/infra/Poller.h rename to third-party/libtm/infra/include/Poller.h diff --git a/third-party/libtm/libtm/infra/Semaphore.h b/third-party/libtm/infra/include/Semaphore.h similarity index 100% rename from third-party/libtm/libtm/infra/Semaphore.h rename to third-party/libtm/infra/include/Semaphore.h diff --git a/third-party/libtm/libtm/infra/Utils.h b/third-party/libtm/infra/include/Utils.h similarity index 100% rename from third-party/libtm/libtm/infra/Utils.h rename to third-party/libtm/infra/include/Utils.h diff --git a/third-party/libtm/libtm/infra/Dispatcher.cpp b/third-party/libtm/infra/src/Dispatcher.cpp similarity index 100% rename from third-party/libtm/libtm/infra/Dispatcher.cpp rename to third-party/libtm/infra/src/Dispatcher.cpp diff --git a/third-party/libtm/libtm/infra/Event_lin.cpp b/third-party/libtm/infra/src/Event_lin.cpp similarity index 100% rename from third-party/libtm/libtm/infra/Event_lin.cpp rename to third-party/libtm/infra/src/Event_lin.cpp diff --git a/third-party/libtm/libtm/infra/Event_win.cpp b/third-party/libtm/infra/src/Event_win.cpp similarity index 100% rename from third-party/libtm/libtm/infra/Event_win.cpp rename to third-party/libtm/infra/src/Event_win.cpp diff --git a/third-party/libtm/libtm/infra/Fsm.cpp b/third-party/libtm/infra/src/Fsm.cpp similarity index 100% rename from third-party/libtm/libtm/infra/Fsm.cpp rename to third-party/libtm/infra/src/Fsm.cpp diff --git a/third-party/libtm/libtm/infra/Log.cpp b/third-party/libtm/infra/src/Log.cpp similarity index 100% rename from third-party/libtm/libtm/infra/Log.cpp rename to third-party/libtm/infra/src/Log.cpp diff --git a/third-party/libtm/libtm/infra/Poller_lin.cpp b/third-party/libtm/infra/src/Poller_lin.cpp similarity index 100% rename from third-party/libtm/libtm/infra/Poller_lin.cpp rename to third-party/libtm/infra/src/Poller_lin.cpp diff --git a/third-party/libtm/libtm/infra/Poller_win.cpp b/third-party/libtm/infra/src/Poller_win.cpp similarity index 100% rename from third-party/libtm/libtm/infra/Poller_win.cpp rename to third-party/libtm/infra/src/Poller_win.cpp diff --git a/third-party/libtm/libtm/infra/Semaphore_lin.cpp b/third-party/libtm/infra/src/Semaphore_lin.cpp similarity index 100% rename from third-party/libtm/libtm/infra/Semaphore_lin.cpp rename to third-party/libtm/infra/src/Semaphore_lin.cpp diff --git a/third-party/libtm/libtm/infra/Semaphore_win.cpp b/third-party/libtm/infra/src/Semaphore_win.cpp similarity index 100% rename from third-party/libtm/libtm/infra/Semaphore_win.cpp rename to third-party/libtm/infra/src/Semaphore_win.cpp diff --git a/third-party/libtm/libtm/infra/Utils.cpp b/third-party/libtm/infra/src/Utils.cpp similarity index 100% rename from third-party/libtm/libtm/infra/Utils.cpp rename to third-party/libtm/infra/src/Utils.cpp diff --git a/third-party/libtm/libtm/CMakeLists.txt b/third-party/libtm/libtm/CMakeLists.txt deleted file mode 100644 index 4380ab709c..0000000000 --- a/third-party/libtm/libtm/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 2.8) -project(libtm) - -STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+\\.[0-9]+" "\\1" LIBTM_VERSION_MAJOR "${HOST_VERSION}") -STRING(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" LIBTM_VERSION_MINOR "${HOST_VERSION}") -STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" LIBTM_VERSION_PATCH "${HOST_VERSION}") -STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" LIBTM_VERSION_BUILD "${HOST_VERSION}") - -set (LIBTM_API_VERSION_MAJOR 10) # Major part of the device supported interface API version, updated upon an incompatible API change -set (LIBTM_API_VERSION_MINOR 0) # Minor part of the device supported interface API version, updated upon a backwards-compatible change - -set(LIBVERSION ${LIBTM_VERSION_MAJOR}.${LIBTM_VERSION_MINOR}.${LIBTM_VERSION_PATCH}) - -# Retrieve Git branch name -execute_process(COMMAND git rev-parse --abbrev-ref HEAD - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" - OUTPUT_VARIABLE GIT_BRANCH - OUTPUT_STRIP_TRAILING_WHITESPACE) - -MESSAGE("--------------------------------------------------------------------------------------------------------------------------------------------------------------") -MESSAGE("Building ${PROJECT_NAME} project on ${OS}, LIBTM version [${LIBTM_VERSION_MAJOR}.${LIBTM_VERSION_MINOR}.${LIBTM_VERSION_PATCH}.${LIBTM_VERSION_BUILD}], API version [${LIBTM_API_VERSION_MAJOR}.${LIBTM_API_VERSION_MINOR}], branch [${GIT_BRANCH}], FW [${FW_VERSION}], Central APP [${CENTRAL_APP_VERSION}], Central BL [${CENTRAL_BL_VERSION}]") - -# Configure version file according to libtm version definitions and branch name above -MESSAGE("Creating version file ${PROJECT_SOURCE_DIR}/src/Version.h") -configure_file("${PROJECT_SOURCE_DIR}/src/Version.h.in" "${PROJECT_SOURCE_DIR}/src/Version.h") -configure_file("${PROJECT_SOURCE_DIR}/src/version.rc.in" "${PROJECT_SOURCE_DIR}/src/version.rc") - -# Add include directories to the search path -include_directories(${LIBTM_INCLUDE_DIR}) -SET(INSTALL_PATH ${CMAKE_INSTALL_PREFIX}) -install(DIRECTORY ${LIBTM_INCLUDE_DIR} DESTINATION ${INSTALL_PATH}) - -include_directories("infra") - -# Add subdirectory to the build -add_subdirectory(infra) -add_subdirectory(src) diff --git a/third-party/libtm/libtm/config.cmake b/third-party/libtm/libtm/config.cmake new file mode 100644 index 0000000000..63fe373d07 --- /dev/null +++ b/third-party/libtm/libtm/config.cmake @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 2.8) + +set(LIBTM_HEADER_DIR ${CMAKE_CURRENT_LIST_DIR}/include) +set(LIBTM_SRC_DIR ${CMAKE_CURRENT_LIST_DIR}/src) + +set(HEADER_FILES_LIBTM + ${CMAKE_CURRENT_LIST_DIR}/include/TrackingManager.h + ${CMAKE_CURRENT_LIST_DIR}/include/TrackingDevice.h + ${CMAKE_CURRENT_LIST_DIR}/include/TrackingCommon.h + ${CMAKE_CURRENT_LIST_DIR}/include/TrackingData.h +) + +set(SOURCE_FILES_LIBTM + ${CMAKE_CURRENT_LIST_DIR}/src/Manager.h + ${CMAKE_CURRENT_LIST_DIR}/src/Manager.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Device.h + ${CMAKE_CURRENT_LIST_DIR}/src/Device.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Message.h + ${CMAKE_CURRENT_LIST_DIR}/src/UsbPlugListener.h + ${CMAKE_CURRENT_LIST_DIR}/src/UsbPlugListener.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/CompleteTask.h + ${CMAKE_CURRENT_LIST_DIR}/src/Common.h + ${CMAKE_CURRENT_LIST_DIR}/src/Common.cpp +) diff --git a/third-party/libtm/libtm/include/TrackingSerializer.h b/third-party/libtm/libtm/include/TrackingSerializer.h deleted file mode 100644 index 6b539eb79d..0000000000 --- a/third-party/libtm/libtm/include/TrackingSerializer.h +++ /dev/null @@ -1,47 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once -#include "TrackingDevice.h" - -namespace perc -{ - class DLL_EXPORT TrackingRecorder : public TrackingDevice::Listener - { - public: - virtual ~TrackingRecorder() = default; - static TrackingRecorder* CreateInstance(TrackingDevice* device, TrackingDevice::Listener* listener, const char* file, bool stereoMode = false); - }; - - class DLL_EXPORT TrackingPlayer - { - public: - virtual ~TrackingPlayer() = default; - /** - * @brief start - * start sending packets from the file to the listener, starts from the beginning of the file. - */ - virtual void start(bool start_after_calibration = true) = 0; - - /** - * @brief device_configure - * configure and the player to send packets to the device, and to start device streaming - */ - virtual bool device_configure(TrackingDevice* device, TrackingDevice::Listener* listener, TrackingData::Profile* profile = nullptr) = 0; - - /** - * @brief isStreaming - * Return if the player is in the middle of streaming from the file - */ - virtual bool isStreaming() = 0; - /** - * @brief stop - * Stop streaming and close the file. - */ - virtual void stop() = 0; - - static TrackingPlayer* CreateInstance(TrackingDevice::Listener* listener, const char* file); - }; -} diff --git a/third-party/libtm/libtm/infra/Android.mk b/third-party/libtm/libtm/infra/Android.mk deleted file mode 100644 index 7fb6e2fac8..0000000000 --- a/third-party/libtm/libtm/infra/Android.mk +++ /dev/null @@ -1,56 +0,0 @@ -SAVED_LOCAL_PATH := $(LOCAL_PATH) -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -#------------------------------------------------------------------------------ -# CFLAGS -LOCAL_CFLAGS += -DANDROID -w -fstack-protector -fPIE -fPIC -pie -Wformat -Wformat-security -LOCAL_CPPFLAGS += -DANDROID -w -fstack-protector -fPIE -fPIC -pie -Wformat -Wformat-security - -ifneq ($(filter userdebug eng tests, $(TARGET_BUILD_VARIANT)),) -LOCAL_CFLAGS += -g -O0 -LOCAL_CPPFLAGS += -g -O0 -endif - -# adding c++11 support -LOCAL_CPPFLAGS += -std=c++11 -fexceptions -include external/libcxx/libcxx.mk - -#------------------------------------------------------------------------------ -# INCLUDES -LOCAL_C_INCLUDES += \ - $(LOCAL_PATH)/../include \ - $(SYSTEM_INCLUDE_PATHS) \ - $(FRAMEWORK_INCLUDE_PATHS) \ - -#------------------------------------------------------------------------------ -# SRC -LOCAL_SRC_FILES := \ - Log.c \ - Trace.c \ - Dispatcher.cpp \ - Fsm.cpp \ - FormatConverter.cpp - -#------------------------------------------------------------------------------ -# LINKER -LOCAL_LDFLAGS += \ - -z noexecstack \ - -LOCAL_STATIC_LIBRARIES += \ - -LOCAL_SHARED_LIBRARIES += \ - libcutils \ - libutils \ - -LOCAL_LDLIBS += \ - -llog \ - -#------------------------------------------------------------------------------ -# BUILD client API -LOCAL_MODULE_TAGS := optional -LOCAL_MULTILIB := 64 -LOCAL_MODULE := libmminfra -include $(BUILD_SHARED_LIBRARY) - -LOCAL_PATH := $(SAVED_LOCAL_PATH) diff --git a/third-party/libtm/libtm/infra/CMakeLists.txt b/third-party/libtm/libtm/infra/CMakeLists.txt deleted file mode 100644 index afd9fa63c4..0000000000 --- a/third-party/libtm/libtm/infra/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -cmake_minimum_required(VERSION 2.8) -project(infra) - -#Source Files -set(SOURCE_FILES - Log.h - Log.cpp - Event.h - Fence.h - EventHandler.h - Poller.h - Poller_${OS}.cpp - Dispatcher.h - Dispatcher.cpp - Fsm.h - Fsm.cpp - Utils.h - Utils.cpp - Semaphore.h - Semaphore_${OS}.cpp - Event_${OS}.cpp -) - - -#Building Library -set(SDK_LIB_TYPE "STATIC") -MESSAGE("Building project ${PROJECT_NAME} as ${SDK_LIB_TYPE} library") -add_library(${PROJECT_NAME} ${SDK_LIB_TYPE} ${SOURCE_FILES}) - -set(CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/../cmake/realsense2") -set_target_properties (${PROJECT_NAME} PROPERTIES FOLDER Library) - -install(TARGETS ${PROJECT_NAME} - EXPORT realsense2Targets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) diff --git a/third-party/libtm/libtm/src/CMakeLists.txt b/third-party/libtm/libtm/src/CMakeLists.txt deleted file mode 100644 index 8b84af0c92..0000000000 --- a/third-party/libtm/libtm/src/CMakeLists.txt +++ /dev/null @@ -1,67 +0,0 @@ -cmake_minimum_required(VERSION 2.8) -cmake_policy(SET CMP0015 NEW) - -project(tm) - -include_directories(${LIBTM_INCLUDE_DIR}) -include_directories(${LIBTM_INFRA_DIR}) - -#Source Files -set(SOURCE_FILES - ${LIBTM_INCLUDE_DIR}/TrackingManager.h - ${LIBTM_INCLUDE_DIR}/TrackingDevice.h - ${LIBTM_INCLUDE_DIR}/TrackingCommon.h - ${LIBTM_INCLUDE_DIR}/TrackingData.h - ${LIBTM_INCLUDE_DIR}/TrackingSerializer.h - Manager.h - Manager.cpp - Device.h - Device.cpp - Message.h - UsbPlugListener.h - UsbPlugListener.cpp - CompleteTask.h - Common.h - Common.cpp - - RcSerializer/Recorder.h - RcSerializer/Recorder.cpp - RcSerializer/Packet.h - RcSerializer/Packet.cpp - RcSerializer/latency_queue.h - RcSerializer/rc_packet.h - RcSerializer/concurrency.h - RcSerializer/Player.h - RcSerializer/Player.cpp -) - -#Add versioning to DLL -IF(WIN32) -SET(SOURCE_FILES "${SOURCE_FILES};version.rc") -ENDIF(WIN32) - -#Building Library -MESSAGE("Building project ${PROJECT_NAME} as ${LIB_TYPE} library") -add_library(${PROJECT_NAME} ${LIB_TYPE} ${SOURCE_FILES}) - -#LINK_LIBRARIES -target_link_libraries(${PROJECT_NAME} -infra -${OS_SPECIFIC_LIBS} -${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/x64/libusb-1.0.lib -) - -set_target_properties(${PROJECT_NAME} PROPERTIES VERSION "${LIBVERSION}" SOVERSION "${LIBTM_VERSION_MAJOR}") -set_target_properties (${PROJECT_NAME} PROPERTIES FOLDER Library) - -#install(TARGETS ${PROJECT_NAME} DESTINATION lib) - -set(CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/../cmake/realsense2") - -install(TARGETS ${PROJECT_NAME} - EXPORT realsense2Targets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) - diff --git a/third-party/libtm/libtm/src/RcSerializer/Packet.cpp b/third-party/libtm/libtm/src/RcSerializer/Packet.cpp deleted file mode 100644 index 99109d4e6e..0000000000 --- a/third-party/libtm/libtm/src/RcSerializer/Packet.cpp +++ /dev/null @@ -1,373 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#define LOG_TAG "Packet" - -#include -#include "Packet.h" -#include "Utils.h" - -using namespace perc; -const uint32_t TS_MAX_GAP_US = 200; - -inline uint64_t ns2us(int64_t ts) -{ - std::chrono::duration ns(ts); - return std::chrono::duration_cast>(ns).count(); -} - -ImagePacket::ImagePacket(TrackingData::VideoFrame& frame) : mExposurePacket(frame.exposuretime, static_cast(frame.gain), frame.sensorIndex, ns2us(frame.timestamp)) -{ - uint32_t bufferSize = frame.profile.stride * frame.profile.height; - mPacket = std::shared_ptr((packet_image_raw_t*) ::operator new (sizeof(packet_image_raw_t) + bufferSize)); - mPacket->height = frame.profile.height; - mPacket->width = frame.profile.width; - mPacket->stride = frame.profile.stride; - mPacket->format = 0; - mPacket->exposure_time_us = static_cast(frame.exposuretime); - perc::copy(mPacket->data, frame.data, bufferSize); - - mPacket->header.sensor_id = frame.sensorIndex; - mPacket->header.type = packet_image_raw; - mPacket->header.bytes = sizeof(packet_image_raw_t) + bufferSize; - mPacket->header.time = ns2us(frame.timestamp); - -} -const uint8_t* ImagePacket::getBytes() -{ - return (uint8_t*)mPacket.get(); -} -size_t ImagePacket::getSize() -{ - return sizeof(packet_image_raw_t) + (mPacket->height * mPacket->stride); -}; -packet_type ImagePacket::getType() -{ - return packet_image_raw; -} -uint16_t ImagePacket::getSensorId() -{ - return mPacket->header.sensor_id; -} -std::shared_ptr ImagePacket::getRCPacket() -{ - return mPacket; -} -ExposurePacket ImagePacket::getExposurePacket() -{ - return mExposurePacket; -} - -const uint8_t* StereoPacket::getBytes() -{ - if (isComplete() == false) - { - return nullptr; - } - mPacket.header.sensor_id = mImagePackets[0]->header.sensor_id / 2; - mPacket.header.type = packet_stereo_raw; - mPacket.header.time = mImagePackets[0]->header.time; - mPacket.height = mImagePackets[0]->height; - mPacket.width = mImagePackets[0]->width; - mPacket.format = mImagePackets[0]->format; - mPacket.exposure_time_us = mImagePackets[0]->exposure_time_us; - mPacket.stride1 = mImagePackets[0]->stride; - mPacket.stride2 = mImagePackets[1]->stride; - mPacket.header.bytes = sizeof(packet_stereo_raw_t) + mPacket.height * (mPacket.stride1 + mPacket.stride2); - - return (uint8_t*)&mPacket; -} -size_t StereoPacket::getSize() -{ - if (isComplete() == false) - { - return 0; - } - return sizeof(packet_stereo_raw_t); -} -packet_type StereoPacket::getType() -{ - return packet_stereo_raw; -} -bool StereoPacket::addPacket(ImagePacket* packet) -{ - std::shared_ptr imagePacket = packet->getRCPacket(); - uint16_t sensorIndex = imagePacket->header.sensor_id % 2; - if (mImagePackets.find(sensorIndex) != mImagePackets.end()) - { - LOGE("Image packet is out of order"); - return false; - } - uint16_t coupeledSensorIndex = (sensorIndex + 1) % 2; - if (mImagePackets.find(coupeledSensorIndex) != mImagePackets.end() && - !areCoupeled(mImagePackets[coupeledSensorIndex]->header.time, imagePacket->header.time)) - { - LOGE("Image packet is out of order"); - return false; - } - mImagePackets[sensorIndex] = imagePacket; - packet_exposure_t imageExposurePacket = packet->getExposurePacket().getRCPacket(); - mExposurePacket = ExposurePacket(imageExposurePacket.exposure_time_us, imageExposurePacket.gain, imagePacket->header.sensor_id / 2, imageExposurePacket.header.time); - return true; -} -bool StereoPacket::areCoupeled(uint64_t time_us1, uint64_t time_us2) -{ - return std::abs(static_cast(time_us1) - static_cast(time_us2)) < TS_MAX_GAP_US; -} -bool StereoPacket::isComplete() -{ - return mImagePackets.size() == 2; -} -const uint8_t* StereoPacket::getImageBytes(uint16_t index) -{ - if (mImagePackets[index]) - { - return (uint8_t*)mImagePackets[index].get()->data; - } - LOGE("Invalid read of stereo packet"); - return nullptr; -} -size_t StereoPacket::getImageSize(uint16_t index) -{ - if (mImagePackets[index]) - { - return mImagePackets[index]->height * mImagePackets[index]->stride; - } - LOGE("Invalid read of stereo packet size"); - return 0; -} -void StereoPacket::clear() -{ - mImagePackets.clear(); -} -ExposurePacket StereoPacket::getExposurePacket() -{ - return mExposurePacket; -} - -GyroPacket::GyroPacket(TrackingData::GyroFrame& frame) : mThermometerPacket(frame.temperature, frame.sensorIndex, frame.timestamp) -{ - mPacket.w[0] = frame.angularVelocity.x; - mPacket.w[1] = frame.angularVelocity.y; - mPacket.w[2] = frame.angularVelocity.z; - - mPacket.header.sensor_id = frame.sensorIndex; - mPacket.header.type = packet_gyroscope; - mPacket.header.bytes = sizeof(packet_gyroscope_t); - mPacket.header.time = ns2us(frame.timestamp); -} -const uint8_t* GyroPacket::getBytes() -{ - return (uint8_t*)&mPacket; -} -size_t GyroPacket::getSize() -{ - return sizeof(mPacket); -} -packet_type GyroPacket::getType() -{ - return packet_gyroscope; -} -ThermometerPacket GyroPacket::getThermometerPacket() -{ - return mThermometerPacket; -} - -VelocimeterPacket::VelocimeterPacket(TrackingData::VelocimeterFrame& frame) : mThermometerPacket(frame.temperature, frame.sensorIndex, frame.timestamp) -{ - mPacket.v[0] = frame.angularVelocity.x; - mPacket.v[1] = frame.angularVelocity.y; - mPacket.v[2] = frame.angularVelocity.z; - - mPacket.header.sensor_id = frame.sensorIndex; - mPacket.header.type = packet_velocimeter; - mPacket.header.bytes = sizeof(packet_velocimeter_t); - mPacket.header.time = ns2us(frame.timestamp); -} -const uint8_t* VelocimeterPacket::getBytes() -{ - return (uint8_t*)&mPacket; -} -size_t VelocimeterPacket::getSize() -{ - return sizeof(mPacket); -} -packet_type VelocimeterPacket::getType() -{ - return packet_velocimeter; -} -ThermometerPacket VelocimeterPacket::getThermometerPacket() -{ - return mThermometerPacket; -} - -AcclPacket::AcclPacket(TrackingData::AccelerometerFrame& frame) : mThermometerPacket(frame.temperature, frame.sensorIndex, frame.timestamp) -{ - mPacket.a[0] = frame.acceleration.x; - mPacket.a[1] = frame.acceleration.y; - mPacket.a[2] = frame.acceleration.z; - - mPacket.header.sensor_id = frame.sensorIndex; - mPacket.header.type = packet_accelerometer; - mPacket.header.bytes = sizeof(packet_accelerometer_t); - mPacket.header.time = ns2us(frame.timestamp); -} -const uint8_t* AcclPacket::getBytes() -{ - return (uint8_t*)&mPacket; -} -size_t AcclPacket::getSize() -{ - return sizeof(mPacket); -} -packet_type AcclPacket::getType() -{ - return packet_accelerometer; -} -ThermometerPacket AcclPacket::getThermometerPacket() -{ - return mThermometerPacket; -} - -ThermometerPacket::ThermometerPacket(float temperature, uint16_t sensor_index, uint64_t time) -{ - mPacket.temperature_C = temperature; - - mPacket.header.sensor_id = sensor_index; - mPacket.header.type = packet_thermometer; - mPacket.header.bytes = sizeof(packet_thermometer_t); - mPacket.header.time = ns2us(time); -} -const uint8_t* ThermometerPacket::getBytes() -{ - return (uint8_t*)&mPacket; -} -size_t ThermometerPacket::getSize() -{ - return sizeof(mPacket); -} -packet_type ThermometerPacket::getType() -{ - return packet_thermometer; -} - -ArrivalTimePacket::ArrivalTimePacket(uint64_t arrivalTime) -{ - mPacket.header.sensor_id = 0; - mPacket.header.type = packet_arrival_time; - mPacket.header.bytes = sizeof(packet_arrival_time_t); - mPacket.header.time = ns2us(arrivalTime); -} -const uint8_t* ArrivalTimePacket::getBytes() -{ - return (uint8_t*)&mPacket; -} -size_t ArrivalTimePacket::getSize() -{ - return sizeof(mPacket); -} -packet_type ArrivalTimePacket::getType() -{ - return packet_arrival_time; -} - -CalibrationPacket::CalibrationPacket(const uint8_t* buffer, uint32_t size) -{ - mPacket = std::shared_ptr((packet_calibration_bin_t*) ::operator new (sizeof(packet_calibration_bin_t) + size)); - mPacket->header.sensor_id = 0; - mPacket->header.type = packet_calibration_bin; - mPacket->header.bytes = sizeof(packet_calibration_bin_t) + size; - mPacket->header.time = 0; - perc::copy(mPacket->data, buffer, size); -} -const uint8_t* CalibrationPacket::getBytes() -{ - return (uint8_t*)mPacket.get(); -} -size_t CalibrationPacket::getSize() -{ - return mPacket->header.bytes; -} -packet_type CalibrationPacket::getType() -{ - return packet_calibration_bin; -} - -ExposurePacket::ExposurePacket(uint64_t exposure_time, float gain, uint16_t sensor_index, uint64_t time) -{ - mPacket.exposure_time_us = exposure_time; - mPacket.gain = gain; - - mPacket.header.sensor_id = sensor_index; - mPacket.header.type = packet_exposure; - mPacket.header.bytes = sizeof(packet_exposure_t); - mPacket.header.time = time; -} -const uint8_t* ExposurePacket::getBytes() -{ - return (uint8_t*)&mPacket; -} -size_t ExposurePacket::getSize() -{ - return sizeof(mPacket); -} -packet_type ExposurePacket::getType() -{ - return packet_exposure; -} -packet_exposure_t ExposurePacket::getRCPacket() -{ - return mPacket; -} - -PhysicalInfoPacket::PhysicalInfoPacket(uint16_t sensor_index, const uint8_t* buffer, uint32_t size) -{ - uint32_t packetSize = sizeof(packet_controller_physical_info_t) + size; - mPacket = std::shared_ptr((packet_controller_physical_info_t*) ::operator new (packetSize)); - mPacket->header.sensor_id = sensor_index; - mPacket->header.type = packet_controller_physical_info; - mPacket->header.bytes = packetSize; - mPacket->header.time = 0; - perc::copy(mPacket->data, buffer, size); -} -const uint8_t* PhysicalInfoPacket::getBytes() -{ - return (uint8_t*)mPacket.get(); -} -size_t PhysicalInfoPacket::getSize() -{ - return mPacket->header.bytes; -} -packet_type PhysicalInfoPacket::getType() -{ - return packet_controller_physical_info; -} - -LedPacket::LedPacket(TrackingData::ControllerLedEventFrame& frame) -{ - uint32_t packetSize = sizeof(packet_led_intensity_t); - mPacket = std::shared_ptr((packet_led_intensity_t*) ::operator new (packetSize)); - mPacket->header.sensor_id = frame.controllerId; - mPacket->header.type = packet_led_intensity; - mPacket->header.bytes = packetSize; - mPacket->header.time = ns2us(frame.timestamp); - mPacket->intensity = frame.intensity; - mPacket->led_id = frame.ledId; -} -const uint8_t* LedPacket::getBytes() -{ - return (uint8_t*)mPacket.get(); -} -size_t LedPacket::getSize() -{ - return mPacket->header.bytes; -} -packet_type LedPacket::getType() -{ - return packet_led_intensity; -} - - - diff --git a/third-party/libtm/libtm/src/RcSerializer/Packet.h b/third-party/libtm/libtm/src/RcSerializer/Packet.h deleted file mode 100644 index a01c1f56f8..0000000000 --- a/third-party/libtm/libtm/src/RcSerializer/Packet.h +++ /dev/null @@ -1,185 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once - -#include -#include -#include -#include "TrackingData.h" -#include "rc_packet.h" - -namespace perc -{ - class Packet - { - public: - virtual const uint8_t* getBytes() = 0; - virtual size_t getSize() = 0; - virtual packet_type getType() = 0; - }; - - class ThermometerPacket : public Packet - { - public: - ThermometerPacket(float temperature, uint16_t sensor_index, uint64_t time); - virtual ~ThermometerPacket() = default; - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - - private: - packet_thermometer_t mPacket; - }; - - class ExposurePacket : public Packet - { - public: - ExposurePacket() : mPacket({ 0 }) {} - ExposurePacket(uint64_t exposure_time_us, float gain, uint16_t sensor_index, uint64_t time); - virtual ~ExposurePacket() = default; - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - packet_exposure_t getRCPacket(); - - private: - packet_exposure_t mPacket; - }; - - class ImagePacket : public Packet - { - public: - ImagePacket(TrackingData::VideoFrame& frame); - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - uint16_t getSensorId(); - std::shared_ptr getRCPacket(); - ExposurePacket getExposurePacket(); - private: - std::shared_ptr mPacket; - ExposurePacket mExposurePacket; - }; - - class StereoPacket : public Packet - { - public: - virtual ~StereoPacket() = default; - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - bool addPacket(ImagePacket* packet); - bool isComplete(); - const uint8_t* getImageBytes(uint16_t index); - size_t getImageSize(uint16_t index); - void clear(); - ExposurePacket getExposurePacket(); - private: - static bool areCoupeled(uint64_t time_us1, uint64_t time_us2); - private: - std::map> mImagePackets; - ExposurePacket mExposurePacket; - packet_stereo_raw_t mPacket; - }; - - class GyroPacket : public Packet - { - public: - GyroPacket(TrackingData::GyroFrame& frame); - ~GyroPacket() = default; - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - ThermometerPacket getThermometerPacket(); - - private: - packet_gyroscope_t mPacket; - ThermometerPacket mThermometerPacket; - }; - - class VelocimeterPacket : public Packet - { - public: - VelocimeterPacket(TrackingData::VelocimeterFrame& frame); - ~VelocimeterPacket() = default; - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - ThermometerPacket getThermometerPacket(); - - private: - packet_velocimeter_t mPacket; - ThermometerPacket mThermometerPacket; - }; - - class AcclPacket : public Packet - { - public: - AcclPacket(TrackingData::AccelerometerFrame& frame); - ~AcclPacket() = default; - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - ThermometerPacket getThermometerPacket(); - - private: - packet_accelerometer_t mPacket; - ThermometerPacket mThermometerPacket; - }; - - class ArrivalTimePacket : public Packet - { - public: - ArrivalTimePacket(uint64_t arrivalTime); - ~ArrivalTimePacket() = default; - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - - private: - packet_arrival_time_t mPacket; - }; - - class CalibrationPacket : public Packet - { - public: - CalibrationPacket(const uint8_t* buffer, uint32_t size); - ~CalibrationPacket() = default; - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - - private: - std::shared_ptr mPacket; - }; - - class PhysicalInfoPacket : public Packet - { - public: - PhysicalInfoPacket(uint16_t sensor_index, const uint8_t* buffer, uint32_t size); - ~PhysicalInfoPacket() = default; - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - - private: - std::shared_ptr mPacket; - }; - - class LedPacket : public Packet - { - public: - LedPacket(TrackingData::ControllerLedEventFrame& frame); - ~LedPacket() = default; - virtual const uint8_t* getBytes() override; - virtual size_t getSize() override; - virtual packet_type getType() override; - - private: - std::shared_ptr mPacket; - - }; -} diff --git a/third-party/libtm/libtm/src/RcSerializer/Player.cpp b/third-party/libtm/libtm/src/RcSerializer/Player.cpp deleted file mode 100644 index ed1fbf4d3c..0000000000 --- a/third-party/libtm/libtm/src/RcSerializer/Player.cpp +++ /dev/null @@ -1,390 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ -#define LOG_TAG "Player" -#include -#include "Player.h" -#include "rc_packet.h" -#include "Utils.h" - -using namespace perc; -const int LEFT_FISHEYE_INDEX = 0; -const int RIGHT_FISHEYE_INDEX = 1; - -TrackingPlayer* TrackingPlayer::CreateInstance(TrackingDevice::Listener* listener, const char* file) -{ - if (listener == nullptr || file == nullptr) - { - LOGE("TrackingRecorder::CreateInstance : Invalid parameters"); - return nullptr; - } - try - { - return new PlayerImpl(listener, file); - } - catch (std::exception& e) - { - std::string error = std::string("Failed to create Player : ") + e.what(); - LOGE(error.c_str()); - return nullptr; - } -} - -PlayerImpl::PlayerImpl(TrackingDevice::Listener* listener, const char* file) : - mListener(listener), mThreadIsAlive(true), mIsStreaming(false), mfilePath(file), mDevice(nullptr), mDeviceListener(nullptr), mProfile(nullptr) -{ - LOGD("By default the device will start streaming after reading the calibration packet"); -} - -bool PlayerImpl::device_configure(TrackingDevice* device, TrackingDevice::Listener* listener, TrackingData::Profile* profile) -{ - if (!device || !listener) - { - LOGE("One of the parameters is invalid"); - return false; - } - mDeviceListener = listener; - mDevice = device; - mProfile = profile; - return true; -} - -inline int64_t us2ns(uint64_t ts) -{ - std::chrono::duration ns(ts); - return std::chrono::duration_cast>(ns).count(); -} - -void PlayerImpl::sendStereoFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime, uint32_t index) -{ - packet_stereo_raw_t *_packet = (packet_stereo_raw_t *)packet.get(); - TrackingData::VideoFrame frame; - frame.profile.height = _packet->height; - frame.profile.width = _packet->width; - frame.exposuretime = static_cast(exposureTime); - frame.profile.pixelFormat = PixelFormat::Y8; - frame.timestamp = us2ns(_packet->header.time); - frame.arrivalTimeStamp = us2ns(arrivalTime); - frame.systemTimestamp = 0; - frame.frameLength = frame.profile.height * frame.profile.width; - frame.gain = gain; - frame.frameId = 0; - if (index == LEFT_FISHEYE_INDEX) - { - frame.sensorIndex = (_packet->header.sensor_id) * 2; - frame.profile.stride = _packet->stride1; - frame.data = reinterpret_cast(_packet->data); - } - else if (index == RIGHT_FISHEYE_INDEX) - { - frame.sensorIndex = (_packet->header.sensor_id) * 2 + 1; - frame.profile.stride = _packet->stride2; - frame.data = reinterpret_cast(_packet->data + (_packet->stride1 * _packet->height)); - } - else - { - LOGE("Invalid stereo packet"); - } - mListener->onVideoFrame(frame); -} - -void PlayerImpl::sendImageFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime) -{ - packet_image_raw_t *_packet = (packet_image_raw_t *)packet.get(); - TrackingData::VideoFrame frame; - frame.profile.height = _packet->height; - frame.profile.width = _packet->width; - frame.exposuretime = static_cast(exposureTime); - frame.profile.pixelFormat = PixelFormat::Y8; - frame.timestamp = us2ns(_packet->header.time); - frame.arrivalTimeStamp = us2ns(arrivalTime); - frame.systemTimestamp = 0; - frame.frameLength = frame.profile.height * frame.profile.width; - frame.gain = gain; - frame.frameId = 0; - frame.sensorIndex = static_cast(_packet->header.sensor_id); - frame.profile.stride = _packet->stride; - frame.data = reinterpret_cast(_packet->data); - mListener->onVideoFrame(frame); -} - -void PlayerImpl::sendGyroFrame(std::shared_ptr packet, uint64_t arrivalTime, float temperature) -{ - packet_gyroscope_t *_packet = (packet_gyroscope_t *)packet.get(); - TrackingData::GyroFrame frame; - frame.sensorIndex = static_cast(_packet->header.sensor_id); - frame.angularVelocity.set(_packet->w[0], _packet->w[1], _packet->w[2]); - frame.timestamp = us2ns(_packet->header.time); - frame.temperature = temperature; - frame.arrivalTimeStamp = us2ns(arrivalTime); - frame.systemTimestamp = 0; - frame.frameId = 0; - mListener->onGyroFrame(frame); -} - -void PlayerImpl::sendAccelFrame(std::shared_ptr packet, uint64_t arrivalTime, float temperature) -{ - packet_accelerometer_t *_packet = (packet_accelerometer_t *)packet.get(); - TrackingData::AccelerometerFrame frame; - frame.sensorIndex = static_cast(_packet->header.sensor_id); - frame.acceleration.set(_packet->a[0], _packet->a[1], _packet->a[2]); - frame.timestamp = us2ns(_packet->header.time); - frame.temperature = temperature; - frame.arrivalTimeStamp = us2ns(arrivalTime); - frame.systemTimestamp = 0; - frame.frameId = 0; - mListener->onAccelerometerFrame(frame); -} - -bool PlayerImpl::startDevice() -{ - if (!mDevice) return false; - - mProfile->playbackEnabled = true; - auto status = mDevice->Start(mDeviceListener, mProfile); - if (status != Status::SUCCESS) - { - LOGE("Failed to start device : (0x%X)", status); - return false; - } - return true; -} - -void PlayerImpl::start(bool start_after_calibration) -{ - LOGD("Set playing of file %s", mfilePath.c_str()); - mReadingThread = std::thread([&]() -> void { - - if (!start_after_calibration && mDevice) - { - if (!startDevice()) return; - } - mIsStreaming = true; - - std::ifstream ifs(mfilePath.c_str(), std::fstream::in | std::fstream::binary); - if (!setProcessPriorityToRealtime()) - { - throw std::runtime_error("Failed to set process priority"); - } - packet_header_t header; - static const uint64_t INITIAL_ARRIVAL_TIME = 0; - static const uint64_t INITIAL_EXPOSURE_TIME = 0; - static const float INITIAL_TEMPERATURE = -100; - static const float INITIAL_GAIN = -100; - - uint64_t arrivalTime = INITIAL_ARRIVAL_TIME, exposureTime = INITIAL_EXPOSURE_TIME; - float temperature = INITIAL_TEMPERATURE, gain = INITIAL_GAIN; - while (ifs.read((char*)&header, sizeof(packet_header_t)) && mThreadIsAlive) - { - if (header.bytes == 0) - { - LOGE("Failed to read a packet - the header is empty"); - mIsStreaming = false; - return; - } - - std::shared_ptr packet = std::shared_ptr((packet_t *) ::operator new (header.bytes)); - packet->header = header; - if (packet->header.bytes - sizeof(packet_header_t) > 0) - { - if (!ifs.read((char*)&packet->data, packet->header.bytes - sizeof(packet_header_t))) - { - LOGE("Failed to read the packet"); - mIsStreaming = false; - return; - } - } - - switch (packet->header.type) - { - case packet_controller_physical_info: - { - if (!mDevice) break; - size_t size = packet->header.bytes - sizeof(packet_controller_physical_info_t); - writePhysicalInfo(packet->header.sensor_id, packet->data, size); - break; - } - case packet_calibration_bin: - { - if (!mDevice) break; - size_t size = packet->header.bytes - sizeof(packet_calibration_bin_t); - writeCalibration(packet->data, size); - if (start_after_calibration) - { - if (!startDevice()) - { - mIsStreaming = false; - return; - } - } - break; - } - case packet_arrival_time: - { - if (arrivalTime != INITIAL_ARRIVAL_TIME) - { - LOGW("packet_arrival_time: arrivalTime packet was not consumed\n"); - } - else - { - arrivalTime = packet->header.time; - } - break; - } - case packet_thermometer: - { - if (arrivalTime == INITIAL_ARRIVAL_TIME) - { - LOGW("packet_thermometer: arrivalTime packet was not consumed\n"); - } - if (temperature != INITIAL_TEMPERATURE) - { - LOGW("packet_thermometer: thermometer packet was not consumed\n"); - } - else - { - packet_thermometer_t *thermometer = (packet_thermometer_t *)packet.get(); - temperature = thermometer->temperature_C; - arrivalTime = INITIAL_ARRIVAL_TIME; - } - break; - } - case packet_exposure: - { - if (arrivalTime == INITIAL_ARRIVAL_TIME) - { - LOGW("packet_exposure: arrivalTime packet was not consumed\n"); - } - if (exposureTime != INITIAL_EXPOSURE_TIME && gain != INITIAL_GAIN) - { - LOGW("packet_exposure: exposure packet was not consumed\n"); - } - packet_exposure_t *exposure = (packet_exposure_t *)packet.get(); - gain = exposure->gain; - exposureTime = exposure->exposure_time_us; - arrivalTime = INITIAL_ARRIVAL_TIME; - break; - } - case packet_stereo_raw: - { - if (arrivalTime == INITIAL_ARRIVAL_TIME) - { - LOGW("packet_stereo_raw: arrivalTime packet was not consumed\n"); - } - packet_stereo_raw_t *stereo = (packet_stereo_raw_t *)packet.get(); - int leftIndex = stereo->header.sensor_id * 2 + LEFT_FISHEYE_INDEX; - int rightIndex = stereo->header.sensor_id * 2 + RIGHT_FISHEYE_INDEX; - - sendStereoFrame(packet, gain, exposureTime, arrivalTime, LEFT_FISHEYE_INDEX); - sendStereoFrame(packet, gain, exposureTime, arrivalTime, RIGHT_FISHEYE_INDEX); - arrivalTime = INITIAL_ARRIVAL_TIME; - exposureTime = INITIAL_EXPOSURE_TIME; - gain = INITIAL_GAIN; - break; - } - case packet_image_raw: - { - if (exposureTime == INITIAL_EXPOSURE_TIME) - { - LOGW("packet_image_raw: exposure packet was not consumed\n"); - } - if (arrivalTime == INITIAL_ARRIVAL_TIME) - { - LOGW("packet_image_raw: arrivalTime packet was not consumed\n"); - } - packet_image_raw_t *image = (packet_image_raw_t *)packet.get(); - sendImageFrame(packet, gain, exposureTime, arrivalTime); - arrivalTime = INITIAL_ARRIVAL_TIME; - exposureTime = INITIAL_EXPOSURE_TIME; - gain = INITIAL_GAIN; - break; - - } - case packet_accelerometer: - { - if (arrivalTime == INITIAL_ARRIVAL_TIME) - { - LOGW("packet_accelerometer: arrivalTime packet was not consumed\n"); - } - if (temperature == INITIAL_TEMPERATURE) - { - LOGW("packet_accelerometer: thermometer packet was not consumed\n"); - } - sendAccelFrame(packet, arrivalTime, temperature); - temperature = INITIAL_TEMPERATURE; - arrivalTime = INITIAL_ARRIVAL_TIME; - break; - } - case packet_gyroscope: - { - if (arrivalTime == INITIAL_ARRIVAL_TIME) - { - LOGW("packet_gyroscope: arrivalTime packet was not consumed\n"); - } - if (temperature == INITIAL_TEMPERATURE) - { - LOGW("packet_gyroscope: thermometer packet was not consumed\n"); - } - sendGyroFrame(packet, arrivalTime, temperature); - temperature = INITIAL_TEMPERATURE; - arrivalTime = INITIAL_ARRIVAL_TIME; - break; - } - default: - { - LOGW("one of the packets has unknown type : %d", packet->header.type); - } - } - } - mIsStreaming = false; - }); -} - -void PlayerImpl::stop() -{ - mIsStreaming = false; - mThreadIsAlive = false; - if (mReadingThread.joinable()) - { - mReadingThread.join(); - } -} - -bool PlayerImpl::isStreaming() -{ - return mIsStreaming; -} - -void PlayerImpl::writeCalibration(uint8_t* buffer, size_t size) -{ - if (mDevice == nullptr) return; - const static int CALIBRATION_TABLE_TYPE = 0x0; - auto status = mDevice->WriteConfiguration(CALIBRATION_TABLE_TYPE, static_cast(size), buffer); - if (status != perc::Status::SUCCESS) - { - LOGE("Failed to write write calibration table with status : (0x%X)", status); - } -} - -void PlayerImpl::writePhysicalInfo(uint16_t sensorIndex, uint8_t* buffer, size_t size) -{ - if (mDevice == nullptr) return; - if(sensorIndex != 1 && sensorIndex != 2) - { - LOGE("Invalid sensor id on physical info message %d", sensorIndex); - return; - } - - int tableType = 0x100 + sensorIndex; - auto status = mDevice->WriteConfiguration(tableType, static_cast(size), buffer); - if (status != perc::Status::SUCCESS) - { - LOGE("Failed to write write calibration table with status : (0x%X)", status); - } -} - -PlayerImpl::~PlayerImpl() -{ - stop(); -} diff --git a/third-party/libtm/libtm/src/RcSerializer/Player.h b/third-party/libtm/libtm/src/RcSerializer/Player.h deleted file mode 100644 index cd0ff05e02..0000000000 --- a/third-party/libtm/libtm/src/RcSerializer/Player.h +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once - -#include -#include -#include -#include -#include - -#include "Packet.h" -#include "TrackingDevice.h" -#include "TrackingSerializer.h" - -namespace perc -{ - class PlayerImpl : public TrackingPlayer - { - public: - - PlayerImpl(TrackingDevice::Listener* listener, const char* file); - virtual ~PlayerImpl(); - virtual bool device_configure(TrackingDevice* device, TrackingDevice::Listener* listener, TrackingData::Profile* profile = nullptr) override; - virtual void start(bool start_after_calibration = true) override; - virtual bool isStreaming() override; - virtual void stop() override; - - private: - void sendImageFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime); - void sendStereoFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime, uint32_t index); - void sendAccelFrame(std::shared_ptr packet, uint64_t arrival_time, float temperature); - void sendGyroFrame(std::shared_ptr packet, uint64_t arrival_time, float temperature); - void writeCalibration(uint8_t* buffer, size_t size); - void writePhysicalInfo(uint16_t sensorIndex, uint8_t* buffer, size_t size); - bool startDevice(); - - private: - TrackingDevice * mDevice; - TrackingDevice::Listener * mListener; - std::thread mReadingThread; - std::atomic mThreadIsAlive; - std::atomic mIsStreaming; - std::string mfilePath; - TrackingDevice::Listener* mDeviceListener; - TrackingData::Profile* mProfile; - }; -} \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp b/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp deleted file mode 100644 index b709d8e328..0000000000 --- a/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp +++ /dev/null @@ -1,249 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ -#define LOG_TAG "Recorder" -#include -#include "Recorder.h" -#include "Packet.h" -#include "Utils.h" - -using namespace perc; -const uint64_t MAX_LATENCY = 1000LL * 1000LL * 1000LL; - -TrackingRecorder* TrackingRecorder::CreateInstance(TrackingDevice* device, TrackingDevice::Listener* listener, const char* file, bool stereoMode) -{ - if (device == nullptr || listener == nullptr || file == nullptr) - { - LOGE("TrackingRecorder::CreateInstance : Invalid parameters"); - return nullptr; - } - try - { - return new RecorderImpl(device, listener, file, stereoMode); - } - catch (std::exception& e) - { - std::string error = std::string("Failed to create Recorder : ") + e.what(); - LOGE(error.c_str()); - return nullptr; - } -} - -RecorderImpl::RecorderImpl(TrackingDevice* device, TrackingDevice::Listener* listener, const char* file, bool stereoMode) : mDevice(device), mListener(listener), mThreadIsAlive(true), mWritingQueue(0), mVideoFrames(), mStereoMode(stereoMode), -mLatencyQueue(MAX_LATENCY, [&](uint64_t arrivalTime, std::shared_ptr packet) -> bool -{ - switch (packet->getType()) - { - case packet_image_raw: - { - ImagePacket * imagePacket = (ImagePacket*)packet.get(); - if (mStereoMode == false) - { - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::make_shared(imagePacket->getExposurePacket())); - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::shared_ptr(packet)); - } - else - { - uint16_t stereoSensorId = imagePacket->getSensorId() / 2; - StereoPacket& _packet = mVideoFrames[stereoSensorId]; - if (_packet.addPacket(imagePacket) == false) - { - LOGE("Failed to record an image packet"); - return false; - } - if (_packet.isComplete()) - { - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::make_shared(_packet.getExposurePacket())); - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::make_shared(_packet)); - mVideoFrames[stereoSensorId].clear(); - } - } - break; - } - case packet_gyroscope: - { - GyroPacket * _packet = (GyroPacket*)packet.get(); - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::make_shared(_packet->getThermometerPacket())); - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::shared_ptr(packet)); - break; - } - case packet_accelerometer: - { - AcclPacket * _packet = (AcclPacket*)packet.get(); - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::make_shared(_packet->getThermometerPacket())); - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::shared_ptr(packet)); - break; - } - case packet_velocimeter: - { - VelocimeterPacket * _packet = (VelocimeterPacket*)packet.get(); - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::make_shared(_packet->getThermometerPacket())); - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::shared_ptr(packet)); - break; - } - case packet_led_intensity: - { - mWritingQueue.enqueue(std::make_shared(arrivalTime)); - mWritingQueue.enqueue(std::shared_ptr(packet)); - break; - } - default: LOGW("Invalid packet type : %d", packet->getType()); - } - return true; -}) -{ - LOGD("Set recording to file %s", file); - uint16_t actualSize = 0; - - if (!setProcessPriorityToRealtime()) - { - throw std::runtime_error("Failed to set process priority"); - } - - uint8_t buffer[MAX_CONFIGURATION_SIZE] = { 0 }; - const static int CALIBRATION_TABLE_TYPE = 0x0; - if (mDevice->ReadConfiguration(CALIBRATION_TABLE_TYPE, MAX_CONFIGURATION_SIZE, buffer, &actualSize) != Status::SUCCESS) - { - throw std::runtime_error("Failed to read calibration table from device"); - } - mWritingQueue.enqueue(std::make_shared(buffer, actualSize)); - - mOutputFile.open(file, std::fstream::out | std::ofstream::binary); - - mWritingThread = std::thread([&]() -> void { - while (mThreadIsAlive || mWritingQueue.size() > 0) - { - if (mWritingQueue.size() == 0) - { - static uint32_t WAIT_FOR_PACKETS = 10; - std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_PACKETS)); - continue; - } - std::shared_ptr packet; - mWritingQueue.dequeue(&packet); //check output - mOutputFile.write((const char*)packet->getBytes(), packet->getSize()); - if (packet->getType() == packet_stereo_raw) - { - StereoPacket * _packet = (StereoPacket*)packet.get(); - mOutputFile.write((const char*)_packet->getImageBytes(0), _packet->getImageSize(0)); - mOutputFile.write((const char*)_packet->getImageBytes(1), _packet->getImageSize(1)); - } - } - }); -} - -RecorderImpl::~RecorderImpl() -{ - mLatencyQueue.drain(); - mThreadIsAlive = false; - if (mWritingThread.joinable()) - { - mWritingThread.join(); - } - mOutputFile.close(); - mWritingQueue.clear(); -} - -void RecorderImpl::onPoseFrame(OUT TrackingData::PoseFrame& pose) -{ - mListener->onPoseFrame(pose); -} - -void RecorderImpl::onVideoFrame(OUT TrackingData::VideoFrame& frame) -{ - mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); - mListener->onVideoFrame(frame); -} - -void RecorderImpl::onAccelerometerFrame(OUT TrackingData::AccelerometerFrame& frame) -{ - mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); - mListener->onAccelerometerFrame(frame); -} - -void RecorderImpl::onGyroFrame(OUT TrackingData::GyroFrame& frame) -{ - mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); - mListener->onGyroFrame(frame); -} - -void RecorderImpl::onVelocimeterFrame(OUT TrackingData::VelocimeterFrame& frame) -{ - mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); - mListener->onVelocimeterFrame(frame); -} - -void RecorderImpl::onControllerDiscoveryEventFrame(OUT TrackingData::ControllerDiscoveryEventFrame& frame) -{ - mListener->onControllerDiscoveryEventFrame(frame); -} - -void RecorderImpl::onControllerFrame(OUT TrackingData::ControllerFrame& frame) -{ - mListener->onControllerFrame(frame); -} - -void RecorderImpl::onControllerConnectedEventFrame(OUT TrackingData::ControllerConnectedEventFrame& frame) -{ - uint16_t actualSize = 0; - if (frame.controllerId != 1 && frame.controllerId != 2) - { - LOGE("Invalid controller id onControllerConnectedEventFrame %d", frame.controllerId); - return; - } - - int tableType = 0x100 + frame.controllerId; - uint8_t buffer[MAX_CONFIGURATION_SIZE] = { 0 }; - if (mDevice->ReadConfiguration(tableType, MAX_CONFIGURATION_SIZE, buffer, &actualSize) == Status::SUCCESS) - { - mWritingQueue.enqueue(std::make_shared(frame.controllerId, buffer, actualSize)); - } - else - { - LOGE("Failed to read physical info table from device"); - } - mListener->onControllerConnectedEventFrame(frame); -} - -void RecorderImpl::onControllerDisconnectedEventFrame(OUT TrackingData::ControllerDisconnectedEventFrame& frame) -{ - mListener->onControllerDisconnectedEventFrame(frame); -} - -void RecorderImpl::onRssiFrame(OUT TrackingData::RssiFrame& frame) -{ - mListener->onRssiFrame(frame); -} - -void RecorderImpl::onLocalizationDataEventFrame(OUT TrackingData::LocalizationDataFrame& frame) -{ - mListener->onLocalizationDataEventFrame(frame); -} - -void RecorderImpl::onFWUpdateEvent(OUT TrackingData::ControllerFWEventFrame& frame) -{ - mListener->onFWUpdateEvent(frame); -} - -void RecorderImpl::onStatusEvent(OUT TrackingData::StatusEventFrame& frame) -{ - mListener->onStatusEvent(frame); -} - -void RecorderImpl::onControllerLedEvent(OUT TrackingData::ControllerLedEventFrame& frame) -{ - mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); - mListener->onControllerLedEvent(frame); - -} diff --git a/third-party/libtm/libtm/src/RcSerializer/Recorder.h b/third-party/libtm/libtm/src/RcSerializer/Recorder.h deleted file mode 100644 index a791bf0e3e..0000000000 --- a/third-party/libtm/libtm/src/RcSerializer/Recorder.h +++ /dev/null @@ -1,68 +0,0 @@ -/******************************************************************************* -INTEL CORPORATION PROPRIETARY INFORMATION -Copyright(c) 2017 Intel Corporation. All Rights Reserved. -*******************************************************************************/ - -#pragma once - -#include -#include -#include -#include "TrackingDevice.h" -#include "latency_queue.h" -#include "concurrency.h" -#include "TrackingSerializer.h" - -namespace perc -{ - class RecorderImpl : public TrackingRecorder - { - public: - - RecorderImpl(TrackingDevice* device, Listener* listener, const char* file, bool stereoMode); - - virtual ~RecorderImpl(); - - virtual void onPoseFrame(OUT TrackingData::PoseFrame& pose) override; - - virtual void onVideoFrame(OUT TrackingData::VideoFrame& frame) override; - - virtual void onAccelerometerFrame(OUT TrackingData::AccelerometerFrame& frame) override; - - virtual void onGyroFrame(OUT TrackingData::GyroFrame& frame) override; - - virtual void onVelocimeterFrame(OUT TrackingData::VelocimeterFrame& frame) override; - - virtual void onControllerDiscoveryEventFrame(OUT TrackingData::ControllerDiscoveryEventFrame& frame) override; - - virtual void onControllerFrame(OUT TrackingData::ControllerFrame& frame) override; - - virtual void onControllerConnectedEventFrame(OUT TrackingData::ControllerConnectedEventFrame& frame) override; - - virtual void onControllerDisconnectedEventFrame(OUT TrackingData::ControllerDisconnectedEventFrame& frame) override; - - virtual void onRssiFrame(OUT TrackingData::RssiFrame& frame) override; - - virtual void onLocalizationDataEventFrame(OUT TrackingData::LocalizationDataFrame& frame) override; - - virtual void onFWUpdateEvent(OUT TrackingData::ControllerFWEventFrame& frame) override; - - virtual void onStatusEvent(OUT TrackingData::StatusEventFrame& frame) override; - - virtual void onControllerLedEvent(OUT TrackingData::ControllerLedEventFrame& frame) override; - - - private: - TrackingDevice * mDevice; - TrackingDevice::Listener * mListener; - latency_queue mLatencyQueue; - single_consumer_queue> mWritingQueue; - std::ofstream mOutputFile; - std::thread mWritingThread; - std::atomic mThreadIsAlive; - std::map mVideoFrames; - std::atomic mStereoMode; - }; - - -} \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/concurrency.h b/third-party/libtm/libtm/src/RcSerializer/concurrency.h deleted file mode 100644 index 8c35e7e957..0000000000 --- a/third-party/libtm/libtm/src/RcSerializer/concurrency.h +++ /dev/null @@ -1,310 +0,0 @@ -// License: Apache 2.0. See LICENSE file in root directory. -// Copyright(c) 2015 Intel Corporation. All Rights Reserved. - -#pragma once -#include -#include -#include -#include -#include -#include - -const int QUEUE_MAX_SIZE = 10; -// Simplest implementation of a blocking concurrent queue for thread messaging -template -class single_consumer_queue -{ - std::deque q; - std::mutex mutex; - std::condition_variable cv; // not empty signal - unsigned int cap; - bool accepting; - - // flush mechanism is required to abort wait on cv - // when need to stop - std::atomic need_to_flush; - std::atomic was_flushed; - std::condition_variable was_flushed_cv; - std::mutex was_flushed_mutex; -public: - explicit single_consumer_queue(unsigned int cap = QUEUE_MAX_SIZE) - : q(), mutex(), cv(), cap(cap), need_to_flush(false), was_flushed(false), accepting(true) - {} - - void enqueue(T&& item) - { - std::unique_lock lock(mutex); - if (accepting) - { - q.push_back(std::move(item)); - if (q.size() > cap && cap != 0) - { - printf("Droppping frame\n"); - q.pop_front(); - } - } - lock.unlock(); - cv.notify_one(); - } - - bool dequeue(T* item ,unsigned int timeout_ms = 5000) - { - std::unique_lock lock(mutex); - accepting = true; - was_flushed = false; - const auto ready = [this]() { return (q.size() > 0) || need_to_flush; }; - if (!ready() && !cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), ready)) - { - return false; - } - - if (q.size() <= 0) - { - return false; - } - *item = std::move(q.front()); - q.pop_front(); - return true; - } - - bool peek(T** item) - { - std::unique_lock lock(mutex); - - if (q.size() <= 0) - { - return false; - } - *item = &q.front(); - return true; - } - - bool try_dequeue(T* item) - { - std::unique_lock lock(mutex); - accepting = true; - if (q.size() > 0) - { - auto val = std::move(q.front()); - q.pop_front(); - *item = std::move(val); - return true; - } - return false; - } - - void clear() - { - std::unique_lock lock(mutex); - - accepting = false; - need_to_flush = true; - - while (q.size() > 0) - { - auto item = std::move(q.front()); - q.pop_front(); - } - cv.notify_all(); - } - - void start() - { - std::unique_lock lock(mutex); - need_to_flush = false; - accepting = true; - } - - size_t size() - { - return q.size(); - } -}; - - -class dispatcher -{ -public: - class cancellable_timer - { - public: - cancellable_timer(dispatcher* owner) - : _owner(owner) - {} - - bool try_sleep(int ms) - { - using namespace std::chrono; - - std::unique_lock lock(_owner->_was_stopped_mutex); - auto good = [&]() { return _owner->_was_stopped.load(); }; - return !(_owner->_was_stopped_cv.wait_for(lock, milliseconds(ms), good)); - } - - private: - dispatcher* _owner; - }; - - dispatcher(unsigned int cap) - : _queue(cap), - _was_stopped(true), - _was_flushed(false), - _is_alive(true) - { - _thread = std::thread([&]() - { - while (_is_alive) - { - std::function item; - - if (_queue.dequeue(&item)) - { - cancellable_timer time(this); - - try - { - item(time); - } - catch(...){} - } - -#ifndef ANDROID - std::unique_lock lock(_was_flushed_mutex); -#endif - _was_flushed = true; - _was_flushed_cv.notify_all(); -#ifndef ANDROID - lock.unlock(); -#endif - } - }); - } - - template - void invoke(T item) - { - if (!_was_stopped) - { - _queue.enqueue(std::move(item)); - } - } - - void start() - { - std::unique_lock lock(_was_stopped_mutex); - _was_stopped = false; - - _queue.start(); - } - - void stop() - { - { - std::unique_lock lock(_was_stopped_mutex); - _was_stopped = true; - _was_stopped_cv.notify_all(); - } - - _queue.clear(); - - { - std::unique_lock lock(_was_flushed_mutex); - _was_flushed = false; - } - - std::unique_lock lock_was_flushed(_was_flushed_mutex); - _was_flushed_cv.wait_for(lock_was_flushed, std::chrono::hours(999999), [&]() { return _was_flushed.load(); }); - - _queue.start(); - } - - ~dispatcher() - { - stop(); - _queue.clear(); - _is_alive = false; - _thread.join(); - } - - bool flush() - { - std::mutex m; - std::condition_variable cv; - bool invoked = false; - auto wait_sucess = std::make_shared(true); - invoke([&, wait_sucess](cancellable_timer t) - { - ///TODO: use _queue to flush, and implement properly - if (_was_stopped || !(*wait_sucess)) - return; - - { - std::lock_guard locker(m); - invoked = true; - } - cv.notify_one(); - }); - std::unique_lock locker(m); - *wait_sucess = cv.wait_for(locker, std::chrono::seconds(10), [&]() { return invoked || _was_stopped; }); - return *wait_sucess; - } -private: - friend cancellable_timer; - single_consumer_queue> _queue; - std::thread _thread; - - std::atomic _was_stopped; - std::condition_variable _was_stopped_cv; - std::mutex _was_stopped_mutex; - - std::atomic _was_flushed; - std::condition_variable _was_flushed_cv; - std::mutex _was_flushed_mutex; - - std::atomic _is_alive; -}; - -template> -class active_object -{ -public: - active_object(T operation) - : _operation(std::move(operation)), _dispatcher(1), _stopped(true) - { - } - - void start() - { - _stopped = false; - _dispatcher.start(); - - do_loop(); - } - - void stop() - { - _stopped = true; - _dispatcher.stop(); - } - - ~active_object() - { - stop(); - } -private: - void do_loop() - { - _dispatcher.invoke([this](dispatcher::cancellable_timer ct) - { - _operation(ct); - if (!_stopped) - { - do_loop(); - } - }); - } - - T _operation; - dispatcher _dispatcher; - std::atomic _stopped; -}; \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/latency_queue.h b/third-party/libtm/libtm/src/RcSerializer/latency_queue.h deleted file mode 100644 index 92934158fc..0000000000 --- a/third-party/libtm/libtm/src/RcSerializer/latency_queue.h +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include -#include - -#include "Packet.h" - -using namespace perc; - -class latency_queue -{ -public: - latency_queue(uint64_t latency, std::function)> pop_func) : - m_latency(latency), m_pop_func(pop_func), m_last_time(0) {} - - void push(uint64_t time, std::shared_ptr buf) - { - std::lock_guard lk(m_push_mutex); - // backward comaptibility - pass-through buffers with 0 timestamp - if (time == 0) { - m_pop_func(0, buf); - } - m_queue.push(queue_element_t(time, buf)); - if (time >= m_last_time) m_last_time = time; - while (!m_queue.empty()) { - queue_element_t top = m_queue.top(); - if (m_last_time - top.first < m_latency) { - break; - } - else { - m_queue.pop(); - m_pop_func(top.first, top.second); - } - } - } - - void drain() - { - std::lock_guard lk(m_push_mutex); - while (!m_queue.empty()) { - queue_element_t top = m_queue.top(); - m_queue.pop(); - m_pop_func(top.first, top.second); - } - } - -private: - uint64_t m_latency; - uint64_t m_last_time; - std::function)> m_pop_func; - std::mutex m_push_mutex; - typedef std::pair> queue_element_t; - struct element_cmp { - bool operator () (const queue_element_t &a, const queue_element_t &b) - { - return a.first > b.first; - } - }; - std::priority_queue, - element_cmp> m_queue; -}; \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/rc_packet.h b/third-party/libtm/libtm/src/RcSerializer/rc_packet.h deleted file mode 100644 index a5a6e217a4..0000000000 --- a/third-party/libtm/libtm/src/RcSerializer/rc_packet.h +++ /dev/null @@ -1,355 +0,0 @@ -// Copyright (c) 2008-2012, Eagle Jones -// Copyright (c) 2012-2015 RealityCap, Inc -// All rights reserved. -// - -#ifndef __PACKET_H -#define __PACKET_H - -#include -#include -#include -#include - -#ifdef MYRIAD2 -// lives in target/host common area -#ifdef USE_TM2_PACKETS -#include "get_pose.h" -#endif -#endif - -#ifdef WIN32 -#pragma warning (push) -#pragma warning (disable : 4200) -#endif - -//WARNING: Do not change the order of this enum, or insert new packet types in the middle -//Only append new packet types after the last one previously defined. -typedef enum packet_type { - packet_none = 0, - packet_camera = 1, - packet_imu = 2, - packet_feature_track = 3, - packet_feature_select = 4, - packet_navsol = 5, - packet_feature_status = 6, - packet_filter_position = 7, - packet_filter_reconstruction = 8, - packet_feature_drop = 9, - packet_sift = 10, - packet_plot_info = 11, - packet_plot = 12, - packet_plot_drop = 13, - packet_recognition_group = 14, - packet_recognition_feature = 15, - packet_recognition_descriptor = 16, - packet_map_edge = 17, - packet_filter_current = 18, - packet_feature_prediction_variance = 19, - packet_accelerometer = 20, - packet_gyroscope = 21, - packet_filter_feature_id_visible = 22, - packet_filter_feature_id_association = 23, - packet_feature_intensity = 24, - packet_filter_control = 25, - packet_ground_truth = 26, - packet_core_motion = 27, - packet_image_with_depth = 28, - packet_image_raw = 29, - packet_diff_velocimeter = 30, - packet_thermometer = 31, - packet_stereo_raw = 40, - packet_image_stereo = 41, - packet_rc_pose = 42, - packet_calibration_json = 43, - packet_arrival_time = 44, - packet_velocimeter = 45, - packet_pose = 46, - packet_control = 47, - packet_calibration_bin = 48, - packet_exposure = 49, - packet_controller_physical_info = 50, - packet_led_intensity = 51, - packet_command_start = 100, - packet_command_stop = 101, -} packet_type; - -typedef struct packet_header_t { - uint32_t bytes; //size of packet including header - uint16_t type; //id of packet - uint16_t sensor_id; //id of sensor - uint64_t time; //time in microseconds -} packet_header_t; - -typedef struct packet_t { - packet_header_t header; - uint8_t data[]; -} packet_t; - -typedef struct { - packet_header_t header; - uint8_t data[]; -} packet_camera_t; - -typedef struct { - packet_header_t header; - uint64_t exposure_time_us; - uint16_t width, height; - uint16_t depth_width, depth_height; - uint8_t data[]; -} packet_image_with_depth_t; - -typedef struct { - packet_header_t header; - uint64_t exposure_time_us; - uint16_t width, height, stride; - uint16_t format; // enum { Y8, Z16_mm }; - uint8_t data[]; -} packet_image_raw_t; - -typedef struct { - packet_header_t header; - uint64_t exposure_time_us; - uint16_t width, height, stride1, stride2; - uint16_t format; // enum { Y8, Z16_mm }; - uint8_t data[]; // image2 starts at data + height*stride1 -} packet_stereo_raw_t; - -typedef struct { - packet_header_t header; - uint64_t exposure_time_us; - float gain; -} packet_exposure_t; - -typedef struct { - packet_header_t header; - packet_image_raw_t *frames[2]; -} packet_image_stereo_t; - -typedef struct { - packet_header_t header; - float a[3]; // m/s^2 - float w[3]; // rad/s -} packet_imu_t; - -typedef struct { - packet_header_t header; - float a[3]; // m/s^2 -} packet_accelerometer_t; - -typedef struct { - packet_header_t header; - float w[3]; // rad/s -} packet_gyroscope_t; - -typedef struct { - packet_header_t header; - float temperature_C; -} packet_thermometer_t; - -typedef struct { - packet_header_t header; - float v[3]; -} packet_velocimeter_t; - -typedef struct { - packet_header_t header; - float v[2]; // m/s in body x for sensor_id, sensor_id+1 -} packet_diff_velocimeter_t; - -typedef struct { - packet_header_t header; - float rotation_rate[3]; // rad/s - float gravity[3]; //m/s^2 -} packet_core_motion_t; - -typedef struct { - packet_header_t header; - struct feature_t { float x, y; } features[]; -} packet_feature_track_t; - -typedef struct { - packet_header_t header; - uint16_t indices[]; -} packet_feature_drop_t; - -typedef struct { - packet_header_t header; - uint8_t status[]; -} packet_feature_status_t; - -typedef struct { - packet_header_t header; - uint32_t led_id; - uint32_t intensity; -}packet_led_intensity_t; - -typedef struct { - packet_header_t header; - uint8_t intensity[]; -} packet_feature_intensity_t; - -typedef struct { - packet_header_t header; - float latitude, longitude; - float altitude; - float velocity[3]; - float orientation[3]; -} packet_navsol_t; - -typedef struct { - packet_header_t header; - float position[3]; - float orientation[3]; -} packet_filter_position_t; - -typedef struct { - packet_header_t header; - float points[][3]; -} packet_filter_reconstruction_t; - -typedef struct { - packet_header_t header; - uint64_t reference; - float T[3]; - float W[3]; - float points[][3]; -} packet_filter_current_t; - -typedef struct { - packet_header_t header; - float T[3]; - float W[3]; - uint64_t feature_id[]; -} packet_filter_feature_id_visible_t; - -typedef struct { - packet_header_t header; - uint64_t feature_id[]; -} packet_filter_feature_id_association_t; - -typedef struct packet_listener { - packet_t *latest; - int type; - bool isnew; -} packet_listener_t; - -typedef struct { - packet_header_t header; - float nominal; - const char identity[]; -} packet_plot_info_t; - -typedef struct { - packet_header_t header; - int count; - float data[]; -} packet_plot_t; - -typedef struct { - packet_header_t header; - uint64_t first, second; - float T[3], W[3]; - float T_var[3], W_var[3]; -} packet_map_edge_t; - -typedef struct { - packet_header_t header; - int64_t id; //signed to indicate add/drop - float W[3], W_var[3]; -} packet_recognition_group_t; - -typedef struct { - packet_header_t header; - uint64_t groupid; - uint64_t id; - float ix; - float iy; - float depth; - float x, y, z; - float variance; -} packet_recognition_feature_t; - -typedef struct { - packet_header_t header; - struct feature_covariance_t { float x, y, cx, cy, cxy; } covariance[]; -} packet_feature_prediction_variance_t; - -typedef struct { - packet_header_t header; - uint64_t groupid; - uint32_t id; - float color; - float x, y, z; - float variance; - uint32_t label; - uint8_t descriptor[128]; -} packet_recognition_descriptor_t; - -typedef struct { - packet_header_t header; - float T[3]; - float velocity[3]; // m/s - float rotation[4]; // axis [0:2] angle in rad [3] - float w[3]; // rad/s - float w_a[3]; // rad/s^2 -} packet_ground_truth_t; - -enum packet_plot_type { - packet_plot_var_T, - packet_plot_var_W, - packet_plot_var_a, - packet_plot_var_w, - packet_plot_inn_a, - packet_plot_inn_w, - packet_plot_meas_a, - packet_plot_meas_w, - packet_plot_unknown = 256 -}; - -#ifdef MYRIAD2 -#ifdef USE_TM2_PACKETS -typedef struct { - packet_header_t header; - sixDof_data data; -} packet_pose_t; -#endif -#endif - -typedef struct { - packet_header_t header; - char data[]; -} packet_calibration_json_t; - -typedef struct { - packet_header_t header; - uint8_t data[]; -} packet_calibration_bin_t; - -typedef struct { - packet_header_t header; - uint8_t data[]; -} packet_controller_physical_info_t; - -typedef struct { - packet_header_t header; // header::timestamp is packet arrival time (To algo), header::sensor_id is not used -} packet_arrival_time_t; - -typedef struct packet_control_t { - struct { - uint32_t bytes; //size of packet including header - uint16_t type; //id of packet - union { - uint16_t sensor_id; //id of sensor - uint16_t control_type; //id of control packet - }; - uint64_t time; //time in microseconds - } header; - uint8_t data[]; -} packet_control_t; - -#ifdef WIN32 -#pragma warning (pop) -#endif - -#endif \ No newline at end of file From 9f5add060454aacc39f84f8c840281cd81694c72 Mon Sep 17 00:00:00 2001 From: abernste Date: Thu, 20 Sep 2018 14:23:05 +0300 Subject: [PATCH 06/25] Cmake fixes --- third-party/libtm/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/third-party/libtm/CMakeLists.txt b/third-party/libtm/CMakeLists.txt index c6d2286efb..0053a379e7 100644 --- a/third-party/libtm/CMakeLists.txt +++ b/third-party/libtm/CMakeLists.txt @@ -4,10 +4,12 @@ cmake_policy(SET CMP0015 NEW) project(tm) include(cmake/os.cmake) - -include(versions.cmake) include(libtm/config.cmake) include(infra/config.cmake) +include(versions.cmake) + +set(LIBTM_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") +set(LIBTM_RESOURCES_DIR "${LIBTM_ROOT}/resources") # Build resources (FW, Central, Controller binaries) add_subdirectory(resources) From 4e4be8bcb1895747a7534bd9b27bd682fd665663 Mon Sep 17 00:00:00 2001 From: Belkin Date: Tue, 25 Sep 2018 10:12:58 +0300 Subject: [PATCH 07/25] disable controller streams --- src/tm2/tm-device.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/tm2/tm-device.cpp b/src/tm2/tm-device.cpp index 29c7c994c8..db5cd98f93 100644 --- a/src/tm2/tm-device.cpp +++ b/src/tm2/tm-device.cpp @@ -162,14 +162,14 @@ namespace librealsense profile->set_format(convertTm2PixelFormat(tm_profile.profile.pixelFormat)); profile->set_framerate(p.fps); profile->set_unique_id(environment::get_instance().generate_stream_id()); - if (tm_profile.sensorIndex == 0) + if (tm_profile.sensorIndex == 0 || tm_profile.sensorIndex == 1) { profile->tag_profile(profile_tag::PROFILE_TAG_DEFAULT | profile_tag::PROFILE_TAG_SUPERSET); } stream_profile sp = { stream, profile->get_stream_index(), p.width, p.height, p.fps, profile->get_format() }; auto intrinsics = get_intrinsics(sp); profile->set_intrinsics([intrinsics]() { return intrinsics; }); - results.push_back(profile); + if (tm_profile.sensorIndex <= 1) results.push_back(profile); //TODO - need to register to have resolve_requests work //native_pixel_format pf; @@ -190,13 +190,13 @@ namespace librealsense profile->set_format(format); profile->set_framerate(tm_profile.fps); profile->set_unique_id(environment::get_instance().generate_stream_id()); + auto intrinsics = get_motion_intrinsics(*profile); + profile->set_intrinsics([intrinsics]() { return intrinsics; }); if (tm_profile.sensorIndex == 0) { profile->tag_profile(profile_tag::PROFILE_TAG_DEFAULT | profile_tag::PROFILE_TAG_SUPERSET); + results.push_back(profile); } - auto intrinsics = get_motion_intrinsics(*profile); - profile->set_intrinsics([intrinsics]() { return intrinsics; }); - results.push_back(profile); } //extract accelerometer profiles @@ -210,13 +210,13 @@ namespace librealsense profile->set_format(format); profile->set_framerate(tm_profile.fps); profile->set_unique_id(environment::get_instance().generate_stream_id()); + auto intrinsics = get_motion_intrinsics(*profile); + profile->set_intrinsics([intrinsics]() { return intrinsics; }); if (tm_profile.sensorIndex == 0) { profile->tag_profile(profile_tag::PROFILE_TAG_DEFAULT | profile_tag::PROFILE_TAG_SUPERSET); + results.push_back(profile); } - auto intrinsics = get_motion_intrinsics(*profile); - profile->set_intrinsics([intrinsics]() { return intrinsics; }); - results.push_back(profile); } //extract 6Dof profiles - TODO @@ -240,10 +240,8 @@ namespace librealsense if (tm_profile.profileType == SixDofProfile0) { profile->tag_profile(profile_tag::PROFILE_TAG_DEFAULT | profile_tag::PROFILE_TAG_SUPERSET); + results.push_back(profile); } - results.push_back(profile); - - //TODO - do I need to define native_pixel_format for RS2_STREAM_POSE? and how to draw it? } From a49f54d32e0d4759815d0fdab0198453632c8ea0 Mon Sep 17 00:00:00 2001 From: Belkin Date: Wed, 26 Sep 2018 11:37:08 +0300 Subject: [PATCH 08/25] 1. disable sync mode in viewer by default, 2. remove trajectory boundary mode --- common/model-views.cpp | 92 +----------------------------------------- common/model-views.h | 4 +- 2 files changed, 2 insertions(+), 94 deletions(-) diff --git a/common/model-views.cpp b/common/model-views.cpp index 85d0340dd1..f35c4ff9e2 100644 --- a/common/model-views.cpp +++ b/common/model-views.cpp @@ -2100,7 +2100,7 @@ namespace rs2 if (render_pose) { total_top_bar_height += top_bar_height; // add additional bar height for pose - const int num_of_pose_buttons = 3; // trajectory, draw camera, boundary selection + const int num_of_pose_buttons = 2; // trajectory, draw camera ImGui::SetNextWindowPos({ stream_rect.x, stream_rect.y + buttons_heights }); ImGui::SetNextWindowSize({ stream_rect.w, buttons_heights }); @@ -2137,25 +2137,6 @@ namespace rs2 if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", tm2.trajectory_button.get_tooltip().c_str()); - // Draw boundary selection button - ImGui::SameLine(); - color_icon = tm2.boundary_button.is_pressed(); //draw boundary is on - color the icon - if (color_icon) - { - ImGui::PushStyleColor(ImGuiCol_Text, light_blue); - ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue); - } - if (ImGui::Button(tm2.boundary_button.get_icon().c_str(), { 24, buttons_heights })) - { - tm2.boundary_button.toggle_button(); - } - if (color_icon) - { - ImGui::PopStyleColor(2); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", tm2.boundary_button.get_tooltip().c_str()); - ImGui::End(); } @@ -3864,7 +3845,6 @@ namespace rs2 rs2_vector translation{ pose_trans.mat[0][3], pose_trans.mat[1][3], pose_trans.mat[2][3] }; tracked_point p{ translation , pose_data.tracker_confidence }; //TODO: Osnat - use tracker_confidence or mapper_confidence ? tm2.draw_trajectory(p); - tm2.draw_boundary(p); } } @@ -6669,76 +6649,6 @@ namespace rs2 } } - void tm2_model::draw_boundary(tracked_point& p) - { - if (!boundary_button.is_pressed()) - { - //TODO - separate button - if (boundary.size() > 0) - { - //cleanup last boundary - boundary.clear(); - } - return; - } - - // if new boundary - grab from trajectory - if (boundary.size() == 0) - { - std::vector trajectory_projection; - //create the boundary from the trajectory - for (auto&& v : trajectory) - { - // project the trajectory on XZ plane - ignore y coordinate of the point - float2 p{ v.first.x, v.first.z }; - trajectory_projection.push_back(p); - } - boundary = simplify_line(trajectory_projection); - } - // check if there is any boundary to render - if (boundary.size() == 0) - { - return; - } - - // check if the current position is inside or outside the boundary, to color it accordingly - float2 point{ p.first.x, p.first.z }; - bool inside = point_in_polygon_2D(boundary, point); - color c; - if (inside) - { - c = { 0.0f, 1.0f, 0.0f }; - } - else - { - c = { 1.0f, 0.0f, 0.0f }; - } - // draw the boundary lines parallel to XZ plane - glLineWidth(1.0f); - for (float height = -1.0f; height < 1.0f; height += 0.2f) - { - glBegin(GL_LINE_STRIP); - glColor3f(c[0], c[1], c[2]); - for (auto&& v : boundary) - { - glVertex3f(v.x, height, v.y); - } - glVertex3f(boundary[0].x, height, boundary[0].y); - glEnd(); - } - - // draw vertical lines along the boundary - glLineWidth(1.0f); - glBegin(GL_LINES); - glColor3f(c[0], c[1], c[2]); - for (auto&& v : boundary) - { - glVertex3f(v.x, -1.0f, v.y); - glVertex3f(v.x, 1.0f, v.y); - } - glEnd(); - } - std::string get_timestamped_file_name() { std::time_t now = std::time(NULL); diff --git a/common/model-views.h b/common/model-views.h index 52f0b9e372..6d181e3299 100644 --- a/common/model-views.h +++ b/common/model-views.h @@ -754,11 +754,9 @@ namespace rs2 void draw_controller_pose_object(); void draw_pose_object(); void draw_trajectory(tracked_point& p); - void draw_boundary(tracked_point& p); press_button_model trajectory_button{ u8"\uf1b0", u8"\uf1b0","Draw trajectory", "Stop drawing trajectory" }; press_button_model camera_object_button{ u8"\uf047", u8"\uf083", "Draw pose axis", "Draw camera pose" }; - press_button_model boundary_button{ u8"\uf278", u8"\uf278", "Set trajectory as boundary", "Discard boundary" }; private: void add_to_trajectory(tracked_point& p); @@ -823,7 +821,7 @@ namespace rs2 viewer_model() : ppf(*this), - synchronization_enable(true) + synchronization_enable(false) { s.start(ppf.syncer_queue); reset_camera(); From af8e5ac63010f76a3fc19f83bdf95375e887adb0 Mon Sep 17 00:00:00 2001 From: Belkin Date: Wed, 26 Sep 2018 13:26:44 +0300 Subject: [PATCH 09/25] update pose and imu stream index --- src/tm2/tm-device.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tm2/tm-device.cpp b/src/tm2/tm-device.cpp index db5cd98f93..9d9d1d4766 100644 --- a/src/tm2/tm-device.cpp +++ b/src/tm2/tm-device.cpp @@ -186,7 +186,7 @@ namespace librealsense rs2_format format = RS2_FORMAT_MOTION_XYZ32F; auto profile = std::make_shared(platform::stream_profile{ 0, 0, tm_profile.fps, static_cast(format) }); profile->set_stream_type(RS2_STREAM_GYRO); - profile->set_stream_index(tm_profile.sensorIndex + 1); // for nice presentation by the viewer - add 1 to stream index + profile->set_stream_index(tm_profile.sensorIndex); // for nice presentation by the viewer - add 1 to stream index profile->set_format(format); profile->set_framerate(tm_profile.fps); profile->set_unique_id(environment::get_instance().generate_stream_id()); @@ -206,7 +206,7 @@ namespace librealsense rs2_format format = RS2_FORMAT_MOTION_XYZ32F; auto profile = std::make_shared(platform::stream_profile{ 0, 0, tm_profile.fps, static_cast(format) }); profile->set_stream_type(RS2_STREAM_ACCEL); - profile->set_stream_index(tm_profile.sensorIndex + 1); // for nice presentation by the viewer - add 1 to stream index + profile->set_stream_index(tm_profile.sensorIndex); // for nice presentation by the viewer - add 1 to stream index profile->set_format(format); profile->set_framerate(tm_profile.fps); profile->set_unique_id(environment::get_instance().generate_stream_id()); @@ -233,7 +233,7 @@ namespace librealsense auto profile = std::make_shared(platform::stream_profile{ 0, 0, fps, static_cast(format) }); profile->set_stream_type(RS2_STREAM_POSE); // TM2_API - 6dof profile type enum behaves the same as stream index - profile->set_stream_index(static_cast(tm_profile.profileType) + 1); + profile->set_stream_index(static_cast(tm_profile.profileType)); profile->set_format(format); profile->set_framerate(fps); profile->set_unique_id(environment::get_instance().generate_stream_id()); @@ -325,14 +325,14 @@ namespace librealsense for (auto&& r : requests) { auto sp = to_profile(r.get()); - int stream_index = sp.index - 1; + int stream_index = sp.index; switch (r->get_stream_type()) { case RS2_STREAM_FISHEYE: { + stream_index -= 1; // for multiple streams, the index starts from 1 //TODO: check bound for _tm_supported_profiles.___[] - auto tm_profile = _tm_supported_profiles.video[stream_index]; if (tm_profile.fps == sp.fps && tm_profile.profile.height == sp.height && @@ -682,7 +682,7 @@ namespace librealsense } float3 data = { tm_frame.acceleration.x, tm_frame.acceleration.y, tm_frame.acceleration.z }; - handle_imu_frame(tm_frame, tm_frame.frameId, RS2_STREAM_ACCEL, tm_frame.sensorIndex + 1, data, tm_frame.temperature); + handle_imu_frame(tm_frame, tm_frame.frameId, RS2_STREAM_ACCEL, tm_frame.sensorIndex, data, tm_frame.temperature); } void tm2_sensor::onGyroFrame(perc::TrackingData::GyroFrame& tm_frame) @@ -693,7 +693,7 @@ namespace librealsense return; } float3 data = { tm_frame.angularVelocity.x, tm_frame.angularVelocity.y, tm_frame.angularVelocity.z }; - handle_imu_frame(tm_frame, tm_frame.frameId, RS2_STREAM_GYRO, tm_frame.sensorIndex + 1, data, tm_frame.temperature); + handle_imu_frame(tm_frame, tm_frame.frameId, RS2_STREAM_GYRO, tm_frame.sensorIndex, data, tm_frame.temperature); } void tm2_sensor::onPoseFrame(perc::TrackingData::PoseFrame& tm_frame) @@ -721,7 +721,7 @@ namespace librealsense { //TODO - assuming single profile per motion stream if (p->get_stream_type() == RS2_STREAM_POSE && - p->get_stream_index() == tm_frame.sourceIndex + 1) + p->get_stream_index() == tm_frame.sourceIndex) { profile = p; break; From d52ec1357b98aeae5f11b7f1048d049ce7aad683 Mon Sep 17 00:00:00 2001 From: Belkin Date: Wed, 26 Sep 2018 17:09:54 +0300 Subject: [PATCH 10/25] Add tm2 temperature option for asic and motion --- src/tm2/tm-device.cpp | 70 +++++++++++++++++++++++++++++++++++++++++++ src/tm2/tm-device.h | 2 ++ 2 files changed, 72 insertions(+) diff --git a/src/tm2/tm-device.cpp b/src/tm2/tm-device.cpp index 9d9d1d4766..8f0357ff19 100644 --- a/src/tm2/tm-device.cpp +++ b/src/tm2/tm-device.cpp @@ -33,6 +33,52 @@ namespace librealsense int64_t arrival_ts; }; + enum temperature_type { TEMPERATURE_TYPE_ASIC, TEMPERATURE_TYPE_MOTION}; + + class temperature_option : public readonly_option + { + public: + float query() const override + { + if (!is_enabled()) + throw wrong_api_call_sequence_exception("query option is allow only in streaming!"); + return _ep.get_temperature().sensor[_type].temperature; + } + + option_range get_range() const override { return _range; } + + bool is_enabled() const override { return true; } + + explicit temperature_option(tm2_sensor& ep, temperature_type type) : _ep(ep), _type(type), + _range(option_range{ 0, ep.get_temperature().sensor[type].threshold, 0, 0 }) { } + + private: + tm2_sensor & _ep; + option_range _range; + temperature_type _type; + }; + + class asic_temperature_option : public temperature_option + { + public: + const char* get_description() const override + { + return "Current TM2 Asic Temperature (degree celsius)"; + } + explicit asic_temperature_option(tm2_sensor& ep) :temperature_option(ep, temperature_type::TEMPERATURE_TYPE_ASIC) { } + }; + + class motion_temperature_option : public temperature_option + { + public: + + const char* get_description() const override + { + return "Current TM2 Motion Module Temperature (degree celsius)"; + } + explicit motion_temperature_option(tm2_sensor& ep) :temperature_option(ep, temperature_type::TEMPERATURE_TYPE_MOTION) { } + }; + class md_tm2_parser : public md_attribute_parser_base { public: @@ -380,6 +426,13 @@ namespace librealsense throw invalid_value_exception("Invalid stream type"); } } + + if (_tm_active_profiles.video[0].enabled == _tm_active_profiles.video[1].enabled && + _tm_active_profiles.video[0].outputEnabled != _tm_active_profiles.video[1].outputEnabled) + { + throw invalid_value_exception("Invalid profile configuration - setting a single FE stream is not supported"); + } + _is_opened = true; set_active_streams(requests); } @@ -909,6 +962,19 @@ namespace librealsense } } + TrackingData::Temperature tm2_sensor::get_temperature() + { + if (!_tm_dev) + throw wrong_api_call_sequence_exception("TM2 device is not available"); + TrackingData::Temperature temperature; + auto status = _tm_dev->GetTemperature(temperature); + if (status != Status::SUCCESS) + { + throw io_exception("Failed to query TM2 temperature option"); + } + return temperature; + } + /////////////// // Device @@ -940,6 +1006,10 @@ namespace librealsense _sensor = std::make_shared(this, dev); add_sensor(_sensor); + + _sensor->register_option(rs2_option::RS2_OPTION_ASIC_TEMPERATURE, std::make_shared(*_sensor)); + _sensor->register_option(rs2_option::RS2_OPTION_MOTION_MODULE_TEMPERATURE, std::make_shared(*_sensor)); + //For manual testing: enable_loopback("C:\\dev\\recording\\tm2.bag"); } diff --git a/src/tm2/tm-device.h b/src/tm2/tm-device.h index 7fd5fa00ac..7d1a04cbc5 100644 --- a/src/tm2/tm-device.h +++ b/src/tm2/tm-device.h @@ -74,6 +74,8 @@ namespace librealsense void attach_controller(const std::array& mac_addr); void detach_controller(int id); void dispose(); + perc::TrackingData::Temperature get_temperature(); + private: void handle_imu_frame(perc::TrackingData::TimestampedData& tm_frame_ts, unsigned long long frame_number, rs2_stream stream_type, int index, float3 imu_data, float temperature); void pass_frames_to_fw(frame_holder fref); From c13e926586e6bbbc2f05f7c5165ec02e03af4170 Mon Sep 17 00:00:00 2001 From: Belkin Date: Thu, 27 Sep 2018 09:47:37 +0300 Subject: [PATCH 11/25] 1. Fix linux compilation issues, 2.Update cmake to support linux --- src/tm2/controller_event_serializer.h | 37 +-------------------------- src/tm2/tm-device.cpp | 6 ++--- third-party/libtm/CMakeLists.txt | 10 ++++++-- 3 files changed, 12 insertions(+), 41 deletions(-) diff --git a/src/tm2/controller_event_serializer.h b/src/tm2/controller_event_serializer.h index c8ee0ae457..fe02e805bd 100644 --- a/src/tm2/controller_event_serializer.h +++ b/src/tm2/controller_event_serializer.h @@ -23,41 +23,6 @@ namespace librealsense return oss.str(); } - std::string get_string(perc::Status value) - { - -#define CASE_RETURN_STR(X) case perc::Status::##X: {\ - static std::string s##X##_str = make_less_screamy(#X);\ - return s##X##_str; } - - switch (value) - { - CASE_RETURN_STR(SUCCESS) - CASE_RETURN_STR(COMMON_ERROR) - CASE_RETURN_STR(FEATURE_UNSUPPORTED) - CASE_RETURN_STR(ERROR_PARAMETER_INVALID) - CASE_RETURN_STR(INIT_FAILED) - CASE_RETURN_STR(ALLOC_FAILED) - CASE_RETURN_STR(ERROR_USB_TRANSFER) - CASE_RETURN_STR(ERROR_EEPROM_VERIFY_FAIL) - CASE_RETURN_STR(ERROR_FW_INTERNAL) - CASE_RETURN_STR(BUFFER_TOO_SMALL) - CASE_RETURN_STR(NOT_SUPPORTED_BY_FW) - CASE_RETURN_STR(DEVICE_BUSY) - CASE_RETURN_STR(TIMEOUT) - CASE_RETURN_STR(TABLE_NOT_EXIST) - CASE_RETURN_STR(TABLE_LOCKED) - CASE_RETURN_STR(DEVICE_STOPPED) - CASE_RETURN_STR(TEMPERATURE_WARNING) - CASE_RETURN_STR(TEMPERATURE_STOP) - CASE_RETURN_STR(CRC_ERROR) - CASE_RETURN_STR(INCOMPATIBLE) - CASE_RETURN_STR(SLAM_NO_DICTIONARY) - default: return to_string() << "Unknown (" << (int)value << ")"; - } -#undef CASE_RETURN_STR - } - inline std::ostream& operator<<(std::ostream& os, const perc::TrackingData::Version& v) { return os << v.major << "." << v.minor << "." << v.patch << "." << v.build; @@ -95,7 +60,7 @@ namespace librealsense static std::string serialized_data(const perc::TrackingData::ControllerConnectedEventFrame& frame) { std::string serialized_data = to_string() << - "\"status\": \"" << get_string(frame.status) << "\"," + "\"status\": \"" << (int)frame.status << "\"," "\"controllerId\": " << (int)frame.controllerId << "," "\"manufacturerId\": " << (int)frame.manufacturerId << "," "\"protocol\": \"" << frame.protocol << "\"," diff --git a/src/tm2/tm-device.cpp b/src/tm2/tm-device.cpp index 8f0357ff19..d8d82dcb58 100644 --- a/src/tm2/tm-device.cpp +++ b/src/tm2/tm-device.cpp @@ -840,7 +840,7 @@ namespace librealsense } else { - raise_error_notification(to_string() << "Connection to controller " << (int)frame.controllerId << " failed. (Statue: " << get_string(frame.status) << ")"); + raise_error_notification(to_string() << "Connection to controller " << (int)frame.controllerId << " failed."); } } @@ -937,7 +937,7 @@ namespace librealsense auto status = _tm_dev->ControllerConnect(c, controller_id); if (status != Status::SUCCESS) { - raise_error_notification(to_string() << "Failed to send connect to controller " << c.macAddress << "(Status: " << get_string(status) << ")"); + raise_error_notification(to_string() << "Failed to send connect to controller " << c.macAddress); } else { @@ -951,7 +951,7 @@ namespace librealsense perc::Status status = _tm_dev->ControllerDisconnect(id); if (status != Status::SUCCESS) { - raise_error_notification(to_string() << "Failed to disconnect to controller " << id << "(Status: " << get_string(status) << ")"); + raise_error_notification(to_string() << "Failed to disconnect to controller " << id); } else { diff --git a/third-party/libtm/CMakeLists.txt b/third-party/libtm/CMakeLists.txt index 0053a379e7..2bb0b341d7 100644 --- a/third-party/libtm/CMakeLists.txt +++ b/third-party/libtm/CMakeLists.txt @@ -53,10 +53,16 @@ ${HEADER_FILES_LIBTM} ${SOURCE_FILES_LIBTM} ) +IF(WIN32) + set(LIBUSB_LIB "${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/x64/libusb-1.0.lib") +ELSE() + set(LIBUSB_LIB "usb-1.0") +ENDIF(WIN32) + #LINK_LIBRARIES target_link_libraries(${PROJECT_NAME} -${OS_SPECIFIC_LIBS} -${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/x64/libusb-1.0.lib + ${OS_SPECIFIC_LIBS} + ${LIBUSB_LIB} ) set_target_properties(${PROJECT_NAME} PROPERTIES VERSION "${LIBVERSION}" SOVERSION "${LIBTM_VERSION_MAJOR}") From 03551d445def70427568cdc20a11938d13276c54 Mon Sep 17 00:00:00 2001 From: belkinirena Date: Thu, 27 Sep 2018 17:04:29 +0300 Subject: [PATCH 12/25] Change cmake to download TM2 FW from amazon s3 --- third-party/libtm/resources/CMakeLists.txt | 6 +++--- third-party/libtm/versions.cmake | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/third-party/libtm/resources/CMakeLists.txt b/third-party/libtm/resources/CMakeLists.txt index ebfa614e15..4493462e57 100644 --- a/third-party/libtm/resources/CMakeLists.txt +++ b/third-party/libtm/resources/CMakeLists.txt @@ -3,9 +3,9 @@ project(resources) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -set(FW_ARTIFACTORY_DIR "http://artifactoryperc01.iil.intel.com:8081/artifactory/libs-release-local/com/intel/realsense/target") -set(CENTRAL_APP_ARTIFACTORY_DIR "http://artifactoryperc01.iil.intel.com:8081/artifactory/libs-release-local/com/intel/realsense/central_app") -set(CENTRAL_BL_ARTIFACTORY_DIR "http://artifactoryperc01.iil.intel.com:8081/artifactory/libs-release-local/com/intel/realsense/central_bl") +set(FW_ARTIFACTORY_DIR "http://realsense-hw-public.s3.amazonaws.com/Releases/TM2/FW/target") +set(CENTRAL_APP_ARTIFACTORY_DIR "http://realsense-hw-public.s3.amazonaws.com/Releases/TM2/FW/app") +set(CENTRAL_BL_ARTIFACTORY_DIR "http://realsense-hw-public.s3.amazonaws.com/Releases/TM2/FW/bl") set(FW_OUTPUT_FILE "${LIBTM_SRC_DIR}/fw.h") set(CENTRAL_APP_OUTPUT_FILE "${LIBTM_SRC_DIR}/CentralAppFw.h") diff --git a/third-party/libtm/versions.cmake b/third-party/libtm/versions.cmake index 0e8383a0f0..7dfe555c57 100644 --- a/third-party/libtm/versions.cmake +++ b/third-party/libtm/versions.cmake @@ -4,9 +4,9 @@ cmake_minimum_required(VERSION 2.8) # If FW source = Remote - FW versions are defined below # If FW source = Local - FW versions are defined according to /resources/remote_versions.log # If FW source = Internal - FW versions are defined according to fw.h, CentralAppFw.h, CentralBlFw.h -set(INTERNAL_HOST_VERSION "0.19.3.1132") -set(INTERNAL_FW_VERSION "0.0.18.3308") -set(INTERNAL_CENTRAL_APP_VERSION "2.0.19.271") +set(INTERNAL_HOST_VERSION "0.19.3.1175") +set(INTERNAL_FW_VERSION "0.0.18.3452") +set(INTERNAL_CENTRAL_APP_VERSION "2.0.19.269") set(INTERNAL_CENTRAL_BL_VERSION "1.0.1.112") set(INTERNAL_FW_SOURCE "Remote") From 062e8e07a596b04ea568f6383cc6b71d1fae85a8 Mon Sep 17 00:00:00 2001 From: Belkin Date: Tue, 2 Oct 2018 11:38:28 +0300 Subject: [PATCH 13/25] Added libtm_util and rc recorder --- third-party/libtm/CMakeLists.txt | 81 +- third-party/libtm/cmake/windows.cmake | 23 +- third-party/libtm/libtm/CMakeLists.txt | 29 + third-party/libtm/libtm/config.cmake | 24 - .../libtm/libtm/include/TrackingSerializer.h | 47 + third-party/libtm/libtm/src/CMakeLists.txt | 84 + third-party/libtm/libtm/src/Device.cpp | 2 +- .../libtm/libtm/src/RcSerializer/Packet.cpp | 373 ++ .../libtm/libtm/src/RcSerializer/Packet.h | 185 + .../libtm/libtm/src/RcSerializer/Player.cpp | 390 ++ .../libtm/libtm/src/RcSerializer/Player.h | 50 + .../libtm/libtm/src/RcSerializer/Recorder.cpp | 249 + .../libtm/libtm/src/RcSerializer/Recorder.h | 68 + .../libtm/src/RcSerializer/concurrency.h | 310 ++ .../libtm/src/RcSerializer/latency_queue.h | 62 + .../libtm/libtm/src/RcSerializer/rc_packet.h | 355 ++ .../libtm/libtm/src/infra/CMakeLists.txt | 30 + .../libtm/libtm/src/infra/Dispatcher.cpp | 411 ++ .../libtm/libtm/src/infra/Dispatcher.h | 253 + .../libtm/libtm/src/infra/EmbeddedList.h | 187 + third-party/libtm/libtm/src/infra/Event.h | 32 + .../libtm/libtm/src/infra/EventHandler.h | 62 + .../libtm/libtm/src/infra/Event_lin.cpp | 35 + .../libtm/libtm/src/infra/Event_win.cpp | 37 + third-party/libtm/libtm/src/infra/Fence.h | 39 + third-party/libtm/libtm/src/infra/Fsm.cpp | 394 ++ third-party/libtm/libtm/src/infra/Fsm.h | 334 ++ third-party/libtm/libtm/src/infra/Log.cpp | 351 ++ third-party/libtm/libtm/src/infra/Log.h | 134 + third-party/libtm/libtm/src/infra/Poller.h | 62 + .../libtm/libtm/src/infra/Poller_lin.cpp | 112 + .../libtm/libtm/src/infra/Poller_win.cpp | 154 + third-party/libtm/libtm/src/infra/Semaphore.h | 40 + .../libtm/libtm/src/infra/Semaphore_lin.cpp | 49 + .../libtm/libtm/src/infra/Semaphore_win.cpp | 63 + third-party/libtm/libtm/src/infra/Utils.cpp | 163 + third-party/libtm/libtm/src/infra/Utils.h | 86 + third-party/libtm/tools/CMakeLists.txt | 7 + .../libtm/tools/libtm_util/CMakeLists.txt | 45 + .../libtm/tools/libtm_util/libtm_util.cpp | 4462 +++++++++++++++++ 40 files changed, 9758 insertions(+), 116 deletions(-) create mode 100644 third-party/libtm/libtm/CMakeLists.txt delete mode 100644 third-party/libtm/libtm/config.cmake create mode 100644 third-party/libtm/libtm/include/TrackingSerializer.h create mode 100644 third-party/libtm/libtm/src/CMakeLists.txt create mode 100644 third-party/libtm/libtm/src/RcSerializer/Packet.cpp create mode 100644 third-party/libtm/libtm/src/RcSerializer/Packet.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/Player.cpp create mode 100644 third-party/libtm/libtm/src/RcSerializer/Player.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/Recorder.cpp create mode 100644 third-party/libtm/libtm/src/RcSerializer/Recorder.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/concurrency.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/latency_queue.h create mode 100644 third-party/libtm/libtm/src/RcSerializer/rc_packet.h create mode 100644 third-party/libtm/libtm/src/infra/CMakeLists.txt create mode 100644 third-party/libtm/libtm/src/infra/Dispatcher.cpp create mode 100644 third-party/libtm/libtm/src/infra/Dispatcher.h create mode 100644 third-party/libtm/libtm/src/infra/EmbeddedList.h create mode 100644 third-party/libtm/libtm/src/infra/Event.h create mode 100644 third-party/libtm/libtm/src/infra/EventHandler.h create mode 100644 third-party/libtm/libtm/src/infra/Event_lin.cpp create mode 100644 third-party/libtm/libtm/src/infra/Event_win.cpp create mode 100644 third-party/libtm/libtm/src/infra/Fence.h create mode 100644 third-party/libtm/libtm/src/infra/Fsm.cpp create mode 100644 third-party/libtm/libtm/src/infra/Fsm.h create mode 100644 third-party/libtm/libtm/src/infra/Log.cpp create mode 100644 third-party/libtm/libtm/src/infra/Log.h create mode 100644 third-party/libtm/libtm/src/infra/Poller.h create mode 100644 third-party/libtm/libtm/src/infra/Poller_lin.cpp create mode 100644 third-party/libtm/libtm/src/infra/Poller_win.cpp create mode 100644 third-party/libtm/libtm/src/infra/Semaphore.h create mode 100644 third-party/libtm/libtm/src/infra/Semaphore_lin.cpp create mode 100644 third-party/libtm/libtm/src/infra/Semaphore_win.cpp create mode 100644 third-party/libtm/libtm/src/infra/Utils.cpp create mode 100644 third-party/libtm/libtm/src/infra/Utils.h create mode 100644 third-party/libtm/tools/CMakeLists.txt create mode 100644 third-party/libtm/tools/libtm_util/CMakeLists.txt create mode 100644 third-party/libtm/tools/libtm_util/libtm_util.cpp diff --git a/third-party/libtm/CMakeLists.txt b/third-party/libtm/CMakeLists.txt index 2bb0b341d7..b3f23ddd6c 100644 --- a/third-party/libtm/CMakeLists.txt +++ b/third-party/libtm/CMakeLists.txt @@ -1,83 +1,24 @@ cmake_minimum_required(VERSION 2.8) -cmake_policy(SET CMP0015 NEW) +project(libtm) -project(tm) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") include(cmake/os.cmake) -include(libtm/config.cmake) -include(infra/config.cmake) -include(versions.cmake) -set(LIBTM_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") +set(LIBTM_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") +set(LIBTM_INCLUDE_DIR "${LIBTM_ROOT}/libtm/include") +set(INFRA_INCLUDE_DIR "${LIBTM_ROOT}/libtm/src/infra") +set(LIBTM_SRC_DIR "${LIBTM_ROOT}/libtm/src") set(LIBTM_RESOURCES_DIR "${LIBTM_ROOT}/resources") -# Build resources (FW, Central, Controller binaries) -add_subdirectory(resources) +include(versions.cmake) -set(LIB_TYPE "STATIC") add_definitions(-DBUILD_STATIC) -STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+\\.[0-9]+" "\\1" LIBTM_VERSION_MAJOR "${HOST_VERSION}") -STRING(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" LIBTM_VERSION_MINOR "${HOST_VERSION}") -STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" LIBTM_VERSION_PATCH "${HOST_VERSION}") -STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" LIBTM_VERSION_BUILD "${HOST_VERSION}") - -set(LIBTM_API_VERSION_MAJOR 10) # Major part of the device supported interface API version, updated upon an incompatible API change -set(LIBTM_API_VERSION_MINOR 0) # Minor part of the device supported interface API version, updated upon a backwards-compatible change - -set(LIBVERSION ${LIBTM_VERSION_MAJOR}.${LIBTM_VERSION_MINOR}.${LIBTM_VERSION_PATCH}) - -# Retrieve Git branch name -execute_process(COMMAND git rev-parse --abbrev-ref HEAD - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" - OUTPUT_VARIABLE GIT_BRANCH - OUTPUT_STRIP_TRAILING_WHITESPACE) - -MESSAGE("--------------------------------------------------------------------------------------------------------------------------------------------------------------") -MESSAGE("Building ${PROJECT_NAME} project on ${OS}, LIBTM version [${LIBTM_VERSION_MAJOR}.${LIBTM_VERSION_MINOR}.${LIBTM_VERSION_PATCH}.${LIBTM_VERSION_BUILD}], API version [${LIBTM_API_VERSION_MAJOR}.${LIBTM_API_VERSION_MINOR}], branch [${GIT_BRANCH}], FW [${FW_VERSION}], Central APP [${CENTRAL_APP_VERSION}], Central BL [${CENTRAL_BL_VERSION}]") - -# Configure version file according to libtm version definitions and branch name above -MESSAGE("Creating version file ${PROJECT_SOURCE_DIR}/src/Version.h") -configure_file("${PROJECT_SOURCE_DIR}/libtm/src/Version.h.in" "${PROJECT_SOURCE_DIR}/libtm/src/Version.h") -configure_file("${PROJECT_SOURCE_DIR}/libtm/src/version.rc.in" "${PROJECT_SOURCE_DIR}/libtm/src/version.rc") - -include_directories( -${INFRA_HEADER_DIR} -${LIBTM_HEADER_DIR} -) - -add_library(${PROJECT_NAME} ${LIB_TYPE} -${HEADER_FILES_INFRA} -${SOURCE_FILES_INFRA} -${HEADER_FILES_LIBTM} -${SOURCE_FILES_LIBTM} -) - -IF(WIN32) - set(LIBUSB_LIB "${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/x64/libusb-1.0.lib") -ELSE() - set(LIBUSB_LIB "usb-1.0") -ENDIF(WIN32) - -#LINK_LIBRARIES -target_link_libraries(${PROJECT_NAME} - ${OS_SPECIFIC_LIBS} - ${LIBUSB_LIB} -) - -set_target_properties(${PROJECT_NAME} PROPERTIES VERSION "${LIBVERSION}" SOVERSION "${LIBTM_VERSION_MAJOR}") -set_target_properties (${PROJECT_NAME} PROPERTIES FOLDER Library) - -#install(TARGETS ${PROJECT_NAME} DESTINATION lib) - -set(CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/../cmake/realsense2") - -install(TARGETS ${PROJECT_NAME} - EXPORT realsense2Targets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) +# Build resources (FW, Central, Controller binaries) +add_subdirectory(resources) +add_subdirectory(libtm) +add_subdirectory(tools) message("----------------------------------------------------------------------------") message("CMake Done") diff --git a/third-party/libtm/cmake/windows.cmake b/third-party/libtm/cmake/windows.cmake index 4eba3b62c4..c59879c6dc 100644 --- a/third-party/libtm/cmake/windows.cmake +++ b/third-party/libtm/cmake/windows.cmake @@ -1,30 +1,11 @@ set(OS "win") set(WINDOWS_LIBUSB_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third-party/win/libusb/include") -set(WINDOWS_BRAINSTEM2_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third-party/win/BrainStem2/include") -set(WINDOWS_BRAINSTEM2_SRC_DIR "${CMAKE_SOURCE_DIR}/third-party/win/BrainStem2/src") - message("Platform is set to ${PLATFORM}") -IF(PLATFORM STREQUAL "x64") - set(WINDOWS_LIBUSB_LIB_DIR "${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/x64") - set(WINDOWS_BRAINSTEM2_LIB_DIR "${CMAKE_SOURCE_DIR}/third-party/win/BrainStem2/bin/x64") -else() - set(WINDOWS_LIBUSB_LIB_DIR "${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/win32") - set(WINDOWS_BRAINSTEM2_LIB_DIR "${CMAKE_SOURCE_DIR}/third-party/win/BrainStem2/bin/w32") -endif() +set(WINDOWS_LIBUSB_LIB_DIR "${CMAKE_SOURCE_DIR}/third-party/win/libusb/bin/x64") include_directories("${WINDOWS_LIBUSB_INCLUDE_DIR}") -link_directories("${WINDOWS_LIBUSB_LIB_DIR}") - -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT /Zi") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") -set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG") -set(LIBUSB_LIB "libusb-1.0") -set(BRAINSTEM2_LIB "BrainStem2") \ No newline at end of file +set(LIBUSB_LIB "${WINDOWS_LIBUSB_LIB_DIR}/libusb-1.0.lib") diff --git a/third-party/libtm/libtm/CMakeLists.txt b/third-party/libtm/libtm/CMakeLists.txt new file mode 100644 index 0000000000..087d3ff66a --- /dev/null +++ b/third-party/libtm/libtm/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 2.8) +project(libtm) + +STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+\\.[0-9]+" "\\1" LIBTM_VERSION_MAJOR "${HOST_VERSION}") +STRING(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" LIBTM_VERSION_MINOR "${HOST_VERSION}") +STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" LIBTM_VERSION_PATCH "${HOST_VERSION}") +STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" LIBTM_VERSION_BUILD "${HOST_VERSION}") + +set (LIBTM_API_VERSION_MAJOR 10) # Major part of the device supported interface API version, updated upon an incompatible API change +set (LIBTM_API_VERSION_MINOR 0) # Minor part of the device supported interface API version, updated upon a backwards-compatible change + +set(LIBVERSION ${LIBTM_VERSION_MAJOR}.${LIBTM_VERSION_MINOR}.${LIBTM_VERSION_PATCH}) + +# Retrieve Git branch name +execute_process(COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY "${LIBTM_ROOT}" + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE) + +MESSAGE("--------------------------------------------------------------------------------------------------------------------------------------------------------------") +MESSAGE("Building ${PROJECT_NAME} project on ${OS}, LIBTM version [${LIBTM_VERSION_MAJOR}.${LIBTM_VERSION_MINOR}.${LIBTM_VERSION_PATCH}.${LIBTM_VERSION_BUILD}], API version [${LIBTM_API_VERSION_MAJOR}.${LIBTM_API_VERSION_MINOR}], branch [${GIT_BRANCH}], FW [${FW_VERSION}], Central APP [${CENTRAL_APP_VERSION}], Central BL [${CENTRAL_BL_VERSION}]") + +# Configure version file according to libtm version definitions and branch name above +MESSAGE("Creating version file ${PROJECT_SOURCE_DIR}/src/Version.h") +configure_file("${PROJECT_SOURCE_DIR}/src/Version.h.in" "${PROJECT_SOURCE_DIR}/src/Version.h") +configure_file("${PROJECT_SOURCE_DIR}/src/version.rc.in" "${PROJECT_SOURCE_DIR}/src/version.rc") + +# Add subdirectory to the build +add_subdirectory(src) diff --git a/third-party/libtm/libtm/config.cmake b/third-party/libtm/libtm/config.cmake deleted file mode 100644 index 63fe373d07..0000000000 --- a/third-party/libtm/libtm/config.cmake +++ /dev/null @@ -1,24 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -set(LIBTM_HEADER_DIR ${CMAKE_CURRENT_LIST_DIR}/include) -set(LIBTM_SRC_DIR ${CMAKE_CURRENT_LIST_DIR}/src) - -set(HEADER_FILES_LIBTM - ${CMAKE_CURRENT_LIST_DIR}/include/TrackingManager.h - ${CMAKE_CURRENT_LIST_DIR}/include/TrackingDevice.h - ${CMAKE_CURRENT_LIST_DIR}/include/TrackingCommon.h - ${CMAKE_CURRENT_LIST_DIR}/include/TrackingData.h -) - -set(SOURCE_FILES_LIBTM - ${CMAKE_CURRENT_LIST_DIR}/src/Manager.h - ${CMAKE_CURRENT_LIST_DIR}/src/Manager.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/Device.h - ${CMAKE_CURRENT_LIST_DIR}/src/Device.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/Message.h - ${CMAKE_CURRENT_LIST_DIR}/src/UsbPlugListener.h - ${CMAKE_CURRENT_LIST_DIR}/src/UsbPlugListener.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/CompleteTask.h - ${CMAKE_CURRENT_LIST_DIR}/src/Common.h - ${CMAKE_CURRENT_LIST_DIR}/src/Common.cpp -) diff --git a/third-party/libtm/libtm/include/TrackingSerializer.h b/third-party/libtm/libtm/include/TrackingSerializer.h new file mode 100644 index 0000000000..6b539eb79d --- /dev/null +++ b/third-party/libtm/libtm/include/TrackingSerializer.h @@ -0,0 +1,47 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "TrackingDevice.h" + +namespace perc +{ + class DLL_EXPORT TrackingRecorder : public TrackingDevice::Listener + { + public: + virtual ~TrackingRecorder() = default; + static TrackingRecorder* CreateInstance(TrackingDevice* device, TrackingDevice::Listener* listener, const char* file, bool stereoMode = false); + }; + + class DLL_EXPORT TrackingPlayer + { + public: + virtual ~TrackingPlayer() = default; + /** + * @brief start + * start sending packets from the file to the listener, starts from the beginning of the file. + */ + virtual void start(bool start_after_calibration = true) = 0; + + /** + * @brief device_configure + * configure and the player to send packets to the device, and to start device streaming + */ + virtual bool device_configure(TrackingDevice* device, TrackingDevice::Listener* listener, TrackingData::Profile* profile = nullptr) = 0; + + /** + * @brief isStreaming + * Return if the player is in the middle of streaming from the file + */ + virtual bool isStreaming() = 0; + /** + * @brief stop + * Stop streaming and close the file. + */ + virtual void stop() = 0; + + static TrackingPlayer* CreateInstance(TrackingDevice::Listener* listener, const char* file); + }; +} diff --git a/third-party/libtm/libtm/src/CMakeLists.txt b/third-party/libtm/libtm/src/CMakeLists.txt new file mode 100644 index 0000000000..193153dabe --- /dev/null +++ b/third-party/libtm/libtm/src/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 2.8) +cmake_policy(SET CMP0015 NEW) + +project(tm) + +include_directories(${LIBTM_INCLUDE_DIR}) +include_directories(${INFRA_INCLUDE_DIR}) + +#Source Files +set(SOURCE_FILES + ${LIBTM_INCLUDE_DIR}/TrackingManager.h + ${LIBTM_INCLUDE_DIR}/TrackingDevice.h + ${LIBTM_INCLUDE_DIR}/TrackingCommon.h + ${LIBTM_INCLUDE_DIR}/TrackingData.h + ${LIBTM_INCLUDE_DIR}/TrackingSerializer.h + + Manager.h + Manager.cpp + Device.h + Device.cpp + Message.h + UsbPlugListener.h + UsbPlugListener.cpp + CompleteTask.h + Common.h + Common.cpp + + RcSerializer/Recorder.h + RcSerializer/Recorder.cpp + RcSerializer/Packet.h + RcSerializer/Packet.cpp + RcSerializer/latency_queue.h + RcSerializer/rc_packet.h + RcSerializer/concurrency.h + RcSerializer/Player.h + RcSerializer/Player.cpp + + infra/Log.h + infra/Log.cpp + infra/Event.h + infra/Fence.h + infra/EventHandler.h + infra/Poller.h + infra/Poller_${OS}.cpp + infra/Dispatcher.h + infra/Dispatcher.cpp + infra/Fsm.h + infra/Fsm.cpp + infra/Utils.h + infra/Utils.cpp + infra/Semaphore.h + infra/Semaphore_${OS}.cpp + infra/Event_${OS}.cpp +) + +#Add versioning to DLL +IF(WIN32) +SET(SOURCE_FILES "${SOURCE_FILES};version.rc") +ENDIF(WIN32) + +#Building Library +set(SDK_LIB_TYPE "STATIC") +add_definitions(-DBUILD_STATIC) + +MESSAGE("Building project ${PROJECT_NAME} as ${SDK_LIB_TYPE} library ${CMAKE_INSTALL_LIBDIR}") +add_library(${PROJECT_NAME} ${SDK_LIB_TYPE} ${SOURCE_FILES}) + +#LINK_LIBRARIES +target_link_libraries(${PROJECT_NAME} +${OS_SPECIFIC_LIBS} +${LIBUSB_LIB} +) + +set_target_properties(${PROJECT_NAME} PROPERTIES VERSION "${LIBVERSION}" SOVERSION "${LIBTM_VERSION_MAJOR}") +set_target_properties (${PROJECT_NAME} PROPERTIES FOLDER Library) + +set(CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/../cmake/realsense2") + +install(TARGETS ${PROJECT_NAME} + EXPORT realsense2Targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) diff --git a/third-party/libtm/libtm/src/Device.cpp b/third-party/libtm/libtm/src/Device.cpp index 099fc23eab..9f08b63620 100644 --- a/third-party/libtm/libtm/src/Device.cpp +++ b/third-party/libtm/libtm/src/Device.cpp @@ -239,7 +239,7 @@ namespace perc { bulk_message_request_get_time req = {0}; bulk_message_response_get_time res = {0}; nsecs_t start; - nsecs_t finish; + nsecs_t finish = systemTime(); uint32_t syncTry = 1; req.header.wMessageID = DEV_GET_TIME; diff --git a/third-party/libtm/libtm/src/RcSerializer/Packet.cpp b/third-party/libtm/libtm/src/RcSerializer/Packet.cpp new file mode 100644 index 0000000000..99109d4e6e --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Packet.cpp @@ -0,0 +1,373 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#define LOG_TAG "Packet" + +#include +#include "Packet.h" +#include "Utils.h" + +using namespace perc; +const uint32_t TS_MAX_GAP_US = 200; + +inline uint64_t ns2us(int64_t ts) +{ + std::chrono::duration ns(ts); + return std::chrono::duration_cast>(ns).count(); +} + +ImagePacket::ImagePacket(TrackingData::VideoFrame& frame) : mExposurePacket(frame.exposuretime, static_cast(frame.gain), frame.sensorIndex, ns2us(frame.timestamp)) +{ + uint32_t bufferSize = frame.profile.stride * frame.profile.height; + mPacket = std::shared_ptr((packet_image_raw_t*) ::operator new (sizeof(packet_image_raw_t) + bufferSize)); + mPacket->height = frame.profile.height; + mPacket->width = frame.profile.width; + mPacket->stride = frame.profile.stride; + mPacket->format = 0; + mPacket->exposure_time_us = static_cast(frame.exposuretime); + perc::copy(mPacket->data, frame.data, bufferSize); + + mPacket->header.sensor_id = frame.sensorIndex; + mPacket->header.type = packet_image_raw; + mPacket->header.bytes = sizeof(packet_image_raw_t) + bufferSize; + mPacket->header.time = ns2us(frame.timestamp); + +} +const uint8_t* ImagePacket::getBytes() +{ + return (uint8_t*)mPacket.get(); +} +size_t ImagePacket::getSize() +{ + return sizeof(packet_image_raw_t) + (mPacket->height * mPacket->stride); +}; +packet_type ImagePacket::getType() +{ + return packet_image_raw; +} +uint16_t ImagePacket::getSensorId() +{ + return mPacket->header.sensor_id; +} +std::shared_ptr ImagePacket::getRCPacket() +{ + return mPacket; +} +ExposurePacket ImagePacket::getExposurePacket() +{ + return mExposurePacket; +} + +const uint8_t* StereoPacket::getBytes() +{ + if (isComplete() == false) + { + return nullptr; + } + mPacket.header.sensor_id = mImagePackets[0]->header.sensor_id / 2; + mPacket.header.type = packet_stereo_raw; + mPacket.header.time = mImagePackets[0]->header.time; + mPacket.height = mImagePackets[0]->height; + mPacket.width = mImagePackets[0]->width; + mPacket.format = mImagePackets[0]->format; + mPacket.exposure_time_us = mImagePackets[0]->exposure_time_us; + mPacket.stride1 = mImagePackets[0]->stride; + mPacket.stride2 = mImagePackets[1]->stride; + mPacket.header.bytes = sizeof(packet_stereo_raw_t) + mPacket.height * (mPacket.stride1 + mPacket.stride2); + + return (uint8_t*)&mPacket; +} +size_t StereoPacket::getSize() +{ + if (isComplete() == false) + { + return 0; + } + return sizeof(packet_stereo_raw_t); +} +packet_type StereoPacket::getType() +{ + return packet_stereo_raw; +} +bool StereoPacket::addPacket(ImagePacket* packet) +{ + std::shared_ptr imagePacket = packet->getRCPacket(); + uint16_t sensorIndex = imagePacket->header.sensor_id % 2; + if (mImagePackets.find(sensorIndex) != mImagePackets.end()) + { + LOGE("Image packet is out of order"); + return false; + } + uint16_t coupeledSensorIndex = (sensorIndex + 1) % 2; + if (mImagePackets.find(coupeledSensorIndex) != mImagePackets.end() && + !areCoupeled(mImagePackets[coupeledSensorIndex]->header.time, imagePacket->header.time)) + { + LOGE("Image packet is out of order"); + return false; + } + mImagePackets[sensorIndex] = imagePacket; + packet_exposure_t imageExposurePacket = packet->getExposurePacket().getRCPacket(); + mExposurePacket = ExposurePacket(imageExposurePacket.exposure_time_us, imageExposurePacket.gain, imagePacket->header.sensor_id / 2, imageExposurePacket.header.time); + return true; +} +bool StereoPacket::areCoupeled(uint64_t time_us1, uint64_t time_us2) +{ + return std::abs(static_cast(time_us1) - static_cast(time_us2)) < TS_MAX_GAP_US; +} +bool StereoPacket::isComplete() +{ + return mImagePackets.size() == 2; +} +const uint8_t* StereoPacket::getImageBytes(uint16_t index) +{ + if (mImagePackets[index]) + { + return (uint8_t*)mImagePackets[index].get()->data; + } + LOGE("Invalid read of stereo packet"); + return nullptr; +} +size_t StereoPacket::getImageSize(uint16_t index) +{ + if (mImagePackets[index]) + { + return mImagePackets[index]->height * mImagePackets[index]->stride; + } + LOGE("Invalid read of stereo packet size"); + return 0; +} +void StereoPacket::clear() +{ + mImagePackets.clear(); +} +ExposurePacket StereoPacket::getExposurePacket() +{ + return mExposurePacket; +} + +GyroPacket::GyroPacket(TrackingData::GyroFrame& frame) : mThermometerPacket(frame.temperature, frame.sensorIndex, frame.timestamp) +{ + mPacket.w[0] = frame.angularVelocity.x; + mPacket.w[1] = frame.angularVelocity.y; + mPacket.w[2] = frame.angularVelocity.z; + + mPacket.header.sensor_id = frame.sensorIndex; + mPacket.header.type = packet_gyroscope; + mPacket.header.bytes = sizeof(packet_gyroscope_t); + mPacket.header.time = ns2us(frame.timestamp); +} +const uint8_t* GyroPacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t GyroPacket::getSize() +{ + return sizeof(mPacket); +} +packet_type GyroPacket::getType() +{ + return packet_gyroscope; +} +ThermometerPacket GyroPacket::getThermometerPacket() +{ + return mThermometerPacket; +} + +VelocimeterPacket::VelocimeterPacket(TrackingData::VelocimeterFrame& frame) : mThermometerPacket(frame.temperature, frame.sensorIndex, frame.timestamp) +{ + mPacket.v[0] = frame.angularVelocity.x; + mPacket.v[1] = frame.angularVelocity.y; + mPacket.v[2] = frame.angularVelocity.z; + + mPacket.header.sensor_id = frame.sensorIndex; + mPacket.header.type = packet_velocimeter; + mPacket.header.bytes = sizeof(packet_velocimeter_t); + mPacket.header.time = ns2us(frame.timestamp); +} +const uint8_t* VelocimeterPacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t VelocimeterPacket::getSize() +{ + return sizeof(mPacket); +} +packet_type VelocimeterPacket::getType() +{ + return packet_velocimeter; +} +ThermometerPacket VelocimeterPacket::getThermometerPacket() +{ + return mThermometerPacket; +} + +AcclPacket::AcclPacket(TrackingData::AccelerometerFrame& frame) : mThermometerPacket(frame.temperature, frame.sensorIndex, frame.timestamp) +{ + mPacket.a[0] = frame.acceleration.x; + mPacket.a[1] = frame.acceleration.y; + mPacket.a[2] = frame.acceleration.z; + + mPacket.header.sensor_id = frame.sensorIndex; + mPacket.header.type = packet_accelerometer; + mPacket.header.bytes = sizeof(packet_accelerometer_t); + mPacket.header.time = ns2us(frame.timestamp); +} +const uint8_t* AcclPacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t AcclPacket::getSize() +{ + return sizeof(mPacket); +} +packet_type AcclPacket::getType() +{ + return packet_accelerometer; +} +ThermometerPacket AcclPacket::getThermometerPacket() +{ + return mThermometerPacket; +} + +ThermometerPacket::ThermometerPacket(float temperature, uint16_t sensor_index, uint64_t time) +{ + mPacket.temperature_C = temperature; + + mPacket.header.sensor_id = sensor_index; + mPacket.header.type = packet_thermometer; + mPacket.header.bytes = sizeof(packet_thermometer_t); + mPacket.header.time = ns2us(time); +} +const uint8_t* ThermometerPacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t ThermometerPacket::getSize() +{ + return sizeof(mPacket); +} +packet_type ThermometerPacket::getType() +{ + return packet_thermometer; +} + +ArrivalTimePacket::ArrivalTimePacket(uint64_t arrivalTime) +{ + mPacket.header.sensor_id = 0; + mPacket.header.type = packet_arrival_time; + mPacket.header.bytes = sizeof(packet_arrival_time_t); + mPacket.header.time = ns2us(arrivalTime); +} +const uint8_t* ArrivalTimePacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t ArrivalTimePacket::getSize() +{ + return sizeof(mPacket); +} +packet_type ArrivalTimePacket::getType() +{ + return packet_arrival_time; +} + +CalibrationPacket::CalibrationPacket(const uint8_t* buffer, uint32_t size) +{ + mPacket = std::shared_ptr((packet_calibration_bin_t*) ::operator new (sizeof(packet_calibration_bin_t) + size)); + mPacket->header.sensor_id = 0; + mPacket->header.type = packet_calibration_bin; + mPacket->header.bytes = sizeof(packet_calibration_bin_t) + size; + mPacket->header.time = 0; + perc::copy(mPacket->data, buffer, size); +} +const uint8_t* CalibrationPacket::getBytes() +{ + return (uint8_t*)mPacket.get(); +} +size_t CalibrationPacket::getSize() +{ + return mPacket->header.bytes; +} +packet_type CalibrationPacket::getType() +{ + return packet_calibration_bin; +} + +ExposurePacket::ExposurePacket(uint64_t exposure_time, float gain, uint16_t sensor_index, uint64_t time) +{ + mPacket.exposure_time_us = exposure_time; + mPacket.gain = gain; + + mPacket.header.sensor_id = sensor_index; + mPacket.header.type = packet_exposure; + mPacket.header.bytes = sizeof(packet_exposure_t); + mPacket.header.time = time; +} +const uint8_t* ExposurePacket::getBytes() +{ + return (uint8_t*)&mPacket; +} +size_t ExposurePacket::getSize() +{ + return sizeof(mPacket); +} +packet_type ExposurePacket::getType() +{ + return packet_exposure; +} +packet_exposure_t ExposurePacket::getRCPacket() +{ + return mPacket; +} + +PhysicalInfoPacket::PhysicalInfoPacket(uint16_t sensor_index, const uint8_t* buffer, uint32_t size) +{ + uint32_t packetSize = sizeof(packet_controller_physical_info_t) + size; + mPacket = std::shared_ptr((packet_controller_physical_info_t*) ::operator new (packetSize)); + mPacket->header.sensor_id = sensor_index; + mPacket->header.type = packet_controller_physical_info; + mPacket->header.bytes = packetSize; + mPacket->header.time = 0; + perc::copy(mPacket->data, buffer, size); +} +const uint8_t* PhysicalInfoPacket::getBytes() +{ + return (uint8_t*)mPacket.get(); +} +size_t PhysicalInfoPacket::getSize() +{ + return mPacket->header.bytes; +} +packet_type PhysicalInfoPacket::getType() +{ + return packet_controller_physical_info; +} + +LedPacket::LedPacket(TrackingData::ControllerLedEventFrame& frame) +{ + uint32_t packetSize = sizeof(packet_led_intensity_t); + mPacket = std::shared_ptr((packet_led_intensity_t*) ::operator new (packetSize)); + mPacket->header.sensor_id = frame.controllerId; + mPacket->header.type = packet_led_intensity; + mPacket->header.bytes = packetSize; + mPacket->header.time = ns2us(frame.timestamp); + mPacket->intensity = frame.intensity; + mPacket->led_id = frame.ledId; +} +const uint8_t* LedPacket::getBytes() +{ + return (uint8_t*)mPacket.get(); +} +size_t LedPacket::getSize() +{ + return mPacket->header.bytes; +} +packet_type LedPacket::getType() +{ + return packet_led_intensity; +} + + + diff --git a/third-party/libtm/libtm/src/RcSerializer/Packet.h b/third-party/libtm/libtm/src/RcSerializer/Packet.h new file mode 100644 index 0000000000..a01c1f56f8 --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Packet.h @@ -0,0 +1,185 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include +#include +#include +#include "TrackingData.h" +#include "rc_packet.h" + +namespace perc +{ + class Packet + { + public: + virtual const uint8_t* getBytes() = 0; + virtual size_t getSize() = 0; + virtual packet_type getType() = 0; + }; + + class ThermometerPacket : public Packet + { + public: + ThermometerPacket(float temperature, uint16_t sensor_index, uint64_t time); + virtual ~ThermometerPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + + private: + packet_thermometer_t mPacket; + }; + + class ExposurePacket : public Packet + { + public: + ExposurePacket() : mPacket({ 0 }) {} + ExposurePacket(uint64_t exposure_time_us, float gain, uint16_t sensor_index, uint64_t time); + virtual ~ExposurePacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + packet_exposure_t getRCPacket(); + + private: + packet_exposure_t mPacket; + }; + + class ImagePacket : public Packet + { + public: + ImagePacket(TrackingData::VideoFrame& frame); + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + uint16_t getSensorId(); + std::shared_ptr getRCPacket(); + ExposurePacket getExposurePacket(); + private: + std::shared_ptr mPacket; + ExposurePacket mExposurePacket; + }; + + class StereoPacket : public Packet + { + public: + virtual ~StereoPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + bool addPacket(ImagePacket* packet); + bool isComplete(); + const uint8_t* getImageBytes(uint16_t index); + size_t getImageSize(uint16_t index); + void clear(); + ExposurePacket getExposurePacket(); + private: + static bool areCoupeled(uint64_t time_us1, uint64_t time_us2); + private: + std::map> mImagePackets; + ExposurePacket mExposurePacket; + packet_stereo_raw_t mPacket; + }; + + class GyroPacket : public Packet + { + public: + GyroPacket(TrackingData::GyroFrame& frame); + ~GyroPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + ThermometerPacket getThermometerPacket(); + + private: + packet_gyroscope_t mPacket; + ThermometerPacket mThermometerPacket; + }; + + class VelocimeterPacket : public Packet + { + public: + VelocimeterPacket(TrackingData::VelocimeterFrame& frame); + ~VelocimeterPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + ThermometerPacket getThermometerPacket(); + + private: + packet_velocimeter_t mPacket; + ThermometerPacket mThermometerPacket; + }; + + class AcclPacket : public Packet + { + public: + AcclPacket(TrackingData::AccelerometerFrame& frame); + ~AcclPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + ThermometerPacket getThermometerPacket(); + + private: + packet_accelerometer_t mPacket; + ThermometerPacket mThermometerPacket; + }; + + class ArrivalTimePacket : public Packet + { + public: + ArrivalTimePacket(uint64_t arrivalTime); + ~ArrivalTimePacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + + private: + packet_arrival_time_t mPacket; + }; + + class CalibrationPacket : public Packet + { + public: + CalibrationPacket(const uint8_t* buffer, uint32_t size); + ~CalibrationPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + + private: + std::shared_ptr mPacket; + }; + + class PhysicalInfoPacket : public Packet + { + public: + PhysicalInfoPacket(uint16_t sensor_index, const uint8_t* buffer, uint32_t size); + ~PhysicalInfoPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + + private: + std::shared_ptr mPacket; + }; + + class LedPacket : public Packet + { + public: + LedPacket(TrackingData::ControllerLedEventFrame& frame); + ~LedPacket() = default; + virtual const uint8_t* getBytes() override; + virtual size_t getSize() override; + virtual packet_type getType() override; + + private: + std::shared_ptr mPacket; + + }; +} diff --git a/third-party/libtm/libtm/src/RcSerializer/Player.cpp b/third-party/libtm/libtm/src/RcSerializer/Player.cpp new file mode 100644 index 0000000000..ed1fbf4d3c --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Player.cpp @@ -0,0 +1,390 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ +#define LOG_TAG "Player" +#include +#include "Player.h" +#include "rc_packet.h" +#include "Utils.h" + +using namespace perc; +const int LEFT_FISHEYE_INDEX = 0; +const int RIGHT_FISHEYE_INDEX = 1; + +TrackingPlayer* TrackingPlayer::CreateInstance(TrackingDevice::Listener* listener, const char* file) +{ + if (listener == nullptr || file == nullptr) + { + LOGE("TrackingRecorder::CreateInstance : Invalid parameters"); + return nullptr; + } + try + { + return new PlayerImpl(listener, file); + } + catch (std::exception& e) + { + std::string error = std::string("Failed to create Player : ") + e.what(); + LOGE(error.c_str()); + return nullptr; + } +} + +PlayerImpl::PlayerImpl(TrackingDevice::Listener* listener, const char* file) : + mListener(listener), mThreadIsAlive(true), mIsStreaming(false), mfilePath(file), mDevice(nullptr), mDeviceListener(nullptr), mProfile(nullptr) +{ + LOGD("By default the device will start streaming after reading the calibration packet"); +} + +bool PlayerImpl::device_configure(TrackingDevice* device, TrackingDevice::Listener* listener, TrackingData::Profile* profile) +{ + if (!device || !listener) + { + LOGE("One of the parameters is invalid"); + return false; + } + mDeviceListener = listener; + mDevice = device; + mProfile = profile; + return true; +} + +inline int64_t us2ns(uint64_t ts) +{ + std::chrono::duration ns(ts); + return std::chrono::duration_cast>(ns).count(); +} + +void PlayerImpl::sendStereoFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime, uint32_t index) +{ + packet_stereo_raw_t *_packet = (packet_stereo_raw_t *)packet.get(); + TrackingData::VideoFrame frame; + frame.profile.height = _packet->height; + frame.profile.width = _packet->width; + frame.exposuretime = static_cast(exposureTime); + frame.profile.pixelFormat = PixelFormat::Y8; + frame.timestamp = us2ns(_packet->header.time); + frame.arrivalTimeStamp = us2ns(arrivalTime); + frame.systemTimestamp = 0; + frame.frameLength = frame.profile.height * frame.profile.width; + frame.gain = gain; + frame.frameId = 0; + if (index == LEFT_FISHEYE_INDEX) + { + frame.sensorIndex = (_packet->header.sensor_id) * 2; + frame.profile.stride = _packet->stride1; + frame.data = reinterpret_cast(_packet->data); + } + else if (index == RIGHT_FISHEYE_INDEX) + { + frame.sensorIndex = (_packet->header.sensor_id) * 2 + 1; + frame.profile.stride = _packet->stride2; + frame.data = reinterpret_cast(_packet->data + (_packet->stride1 * _packet->height)); + } + else + { + LOGE("Invalid stereo packet"); + } + mListener->onVideoFrame(frame); +} + +void PlayerImpl::sendImageFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime) +{ + packet_image_raw_t *_packet = (packet_image_raw_t *)packet.get(); + TrackingData::VideoFrame frame; + frame.profile.height = _packet->height; + frame.profile.width = _packet->width; + frame.exposuretime = static_cast(exposureTime); + frame.profile.pixelFormat = PixelFormat::Y8; + frame.timestamp = us2ns(_packet->header.time); + frame.arrivalTimeStamp = us2ns(arrivalTime); + frame.systemTimestamp = 0; + frame.frameLength = frame.profile.height * frame.profile.width; + frame.gain = gain; + frame.frameId = 0; + frame.sensorIndex = static_cast(_packet->header.sensor_id); + frame.profile.stride = _packet->stride; + frame.data = reinterpret_cast(_packet->data); + mListener->onVideoFrame(frame); +} + +void PlayerImpl::sendGyroFrame(std::shared_ptr packet, uint64_t arrivalTime, float temperature) +{ + packet_gyroscope_t *_packet = (packet_gyroscope_t *)packet.get(); + TrackingData::GyroFrame frame; + frame.sensorIndex = static_cast(_packet->header.sensor_id); + frame.angularVelocity.set(_packet->w[0], _packet->w[1], _packet->w[2]); + frame.timestamp = us2ns(_packet->header.time); + frame.temperature = temperature; + frame.arrivalTimeStamp = us2ns(arrivalTime); + frame.systemTimestamp = 0; + frame.frameId = 0; + mListener->onGyroFrame(frame); +} + +void PlayerImpl::sendAccelFrame(std::shared_ptr packet, uint64_t arrivalTime, float temperature) +{ + packet_accelerometer_t *_packet = (packet_accelerometer_t *)packet.get(); + TrackingData::AccelerometerFrame frame; + frame.sensorIndex = static_cast(_packet->header.sensor_id); + frame.acceleration.set(_packet->a[0], _packet->a[1], _packet->a[2]); + frame.timestamp = us2ns(_packet->header.time); + frame.temperature = temperature; + frame.arrivalTimeStamp = us2ns(arrivalTime); + frame.systemTimestamp = 0; + frame.frameId = 0; + mListener->onAccelerometerFrame(frame); +} + +bool PlayerImpl::startDevice() +{ + if (!mDevice) return false; + + mProfile->playbackEnabled = true; + auto status = mDevice->Start(mDeviceListener, mProfile); + if (status != Status::SUCCESS) + { + LOGE("Failed to start device : (0x%X)", status); + return false; + } + return true; +} + +void PlayerImpl::start(bool start_after_calibration) +{ + LOGD("Set playing of file %s", mfilePath.c_str()); + mReadingThread = std::thread([&]() -> void { + + if (!start_after_calibration && mDevice) + { + if (!startDevice()) return; + } + mIsStreaming = true; + + std::ifstream ifs(mfilePath.c_str(), std::fstream::in | std::fstream::binary); + if (!setProcessPriorityToRealtime()) + { + throw std::runtime_error("Failed to set process priority"); + } + packet_header_t header; + static const uint64_t INITIAL_ARRIVAL_TIME = 0; + static const uint64_t INITIAL_EXPOSURE_TIME = 0; + static const float INITIAL_TEMPERATURE = -100; + static const float INITIAL_GAIN = -100; + + uint64_t arrivalTime = INITIAL_ARRIVAL_TIME, exposureTime = INITIAL_EXPOSURE_TIME; + float temperature = INITIAL_TEMPERATURE, gain = INITIAL_GAIN; + while (ifs.read((char*)&header, sizeof(packet_header_t)) && mThreadIsAlive) + { + if (header.bytes == 0) + { + LOGE("Failed to read a packet - the header is empty"); + mIsStreaming = false; + return; + } + + std::shared_ptr packet = std::shared_ptr((packet_t *) ::operator new (header.bytes)); + packet->header = header; + if (packet->header.bytes - sizeof(packet_header_t) > 0) + { + if (!ifs.read((char*)&packet->data, packet->header.bytes - sizeof(packet_header_t))) + { + LOGE("Failed to read the packet"); + mIsStreaming = false; + return; + } + } + + switch (packet->header.type) + { + case packet_controller_physical_info: + { + if (!mDevice) break; + size_t size = packet->header.bytes - sizeof(packet_controller_physical_info_t); + writePhysicalInfo(packet->header.sensor_id, packet->data, size); + break; + } + case packet_calibration_bin: + { + if (!mDevice) break; + size_t size = packet->header.bytes - sizeof(packet_calibration_bin_t); + writeCalibration(packet->data, size); + if (start_after_calibration) + { + if (!startDevice()) + { + mIsStreaming = false; + return; + } + } + break; + } + case packet_arrival_time: + { + if (arrivalTime != INITIAL_ARRIVAL_TIME) + { + LOGW("packet_arrival_time: arrivalTime packet was not consumed\n"); + } + else + { + arrivalTime = packet->header.time; + } + break; + } + case packet_thermometer: + { + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_thermometer: arrivalTime packet was not consumed\n"); + } + if (temperature != INITIAL_TEMPERATURE) + { + LOGW("packet_thermometer: thermometer packet was not consumed\n"); + } + else + { + packet_thermometer_t *thermometer = (packet_thermometer_t *)packet.get(); + temperature = thermometer->temperature_C; + arrivalTime = INITIAL_ARRIVAL_TIME; + } + break; + } + case packet_exposure: + { + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_exposure: arrivalTime packet was not consumed\n"); + } + if (exposureTime != INITIAL_EXPOSURE_TIME && gain != INITIAL_GAIN) + { + LOGW("packet_exposure: exposure packet was not consumed\n"); + } + packet_exposure_t *exposure = (packet_exposure_t *)packet.get(); + gain = exposure->gain; + exposureTime = exposure->exposure_time_us; + arrivalTime = INITIAL_ARRIVAL_TIME; + break; + } + case packet_stereo_raw: + { + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_stereo_raw: arrivalTime packet was not consumed\n"); + } + packet_stereo_raw_t *stereo = (packet_stereo_raw_t *)packet.get(); + int leftIndex = stereo->header.sensor_id * 2 + LEFT_FISHEYE_INDEX; + int rightIndex = stereo->header.sensor_id * 2 + RIGHT_FISHEYE_INDEX; + + sendStereoFrame(packet, gain, exposureTime, arrivalTime, LEFT_FISHEYE_INDEX); + sendStereoFrame(packet, gain, exposureTime, arrivalTime, RIGHT_FISHEYE_INDEX); + arrivalTime = INITIAL_ARRIVAL_TIME; + exposureTime = INITIAL_EXPOSURE_TIME; + gain = INITIAL_GAIN; + break; + } + case packet_image_raw: + { + if (exposureTime == INITIAL_EXPOSURE_TIME) + { + LOGW("packet_image_raw: exposure packet was not consumed\n"); + } + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_image_raw: arrivalTime packet was not consumed\n"); + } + packet_image_raw_t *image = (packet_image_raw_t *)packet.get(); + sendImageFrame(packet, gain, exposureTime, arrivalTime); + arrivalTime = INITIAL_ARRIVAL_TIME; + exposureTime = INITIAL_EXPOSURE_TIME; + gain = INITIAL_GAIN; + break; + + } + case packet_accelerometer: + { + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_accelerometer: arrivalTime packet was not consumed\n"); + } + if (temperature == INITIAL_TEMPERATURE) + { + LOGW("packet_accelerometer: thermometer packet was not consumed\n"); + } + sendAccelFrame(packet, arrivalTime, temperature); + temperature = INITIAL_TEMPERATURE; + arrivalTime = INITIAL_ARRIVAL_TIME; + break; + } + case packet_gyroscope: + { + if (arrivalTime == INITIAL_ARRIVAL_TIME) + { + LOGW("packet_gyroscope: arrivalTime packet was not consumed\n"); + } + if (temperature == INITIAL_TEMPERATURE) + { + LOGW("packet_gyroscope: thermometer packet was not consumed\n"); + } + sendGyroFrame(packet, arrivalTime, temperature); + temperature = INITIAL_TEMPERATURE; + arrivalTime = INITIAL_ARRIVAL_TIME; + break; + } + default: + { + LOGW("one of the packets has unknown type : %d", packet->header.type); + } + } + } + mIsStreaming = false; + }); +} + +void PlayerImpl::stop() +{ + mIsStreaming = false; + mThreadIsAlive = false; + if (mReadingThread.joinable()) + { + mReadingThread.join(); + } +} + +bool PlayerImpl::isStreaming() +{ + return mIsStreaming; +} + +void PlayerImpl::writeCalibration(uint8_t* buffer, size_t size) +{ + if (mDevice == nullptr) return; + const static int CALIBRATION_TABLE_TYPE = 0x0; + auto status = mDevice->WriteConfiguration(CALIBRATION_TABLE_TYPE, static_cast(size), buffer); + if (status != perc::Status::SUCCESS) + { + LOGE("Failed to write write calibration table with status : (0x%X)", status); + } +} + +void PlayerImpl::writePhysicalInfo(uint16_t sensorIndex, uint8_t* buffer, size_t size) +{ + if (mDevice == nullptr) return; + if(sensorIndex != 1 && sensorIndex != 2) + { + LOGE("Invalid sensor id on physical info message %d", sensorIndex); + return; + } + + int tableType = 0x100 + sensorIndex; + auto status = mDevice->WriteConfiguration(tableType, static_cast(size), buffer); + if (status != perc::Status::SUCCESS) + { + LOGE("Failed to write write calibration table with status : (0x%X)", status); + } +} + +PlayerImpl::~PlayerImpl() +{ + stop(); +} diff --git a/third-party/libtm/libtm/src/RcSerializer/Player.h b/third-party/libtm/libtm/src/RcSerializer/Player.h new file mode 100644 index 0000000000..cd0ff05e02 --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Player.h @@ -0,0 +1,50 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +#include "Packet.h" +#include "TrackingDevice.h" +#include "TrackingSerializer.h" + +namespace perc +{ + class PlayerImpl : public TrackingPlayer + { + public: + + PlayerImpl(TrackingDevice::Listener* listener, const char* file); + virtual ~PlayerImpl(); + virtual bool device_configure(TrackingDevice* device, TrackingDevice::Listener* listener, TrackingData::Profile* profile = nullptr) override; + virtual void start(bool start_after_calibration = true) override; + virtual bool isStreaming() override; + virtual void stop() override; + + private: + void sendImageFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime); + void sendStereoFrame(std::shared_ptr packet, float gain, uint64_t exposureTime, uint64_t arrivalTime, uint32_t index); + void sendAccelFrame(std::shared_ptr packet, uint64_t arrival_time, float temperature); + void sendGyroFrame(std::shared_ptr packet, uint64_t arrival_time, float temperature); + void writeCalibration(uint8_t* buffer, size_t size); + void writePhysicalInfo(uint16_t sensorIndex, uint8_t* buffer, size_t size); + bool startDevice(); + + private: + TrackingDevice * mDevice; + TrackingDevice::Listener * mListener; + std::thread mReadingThread; + std::atomic mThreadIsAlive; + std::atomic mIsStreaming; + std::string mfilePath; + TrackingDevice::Listener* mDeviceListener; + TrackingData::Profile* mProfile; + }; +} \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp b/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp new file mode 100644 index 0000000000..b709d8e328 --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Recorder.cpp @@ -0,0 +1,249 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ +#define LOG_TAG "Recorder" +#include +#include "Recorder.h" +#include "Packet.h" +#include "Utils.h" + +using namespace perc; +const uint64_t MAX_LATENCY = 1000LL * 1000LL * 1000LL; + +TrackingRecorder* TrackingRecorder::CreateInstance(TrackingDevice* device, TrackingDevice::Listener* listener, const char* file, bool stereoMode) +{ + if (device == nullptr || listener == nullptr || file == nullptr) + { + LOGE("TrackingRecorder::CreateInstance : Invalid parameters"); + return nullptr; + } + try + { + return new RecorderImpl(device, listener, file, stereoMode); + } + catch (std::exception& e) + { + std::string error = std::string("Failed to create Recorder : ") + e.what(); + LOGE(error.c_str()); + return nullptr; + } +} + +RecorderImpl::RecorderImpl(TrackingDevice* device, TrackingDevice::Listener* listener, const char* file, bool stereoMode) : mDevice(device), mListener(listener), mThreadIsAlive(true), mWritingQueue(0), mVideoFrames(), mStereoMode(stereoMode), +mLatencyQueue(MAX_LATENCY, [&](uint64_t arrivalTime, std::shared_ptr packet) -> bool +{ + switch (packet->getType()) + { + case packet_image_raw: + { + ImagePacket * imagePacket = (ImagePacket*)packet.get(); + if (mStereoMode == false) + { + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(imagePacket->getExposurePacket())); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::shared_ptr(packet)); + } + else + { + uint16_t stereoSensorId = imagePacket->getSensorId() / 2; + StereoPacket& _packet = mVideoFrames[stereoSensorId]; + if (_packet.addPacket(imagePacket) == false) + { + LOGE("Failed to record an image packet"); + return false; + } + if (_packet.isComplete()) + { + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(_packet.getExposurePacket())); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(_packet)); + mVideoFrames[stereoSensorId].clear(); + } + } + break; + } + case packet_gyroscope: + { + GyroPacket * _packet = (GyroPacket*)packet.get(); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(_packet->getThermometerPacket())); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::shared_ptr(packet)); + break; + } + case packet_accelerometer: + { + AcclPacket * _packet = (AcclPacket*)packet.get(); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(_packet->getThermometerPacket())); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::shared_ptr(packet)); + break; + } + case packet_velocimeter: + { + VelocimeterPacket * _packet = (VelocimeterPacket*)packet.get(); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::make_shared(_packet->getThermometerPacket())); + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::shared_ptr(packet)); + break; + } + case packet_led_intensity: + { + mWritingQueue.enqueue(std::make_shared(arrivalTime)); + mWritingQueue.enqueue(std::shared_ptr(packet)); + break; + } + default: LOGW("Invalid packet type : %d", packet->getType()); + } + return true; +}) +{ + LOGD("Set recording to file %s", file); + uint16_t actualSize = 0; + + if (!setProcessPriorityToRealtime()) + { + throw std::runtime_error("Failed to set process priority"); + } + + uint8_t buffer[MAX_CONFIGURATION_SIZE] = { 0 }; + const static int CALIBRATION_TABLE_TYPE = 0x0; + if (mDevice->ReadConfiguration(CALIBRATION_TABLE_TYPE, MAX_CONFIGURATION_SIZE, buffer, &actualSize) != Status::SUCCESS) + { + throw std::runtime_error("Failed to read calibration table from device"); + } + mWritingQueue.enqueue(std::make_shared(buffer, actualSize)); + + mOutputFile.open(file, std::fstream::out | std::ofstream::binary); + + mWritingThread = std::thread([&]() -> void { + while (mThreadIsAlive || mWritingQueue.size() > 0) + { + if (mWritingQueue.size() == 0) + { + static uint32_t WAIT_FOR_PACKETS = 10; + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_PACKETS)); + continue; + } + std::shared_ptr packet; + mWritingQueue.dequeue(&packet); //check output + mOutputFile.write((const char*)packet->getBytes(), packet->getSize()); + if (packet->getType() == packet_stereo_raw) + { + StereoPacket * _packet = (StereoPacket*)packet.get(); + mOutputFile.write((const char*)_packet->getImageBytes(0), _packet->getImageSize(0)); + mOutputFile.write((const char*)_packet->getImageBytes(1), _packet->getImageSize(1)); + } + } + }); +} + +RecorderImpl::~RecorderImpl() +{ + mLatencyQueue.drain(); + mThreadIsAlive = false; + if (mWritingThread.joinable()) + { + mWritingThread.join(); + } + mOutputFile.close(); + mWritingQueue.clear(); +} + +void RecorderImpl::onPoseFrame(OUT TrackingData::PoseFrame& pose) +{ + mListener->onPoseFrame(pose); +} + +void RecorderImpl::onVideoFrame(OUT TrackingData::VideoFrame& frame) +{ + mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); + mListener->onVideoFrame(frame); +} + +void RecorderImpl::onAccelerometerFrame(OUT TrackingData::AccelerometerFrame& frame) +{ + mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); + mListener->onAccelerometerFrame(frame); +} + +void RecorderImpl::onGyroFrame(OUT TrackingData::GyroFrame& frame) +{ + mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); + mListener->onGyroFrame(frame); +} + +void RecorderImpl::onVelocimeterFrame(OUT TrackingData::VelocimeterFrame& frame) +{ + mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); + mListener->onVelocimeterFrame(frame); +} + +void RecorderImpl::onControllerDiscoveryEventFrame(OUT TrackingData::ControllerDiscoveryEventFrame& frame) +{ + mListener->onControllerDiscoveryEventFrame(frame); +} + +void RecorderImpl::onControllerFrame(OUT TrackingData::ControllerFrame& frame) +{ + mListener->onControllerFrame(frame); +} + +void RecorderImpl::onControllerConnectedEventFrame(OUT TrackingData::ControllerConnectedEventFrame& frame) +{ + uint16_t actualSize = 0; + if (frame.controllerId != 1 && frame.controllerId != 2) + { + LOGE("Invalid controller id onControllerConnectedEventFrame %d", frame.controllerId); + return; + } + + int tableType = 0x100 + frame.controllerId; + uint8_t buffer[MAX_CONFIGURATION_SIZE] = { 0 }; + if (mDevice->ReadConfiguration(tableType, MAX_CONFIGURATION_SIZE, buffer, &actualSize) == Status::SUCCESS) + { + mWritingQueue.enqueue(std::make_shared(frame.controllerId, buffer, actualSize)); + } + else + { + LOGE("Failed to read physical info table from device"); + } + mListener->onControllerConnectedEventFrame(frame); +} + +void RecorderImpl::onControllerDisconnectedEventFrame(OUT TrackingData::ControllerDisconnectedEventFrame& frame) +{ + mListener->onControllerDisconnectedEventFrame(frame); +} + +void RecorderImpl::onRssiFrame(OUT TrackingData::RssiFrame& frame) +{ + mListener->onRssiFrame(frame); +} + +void RecorderImpl::onLocalizationDataEventFrame(OUT TrackingData::LocalizationDataFrame& frame) +{ + mListener->onLocalizationDataEventFrame(frame); +} + +void RecorderImpl::onFWUpdateEvent(OUT TrackingData::ControllerFWEventFrame& frame) +{ + mListener->onFWUpdateEvent(frame); +} + +void RecorderImpl::onStatusEvent(OUT TrackingData::StatusEventFrame& frame) +{ + mListener->onStatusEvent(frame); +} + +void RecorderImpl::onControllerLedEvent(OUT TrackingData::ControllerLedEventFrame& frame) +{ + mLatencyQueue.push(frame.arrivalTimeStamp, std::make_shared(frame)); + mListener->onControllerLedEvent(frame); + +} diff --git a/third-party/libtm/libtm/src/RcSerializer/Recorder.h b/third-party/libtm/libtm/src/RcSerializer/Recorder.h new file mode 100644 index 0000000000..a791bf0e3e --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/Recorder.h @@ -0,0 +1,68 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include +#include +#include +#include "TrackingDevice.h" +#include "latency_queue.h" +#include "concurrency.h" +#include "TrackingSerializer.h" + +namespace perc +{ + class RecorderImpl : public TrackingRecorder + { + public: + + RecorderImpl(TrackingDevice* device, Listener* listener, const char* file, bool stereoMode); + + virtual ~RecorderImpl(); + + virtual void onPoseFrame(OUT TrackingData::PoseFrame& pose) override; + + virtual void onVideoFrame(OUT TrackingData::VideoFrame& frame) override; + + virtual void onAccelerometerFrame(OUT TrackingData::AccelerometerFrame& frame) override; + + virtual void onGyroFrame(OUT TrackingData::GyroFrame& frame) override; + + virtual void onVelocimeterFrame(OUT TrackingData::VelocimeterFrame& frame) override; + + virtual void onControllerDiscoveryEventFrame(OUT TrackingData::ControllerDiscoveryEventFrame& frame) override; + + virtual void onControllerFrame(OUT TrackingData::ControllerFrame& frame) override; + + virtual void onControllerConnectedEventFrame(OUT TrackingData::ControllerConnectedEventFrame& frame) override; + + virtual void onControllerDisconnectedEventFrame(OUT TrackingData::ControllerDisconnectedEventFrame& frame) override; + + virtual void onRssiFrame(OUT TrackingData::RssiFrame& frame) override; + + virtual void onLocalizationDataEventFrame(OUT TrackingData::LocalizationDataFrame& frame) override; + + virtual void onFWUpdateEvent(OUT TrackingData::ControllerFWEventFrame& frame) override; + + virtual void onStatusEvent(OUT TrackingData::StatusEventFrame& frame) override; + + virtual void onControllerLedEvent(OUT TrackingData::ControllerLedEventFrame& frame) override; + + + private: + TrackingDevice * mDevice; + TrackingDevice::Listener * mListener; + latency_queue mLatencyQueue; + single_consumer_queue> mWritingQueue; + std::ofstream mOutputFile; + std::thread mWritingThread; + std::atomic mThreadIsAlive; + std::map mVideoFrames; + std::atomic mStereoMode; + }; + + +} \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/concurrency.h b/third-party/libtm/libtm/src/RcSerializer/concurrency.h new file mode 100644 index 0000000000..8c35e7e957 --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/concurrency.h @@ -0,0 +1,310 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2015 Intel Corporation. All Rights Reserved. + +#pragma once +#include +#include +#include +#include +#include +#include + +const int QUEUE_MAX_SIZE = 10; +// Simplest implementation of a blocking concurrent queue for thread messaging +template +class single_consumer_queue +{ + std::deque q; + std::mutex mutex; + std::condition_variable cv; // not empty signal + unsigned int cap; + bool accepting; + + // flush mechanism is required to abort wait on cv + // when need to stop + std::atomic need_to_flush; + std::atomic was_flushed; + std::condition_variable was_flushed_cv; + std::mutex was_flushed_mutex; +public: + explicit single_consumer_queue(unsigned int cap = QUEUE_MAX_SIZE) + : q(), mutex(), cv(), cap(cap), need_to_flush(false), was_flushed(false), accepting(true) + {} + + void enqueue(T&& item) + { + std::unique_lock lock(mutex); + if (accepting) + { + q.push_back(std::move(item)); + if (q.size() > cap && cap != 0) + { + printf("Droppping frame\n"); + q.pop_front(); + } + } + lock.unlock(); + cv.notify_one(); + } + + bool dequeue(T* item ,unsigned int timeout_ms = 5000) + { + std::unique_lock lock(mutex); + accepting = true; + was_flushed = false; + const auto ready = [this]() { return (q.size() > 0) || need_to_flush; }; + if (!ready() && !cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), ready)) + { + return false; + } + + if (q.size() <= 0) + { + return false; + } + *item = std::move(q.front()); + q.pop_front(); + return true; + } + + bool peek(T** item) + { + std::unique_lock lock(mutex); + + if (q.size() <= 0) + { + return false; + } + *item = &q.front(); + return true; + } + + bool try_dequeue(T* item) + { + std::unique_lock lock(mutex); + accepting = true; + if (q.size() > 0) + { + auto val = std::move(q.front()); + q.pop_front(); + *item = std::move(val); + return true; + } + return false; + } + + void clear() + { + std::unique_lock lock(mutex); + + accepting = false; + need_to_flush = true; + + while (q.size() > 0) + { + auto item = std::move(q.front()); + q.pop_front(); + } + cv.notify_all(); + } + + void start() + { + std::unique_lock lock(mutex); + need_to_flush = false; + accepting = true; + } + + size_t size() + { + return q.size(); + } +}; + + +class dispatcher +{ +public: + class cancellable_timer + { + public: + cancellable_timer(dispatcher* owner) + : _owner(owner) + {} + + bool try_sleep(int ms) + { + using namespace std::chrono; + + std::unique_lock lock(_owner->_was_stopped_mutex); + auto good = [&]() { return _owner->_was_stopped.load(); }; + return !(_owner->_was_stopped_cv.wait_for(lock, milliseconds(ms), good)); + } + + private: + dispatcher* _owner; + }; + + dispatcher(unsigned int cap) + : _queue(cap), + _was_stopped(true), + _was_flushed(false), + _is_alive(true) + { + _thread = std::thread([&]() + { + while (_is_alive) + { + std::function item; + + if (_queue.dequeue(&item)) + { + cancellable_timer time(this); + + try + { + item(time); + } + catch(...){} + } + +#ifndef ANDROID + std::unique_lock lock(_was_flushed_mutex); +#endif + _was_flushed = true; + _was_flushed_cv.notify_all(); +#ifndef ANDROID + lock.unlock(); +#endif + } + }); + } + + template + void invoke(T item) + { + if (!_was_stopped) + { + _queue.enqueue(std::move(item)); + } + } + + void start() + { + std::unique_lock lock(_was_stopped_mutex); + _was_stopped = false; + + _queue.start(); + } + + void stop() + { + { + std::unique_lock lock(_was_stopped_mutex); + _was_stopped = true; + _was_stopped_cv.notify_all(); + } + + _queue.clear(); + + { + std::unique_lock lock(_was_flushed_mutex); + _was_flushed = false; + } + + std::unique_lock lock_was_flushed(_was_flushed_mutex); + _was_flushed_cv.wait_for(lock_was_flushed, std::chrono::hours(999999), [&]() { return _was_flushed.load(); }); + + _queue.start(); + } + + ~dispatcher() + { + stop(); + _queue.clear(); + _is_alive = false; + _thread.join(); + } + + bool flush() + { + std::mutex m; + std::condition_variable cv; + bool invoked = false; + auto wait_sucess = std::make_shared(true); + invoke([&, wait_sucess](cancellable_timer t) + { + ///TODO: use _queue to flush, and implement properly + if (_was_stopped || !(*wait_sucess)) + return; + + { + std::lock_guard locker(m); + invoked = true; + } + cv.notify_one(); + }); + std::unique_lock locker(m); + *wait_sucess = cv.wait_for(locker, std::chrono::seconds(10), [&]() { return invoked || _was_stopped; }); + return *wait_sucess; + } +private: + friend cancellable_timer; + single_consumer_queue> _queue; + std::thread _thread; + + std::atomic _was_stopped; + std::condition_variable _was_stopped_cv; + std::mutex _was_stopped_mutex; + + std::atomic _was_flushed; + std::condition_variable _was_flushed_cv; + std::mutex _was_flushed_mutex; + + std::atomic _is_alive; +}; + +template> +class active_object +{ +public: + active_object(T operation) + : _operation(std::move(operation)), _dispatcher(1), _stopped(true) + { + } + + void start() + { + _stopped = false; + _dispatcher.start(); + + do_loop(); + } + + void stop() + { + _stopped = true; + _dispatcher.stop(); + } + + ~active_object() + { + stop(); + } +private: + void do_loop() + { + _dispatcher.invoke([this](dispatcher::cancellable_timer ct) + { + _operation(ct); + if (!_stopped) + { + do_loop(); + } + }); + } + + T _operation; + dispatcher _dispatcher; + std::atomic _stopped; +}; \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/latency_queue.h b/third-party/libtm/libtm/src/RcSerializer/latency_queue.h new file mode 100644 index 0000000000..92934158fc --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/latency_queue.h @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +#include "Packet.h" + +using namespace perc; + +class latency_queue +{ +public: + latency_queue(uint64_t latency, std::function)> pop_func) : + m_latency(latency), m_pop_func(pop_func), m_last_time(0) {} + + void push(uint64_t time, std::shared_ptr buf) + { + std::lock_guard lk(m_push_mutex); + // backward comaptibility - pass-through buffers with 0 timestamp + if (time == 0) { + m_pop_func(0, buf); + } + m_queue.push(queue_element_t(time, buf)); + if (time >= m_last_time) m_last_time = time; + while (!m_queue.empty()) { + queue_element_t top = m_queue.top(); + if (m_last_time - top.first < m_latency) { + break; + } + else { + m_queue.pop(); + m_pop_func(top.first, top.second); + } + } + } + + void drain() + { + std::lock_guard lk(m_push_mutex); + while (!m_queue.empty()) { + queue_element_t top = m_queue.top(); + m_queue.pop(); + m_pop_func(top.first, top.second); + } + } + +private: + uint64_t m_latency; + uint64_t m_last_time; + std::function)> m_pop_func; + std::mutex m_push_mutex; + typedef std::pair> queue_element_t; + struct element_cmp { + bool operator () (const queue_element_t &a, const queue_element_t &b) + { + return a.first > b.first; + } + }; + std::priority_queue, + element_cmp> m_queue; +}; \ No newline at end of file diff --git a/third-party/libtm/libtm/src/RcSerializer/rc_packet.h b/third-party/libtm/libtm/src/RcSerializer/rc_packet.h new file mode 100644 index 0000000000..a5a6e217a4 --- /dev/null +++ b/third-party/libtm/libtm/src/RcSerializer/rc_packet.h @@ -0,0 +1,355 @@ +// Copyright (c) 2008-2012, Eagle Jones +// Copyright (c) 2012-2015 RealityCap, Inc +// All rights reserved. +// + +#ifndef __PACKET_H +#define __PACKET_H + +#include +#include +#include +#include + +#ifdef MYRIAD2 +// lives in target/host common area +#ifdef USE_TM2_PACKETS +#include "get_pose.h" +#endif +#endif + +#ifdef WIN32 +#pragma warning (push) +#pragma warning (disable : 4200) +#endif + +//WARNING: Do not change the order of this enum, or insert new packet types in the middle +//Only append new packet types after the last one previously defined. +typedef enum packet_type { + packet_none = 0, + packet_camera = 1, + packet_imu = 2, + packet_feature_track = 3, + packet_feature_select = 4, + packet_navsol = 5, + packet_feature_status = 6, + packet_filter_position = 7, + packet_filter_reconstruction = 8, + packet_feature_drop = 9, + packet_sift = 10, + packet_plot_info = 11, + packet_plot = 12, + packet_plot_drop = 13, + packet_recognition_group = 14, + packet_recognition_feature = 15, + packet_recognition_descriptor = 16, + packet_map_edge = 17, + packet_filter_current = 18, + packet_feature_prediction_variance = 19, + packet_accelerometer = 20, + packet_gyroscope = 21, + packet_filter_feature_id_visible = 22, + packet_filter_feature_id_association = 23, + packet_feature_intensity = 24, + packet_filter_control = 25, + packet_ground_truth = 26, + packet_core_motion = 27, + packet_image_with_depth = 28, + packet_image_raw = 29, + packet_diff_velocimeter = 30, + packet_thermometer = 31, + packet_stereo_raw = 40, + packet_image_stereo = 41, + packet_rc_pose = 42, + packet_calibration_json = 43, + packet_arrival_time = 44, + packet_velocimeter = 45, + packet_pose = 46, + packet_control = 47, + packet_calibration_bin = 48, + packet_exposure = 49, + packet_controller_physical_info = 50, + packet_led_intensity = 51, + packet_command_start = 100, + packet_command_stop = 101, +} packet_type; + +typedef struct packet_header_t { + uint32_t bytes; //size of packet including header + uint16_t type; //id of packet + uint16_t sensor_id; //id of sensor + uint64_t time; //time in microseconds +} packet_header_t; + +typedef struct packet_t { + packet_header_t header; + uint8_t data[]; +} packet_t; + +typedef struct { + packet_header_t header; + uint8_t data[]; +} packet_camera_t; + +typedef struct { + packet_header_t header; + uint64_t exposure_time_us; + uint16_t width, height; + uint16_t depth_width, depth_height; + uint8_t data[]; +} packet_image_with_depth_t; + +typedef struct { + packet_header_t header; + uint64_t exposure_time_us; + uint16_t width, height, stride; + uint16_t format; // enum { Y8, Z16_mm }; + uint8_t data[]; +} packet_image_raw_t; + +typedef struct { + packet_header_t header; + uint64_t exposure_time_us; + uint16_t width, height, stride1, stride2; + uint16_t format; // enum { Y8, Z16_mm }; + uint8_t data[]; // image2 starts at data + height*stride1 +} packet_stereo_raw_t; + +typedef struct { + packet_header_t header; + uint64_t exposure_time_us; + float gain; +} packet_exposure_t; + +typedef struct { + packet_header_t header; + packet_image_raw_t *frames[2]; +} packet_image_stereo_t; + +typedef struct { + packet_header_t header; + float a[3]; // m/s^2 + float w[3]; // rad/s +} packet_imu_t; + +typedef struct { + packet_header_t header; + float a[3]; // m/s^2 +} packet_accelerometer_t; + +typedef struct { + packet_header_t header; + float w[3]; // rad/s +} packet_gyroscope_t; + +typedef struct { + packet_header_t header; + float temperature_C; +} packet_thermometer_t; + +typedef struct { + packet_header_t header; + float v[3]; +} packet_velocimeter_t; + +typedef struct { + packet_header_t header; + float v[2]; // m/s in body x for sensor_id, sensor_id+1 +} packet_diff_velocimeter_t; + +typedef struct { + packet_header_t header; + float rotation_rate[3]; // rad/s + float gravity[3]; //m/s^2 +} packet_core_motion_t; + +typedef struct { + packet_header_t header; + struct feature_t { float x, y; } features[]; +} packet_feature_track_t; + +typedef struct { + packet_header_t header; + uint16_t indices[]; +} packet_feature_drop_t; + +typedef struct { + packet_header_t header; + uint8_t status[]; +} packet_feature_status_t; + +typedef struct { + packet_header_t header; + uint32_t led_id; + uint32_t intensity; +}packet_led_intensity_t; + +typedef struct { + packet_header_t header; + uint8_t intensity[]; +} packet_feature_intensity_t; + +typedef struct { + packet_header_t header; + float latitude, longitude; + float altitude; + float velocity[3]; + float orientation[3]; +} packet_navsol_t; + +typedef struct { + packet_header_t header; + float position[3]; + float orientation[3]; +} packet_filter_position_t; + +typedef struct { + packet_header_t header; + float points[][3]; +} packet_filter_reconstruction_t; + +typedef struct { + packet_header_t header; + uint64_t reference; + float T[3]; + float W[3]; + float points[][3]; +} packet_filter_current_t; + +typedef struct { + packet_header_t header; + float T[3]; + float W[3]; + uint64_t feature_id[]; +} packet_filter_feature_id_visible_t; + +typedef struct { + packet_header_t header; + uint64_t feature_id[]; +} packet_filter_feature_id_association_t; + +typedef struct packet_listener { + packet_t *latest; + int type; + bool isnew; +} packet_listener_t; + +typedef struct { + packet_header_t header; + float nominal; + const char identity[]; +} packet_plot_info_t; + +typedef struct { + packet_header_t header; + int count; + float data[]; +} packet_plot_t; + +typedef struct { + packet_header_t header; + uint64_t first, second; + float T[3], W[3]; + float T_var[3], W_var[3]; +} packet_map_edge_t; + +typedef struct { + packet_header_t header; + int64_t id; //signed to indicate add/drop + float W[3], W_var[3]; +} packet_recognition_group_t; + +typedef struct { + packet_header_t header; + uint64_t groupid; + uint64_t id; + float ix; + float iy; + float depth; + float x, y, z; + float variance; +} packet_recognition_feature_t; + +typedef struct { + packet_header_t header; + struct feature_covariance_t { float x, y, cx, cy, cxy; } covariance[]; +} packet_feature_prediction_variance_t; + +typedef struct { + packet_header_t header; + uint64_t groupid; + uint32_t id; + float color; + float x, y, z; + float variance; + uint32_t label; + uint8_t descriptor[128]; +} packet_recognition_descriptor_t; + +typedef struct { + packet_header_t header; + float T[3]; + float velocity[3]; // m/s + float rotation[4]; // axis [0:2] angle in rad [3] + float w[3]; // rad/s + float w_a[3]; // rad/s^2 +} packet_ground_truth_t; + +enum packet_plot_type { + packet_plot_var_T, + packet_plot_var_W, + packet_plot_var_a, + packet_plot_var_w, + packet_plot_inn_a, + packet_plot_inn_w, + packet_plot_meas_a, + packet_plot_meas_w, + packet_plot_unknown = 256 +}; + +#ifdef MYRIAD2 +#ifdef USE_TM2_PACKETS +typedef struct { + packet_header_t header; + sixDof_data data; +} packet_pose_t; +#endif +#endif + +typedef struct { + packet_header_t header; + char data[]; +} packet_calibration_json_t; + +typedef struct { + packet_header_t header; + uint8_t data[]; +} packet_calibration_bin_t; + +typedef struct { + packet_header_t header; + uint8_t data[]; +} packet_controller_physical_info_t; + +typedef struct { + packet_header_t header; // header::timestamp is packet arrival time (To algo), header::sensor_id is not used +} packet_arrival_time_t; + +typedef struct packet_control_t { + struct { + uint32_t bytes; //size of packet including header + uint16_t type; //id of packet + union { + uint16_t sensor_id; //id of sensor + uint16_t control_type; //id of control packet + }; + uint64_t time; //time in microseconds + } header; + uint8_t data[]; +} packet_control_t; + +#ifdef WIN32 +#pragma warning (pop) +#endif + +#endif \ No newline at end of file diff --git a/third-party/libtm/libtm/src/infra/CMakeLists.txt b/third-party/libtm/libtm/src/infra/CMakeLists.txt new file mode 100644 index 0000000000..e72e5d8c42 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 2.8) +project(infra) + +#Source Files +set(SOURCE_FILES + Log.h + Log.cpp + Event.h + Fence.h + EventHandler.h + Poller.h + Poller_${OS}.cpp + Dispatcher.h + Dispatcher.cpp + Fsm.h + Fsm.cpp + Utils.h + Utils.cpp + Semaphore.h + Semaphore_${OS}.cpp + Event_${OS}.cpp +) + + +#Building Library +set(SDK_LIB_TYPE "STATIC") +MESSAGE("Building project ${PROJECT_NAME} as ${SDK_LIB_TYPE} library") +add_library(${PROJECT_NAME} ${SDK_LIB_TYPE} ${SOURCE_FILES}) + + diff --git a/third-party/libtm/libtm/src/infra/Dispatcher.cpp b/third-party/libtm/libtm/src/infra/Dispatcher.cpp new file mode 100644 index 0000000000..ff102c4dda --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Dispatcher.cpp @@ -0,0 +1,411 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#define LOG_TAG "Dispatcher" +#define LOG_NDEBUG 1 // controls LOGV only +#include "Log.h" +#include "Dispatcher.h" + + +namespace perc { +// ---------------------------------------------------------------------------- + Dispatcher::Dispatcher() : mThreadId(), m_Handlers(), m_HandlersGuard(), m_MessagesGuard(), m_Timers(), m_TimersGuard(), m_HoldersGuard(), m_Holders() +{ + bool ret = mPoller.add(Poller::event(mEvent.handle(), Poller::READ_MASK, this)); + if (ret != true) + { + ASSERT(ret); + } +} + +// ---------------------------------------------------------------------------- +Dispatcher::~Dispatcher() +{ + processExit(); + { + std::lock_guard guard(m_MessagesGuard); + for (int i = 0; i < PRIORITY_MAX; i++) { + Holder *holder = (Holder *)m_Messages[i].GetHead(); + while (holder) { + do { + m_Messages[i].RemoveHead(); + delete holder; + } while ((holder = (Holder *)m_Messages[i].GetHead()) != 0); + } + } + } + { + std::lock_guard guard(m_HandlersGuard); + m_Handlers.clear(); + } + { + std::lock_guard guard(m_TimersGuard); + Holder *holder = (Holder *)m_Timers.GetHead(); + while (holder) { + do { + m_Timers.RemoveHead(); + delete holder; + } while ((holder = (Holder *)m_Timers.GetHead()) != 0); + } + } + mPoller.remove(mEvent.handle()); +} + +// ---------------------------------------------------------------------------- +// DISPATCHER LOOP +// +// Returns: +// >0 - the total number of timers, I/Oevents and messages that were dispatched in single call +// 0 - if the elapsed without dispatching any handlers +// -1 - if an error occurs +int Dispatcher::handleEvents(nsecs_t timeout) +{ + if (mExitPending) { + LOGV("handleEvents(): processExit"); + processExit(); + return -1; + } + int ret = 0; + mThreadId = std::this_thread::get_id(); + LOGV("handleEvents(): Poller::poll()"); + Poller::event event; + int n = mPoller.poll(event, calculatePollTimeout(timeout)); + LOGV("handleEvents(): Poller::poll() ret %d", n); + if (n > 0) { + // process message queues: complete number of messages from message list that was signaled by event + if (event.handle == mEvent.handle()) { + LOGV("handleEvents(): processMessages"); + ret += processMessages(); + } + else { + // process file descriptor + LOGV("handleEvents(): processEvent fd %d, revents %x", event.handle, event.mask); + ret += processEvents(event); + } + } + else if (n == -1) { + LOGE("handleEvents(): Poller::poll() ret %d", n); + return -1; + } + // process timers + ret += processTimers(); + return ret; +} + +// ---------------------------------------------------------------------------- +void Dispatcher::endEventsLoop() +{ +// TRACE(""); + mExitPending = true; + wakeup(); +} + +// ---------------------------------------------------------------------------- +int Dispatcher::registerHandler(EventHandler *handler, Handle fd, unsigned long mask, void *act) +{ + if (mExitPending) return -1; + ASSERT(handler); + ASSERT(mThreadId == std::this_thread::get_id()); + int res = -1; + HandlerHolder holder(handler, fd, mask, act); + if (mPoller.add(holder.Event)) { + std::lock_guard guard(m_HandlersGuard); + if (m_Handlers.count(fd) == 0) { + // adding new one + m_Handlers.emplace(fd, holder); + } + else { + // modifying old one + m_Handlers[fd] = holder; + } + res = 0; + } + return res; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::registerHandler(EventHandler *handler) +{ + if (mExitPending) return -1; + ASSERT(handler); + std::lock_guard guard(m_HoldersGuard); + EmbeddedList::Iterator it(m_Holders); + Holder *h = (Holder *)it.Current(); + // check if handler exists in a list + while(h) { + if (h->Handler == handler) + return -1; + h = (Holder *)it.Next(); + } + Holder *holder = new Holder(handler); + if (!holder) return -1; + m_Holders.AddTail(holder); + return 0; +} + +// ---------------------------------------------------------------------------- +void Dispatcher::removeHandle(Handle fd) +{ + ASSERT(mThreadId == std::this_thread::get_id()); + mPoller.remove(fd); + std::lock_guard guard(m_HandlersGuard); + if (m_Handlers.count(fd) > 0) { + m_Handlers.erase(fd); + } +} + +// ---------------------------------------------------------------------------- +void Dispatcher::cancelTimer (uintptr_t timerId) +{ + ASSERT(timerId); + ASSERT(mThreadId == std::this_thread::get_id()); + std::lock_guard guard(m_TimersGuard); + HolderTimer *holder = (HolderTimer *)timerId; + // check if trying to cancel timer from callback + if (holder->Uptime) { + m_Timers.Remove(holder); + delete holder; + } +} + +// ---------------------------------------------------------------------------- +int Dispatcher::removeHandler(EventHandler *handler, unsigned int mask) +{ + ASSERT(handler); + ASSERT(mThreadId == std::this_thread::get_id()); + int removedHandlers = 0; + + if (mask & MESSAGES_MASK) { + std::lock_guard guard(m_MessagesGuard); + for (int i = 0; i < PRIORITY_MAX; i++) { + EmbeddedList::Iterator it(m_Messages[i]); + Holder *holder = (Holder *)it.Current(); + while (holder) { + Holder *curr = holder; + holder = (Holder *)it.Next(); + if (curr->Handler == handler) { + m_Messages[i].Remove(curr); + delete curr; + removedHandlers++; + } + } + } + } + if (mask & HANDLES_MASK) { + std::lock_guard guard(m_HandlersGuard); + for (auto pair : m_Handlers) { + const HandlerHolder &holder = pair.second; + if (holder.Handler == handler) { + Handle fd = pair.first; + mPoller.remove(fd); + m_Handlers.erase(fd); + removedHandlers++; + } + } + } + if (mask & TIMERS_MASK) { + std::lock_guard guard(m_TimersGuard); + EmbeddedList::Iterator it(m_Timers); + Holder *holder = (Holder *)it.Current(); + while (holder) { + Holder *curr = holder; + holder = (Holder *)it.Next(); + if (curr->Handler == handler) { + m_Timers.Remove(curr); + delete curr; + removedHandlers++; + } + } + } + if (mask & EXIT_MASK) { + std::lock_guard guard(m_HoldersGuard); + EmbeddedList::Iterator it(m_Holders); + Holder *holder = (Holder *)it.Current(); + while (holder) { + Holder *curr = holder; + holder = (Holder *)it.Next(); + if (curr->Handler == handler) { + m_Holders.Remove(curr); + delete curr; + removedHandlers++; + break; + } + } + } + + return removedHandlers; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::putMessage(Holder *holder, int priority) +{ + if (mExitPending) return -1; + ASSERT(holder); + if (priority >= PRIORITY_MAX) priority = PRIORITY_MAX - 1; + if (priority < 0) priority = 0; + std::lock_guard guard(m_MessagesGuard); + m_Messages[priority].AddTail(holder); + // wake up running loop + if (!wakeup()) { + m_Messages[priority].Remove(holder); + delete holder; + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +uintptr_t Dispatcher::putTimer(EventHandler *handler, nsecs_t delay, Message *msg, int priority) +{ + // sanity check + if ((mExitPending == true) || (msg == NULL)) + { + return 0; + } + + ASSERT(handler && delay != 0); + // TODO: optimization: implement free list with local allocator + HolderTimer *holder = new HolderTimer(handler, msg, delay); + if (!holder) + { + delete msg; + return 0; + } + std::lock_guard guard(m_TimersGuard); + EmbeddedList::Iterator it(m_Timers); + HolderTimer *curr = (HolderTimer *)it.Current(); + if (!curr) { + m_Timers.AddHead(holder); + } + else { + do { + if (holder->Uptime < curr->Uptime) { + m_Timers.AddBefore(curr, holder); + break; + } + } while ((curr = (HolderTimer *)it.Next()) != 0); + if (!curr) { + m_Timers.AddTail(holder); + } + } + // reschedule timers by wake up running loop if was performed from another context + if (mThreadId != std::this_thread::get_id()) + wakeup(); + return (uintptr_t)holder; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::processMessages() +{ + mEvent.reset(); + // normalize wake-up events counter and real messages number before callback + int cnt = 0; + for (int i = 0; i < PRIORITY_MAX; i++) { + cnt += m_Messages[i].Size(); + } + int cntMsgs = cnt; + while (cnt) { + int priority = 0; + for (int i = (PRIORITY_MAX - 1); i >= 0; i--) { + if (m_Messages[i].Size()) { + priority = i; + break; + } + } + m_MessagesGuard.lock(); + Holder *holder = (Holder *)m_Messages[priority].RemoveHead(); + m_MessagesGuard.unlock(); + if (holder) { + holder->complete(); + delete holder; + } + else { + break; + } + cnt--; + } + return cntMsgs; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::processEvents(Poller::event &event) +{ + m_HandlersGuard.lock(); + auto it = m_Handlers.find(event.handle); + if (it != m_Handlers.end()) { + const HandlerHolder &holder = it->second; + m_HandlersGuard.unlock(); + holder.Handler->onEvent(holder.Event.handle, event.mask, holder.Event.act); + return 1; + } + else { + mPoller.remove(event.handle); + m_HandlersGuard.unlock(); + } + return 0; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::processTimers() +{ + int cnt = 0; + HolderTimer* timer; + m_TimersGuard.lock(); + timer = (HolderTimer *)m_Timers.GetHead(); + if (timer) { + // work with head only, user can add new timers in a callback + do { + nsecs_t now = systemTime(); + if (timer->Uptime <= now) { + m_Timers.RemoveHead (); + m_TimersGuard.unlock(); + timer->complete(); + delete timer; + m_TimersGuard.lock(); + cnt++; + } + else { + break; + } + } while ((timer = (HolderTimer *)m_Timers.GetHead()) != 0); + } + m_TimersGuard.unlock(); + return cnt; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::processExit() +{ + m_HoldersGuard.lock(); + Holder *holder; + while (holder = (Holder *)m_Holders.RemoveHead()) { + m_HoldersGuard.unlock(); + holder->Handler->onExit(); + delete holder; + m_HoldersGuard.lock(); + } + m_HoldersGuard.unlock(); + return 0; +} + +// ---------------------------------------------------------------------------- +int Dispatcher::calculatePollTimeout(nsecs_t timeout) +{ + // calculate next wake-up time according to timers queue or user timeout + std::lock_guard guard(m_TimersGuard); + HolderTimer *timer = (HolderTimer *)m_Timers.GetHead(); + if (timer) { + int t1 = toMillisecondTimeoutDelay(systemTime(), timer->Uptime); + int t2 = ns2ms(timeout); + return ((unsigned long)t2 == INFINITE)? t1 : (t1 > t2 ? t2 : t1); + } + + // No timer in timer queue, returning timeout input in msec + return ns2ms(timeout); +} + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Dispatcher.h b/third-party/libtm/libtm/src/infra/Dispatcher.h new file mode 100644 index 0000000000..4446e3c803 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Dispatcher.h @@ -0,0 +1,253 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include "Utils.h" +#include "Event.h" +#include "Fence.h" +#include "EmbeddedList.h" +#include "EventHandler.h" +#include "Poller.h" + +namespace perc { +// ---------------------------------------------------------------------------- +/// +/// @class Dispatcher +/// +/// @brief Provides a event demultiplexing mechanism for messages, timers +/// and file descriptors events. +/// +// ---------------------------------------------------------------------------- +class Dispatcher +{ +public: + Dispatcher(); + ~Dispatcher(); + + // Message notification mechanism + enum + { + PRIORITY_IDLE = 0, // Lowest priority + PRIORITY_NORMAL, + PRIORITY_HIGH, + PRIORITY_MAX, + }; + + // = Post/Send message + // + // User can call to this function from any running context. + // Call to these functions will trigger callback. + // Post message to via the Dispatcher's notification mechanism, don't wait for execution. + // This method will internally copy message (using copy constructor) and free after dispatching. + template + int postMessage(EventHandler *, const T&, int priority = Dispatcher::PRIORITY_IDLE); + + // Send message to via the Dispatcher's notification mechanism and blocking wait for execution. + // User SHOULD call to this function from different running context not one. + // This function waits to the message processing by client and returns after returns + // from callback. + // parameter is transferred by reference to callback - user may indicate return parameters if needed. + // Don't call to this function in the same running context. + template + int sendMessage(EventHandler *, const T&, int priority = Dispatcher::PRIORITY_IDLE); + + // = Register/remove handler for I/O and OS events. + // + // A handler can be associated with multiple handles. + // A handle cannot be associated with multiple handlers. + // User can call to this function from any running context. + // Call to these functions will trigger callback, + // according to parameter + int registerHandler(EventHandler *, Handle fd, unsigned long mask = Poller::READ_MASK, void *act = 0); + + // Register handler for Dispatcher exit processing, callback should be called + // during Disptcher deactivation. + int registerHandler(EventHandler *); + + // Remove handle from dispatcher and it's associated handler. + // User SHOULD call these functions from dispatcher context only (from callbacks)!!! + void removeHandle(Handle fd); + + // Remove all messages (and release them), handles and timers (cancel them) associated with this handler + // according to mask parameters. + // + // User SHOULD call these functions from dispatcher context only (from callbacks)!!! + enum + { + MESSAGES_MASK = (1<<0), + HANDLES_MASK = (1<<1), + TIMERS_MASK = (1<<2), + EXIT_MASK = (1<<3), + ALL_MASK = MESSAGES_MASK | HANDLES_MASK | TIMERS_MASK | EXIT_MASK, + }; + int removeHandler(EventHandler *, unsigned int mask = Dispatcher::ALL_MASK); + + // = Timers. + // + // @params: delayMs Time interval in nanoseconds after which the timer will expire. + // @return or 0 in case of error. + template + uintptr_t scheduleTimer (EventHandler *, nsecs_t delay, const T&, int priority = Dispatcher::PRIORITY_IDLE); + + // Cancel timer associated with this . + // User SHOULD call these functions from dispatcher context only (from callbacks)!!! + void cancelTimer (uintptr_t timerId); + + // Main dispatch function + // + // Returns: + // >0 - the total number of timers, I/Oevents and messages that were dispatched in single call + // 0 - if the elapsed without dispatching any handlers + // -1 - if an error occurs + int handleEvents(nsecs_t timeout = INFINITE); + void endEventsLoop(); + +protected: + + // holder of messages base class + class Holder : public EmbeddedListElement + { + public: + Holder(EventHandler *h) : Handler(h) {} + virtual ~Holder() {} + virtual void complete() {} + EventHandler *Handler; + }; + +private: + std::thread::id mThreadId; + Event mEvent; + Poller mPoller; + bool mExitPending = false; + + int putMessage(Holder *, int priority); + uintptr_t putTimer (EventHandler *, nsecs_t delay, Message *, int priority); + int processMessages(); + int processEvents(Poller::event &); + int processTimers(); + int processExit(); + + /** + * @brief This function calculates the next wake-up time according to timers queue or user timeout + * + * @param[in] timeout - poll timeout in nsec. + * @return Next wake up time in msec. + */ + int calculatePollTimeout(nsecs_t); + bool wakeup() { return mEvent.signal(); } + + class HolderPost : public Holder + { + public: + HolderPost(EventHandler *h, Message *m) : Holder(h), Msg(m) {} + virtual ~HolderPost() { delete Msg; } + virtual void complete() { Handler->onMessage(*Msg); } + Message *Msg; + + /* Disallow these operations */ + HolderPost &operator=(const HolderPost &); + HolderPost(const HolderPost &); + }; + + class HolderSend : public Holder + { + public: + HolderSend(EventHandler *h, const Message &m, Fence &w) : + Holder(h), Msg(m), Waiter(w) {} + virtual ~HolderSend() { Waiter.notify(); } + virtual void complete() { Handler->onMessage(Msg); } + const Message &Msg; + Fence &Waiter; // sync wait lock handle + }; + EmbeddedList m_Messages[PRIORITY_MAX]; + std::mutex m_MessagesGuard; + + // I/O HANDLERS + struct HandlerHolder + { + EventHandler *Handler; + Poller::event Event; + HandlerHolder() : Handler(NULL) {} + HandlerHolder(EventHandler *handler, Handle fd, unsigned long mask, void *act) : + Handler(handler), Event(fd, mask, act) {} + + }; + std::unordered_map m_Handlers; + std::mutex m_HandlersGuard; + + // TIMERS + class HolderTimer : public HolderPost + { + public: + HolderTimer(EventHandler *h, Message *m, nsecs_t d) : HolderPost(h, m) + { Uptime = systemTime() + d; } + virtual void complete() { Uptime = 0; Handler->onTimeout((uintptr_t)this, *Msg); } + nsecs_t Uptime; + }; + EmbeddedList m_Timers; + std::mutex m_TimersGuard; + + // HANDLERS + EmbeddedList m_Holders; + std::mutex m_HoldersGuard; + + // Disallow these operations. + Dispatcher &operator=(const Dispatcher &); + Dispatcher(const Dispatcher &); +}; + + +// ---------------------------------------------------------------------------- +// Template functions MUST be implemented in header file with minimal code size +template +int Dispatcher::postMessage(EventHandler *handler, const T &msg, int priority) +{ + T::IS_DERIVED_FROM_Message; + if (!handler) return -1; + + T *m = new T(msg); // use copy constructor to create message clone + if (!m) return -ENOMEM; + + Holder *holder = new HolderPost(handler, m); + if (!holder) + { + delete m; + return -ENOMEM; + } + return putMessage(holder, priority); +} + +// ---------------------------------------------------------------------------- +template +int Dispatcher::sendMessage(EventHandler *handler, const T &msg, int priority) +{ + T::IS_DERIVED_FROM_Message; + ASSERT(mThreadId != std::this_thread::get_id()); + // use message reference + Fence f; + Holder *holder = new HolderSend(handler, msg, f); + if (!holder ) + return -ENOMEM; + + if (putMessage(holder, priority) < 0) + return -1; + + // wake for message completion or reset by dispatcher + f.wait(); + return 0; +} + +// ---------------------------------------------------------------------------- +template +uintptr_t Dispatcher::scheduleTimer (EventHandler *handler, nsecs_t delay, const T &msg, int priority) +{ + T::IS_DERIVED_FROM_Message; + return putTimer(handler, delay, new T(msg), priority); +} + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/EmbeddedList.h b/third-party/libtm/libtm/src/infra/EmbeddedList.h new file mode 100644 index 0000000000..5e18777097 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/EmbeddedList.h @@ -0,0 +1,187 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + + +namespace perc { +// ---------------------------------------------------------------------------- +/// +/// @class EmbeddedList +/// +/// @brief Doubly linked list. +/// +/// @note This implementation of a doubly linked list does not require +/// use of dynamically allocated memory. Instead, each class +/// that is a potential list element must inherite from a class +/// . All of the list functions operate on these +/// embedded element. +/// +// ---------------------------------------------------------------------------- +class EmbeddedListElement +{ +public: + EmbeddedListElement *Next () const {return m_EmbeddedListElementNext; } + void Next (EmbeddedListElement *Element) { m_EmbeddedListElementNext = Element; } + EmbeddedListElement *Prev () const {return m_EmbeddedListElementPrev; } + void Prev (EmbeddedListElement *Element) { m_EmbeddedListElementPrev = Element; } +private: + EmbeddedListElement *m_EmbeddedListElementNext; + EmbeddedListElement *m_EmbeddedListElementPrev; +}; + +class EmbeddedList +{ +public: + + EmbeddedList () : Head (0), Tail (0), m_Size(0) {} + + // Check Head + EmbeddedListElement *GetHead () + { + return Head; + } + + // Inserts an element onto the top of the list + void AddHead (EmbeddedListElement *Element) + { + //ASSERT (Element); + Element->Prev (0); + Element->Next (Head); + + // if first element, the tail also points to this element + if (!Head) + Tail = Element; + // more than one + else + // poin to the new head + Head->Prev (Element); + Head = Element; + m_Size++; + } + + // Takes a element off from the top of the list + EmbeddedListElement *RemoveHead () + { + EmbeddedListElement *element = Head; + if (element) + { + // if only one element + if (Tail == Head) + Tail = 0; + else + element->Next ()->Prev (0); + Head = element->Next (); + m_Size--; + } + return element; + } + + // Inserts a element onto the end of the list + void AddTail (EmbeddedListElement *Element) + { + //ASSERT (Element); + Element->Prev (Tail); + Element->Next (0); + + // if first element, the tail also points to this element + if (!Tail) + Head = Element; + // more than one + else + // poin to the new tail + Tail->Next (Element); + Tail = Element; + m_Size++; + } + + // Takes a element off from the end of the list + EmbeddedListElement *RemoveTail () + { + EmbeddedListElement *element = Tail; + if (element) + { + // if only one element + if (Tail == Head) + Head = 0; + else + element->Prev ()->Next (0); + Tail = element->Prev (); + m_Size--; + } + return element; + } + + // Inserts a element onto the end of the list + void AddBefore (EmbeddedListElement *ElementCurr, EmbeddedListElement *ElementNew) + { + //ASSERT (ElementCurr && ElementNew); + if (ElementCurr == Head) + AddHead (ElementNew); + else + { + ElementNew->Prev (ElementCurr->Prev ()); + ElementCurr->Prev (ElementNew); + ElementNew->Next (ElementCurr); + ElementNew->Prev ()->Next (ElementNew); + m_Size++; + } + } + + // Takes a element off from the list + int Remove (EmbeddedListElement *Element) + { + //ASSERT (Element); + if (Element == Head) + RemoveHead (); + else if (Element == Tail) + RemoveTail (); + else + { + Element->Next ()->Prev (Element->Prev ()); + Element->Prev ()->Next (Element->Next ()); + m_Size--; + } + return 0; + } + + /// @class Iterator + class Iterator + { + const EmbeddedList &m_List; + EmbeddedListElement *m_CurrentNode; + public: + Iterator (const EmbeddedList &List) : m_List (List) { Reset (); } + + // Reset the Iterator so that GetNext will give you the first list element + void Reset () + { + m_CurrentNode = m_List.Head; + } + + // Return the next list element (i.e. advance iterator and return next list element) + EmbeddedListElement *Next () + { + // move the iterator one step forward + if (m_CurrentNode) + m_CurrentNode = m_CurrentNode->Next (); + return m_CurrentNode; + } + + // Return the current list element + EmbeddedListElement *Current () const { return m_CurrentNode; } + }; + + int Size() const { return m_Size; } + +private: + EmbeddedListElement *Head; + EmbeddedListElement *Tail; + int m_Size; +}; + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Event.h b/third-party/libtm/libtm/src/infra/Event.h new file mode 100644 index 0000000000..e033457900 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Event.h @@ -0,0 +1,32 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "TrackingCommon.h" +#include "Log.h" + +namespace perc +{ + class Event + { + public: + Event(); + + inline Handle handle() { return mEvent; } + + int signal(); + + int reset(); + + ~Event(); + + private: + Handle mEvent; + + void operator= (const Event &) = delete; + Event(const Event &) = delete; + }; + +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/EventHandler.h b/third-party/libtm/libtm/src/infra/EventHandler.h new file mode 100644 index 0000000000..096cccb607 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/EventHandler.h @@ -0,0 +1,62 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "TrackingCommon.h" + +namespace perc { +// ---------------------------------------------------------------------------- +/// +/// @class Message +/// +/// @brief Declare a base class for all messages that can be posted to Dispatcher. +/// User MUST manage allocation policy if needed by defining copy constructor +// and appropriate destructor. and its derives will be copied internally and +/// SHOULD be freed after dispatching callback by Dispatcher. +/// +// ---------------------------------------------------------------------------- +class Message +{ +public: + Message(int type, int param = 0) : Type(type), Param(param), Result(-1) {} + virtual ~Message() {} + int Type; + int Param; + mutable int Result; + + // replace mechanism that generate compilation error + // if derived class was not inherited from class + enum { IS_DERIVED_FROM_Message = true }; +}; + +// ---------------------------------------------------------------------------- +/// +/// @class EventHandler +/// +/// @brief This base class defines the interface for receiving the +/// results of sync and asynchronous operations. +/// Subclasses of this class will fill in appropriate methods. +/// @note +/// +// ---------------------------------------------------------------------------- +class EventHandler +{ +public: + virtual ~EventHandler() {} + + // = Completion callbacks + virtual void onMessage(const Message &) {} + virtual void onEvent(Handle fd, unsigned long mask, void *act) {} + virtual void onTimeout(uintptr_t timerId, const Message &) {} + virtual void onExit() {} + +protected: + // Hide the constructor + EventHandler() {} +}; + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Event_lin.cpp b/third-party/libtm/libtm/src/infra/Event_lin.cpp new file mode 100644 index 0000000000..30071e4394 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Event_lin.cpp @@ -0,0 +1,35 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include "Event.h" +#include +# include + +perc::Event::Event() +{ + // LINUX: NONBLOCK + mEvent = ::eventfd(0, EFD_NONBLOCK); + + ASSERT(mEvent != ILLEGAL_HANDLE); +} + +perc::Event::~Event() +{ + ::close(mEvent); +} + +int perc::Event::reset() +{ + ASSERT(mEvent != ILLEGAL_HANDLE); + + eventfd_t cnt; + return !::eventfd_read(mEvent, &cnt); +} + +int perc::Event::signal() { + ASSERT(mEvent != ILLEGAL_HANDLE); + + return !::eventfd_write(mEvent, 1); +} \ No newline at end of file diff --git a/third-party/libtm/libtm/src/infra/Event_win.cpp b/third-party/libtm/libtm/src/infra/Event_win.cpp new file mode 100644 index 0000000000..b9f832751c --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Event_win.cpp @@ -0,0 +1,37 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include "Event.h" + +perc::Event::Event() +{ + // WINDOWS: manual reset event + mEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + NULL // Unnamed event + ); + + ASSERT(mEvent != ILLEGAL_HANDLE); +} + +perc::Event::~Event() +{ + CloseHandle(mEvent); +} + +int perc::Event::reset() +{ + ASSERT(mEvent != ILLEGAL_HANDLE); + return ResetEvent(mEvent); +} + +int perc::Event::signal() +{ + ASSERT(mEvent != ILLEGAL_HANDLE); + + return SetEvent(mEvent); +} \ No newline at end of file diff --git a/third-party/libtm/libtm/src/infra/Fence.h b/third-party/libtm/libtm/src/infra/Fence.h new file mode 100644 index 0000000000..06401a9ab8 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Fence.h @@ -0,0 +1,39 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include +#include + + +namespace perc { + +class Fence +{ +public: + Fence(){} + + inline void notify() { + std::unique_lock l(m); + fired = true; + cv.notify_one(); + } + + inline void wait() { + std::unique_lock l(m); + cv.wait(l, [this]{ return fired; }); + fired = false; + } + +private: + std::mutex m; + std::condition_variable cv; + bool fired = false; + // Prevent assignment and initialization. + void operator= (const Fence &); + Fence (const Fence &); +}; + +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Fsm.cpp b/third-party/libtm/libtm/src/infra/Fsm.cpp new file mode 100644 index 0000000000..4bd281cca6 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Fsm.cpp @@ -0,0 +1,394 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ +#define LOG_TAG "Fsm" + +#include "Fsm.h" + +namespace perc { +// -[statics]------------------------------------------------------------------ +// Default NONE transition definition. +const FsmTransition s_FsmTransitionNone = +{ + FSM_TRANSITION_NONE_NAME, + FSM_TRANSITION_NONE, + FSM_EVENT_NONE, + FSM_STATE_FINAL, + 0, + 0, + FSM_TRANSITION_AFTER_MAX_TIMEOUT +}; + +// Default FINAL state definition. +const FsmState s_FsmStateFinal = +{ + FSM_STATE_FINAL_NAME, + FSM_STATE_FINAL, + 0, + 0, + (FsmTransition *)&s_FsmTransitionNone +}; + + +// ---------------------------------------------------------------------------- +Fsm::Fsm() : + m_pFsm(0), + m_CurrStateId(FSM_STATE_FINAL), + m_Owner(NULL), + m_Dispatcher(NULL), + m_Name(""), + m_SelfEvent(NULL) +{ +} + +// ---------------------------------------------------------------------------- +Fsm::~Fsm() +{ + done(); +} + +// ---------------------------------------------------------------------------- +int +Fsm::init (const FsmState * const *pFsm, + void *Owner, + Dispatcher *pDispatcher, + const char *Name) +{ + // User MUST define FSM definition!!! + ASSERT (pFsm); + + // Save context parameters + m_pFsm = pFsm; + m_Owner = Owner; + m_Dispatcher = pDispatcher; + m_Name = Name; + + // Check if Dispatcher was set + if (!dispatcher ()) + { + LOGW("engine not found, can't schedule after transitions!"); + } + + // Init Initial state and call to initial state entry function. + // Initial state always first into the states list! + resetSelfEvent(); + int ret_code = InitNewState (m_pFsm[0]->Type); + if (FSM_CONTEXT_STATUS_OK != ret_code) + { + ASSERT(!m_SelfEvent); + logRetCode(ret_code, m_pFsm[m_CurrStateId], Message(FSM_EVENT_NONE)); + return ret_code; + } + // Process self event if exist + return processSelfEvent(); +} + +// ---------------------------------------------------------------------------- +int +Fsm::done () +{ + if (!m_pFsm) return FSM_CONTEXT_STATUS_ERROR; + + CancelAfterTransitions (); + + // Reset FSM context parameters. + m_pFsm = 0; + m_Owner = 0; + m_Dispatcher = 0; + + return FSM_CONTEXT_STATUS_OK; +} + +// ---------------------------------------------------------------------------- +int +Fsm::fireEvent (const Message &pEvent) +{ + ASSERT (m_pFsm); + + resetSelfEvent(); + + // Find transition that wait for this event + const FsmState *state = m_pFsm[m_CurrStateId]; + int transition_id = FSM_TRANSITION_NONE; + int ret_code = FSM_CONTEXT_STATUS_ERROR; + const FsmTransition *transition = 0; + + if ((ret_code = FindTransition (&transition_id, pEvent)) != FSM_CONTEXT_STATUS_OK) + goto exit_; + else + transition = &state->TransitionList[transition_id]; + + ASSERT (transition); + + // Check if transition internal - in this case only call to transition action and return + if (transition->NewState == FSM_STATE_SAME) + { + CallTransitionAction (transition, pEvent); + ret_code = FSM_CONTEXT_STATUS_OK; + goto exit_; + } + + // Transition is not internal - we must to do some work... + // Call to state exit function. + DoneCurrState (); + + // Call to transition action. + CallTransitionAction (transition, pEvent); + + // Go to new state, that may be final... + ret_code = InitNewState (transition->NewState); + +exit_: + if (FSM_CONTEXT_STATUS_OK != ret_code) + { + ASSERT(!m_SelfEvent); + logRetCode(ret_code, state, pEvent); + return ret_code; + } + // Process self event if exist + return processSelfEvent(); +} + +// -[Private functions]-------------------------------------------------------- +int +Fsm::InitNewState (int StateType) +{ + // Check if new state - final state + if (StateType == FSM_STATE_FINAL) + return FSM_CONTEXT_STATUS_STATE_FINAL; + + // Find new state definition + for (int state_id = 0; m_pFsm[state_id]->Type != FSM_STATE_FINAL; state_id++) + { + if (m_pFsm[state_id]->Type == StateType) + { + // Remember new state index. + m_CurrStateId = state_id; + + CallStateEntry (); + + // Schedule After transitions timers via engine object. + ScheduleAfterTransitions (); + + return FSM_CONTEXT_STATUS_OK; + } + } + return FSM_CONTEXT_STATUS_STATE_NOT_FOUND; +} + +// ---------------------------------------------------------------------------- +int +Fsm::DoneCurrState () +{ + // Cancel after transitions timers + CancelAfterTransitions (); + + // Call to old state exit function + CallStateExit (); + + return FSM_CONTEXT_STATUS_OK; +} + +// ---------------------------------------------------------------------------- +int +Fsm::FindTransition (int *pTransitionId, const Message &pEvent) +{ + const FsmTransition *transition_list = m_pFsm[m_CurrStateId]->TransitionList; + int ret_code = FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND; + + // = Find transition that wait for this event type + // + if (pEvent.Type == FSM_EVENT_TIMEOUT) + { + int tid = pEvent.Param; + + ASSERT (transition_list[tid].Type == FSM_TRANSITION_AFTER || + transition_list[tid].Type == FSM_TRANSITION_INTERNAL_AFTER); + + // Check transition Guard function + if (CallTransitionGuard (&transition_list[tid], pEvent)) + { + // Save found transition + *pTransitionId = tid; + ret_code = FSM_CONTEXT_STATUS_OK; + } + else + ret_code = FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED; + } + else + { + for (int i = 0; transition_list[i].Type != FSM_TRANSITION_NONE; i++) + { + // Check if this transition wait for current event. + if (transition_list[i].EventType == pEvent.Type) + { + ret_code = FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED; + + if (CallTransitionGuard (&transition_list[i], pEvent)) + { + // Save found transition + *pTransitionId = i; + return FSM_CONTEXT_STATUS_OK; + } + } + } + } + return ret_code; +} + +// ---------------------------------------------------------------------------- +int +Fsm::ScheduleAfterTransitions () +{ + if (!dispatcher ()) return -1; + + const FsmState *state = m_pFsm[m_CurrStateId]; + const FsmTransition *transition_list = state->TransitionList; + + // Find after transitions and schedule timers for them. + for (int tid = 0; transition_list[tid].Type != FSM_TRANSITION_NONE; tid++) + { + // Check if this after transition + if (transition_list[tid].TimeOut != FSM_TRANSITION_AFTER_MAX_TIMEOUT) + { + uintptr_t timerId = dispatcher ()->scheduleTimer(this, ms2ns(transition_list[tid].TimeOut), Message(FSM_EVENT_TIMEOUT, tid)); + if (!timerId) + { + LOGE("[%s]:invalid timer id, can't schedule more!", state->Name); + } + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +int Fsm::CancelAfterTransitions() +{ + if (!dispatcher()) return -1; + + // Cancel timer for all after transitions that was scheduled. + dispatcher()->removeHandler(this, Dispatcher::TIMERS_MASK); + return 0; +} + +// ---------------------------------------------------------------------------- +bool Fsm::CallTransitionGuard(const FsmTransition *pTransition, const Message &pEvent) +{ + bool ret = true; + + if (pTransition->Guard) + ret = pTransition->Guard(this, pEvent); + + LOGV("[%s]:%s[%s]:guard%s %d", + m_pFsm[m_CurrStateId]->Name, + TransitionType (pTransition->Type), + pTransition->Name, + pTransition->Guard ? "()": "(none)", + ret); + return ret; +} + +// ---------------------------------------------------------------------------- +void Fsm::CallTransitionAction(const FsmTransition *pTransition, const Message &pEvent) +{ + LOGV("[%s]:%s[%s]:action%s", + m_pFsm[m_CurrStateId]->Name, + TransitionType (pTransition->Type), + pTransition->Name, + pTransition->Action ? "()": "(none)"); + if (pTransition->Action) + pTransition->Action(this, pEvent); +} + +// ---------------------------------------------------------------------------- +void Fsm::CallStateEntry() +{ + const FsmState *state = m_pFsm[m_CurrStateId]; + LOGV("[%s]:entry%s", + state->Name, + state->Entry ? "()": "(none)"); + if (state->Entry) + state->Entry(this); +} + +// ---------------------------------------------------------------------------- +void Fsm::CallStateExit() +{ + const FsmState *state = m_pFsm[m_CurrStateId]; + LOGV("[%s]:exit%s", + state->Name, + state->Exit ? "()": "(none)"); + if (state->Exit) + state->Exit(this); +} + +// ---------------------------------------------------------------------------- +// A utility static method that translates FSM status to a string. +const char* +Fsm::statusName (int Status) +{ + switch (Status) + { + case FSM_CONTEXT_STATUS_ERROR: return "Error"; + case FSM_CONTEXT_STATUS_OK: return "OK"; + case FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND: return "Transition not found"; + case FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED: return "Event not handled"; + case FSM_CONTEXT_STATUS_STATE_FINAL: return "State final"; + case FSM_CONTEXT_STATUS_STATE_NOT_FOUND: return "State not found"; + } + return "Unknown status"; +} + +// ---------------------------------------------------------------------------- +// A utility static method that translates FSM transition to a string. +const char* +Fsm::TransitionType (int Transition) +{ + switch (Transition) + { + case FSM_TRANSITION_NONE: return "TN"; + case FSM_TRANSITION: return "T"; + case FSM_TRANSITION_AFTER: return "TA"; + case FSM_TRANSITION_INTERNAL: return "TI"; + case FSM_TRANSITION_INTERNAL_AFTER: return "TIA"; + } + return "T?"; +} + +// ---------------------------------------------------------------------------- +// Debug print of FSM status +void +Fsm::logRetCode (int retCode, const FsmState *state, const Message &pEvent) +{ + if (FSM_CONTEXT_STATUS_STATE_FINAL == retCode) + { + LOGD("final state reached"); + } + else + { + if (FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED == retCode) + { + LOGW("[%s]:event[%d] not handled", state->Name, pEvent.Type); + } + else if (FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND == retCode) + { + LOGW("[%s]:no appropriate transition for this event[%d]", + state->Name, pEvent.Type); + } + else if (FSM_CONTEXT_STATUS_STATE_NOT_FOUND == retCode) + { + LOGW("[%s]:no appropriate state found for this event[%d]", + state->Name, pEvent.Type); + } + else + { + LOGE("[%s]:undefined status error - %d, event[%d]", + state->Name, retCode, pEvent.Type); + } + } +} + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Fsm.h b/third-party/libtm/libtm/src/infra/Fsm.h new file mode 100644 index 0000000000..3ea0c6b43f --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Fsm.h @@ -0,0 +1,334 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "Log.h" +#include "Dispatcher.h" + + +namespace perc { +// -[Defines]------------------------------------------------------------------ +/// C++ helpers macros that transform static FSM functions interface to class methods. +// Put these macros into header file (protected/private) section. +#define ACTION(Class, State, Event) Class::sAction_s ## State ## _e ## Event +#define GUARD(Class, State, Event) Class::sGuard_s ## State ## _e ## Event +#define ENTRY(Class, State) Class::sEntry_s ## State +#define EXIT(Class, State) Class::sExit_s ## State + +#define DECLARE_FSM_ACTION(State, Event) \ + static void sAction_s##State##_e##Event(Fsm *, const Message &); \ + void Action_s##State##_e##Event(const Message &) + +#define DECLARE_FSM_GUARD(State, Event) \ + static bool sGuard_s##State##_e##Event(Fsm *, const Message &); \ + bool Guard_s##State##_e##Event(const Message &) + +#define DECLARE_FSM_STATE_ENTRY(State) \ + static void sEntry_s##State(Fsm *); \ + void Entry_s##State() + +#define DECLARE_FSM_STATE_EXIT(State) \ + static void sExit_s##State(Fsm *); \ + void Exit_s##State() + +// Put these macros into source file and define body. +#define DEFINE_FSM_ACTION(Class, State, Event, Msg) \ + void ACTION(Class, State, Event)(Fsm *pContext, const Message &Msg) \ + { Class* pThis = reinterpret_cast (pContext->owner()); pThis->Action_s ## State ## _e ## Event(Msg); } \ + void Class::Action_s ## State ## _e ## Event(const Message &Msg) + +#define DEFINE_FSM_GUARD(Class, State, Event, Msg) \ + bool GUARD(Class, State, Event)(Fsm *pContext, const Message &Msg) \ + { Class* pThis = reinterpret_cast (pContext->owner()); return pThis->Guard_s##State##_e##Event(Msg); } \ + bool Class::Guard_s##State##_e##Event(const Message &Msg) + +#define DEFINE_FSM_STATE_ENTRY(Class, State) \ + void ENTRY(Class, State)(Fsm *pContext) \ + { Class* pThis = reinterpret_cast (pContext->owner()); pThis->Entry_s##State(); } \ + void Class::Entry_s##State() + +#define DEFINE_FSM_STATE_EXIT(Class, State) \ + void EXIT(Class, State)(Fsm *pContext) \ + { Class* pThis = reinterpret_cast (pContext->owner()); pThis->Exit_s##State(); } \ + void Class::Exit_s##State() + + +// -[FSM Event]---------------------------------------------------------------- +/// +/// Event types definitions. +/// +/// @note: event types - this is a user defined type exclude timeout event type. +/// User must define application specific events like this (offset from USER_DEFINED): +/// - #define EVENT_TYPE_0 (0 + FSM_EVENT_USER_DEFINED) +/// - #define EVENT_TYPE_1 (1 + FSM_EVENT_USER_DEFINED) +/// - ... +/// +#define FSM_EVENT_NONE ((char)-1) +#define FSM_EVENT_TIMEOUT 0 +#define FSM_EVENT_USER_DEFINED 1 + + +// -[FSM Transition]----------------------------------------------------------- +/// +/// Internal FSM transition type definitions. +/// +/// @note: Only for Infra::Fsm internal use. +/// +#define FSM_TRANSITION_NONE_NAME "NONE" +#define FSM_TRANSITION_NONE ((char)-1) +#define FSM_TRANSITION 0 +#define FSM_TRANSITION_AFTER 1 +#define FSM_TRANSITION_INTERNAL 2 +#define FSM_TRANSITION_INTERNAL_AFTER 3 +#define FSM_TRANSITION_UNCONDITIONAL 4 + +// Define max timeout of after transition. +#define FSM_TRANSITION_AFTER_MAX_TIMEOUT ((unsigned long)~0) + +// Guard and Action function prototypes. +class Fsm; +class FsmEvent; +typedef bool (*FsmTransitionGuard) (Fsm *, const Message &); +typedef void (*FsmTransitionAction)(Fsm *, const Message &); + + +// -[Infra:FSM State]---------------------------------------------------------- +/// +/// FSM state type definitions. +/// +/// @note: !!! WARNING - Forbidden defining state with typeid - (-1) +/// User must define application specific states like this(offset from USER_DEFINED): +/// - #define STATE_0 (0 + FSM_STATE_USER_DEFINED) +/// - #define STATE_1 (1 + FSM_STATE_USER_DEFINED) +/// - ... +/// +#define FSM_STATE_FINAL_NAME "FINAL" +#define FSM_STATE_FINAL ((char)-1) ///< !!! Don't use this define for user defined states +#define FSM_STATE_SAME 0 +#define FSM_STATE_USER_DEFINED 1 + +// State entry/exit functions prototypes. +typedef void (*FsmStateEntry)(Fsm *pFsmContext); +typedef void (*FsmStateExit) (Fsm *pFsmContext); + + +// -[FSM Definition]----------------------------------------------------------- +/// Transition definition +typedef struct FsmTransition_T +{ + const char *Name; + char Type; + char EventType; + char NewState; + FsmTransitionGuard Guard; + FsmTransitionAction Action; + unsigned long TimeOut; +} FsmTransition; + +/// Default NONE transition definition. +extern const FsmTransition s_FsmTransitionNone; + + +/// State definition +typedef struct FsmState_T +{ + const char *Name; + char Type; + FsmStateEntry Entry; + FsmStateExit Exit; + FsmTransition *TransitionList; +} FsmState; + +/// Final state default definitions +extern const FsmState s_FsmStateFinal; + + +/// Internal use: FSM loggings definition macros +# define FSM_NAME(_name) _name, + +/// Transition list definition macros +#define TRANSITION_INTERNAL(_event, _guard, _action) \ + { FSM_NAME (#_event) FSM_TRANSITION_INTERNAL, _event, FSM_STATE_SAME, _guard, _action, FSM_TRANSITION_AFTER_MAX_TIMEOUT }, + +#define TRANSITION_INTERNAL_AFTER(_guard, _action, _timeout) \ + { FSM_NAME ("INTERNAL_AFTER") FSM_TRANSITION_INTERNAL_AFTER, FSM_EVENT_TIMEOUT, FSM_STATE_SAME, _guard, _action, _timeout }, + +#define TRANSITION(_event, _guard, _action, _new_state_type) \ + { FSM_NAME (#_event) FSM_TRANSITION, _event, _new_state_type, _guard, _action, FSM_TRANSITION_AFTER_MAX_TIMEOUT }, + +#define TRANSITION_AFTER(_guard, _action, _new_state_type, _timeout) \ + { FSM_NAME ("AFTER") FSM_TRANSITION_AFTER, FSM_EVENT_TIMEOUT, _new_state_type, _guard, _action, _timeout }, + + +/// State definition macros +#define DECLARE_FSM_STATE(_state) \ + static const FsmState s_Fsm##_s##_state; \ + static const FsmTransition s_Fsm##_s##_state##_##TransitionList[] + +#define DEFINE_FSM_STATE_BEGIN(Class, _state) \ + const FsmTransition Class::s_Fsm##_s##_state##_##TransitionList[] = { + +#define DEFINE_FSM_STATE_END(Class, _state, _entry, _exit) \ + { FSM_NAME (FSM_TRANSITION_NONE_NAME) FSM_TRANSITION_NONE, FSM_EVENT_NONE, FSM_STATE_FINAL, 0, 0, FSM_TRANSITION_AFTER_MAX_TIMEOUT } }; \ + const FsmState Class::s_Fsm##_s##_state = \ + { FSM_NAME (#_state) _state, _entry, _exit, (FsmTransition *)Class::s_Fsm##_s##_state##_##TransitionList }; + + +/// Macros for defining FSM. +#define FSM(_fsm) s_Fsm##_##_fsm + +#define DEFINE_FSM_BEGIN(Class, _fsm) \ + const FsmState * const Class::FSM(_fsm)[] = { + +#define STATE(Class, _state) \ + &Class::s_Fsm##_s##_state, + +#define DEFINE_FSM_END() \ + &s_FsmStateFinal \ + }; + +#define DECLARE_FSM(_fsm) \ + static const FsmState * const FSM(_fsm)[]; + + +// ---------------------------------------------------------------------------- +/// +/// @class Fsm +/// +/// @brief FSM context class. +/// +// ---------------------------------------------------------------------------- +/// FSM engine handle event return codes. +#define FSM_CONTEXT_STATUS_ERROR (-1) +#define FSM_CONTEXT_STATUS_OK 0 +#define FSM_CONTEXT_STATUS_TRANSITION_NOT_FOUND 1 +#define FSM_CONTEXT_STATUS_UNCOND_TRANSITION_NOT_FOUND 2 +#define FSM_CONTEXT_STATUS_EVENT_NOT_HANDLED 3 +#define FSM_CONTEXT_STATUS_STATE_FINAL 4 +#define FSM_CONTEXT_STATUS_STATE_NOT_FOUND 5 + +#define FSM_CONTEXT_DEFAULT_NAME "Fsm" +#define FSM_CONTEXT_TIMER_LIST_LEN 8 + +class Fsm : public EventHandler +{ +public: + + Fsm (); + virtual ~Fsm (); + + /// Explicit init function. + int init (const FsmState * const *pFsm, + void *Owner = 0, + Dispatcher *pDispatcher = 0, + const char *Name = FSM_CONTEXT_DEFAULT_NAME); + + /// + /// FSM context main function - handle user events and run defined state machine. + /// Before calling this function user MUST call Init() function. + /// User MUST call this function from well defined (single) context. + /// + /// @param pEvent Event to proceed. + /// + /// @return See Infra FSM engine handle event return codes. + /// In case of successful event handling function returns - FSM_CONTEXT_STATUS_OK. + /// In another case function returns error code that user MUST check and process. + /// + /// @note May enter recursively !!! + /// + int fireEvent(const Message &); + + int done(); + + // = Get/Set methods + // + /// Get User param method. + void *owner() const { return m_Owner; } + void owner(void *pOwner) { m_Owner = pOwner; } + + // = Self event mechanism + // + // Call to function will enable recursive message processing without + // main dispatcher loop. User SHOULD call once to this function in function, + // and transitions only. + void selfEvent(const Message &selfEvent) + { + ASSERT(!m_SelfEvent); + m_SelfEvent = new Message(selfEvent); + ASSERT(m_SelfEvent); + } + + /// Utility + int getCurrentState (void) const + { + if (m_pFsm) + return m_pFsm[m_CurrStateId]->Type; + return FSM_STATE_FINAL; + } + + static const char *statusName (int Status); + +protected: + + // = Interface: EventHandler + // + void onMessage(const Message &msg) { fireEvent (msg); } + void onTimeout(uintptr_t timerId, const Message &msg) { fireEvent (msg); } + + // Get the dispatcher associated with this handler + Dispatcher *dispatcher() const { return m_Dispatcher; } + +private: + /// FSM context parameters. + const FsmState * const *m_pFsm; + void *m_Owner; + Dispatcher *m_Dispatcher; + int m_CurrStateId; + const char *m_Name; + + /// Self event + Message *m_SelfEvent; + void resetSelfEvent() { m_SelfEvent = 0; } + int processSelfEvent() + { + int ret = FSM_CONTEXT_STATUS_OK; + if (m_SelfEvent) + { + Message *savedSelfEvent = m_SelfEvent; + ret = fireEvent (*m_SelfEvent); + delete savedSelfEvent; + } + return ret; + } + + // = Internal functions + // + int InitNewState(int StateType); + int DoneCurrState(); + static const char *TransitionType(int Transition); + void logRetCode (int retCode, const FsmState *state, const Message &); + + /// + /// This function returns id of transition that wait for this event type in OUT parameter. + /// Checks guard function of transition. In case that we reached last state's defined transition + /// this function returns error - INFRA_FSM_TRANSITION_NONE. + /// + int FindTransition(int /*OUT*/ *, const Message &); + + /// Init after transition list: schedule timer via engine object + int ScheduleAfterTransitions(); + + /// Cancel all timers and reset after transition list + int CancelAfterTransitions(); + + bool CallTransitionGuard(const FsmTransition *, const Message &); + void CallTransitionAction(const FsmTransition *, const Message &); + void CallStateEntry(); + void CallStateExit(); +}; + + +// ---------------------------------------------------------------------------- +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Log.cpp b/third-party/libtm/libtm/src/infra/Log.cpp new file mode 100644 index 0000000000..47113020e7 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Log.cpp @@ -0,0 +1,351 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include "Log.h" +#include "Utils.h" +#include +#include +#include +#include +#include "../include/TrackingData.h" + +#ifdef _WIN32 +#include +#include +#include +#include +#endif + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#include +#define gettid() syscall(SYS_gettid) +#endif + +using namespace perc; + +/* Define TIME_OF_DAY_LOG_HEADER to enable time of day (HH:MM:SS.mmm) log */ +/* Undefine TIME_OF_DAY_LOG_HEADER to enable epoch time log */ +#define TIME_OF_DAY_LOG_HEADER + +#define MAX_LOG_CONTAINERS 2 + +// Default priority mask - ALL enabled +static int s_PriorityMask = /*LOG_PRI2MASK(LOG_VERBOSE) | */ +LOG_PRI2MASK(LOG_DEBUG) | +LOG_PRI2MASK(LOG_TRACE) | +LOG_PRI2MASK(LOG_INFO) | +LOG_PRI2MASK(LOG_WARN) | +LOG_PRI2MASK(LOG_ERROR); + +class AdvancedLog : public TrackingData::Log { +public: + AdvancedLog() : rolledOver(0) {}; + + uint8_t rolledOver; +}; + + +class LogConfiguration { +public: + uint32_t outputMode; + uint8_t verbosityMask; + uint8_t rolloverMode; +} ; + +class logManager { +public: + logManager() : activeContainer(0) + { + loadTimestamp = systemTime(); + configuration[LogSourceHost].verbosityMask = s_PriorityMask; + configuration[LogSourceHost].rolloverMode = true; + configuration[LogSourceHost].outputMode = LogOutputModeScreen; + }; + + nsecs_t loadTimestamp; + + std::mutex configurationMutex; + LogConfiguration configuration[LogSourceMax]; + + std::atomic activeContainer; + std::mutex logContainerMutex[MAX_LOG_CONTAINERS]; + AdvancedLog logContainer[MAX_LOG_CONTAINERS]; +}; + +logManager gLogManager; + +FILE *gStream = NULL; + +const char* logPrioritySign[] = { "U" ,/* LOG_UNKNOWN */ +"T" ,/* LOG_DEFAULT */ +"V" ,/* LOG_VERBOSE */ +"D" ,/* LOG_DEBUG */ +"T" ,/* LOG_TRACE */ +"I" ,/* LOG_INFO */ +"W" ,/* LOG_WARN */ +"E" ,/* LOG_ERROR */ +"F" ,/* LOG_FATAL */ +"S" ,/* LOG_SILENT */ }; + +const LogVerbosityLevel prio2verbosity[] = {None, None, Verbose, Debug, Trace, Info, Warning, Error, Error, None}; + +// LOG_FATAL is always enabled by default +#define isPriorityEnabled(prio) (LOG_FATAL == prio || (gLogManager.configuration[LogSourceHost].verbosityMask & LOG_PRI2MASK(prio))) + +void __perc_Log_write(int prio, const char *tag, const char *text) +{ + if (!isPriorityEnabled(prio)) + return; + + fprintf(stdout, "%s", text); + +} + + void __perc_Log_Save(void* deviceId, int prio, const char *tag, int line, int payloadLen, char* payload) +{ + std::lock_guard guardLogContainer(gLogManager.logContainerMutex[gLogManager.activeContainer]); + AdvancedLog* logContainer = &gLogManager.logContainer[gLogManager.activeContainer]; + uint32_t entries = logContainer->entries; + + if ((gLogManager.configuration[LogSourceHost].rolloverMode == 0) && (logContainer->rolledOver == 1)) + { + printf("rolled over - stopped saving prints on container %d, entries = %d...\n", (uint32_t)gLogManager.activeContainer, entries); + return; + } + + logContainer->entry[entries].timeStamp = systemTime() - gLogManager.loadTimestamp; + + HostLocalTime localTime = getLocalTime(); + + logContainer->entry[entries].localTimeStamp.year = localTime.year; + logContainer->entry[entries].localTimeStamp.month = localTime.month; + logContainer->entry[entries].localTimeStamp.dayOfWeek = localTime.dayOfWeek; + logContainer->entry[entries].localTimeStamp.day = localTime.day; + logContainer->entry[entries].localTimeStamp.hour = localTime.hour; + logContainer->entry[entries].localTimeStamp.minute = localTime.minute; + logContainer->entry[entries].localTimeStamp.second = localTime.second; + logContainer->entry[entries].localTimeStamp.milliseconds = localTime.milliseconds; + + logContainer->entry[entries].lineNumber = line; + + if (tag != NULL) + { + perc::copy(&logContainer->entry[entries].moduleID, tag, MAX_LOG_BUFFER_MODULE_SIZE); + } + + logContainer->entry[entries].verbosity = prio2verbosity[prio]; + logContainer->entry[entries].deviceID = (uint64_t)deviceId; + logContainer->entry[entries].threadID = getThreadId(); + + snprintf(logContainer->entry[entries].payload, payloadLen + 1, "%s", payload); + logContainer->entry[entries].payloadSize = payloadLen; + + if (entries == (MAX_LOG_BUFFER_ENTRIES-1)) + { + printf("entries %d, setting rollover = 1...\n", logContainer->entries); + logContainer->rolledOver = 1; + } + + logContainer->entries = (entries + 1) % MAX_LOG_BUFFER_ENTRIES; + +} + + +#ifdef __linux__ +int __perc_Log_print_header(char * buf, int bufSize, int prio, const char *tag, const char* deviceId) +{ + struct timeval tv; + int headerLen; + static struct timeval loadTime = { 0 }; + + gettimeofday(&tv, NULL); + +#ifdef TIME_OF_DAY_LOG_HEADER + + // Round to nearest millisec + int millisec = lrint(tv.tv_usec / 1000.0); + if (millisec >= 1000) + { + millisec -= 1000; + tv.tv_sec++; + } + + struct tm* tm_info = localtime(&tv.tv_sec); // Transform time to ASCII + int timeLen = 8; // Length of adding HH:MM:SS + strftime(buf, timeLen + 2, "%H:%M:%S", tm_info); // Adding HH:MM:SS/0 to buffer + headerLen = snprintf(buf + timeLen, bufSize - timeLen, ".%03d [%lu] [%s] %s%s: ", millisec, gettid(), logPrioritySign[prio], tag, deviceId) + timeLen; + +#else + if (loadTime.tv_sec == 0) + { + loadTime = tv; + } + + unsigned long currentTime = ((tv.tv_sec * 1000000) + tv.tv_usec) - ((loadTime.tv_sec * 1000000) + loadTime.tv_usec); + headerLen = snprintf(buf, bufSize, "%014lu [%lu] [%s] %s: ", currentTime, gettid(), logPrioritySign[prio], tag); + +#endif //TIME_OF_DAY_LOG_HEADER + + return headerLen; +} +#elif _WIN32 +int __perc_Log_print_header(char * buf, int bufSize, int prio, const char *tag, const char* deviceId) +{ + int headerLen = 0; + +#ifdef TIME_OF_DAY_LOG_HEADER + + SYSTEMTIME lt; + GetLocalTime(<); + + headerLen = snprintf(buf, bufSize, "%02d:%02d:%02d:%03d [%06lu] [%s] %s%s: ", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds, GetCurrentThreadId(), logPrioritySign[prio], tag, deviceId); + +#else + + static unsigned long long loadTime = 0; + FILETIME currentFileTime; + GetSystemTimeAsFileTime(¤tFileTime); + unsigned long long currentTime = (((ULONGLONG)currentFileTime.dwHighDateTime << 32) | (ULONGLONG)currentFileTime.dwLowDateTime) / 10000; + + if (loadTime == 0) + { + loadTime = currentTime; + } + + headerLen = snprintf(buf, bufSize, "%011llu [%06lu] [%s] %s: ", currentTime - loadTime, GetCurrentThreadId(), logPrioritySign[prio], tag); + +#endif // TIME_OF_DAY_LOG_HEADER + + return headerLen; + +} +#endif + +void __perc_Log_print(void* deviceId, int prio, const char *tag, int line, const char *fmt, ...) +{ + if (isPriorityEnabled(prio)) { + uint8_t outputMode; + uint8_t verbosity; + uint8_t rolloverMode; + int headerLen = 0; + int payloadLen = 0; + + va_list ap; + va_start(ap, fmt); + + __perc_Log_Get_Configuration(LogSourceHost, &outputMode, &verbosity, &rolloverMode); + + char buf[BUFSIZ * 4]; + + if (outputMode == LogOutputModeScreen) + { + char deviceBuf[30] = { 0 }; + if (deviceId != NULL) + { + short device = ((uintptr_t)deviceId & 0xFFFF); + snprintf(deviceBuf, sizeof(deviceBuf), "-%04hX", device); + } + + headerLen = __perc_Log_print_header(buf, sizeof(buf), prio, tag, deviceBuf); + + ASSERT((size_t)headerLen < sizeof(buf)); + payloadLen = vsnprintf(buf + headerLen, sizeof(buf) - headerLen, fmt, ap); + fprintf(stdout, "%s\n", buf); + } + else + { + payloadLen = vsnprintf(buf, sizeof(buf), fmt, ap); + __perc_Log_Save(deviceId, prio, tag, line, payloadLen, buf); + } + + va_end(ap); + } +} + +void __perc_Log_Set_Configuration(uint8_t source, uint8_t outputMode, uint8_t verbosity, uint8_t rolloverMode) +{ + std::lock_guard guardLogConfiguration(gLogManager.configurationMutex); + + gLogManager.configuration[source].outputMode = outputMode; + gLogManager.configuration[source].rolloverMode = rolloverMode; + + uint8_t verbosityMask = 0; + switch (verbosity) + { + case Trace: + verbosityMask |= LOG_PRI2MASK(LOG_TRACE); + case Verbose: + verbosityMask |= LOG_PRI2MASK(LOG_VERBOSE); + case Debug: + verbosityMask |= LOG_PRI2MASK(LOG_DEBUG); + case Warning: + verbosityMask |= LOG_PRI2MASK(LOG_WARN); + case Info: + verbosityMask |= LOG_PRI2MASK(LOG_INFO); + case Error: + verbosityMask |= LOG_PRI2MASK(LOG_ERROR); + case None: + verbosityMask |= 0; + } + + gLogManager.configuration[source].verbosityMask = verbosityMask; +} + +void __perc_Log_Get_Configuration(uint8_t source, uint8_t* outputMode, uint8_t* verbosity, uint8_t* rolloverMode) +{ + std::lock_guard guardLogConfiguration(gLogManager.configurationMutex); + + *outputMode = gLogManager.configuration[source].outputMode; + *verbosity = gLogManager.configuration[source].verbosityMask; + *rolloverMode = gLogManager.configuration[source].rolloverMode; +} + + +void __perc_Log_Get_Log(void* log) +{ + TrackingData::Log* logOutput = (TrackingData::Log*)log; + uint32_t entryIndex = 0; + + uint8_t activeContainer = gLogManager.activeContainer; + + gLogManager.activeContainer ^= 1; + + { + std::lock_guard guardLogContainer(gLogManager.logContainerMutex[activeContainer]); + AdvancedLog* logContainer = &gLogManager.logContainer[activeContainer]; + + if (logContainer->rolledOver == 1) + { + for (uint32_t i = logContainer->entries; i < MAX_LOG_BUFFER_ENTRIES; i++, entryIndex++) + { + logOutput->entry[entryIndex] = logContainer->entry[i]; + } + + logOutput->entries = MAX_LOG_BUFFER_ENTRIES; + } + else + { + logOutput->entries = logContainer->entries; + } + + for (uint32_t i = 0; i < logContainer->entries; i++, entryIndex++) + { + logOutput->entry[entryIndex] = logContainer->entry[i]; + } + + logOutput->maxEntries = MAX_LOG_BUFFER_ENTRIES; + + logContainer->entries = 0; + logContainer->rolledOver = 0; + + //printf("got log on container %d - %d entries (new container %d)\n", activeContainer, logOutput->entries, (uint32_t)gLogManager.activeContainer); + } +} diff --git a/third-party/libtm/libtm/src/infra/Log.h b/third-party/libtm/libtm/src/infra/Log.h new file mode 100644 index 0000000000..cde77a2042 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Log.h @@ -0,0 +1,134 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include +#include +#include +#include + +// Log priority values, in ascending priority order. +// Note: This definition is using the same priority order as android log subsystem. +typedef enum LogPriority { + LOG_UNKNOWN = 0, + LOG_DEFAULT, + LOG_VERBOSE, + LOG_DEBUG, + LOG_TRACE, + LOG_INFO, + LOG_WARN, + LOG_ERROR, + LOG_FATAL, + LOG_SILENT, +#define LOG_PRI2MASK(_pri) (1 << _pri) +} LogPriority; + +enum LogSource { + LogSourceHost = 0x0000, /**< Host Log Configuration */ + LogSourceFW = 0x0001, /**< FW Log Configuration */ + LogSourceMax = 0x0002, +}; + +// Normally we strip LOGV (VERBOSE messages) from release builds. +// You can modify this (for example with "#define LOG_NDEBUG 0" at the top of your source file) to change that behavior. +#ifndef NDEBUG +#define NDEBUG +#endif + +#ifndef LOG_NDEBUG +#ifdef NDEBUG +#define LOG_NDEBUG 1 +#else +#define LOG_NDEBUG 0 +#endif +#endif + +#ifndef LOG_TAG +#define LOG_TAG NULL +#endif + +// Macro to send a verbose log message using the current LOG_TAG. +#ifndef LOGV +#if LOG_NDEBUG +#define LOGV(...) ((void)0) +#else +#define LOGV(...) ((void)LOG(NULL, LOG_VERBOSE, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif +#endif + +#ifndef DEVICELOGV +#if LOG_NDEBUG +#define DEVICELOGV(...) ((void)0) +#else +#define DEVICELOGV(...) ((void)LOG(this, LOG_VERBOSE, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif +#endif + +// Macros to send a priority log message using the current LOG_TAG. +#ifndef LOGD +#define LOGD(...) ((void)LOG(NULL, LOG_DEBUG, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOGT +#define LOGT(...) ((void)LOG(NULL, LOG_TRACE, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOGI +#define LOGI(...) ((void)LOG(NULL, LOG_INFO, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOGW +#define LOGW(...) ((void)LOG(NULL, LOG_WARN, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOGE +#define LOGE(...) ((void)LOG(NULL, LOG_ERROR, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOGF +#define LOGF(...) ((void)LOG(NULL, LOG_FATAL, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGD +#define DEVICELOGD(...) ((void)LOG(this, LOG_DEBUG, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGT +#define DEVICELOGT(...) ((void)LOG(this, LOG_TRACE, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGI +#define DEVICELOGI(...) ((void)LOG(this, LOG_INFO, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGW +#define DEVICELOGW(...) ((void)LOG(this, LOG_WARN, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGE +#define DEVICELOGE(...) ((void)LOG(this, LOG_ERROR, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef DEVICELOGF +#define DEVICELOGF(...) ((void)LOG(this, LOG_FATAL, LOG_TAG, __LINE__, __VA_ARGS__)) +#endif + +#ifndef LOG +#define LOG(_deviceId, _prio, _tag, _line, ...) \ + __perc_Log_print(_deviceId, _prio, _tag, _line, __VA_ARGS__) +#endif + +#define ASSERT assert +#define UNUSED(_var) (void)(_var) + +// platform depended print declaration +void __perc_Log_write(int prio, const char *tag, const char *text); +void __perc_Log_print(void* deviceId, int prio, const char *tag, int line, const char *fmt, ...); +int __perc_Log_print_header(char * buf, int bufSize, int prio, const char *tag, const char* deviceId); +void __perc_Log_Save(void* deviceId, int prio, int payloadLen, char* payload); +void __perc_Log_Set_Configuration(uint8_t source, uint8_t outputMode, uint8_t verbosity, uint8_t rolloverMode); +void __perc_Log_Get_Configuration(uint8_t source, uint8_t* outputMode, uint8_t* verbosityMask, uint8_t* rolloverMode); +void __perc_Log_Get_Log(void* log); + diff --git a/third-party/libtm/libtm/src/infra/Poller.h b/third-party/libtm/libtm/src/infra/Poller.h new file mode 100644 index 0000000000..7190c35952 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Poller.h @@ -0,0 +1,62 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include +#include +#include +#include +#include "TrackingCommon.h" + +namespace perc { + + class Poller + { + public: + + struct event + { + Handle handle; + unsigned long mask; + void *act; + event() : + handle(ILLEGAL_HANDLE), mask(Poller::NULL_MASK), act(0) {} + event(Handle h, unsigned long m, void *a) : + handle(h), mask(m), act(a) {} + }; + + Poller(); + ~Poller(); + bool add(const Poller::event &); + bool remove(Handle); + + // POLLING LOOP + // + // Returns: + // >0 - the total number of events + // 0 - if the elapsed without dispatching any handle + // -1 - if an error occurs + int poll(Poller::event &, unsigned long timeoutMs); + + public: + + static const int NULL_MASK; + static const int READ_MASK; + static const int WRITE_MASK; + static const int EXCEPT_MASK; + static const int ALL_MASK; + + + private: + + struct CheshireCat; + std::unique_ptr mData; + + // Prevent assignment and initialization. + void operator= (const Poller &) = delete; + Poller(const Poller &) = delete; + }; + +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Poller_lin.cpp b/third-party/libtm/libtm/src/infra/Poller_lin.cpp new file mode 100644 index 0000000000..90050cf34e --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Poller_lin.cpp @@ -0,0 +1,112 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#define LOG_TAG "infra/Poller" +#define LOG_NDEBUG 1 // controls LOGV only +#include "Log.h" +#include "Utils.h" +#include "Poller.h" +#include "TrackingCommon.h" +#include +#include + +namespace perc +{ + + struct Poller::CheshireCat + { + std::unordered_map mEvents; + std::mutex mEventsGuard; + int mEpoll; + }; + + const int Poller::READ_MASK = EPOLLIN | EPOLLRDNORM; + const int Poller::NULL_MASK = 0; + const int Poller::WRITE_MASK = EPOLLOUT | EPOLLWRNORM; + const int Poller::EXCEPT_MASK = EPOLLPRI | EPOLLERR | EPOLLHUP | EPOLLRDHUP; + const int Poller::ALL_MASK = READ_MASK | WRITE_MASK | EXCEPT_MASK; + + Poller::Poller() : mData(new CheshireCat()) { + mData->mEpoll = ::epoll_create(1); + ASSERT(mData->mEpoll != -1); + } + + Poller::~Poller() + { + ::close(mData->mEpoll); + } + + bool Poller::add(const Poller::event &evt) + { + int res = -1; + if (evt.handle != -1) { + struct epoll_event e; + e.events = evt.mask; + e.data.fd = evt.handle; + // synchronized section + std::lock_guard guard(mData->mEventsGuard); + if (mData->mEvents.count(evt.handle) == 0) { + // adding new one + res = ::epoll_ctl(mData->mEpoll, EPOLL_CTL_ADD, evt.handle, &e); + if (!res) + mData->mEvents.emplace(evt.handle, evt); + } + else { + // modifying old one + res = ::epoll_ctl(mData->mEpoll, EPOLL_CTL_MOD, evt.handle, &e); + if (!res) + mData->mEvents[evt.handle] = evt; + } + } + return res == 0; + } + + bool Poller::remove(Handle handle) + { + if (handle != -1) { + //ASSERT(::pthread_equal(tid, ::pthread_self())); + std::lock_guard guard(mData->mEventsGuard); + if (mData->mEvents.count(handle) > 0) { + int res = ::epoll_ctl(mData->mEpoll, EPOLL_CTL_DEL, handle, 0); + mData->mEvents.erase(handle); + return res == 0; + } + } + return false; + } + + int Poller::poll(Poller::event &evt, unsigned long timeoutMs) + { + struct epoll_event e; + int timeoutAbs = (int)timeoutMs == INFINITE ? INFINITE : ns2ms(systemTime()) + (int)timeoutMs; + again: + int n = ::epoll_wait(mData->mEpoll, &e, 1, (int)timeoutMs); + if (n > 0) { + std::lock_guard guard(mData->mEventsGuard); + LOGV("poll: e.data.fd %d", e.data.fd); + if (mData->mEvents.count(e.data.fd) > 0) { + evt = mData->mEvents[e.data.fd]; + } + else { + ::epoll_ctl(mData->mEpoll, EPOLL_CTL_DEL, e.data.fd, 0); + int cur = ns2ms(systemTime()); + if ((int)timeoutMs != INFINITE) { + if (cur < (int)timeoutAbs) + timeoutMs = timeoutAbs - cur; + else + return 0; + } + goto again; + } + } + else if (n == -1) { + // TODO: add logs or clear errors + LOGE("poll: epoll_wait error %d", errno); + } + // timeout + return n; + } + +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Poller_win.cpp b/third-party/libtm/libtm/src/infra/Poller_win.cpp new file mode 100644 index 0000000000..1b5f0670c4 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Poller_win.cpp @@ -0,0 +1,154 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#define LOG_TAG "Poller" +#define LOG_NDEBUG 1 // controls LOGV only +#include "Log.h" +#include "Utils.h" +#include "Poller.h" +#include "TrackingCommon.h" + + +namespace perc +{ + + struct Poller::CheshireCat + { + std::unordered_map mEvents; + std::mutex mEventsGuard; + + std::mutex mAddRemoveMutex; + std::vector mHandles; + HANDLE mAddRemoveDoneEvent; + }; + + const int Poller::READ_MASK = 0; + const int Poller::NULL_MASK = 0; + const int Poller::WRITE_MASK = 0; + const int Poller::EXCEPT_MASK = 0; + const int Poller::ALL_MASK = 0; + + + Poller::Poller() : mData(new CheshireCat()) + { + mData->mAddRemoveDoneEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + NULL // Unnamed event + ); + ASSERT(mData->mAddRemoveDoneEvent != ILLEGAL_HANDLE); + + mData->mHandles.push_back(mData->mAddRemoveDoneEvent); + } + + Poller::~Poller() + { + CloseHandle(mData->mAddRemoveDoneEvent); + } + + bool Poller::add(const Poller::event &evt) + { + // Take Add remove/mutex + std::lock_guard guardAddRemove(mData->mAddRemoveMutex); + + // Notify poll thread to exit and release mEventsGuard + SetEvent(mData->mAddRemoveDoneEvent); + + // Take mEventsGuard - wait for poll thread to release the mutex + std::lock_guard guardEvents(mData->mEventsGuard); + + ResetEvent(mData->mAddRemoveDoneEvent); + + //check if this handle already exists + auto it = std::find(mData->mHandles.begin(), mData->mHandles.end(), evt.handle); + + // we are already at maximum events + if (mData->mHandles.size() >= MAXIMUM_WAIT_OBJECTS && it == mData->mHandles.end()) + return false; + + if (it == mData->mHandles.end()) + mData->mHandles.push_back(evt.handle); + + // Add or modify event in events map + mData->mEvents[evt.handle] = evt; + + return true; + } + + bool Poller::remove(Handle handle) + { + // Take Add remove/mutex + std::lock_guard guardAddRemove(mData->mAddRemoveMutex); + + // Notify poll thread to exit and release mEventsGuard + SetEvent(mData->mAddRemoveDoneEvent); + + // Take mEventsGuard - wait for poll thread to release the mutex + std::lock_guard guardEvents(mData->mEventsGuard); + + ResetEvent(mData->mAddRemoveDoneEvent); + + //check if this handle already exists + auto it = std::find(mData->mHandles.begin(), mData->mHandles.end(), handle); + + if (it == mData->mHandles.end()) + return false; + + // Remove the handle from vector + mData->mHandles.erase(it); + + // remove the evt from events map + mData->mEvents.erase(handle); + + return true; + } + + int Poller::poll(Poller::event &evt, unsigned long timeoutMs) + { + int timeoutAbs = (timeoutMs == INFINITE)? INFINITE : ns2ms(systemTime()) + (int)timeoutMs; + + while (true) + { + int exitReason; + { + // Take events guard, to wait safely on vector of handles + std::lock_guard guardEvents(mData->mEventsGuard); + + exitReason = WaitForMultipleObjects((DWORD)mData->mHandles.size(), + mData->mHandles.data(), + FALSE, + timeoutMs); + + int eventIndex = exitReason - WAIT_OBJECT_0; + if ((exitReason < 0) || ((unsigned int)eventIndex >= mData->mHandles.size())) + { + return 0; + } + else if (eventIndex != 0) // zero is special case - it is our private event + { + evt = mData->mEvents[mData->mHandles[eventIndex]]; + return 1; + } + } // here we release the events guard, so Add/remove function may continue + + // WAIT_OBJECT_0 means add/remove function called, lets wait for it completion by just waiting for the lock + { + std::lock_guard guardAddRemove(mData->mAddRemoveMutex); + + // we are going to a second iteration - update timeout + if (timeoutMs != INFINITE) + { + auto nowMs = ns2ms(systemTime()); + if (nowMs >= timeoutAbs) + timeoutMs = 0; + else + timeoutMs = timeoutAbs - nowMs; + } + } + } + return 0; + } +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Semaphore.h b/third-party/libtm/libtm/src/infra/Semaphore.h new file mode 100644 index 0000000000..371b352527 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Semaphore.h @@ -0,0 +1,40 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once +#include "Utils.h" +#include +/* +* Wrapper class for pthread semaphore implementation. +*/ + + +namespace perc +{ + + class Semaphore + { + public: + Semaphore(unsigned int initValue = 0); + + int get(nsecs_t timeoutNs = -1); + int put(); + + ~Semaphore(); + + private: + + struct CheshireCat; + + std::unique_ptr mData; + + // = Prevent assignment and initialization. + void operator= (const Semaphore &) = delete; + Semaphore(const Semaphore &) = delete; + + }; + + +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Semaphore_lin.cpp b/third-party/libtm/libtm/src/infra/Semaphore_lin.cpp new file mode 100644 index 0000000000..7fdfc94353 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Semaphore_lin.cpp @@ -0,0 +1,49 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include "Semaphore.h" +#include "Utils.h" +#include + +namespace perc +{ + + struct Semaphore::CheshireCat + { + ::sem_t m_Semaphore; + }; + + int Semaphore::get(nsecs_t timeoutNs) + { + if (timeoutNs == -1) + return ::sem_wait(&mData->m_Semaphore); + if (timeoutNs == 0) + return ::sem_trywait(&mData->m_Semaphore); + nsecs_t nsecsTime = systemTime() + timeoutNs; + struct timespec absTimeout; + absTimeout.tv_sec = ns2s(nsecsTime); + absTimeout.tv_nsec = nsecsTime % 1000000000; + return ::sem_timedwait(&mData->m_Semaphore, &absTimeout); + } + + + Semaphore::Semaphore(unsigned int initValue) : mData(new CheshireCat()) + { + ::sem_init(&mData->m_Semaphore, 0, initValue); + } + + Semaphore::~Semaphore() + { + ::sem_destroy(&mData->m_Semaphore); + } + + int Semaphore::put() + { + return ::sem_post(&mData->m_Semaphore); + } + + + +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Semaphore_win.cpp b/third-party/libtm/libtm/src/infra/Semaphore_win.cpp new file mode 100644 index 0000000000..5fac93e5a5 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Semaphore_win.cpp @@ -0,0 +1,63 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#include "Semaphore.h" +#include "Utils.h" +#include +#include + +#define MAX_SEMAPHORE_COUNT 128 + +namespace perc { + + struct Semaphore::CheshireCat + { + HANDLE m_Semaphore; + }; + + int Semaphore::get(nsecs_t timeoutNs) + { + DWORD timeoutMs; + + timeoutMs = ns2ms(timeoutNs); + + auto ret = WaitForSingleObject(mData->m_Semaphore, timeoutMs); + + if (ret == WAIT_OBJECT_0) // well this is useless , since WAIT_OBJECT_0 is equal to 0, but at least this is clear; + return 0; + + return -1; + } + + + Semaphore::Semaphore(unsigned int initValue) : mData(new CheshireCat()) + { + mData->m_Semaphore = CreateSemaphore( + NULL, // default security attributes + initValue, // initial count + MAX_SEMAPHORE_COUNT, // maximum count + NULL); + + assert(mData->m_Semaphore != NULL); + } + + Semaphore::~Semaphore() + { + CloseHandle(mData->m_Semaphore); + } + + int Semaphore::put() + { + int ret = ReleaseSemaphore( + mData->m_Semaphore, // handle to semaphore + 1, // increase count by one + NULL); // not interested in previous count + + return !ret; // return zero on success; same as in linux + } + + + +} // namespace perc diff --git a/third-party/libtm/libtm/src/infra/Utils.cpp b/third-party/libtm/libtm/src/infra/Utils.cpp new file mode 100644 index 0000000000..0713f5c5a0 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Utils.cpp @@ -0,0 +1,163 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ +#define LOG_TAG "Utils" +#include "Utils.h" +#include +#include + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#define gettid() syscall(SYS_gettid) +#else +#include +#include +#endif + +nsecs_t systemTime() +{ +#ifdef _WIN32 + /* + auto start = std::chrono::high_resolution_clock::now(); + std::chrono::time_point time_point_ns(start); + return time_point_ns.time_since_epoch().count();*/ + LARGE_INTEGER StartingTime; + LARGE_INTEGER Frequency; + + QueryPerformanceFrequency(&Frequency); + QueryPerformanceCounter(&StartingTime); + + StartingTime.QuadPart *= 1000000LL; // convert to microseconds + StartingTime.QuadPart /= Frequency.QuadPart; + StartingTime.QuadPart *= 1000; // Convert to nano seconds + + return StartingTime.QuadPart; + +#else + struct timespec ts; + ts.tv_sec = ts.tv_nsec = 0; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ((nsecs_t)(ts.tv_sec)) * 1000000000LL + ts.tv_nsec; +#endif +} + + +HostLocalTime getLocalTime() +{ + HostLocalTime localTime; + +#ifdef _WIN32 + SYSTEMTIME lt; + GetLocalTime(<); + + localTime.year = lt.wYear; + localTime.month = lt.wMonth; + localTime.dayOfWeek = lt.wDayOfWeek; + localTime.day = lt.wDay; + localTime.hour = lt.wHour; + localTime.minute = lt.wMinute; + localTime.second = lt.wSecond; + localTime.milliseconds = lt.wMilliseconds; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + + // Round to nearest millisec + int millisec = lrint(tv.tv_usec / 1000.0); + if (millisec >= 1000) + { + millisec -= 1000; + tv.tv_sec++; + } + + struct tm* tm_info = localtime(&tv.tv_sec); // Transform time to ASCII + + localTime.year = tm_info->tm_year; + localTime.month = tm_info->tm_mon; + localTime.dayOfWeek = tm_info->tm_wday; + localTime.day = tm_info->tm_mday; + localTime.hour = tm_info->tm_hour; + localTime.minute = tm_info->tm_min; + localTime.second = tm_info->tm_sec; + localTime.milliseconds = millisec; +#endif + + return localTime; +} + + +uint64_t bytesSwap(uint64_t val) +{ +#ifdef _WIN32 + return _byteswap_uint64(val); +#else + return htobe64(val); +#endif +} + +uint32_t getThreadId(void) +{ +#ifdef _WIN32 + return GetCurrentThreadId(); +#else + return gettid(); +#endif +} + + +void perc::copy(void* dst, void const* src, size_t size) +{ +#ifdef _WIN32 + memcpy_s(dst, size, src, size); +#else + auto from = reinterpret_cast(src); + std::copy(from, from + size, reinterpret_cast(dst)); +#endif +} + + +size_t perc::stringLength(const char* string, size_t maxSize) +{ +#ifdef _WIN32 + return strnlen_s(string, maxSize); +#else + return strnlen(string, maxSize); +#endif +} + +bool setProcessPriorityToRealtime() +{ +#ifdef _WIN32 + HANDLE pid = GetCurrentProcess(); + if (!SetPriorityClass(pid, REALTIME_PRIORITY_CLASS)) + { + LOGE("Error: Failed to set process priority to runtime"); + return false; + } + // Display priority class + DWORD dwPriClass = GetPriorityClass(pid); + LOGD("Setting process priority to 0x%X", dwPriClass); +#else + const int MAX_NICENESS = -20; + + id_t pid = getpid(); + int ret = setpriority(PRIO_PROCESS, pid, MAX_NICENESS); + if (ret == -1) + { + LOGE("Error: Failed to set process priority - to set priority rerun with sudo\n"); + return true; + } + // Display priority class + ret = getpriority(PRIO_PROCESS, pid); + LOGD("Setting process priority to 0x%X", ret); +#endif + + return true; +} + diff --git a/third-party/libtm/libtm/src/infra/Utils.h b/third-party/libtm/libtm/src/infra/Utils.h new file mode 100644 index 0000000000..94039f2e39 --- /dev/null +++ b/third-party/libtm/libtm/src/infra/Utils.h @@ -0,0 +1,86 @@ +/******************************************************************************* +INTEL CORPORATION PROPRIETARY INFORMATION +Copyright(c) 2017 Intel Corporation. All Rights Reserved. +*******************************************************************************/ + +#pragma once + +#include +#include "Log.h" + +#ifdef _WIN32 +#include +#else +#include +#define INFINITE (-1) +#endif + +class HostLocalTime { +public: + HostLocalTime() : year(0), month(0), dayOfWeek(0), day(0), hour(0), minute(0), second(0), milliseconds(0) {} + uint16_t year; + uint16_t month; + uint16_t dayOfWeek; + uint16_t day; + uint16_t hour; + uint16_t minute; + uint16_t second; + uint16_t milliseconds; +}; + +HostLocalTime getLocalTime(); + +#ifdef __cplusplus +extern "C" { +#endif + typedef int64_t nsecs_t; + + inline nsecs_t s2ns(nsecs_t sec) + { + return (sec < 0) ? INFINITE : sec * 1000000000; + } + + inline nsecs_t ms2ns(nsecs_t ms) + { + return (ms < 0) ? INFINITE : ms * 1000000; + } + + inline int ns2s(nsecs_t ns) + { + return (ns < 0) ? INFINITE : (int)(ns / 1000000000); + } + + inline int ns2ms(nsecs_t ns) + { + return (ns < 0) ? INFINITE : (int)(ns / 1000000); + } + + nsecs_t systemTime(); + + uint64_t bytesSwap(uint64_t val); + + uint32_t getThreadId(void); + + inline int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime) + { + int timeoutDelayMsec = 0; + + if (timeoutTime > referenceTime) + { + timeoutDelayMsec = (int)(((uint64_t)(timeoutTime - referenceTime) + 999999LL) / 1000000LL); + } + + return timeoutDelayMsec; + } + + bool setProcessPriorityToRealtime(); + + namespace perc + { + void copy(void* dst, void const* src, size_t size); + size_t stringLength(const char* string, size_t maxSize); + } + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/third-party/libtm/tools/CMakeLists.txt b/third-party/libtm/tools/CMakeLists.txt new file mode 100644 index 0000000000..2efa998e4e --- /dev/null +++ b/third-party/libtm/tools/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 2.8) +project(libtm_samples) + +MESSAGE("--------------------------------------------------------------------------------------------------------------------------------------------------------------") +MESSAGE("Building all projects of ${PROJECT_NAME}") + +add_subdirectory(libtm_util) diff --git a/third-party/libtm/tools/libtm_util/CMakeLists.txt b/third-party/libtm/tools/libtm_util/CMakeLists.txt new file mode 100644 index 0000000000..9ae2428614 --- /dev/null +++ b/third-party/libtm/tools/libtm_util/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 2.8) + +project(libtm_util) +MESSAGE("Building project ${PROJECT_NAME}") + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +endif() + +if (UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") +endif (UNIX) + +# Specify include files search directories for the compiler +include_directories(${LIBTM_INCLUDE_DIR}) +include_directories(${LIBTM_SRC_DIR}) +include_directories(${INFRA_INCLUDE_DIR}) + +IF(WIN32) + set(SOURCE_FILE libtm_util.cpp) + +# Add source Files +set(SOURCE_FILES + ${SOURCE_FILE} +) + +# Link all source files to a single binary named hello_perc +add_executable(${PROJECT_NAME} + ${SOURCE_FILES} +) + +# Link hello_perc binary against pthread and libtm libraries +target_link_libraries(${PROJECT_NAME} + tm +) + +set_target_properties (${PROJECT_NAME} PROPERTIES FOLDER Tools) + +install( + TARGETS + ${PROJECT_NAME} + RUNTIME DESTINATION + ${CMAKE_INSTALL_PREFIX}/bin +) +ENDIF(WIN32) \ No newline at end of file diff --git a/third-party/libtm/tools/libtm_util/libtm_util.cpp b/third-party/libtm/tools/libtm_util/libtm_util.cpp new file mode 100644 index 0000000000..609a75112d --- /dev/null +++ b/third-party/libtm/tools/libtm_util/libtm_util.cpp @@ -0,0 +1,4462 @@ +#define LOG_TAG "libtmutil" +#include "Log.h" +#include "TrackingManager.h" +#include "TrackingSerializer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Version.h" +#include "Utils.h" +#include "fw.h" + +#define LIBTM_UTIL_VERSION_MAJOR 1 +#define LIBTM_UTIL_VERSION_MINOR 0 + +#define MAX_RUN_TIME_SEC 86400 /* 24 hours */ +#define MIN_RUN_TIME_SEC 2 +#define START_STREAM_TIME_SEC 10 +#define STOP_STREAM_TIME_SEC 2 +#define RESET_DEVICE_TIME_SEC 5 +#define MAX_SUPPORTED_RAW_STREAMS 20 +#define MAX_START_STOP_LOOP_COUNT 1 +#define WAIT_FOR_DEVICE_SEC 1 +#define WAIT_FOR_CONTROLLERS_MSEC 100 +#define MAX_WAIT_FOR_CONTROLLERS_MSEC (3000 * WAIT_FOR_CONTROLLERS_MSEC) +#define MAX_WAIT_FOR_CALIBRATED_CONTROLLERS_MSEC (600 * WAIT_FOR_CONTROLLERS_MSEC) +#define WAIT_FOR_LOCALIZATION_SEC 1 +#define MAX_WAIT_FOR_LOCALIZATION_SEC (WAIT_FOR_LOCALIZATION_SEC * 10) +#define MAX_FIRST_POSE_DELAY_MSEC 1200 +#define MAX_LOCALIZATION_MAP_SIZE 104857600 /* 100 MB*/ +#define CONTROLLER_CONNECT_TIMEOUT_MSEC 16000 +#define MAX_WAIT_FOR_LOG_MSEC 500 +#define MIN_WAIT_FOR_LOG_MSEC 4 +#define WAIT_FOR_LOG_MULTIPLIER_STEP 5 +#define MAX_WAIT_FOR_IMAGE_MSEC 100 +#define TEMPERATURE_THRESHOLD_OVERRIDE_KEY 0xC10F +#define MAX_WAIT_FOR_STATIC_NODE_MSEC 10 +#define MAX_FRAME_LATENCY_MSEC 100 + +/* Max float accuracy */ +typedef std::numeric_limits flt; + +std::ostringstream poseCSV; +std::ostringstream poseTUM; +std::ostringstream videoCSV; +std::ostringstream gyroCSV; +std::ostringstream velocimeterCSV; +std::ostringstream accelerometerCSV; +std::ostringstream controllerCSV; +std::ostringstream rssiCSV; +char gFileHeaderName[80]; + +using namespace std; +shared_ptr gManagerInstance; + +#ifdef _WIN32 +using namespace perc; + + +const std::string statusToString(perc::Status status) +{ + switch (status) + { + case Status::SUCCESS: return "SUCCESS"; + case Status::COMMON_ERROR: return "COMMON_ERROR"; + case Status::FEATURE_UNSUPPORTED: return "FEATURE_UNSUPPORTED"; + case Status::ERROR_PARAMETER_INVALID: return "ERROR_PARAMETER_INVALID"; + case Status::INIT_FAILED: return "INIT_FAILED"; + case Status::ALLOC_FAILED: return "ALLOC_FAILED"; + case Status::ERROR_USB_TRANSFER: return "ERROR_USB_TRANSFER"; + case Status::ERROR_EEPROM_VERIFY_FAIL: return "ERROR_EEPROM_VERIFY_FAIL"; + case Status::ERROR_FW_INTERNAL: return "ERROR_FW_INTERNAL"; + case Status::BUFFER_TOO_SMALL: return "BUFFER_TOO_SMALL"; + case Status::NOT_SUPPORTED_BY_FW: return "NOT_SUPPORTED_BY_FW"; + case Status::DEVICE_BUSY: return "DEVICE_BUSY"; + case Status::TIMEOUT: return "TIMEOUT"; + case Status::TABLE_NOT_EXIST: return "TABLE_NOT_EXIST"; + case Status::TABLE_LOCKED: return "TABLE_LOCKED"; + case Status::DEVICE_STOPPED: return "DEVICE_STOPPED"; + case Status::TEMPERATURE_WARNING: return "TEMPERATURE_WARNING"; + case Status::TEMPERATURE_STOP: return "TEMPERATURE_STOP"; + case Status::CRC_ERROR: return "CRC_ERROR"; + case Status::INCOMPATIBLE: return "INCOMPATIBLE"; + case Status::SLAM_NO_DICTIONARY: return "SLAM_NO_DICTIONARY"; + case Status::NO_CALIBRATION_DATA: return "NO_CALIBRATION_DATA"; + case Status::SLAM_LOCALIZATION_DATA_SET_SUCCESS: return "SLAM_LOCALIZATION_DATA_SET_SUCCESS"; + case Status::SLAM_ERROR_CODE_VISION: return "SLAM_ERROR_CODE_VISION"; + case Status::SLAM_ERROR_CODE_SPEED: return "SLAM_ERROR_CODE_SPEED"; + case Status::SLAM_ERROR_CODE_OTHER: return "SLAM_ERROR_CODE_OTHER"; + case Status::CONTROLLER_CALIBRATION_VALIDATION_FAILURE: return "CONTROLLER_CALIBRATION_VALIDATION_FAILURE"; + case Status::CONTROLLER_CALIBRATION_FLASH_ACCESS_FAILURE: return "CONTROLLER_CALIBRATION_FLASH_ACCESS_FAILURE"; + case Status::CONTROLLER_CALIBRATION_IMU_FAILURE: return "CONTROLLER_CALIBRATION_IMU_FAILURE"; + case Status::CONTROLLER_CALIBRATION_INTERNAL_FAILURE: return "CONTROLLER_CALIBRATION_INTERNAL_FAILURE"; + default: return "UNKNOWN STATUS"; + } +} + +const char* fwLogVerbosityLevel(LogVerbosityLevel level) +{ + switch (level) + { + case Error: return "E"; + case Info: return "I"; + case Warning: return "W"; + case Debug: return "D"; + case Verbose: return "V"; + case Trace: return "T"; + } + return "X"; +} + +class CalibrationInfo { +public: + CalibrationInfo() : accelerometerBiasX(0), accelerometerBiasY(0), accelerometerBiasZ(0) { + memset(accelerometerScale, 0, sizeof(accelerometerScale)); + } + float accelerometerScale[3][3]; + float accelerometerBiasX; + float accelerometerBiasY; + float accelerometerBiasZ; +}; + +class UtilTimeStamps { +public: + class Loadtime { + public: + unsigned long long fw; + unsigned long long host; + unsigned long long hostCorrelated; + } loadTime; + + UtilTimeStamps(TrackingData::TimestampedData timeStampData) + { + loadTime.fw = timeStampData.timestamp; + loadTime.hostCorrelated = timeStampData.systemTimestamp; + }; + + void setTime(TrackingData::TimestampedData timeStampData) + { + hostCurrentSystemTime = std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + if (loadTime.host == 0) + { + loadTime.host = hostCurrentSystemTime; + } + hostTimeStamp = hostCurrentSystemTime - loadTime.host; + hostCorrelatedTimeStamp = timeStampData.systemTimestamp - loadTime.hostCorrelated; + fwTimeStamp = timeStampData.timestamp; + arrivalTimeStamp = timeStampData.arrivalTimeStamp; + latency = hostCurrentSystemTime - timeStampData.systemTimestamp; + } + + unsigned long long fwTimeStamp; + unsigned long long hostTimeStamp; + unsigned long long hostCorrelatedTimeStamp; + unsigned long long arrivalTimeStamp; + unsigned long long hostCurrentSystemTime; + unsigned long long latency; +}; + +enum LocalizationType +{ + LocalizationTypeGet = 0, + LocalizationTypeSet = 1, + LocalizationTypeReset = 2, + LocalizationTypeMax = 3, +}; + +enum ControllerBurnState +{ + ControllerBurnStateNotStarted = 0, /* Controller FW burn not started */ + ControllerBurnStateProcessing = 1, /* Controller FW burn is currently in process */ + ControllerBurnStateDone = 2, /* Controller FW burn is done */ + ControllerBurnStateMax = 3, +}; + +enum ControllerBurnConfigure +{ + ControllerBurnDisabled = 0, /* Don't burn controller on discovery */ + ControllerBurnOnNewVersion = 1, /* Burn controller on discovery if image version is newer than discovery version */ + ControllerBurnForce = 2, /* Burn controller on discovery */ + ControllerBurnMax = 3, +}; + +class ArgumentConfigurarion { +public: + ArgumentConfigurarion() : inputFilename(""), controllerDataFilename(""), velocimeterFilename(""), nodeFilename(""), temperature{0}, maxLoop(MAX_START_STOP_LOOP_COUNT), startStreamTime(START_STREAM_TIME_SEC), + resetLoop(0), statistics(false), jtag(false), errorCheck(false), errorExit(false), videoFile(false), videoCount(0), gyroCount(0), velocimeterCount(0), accelerometerCount(0), sixdofCount(0), + controllersCount(0), setExposure(false), verifyConfiguration(true), geoLocationEnabled(false), gpioEnabled(0), gpioControlBitMask(0), mode("live"), stereoMode(false), tumFormat(0) + { + for (uint8_t i = 0; i < LogSourceMax; i++) + { + logConfiguration[i].setLog = false; + logConfiguration[i].logOutputMode = LogOutputModeScreen; + logConfiguration[i].logRolloverMode = false; + logConfiguration[i].logVerbosity = None; + } + + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + video[i].enabled = false; + video[i].outputEnabled = false; + } + + for (uint8_t i = 0; i < GyroProfileMax; i++) + { + gyro[i].enabled = false; + gyro[i].outputEnabled = false; + gyro[i].fps = 0; + } + + for (uint8_t i = 0; i < AccelerometerProfileMax; i++) + { + accelerometer[i].enabled = false; + accelerometer[i].outputEnabled = false; + accelerometer[i].fps = 0; + } + + for (uint8_t i = 0; i < SixDofProfileMax; i++) + { + sixdof[i] = false; + } + + for (uint8_t i = 0; i < ProfileTypeMax; i++) + { + memset(controllers[i].macAddress, 0, MAC_ADDRESS_SIZE); + controllers[i].controllerID = ProfileTypeMax; + controllers[i].needed = false; + } + + for (uint8_t i = 0; i < ProfileTypeMax; i++) + { + rssi[i].time = 0; + } + + for (uint8_t i = 0; i < LocalizationTypeMax; i++) + { + localization[i].enabled = false; + localization[i].filename = ""; + } + + geoLocation.set(0, 0, 0); + } + + std::string inputFilename; + std::string controllerDataFilename; + std::string velocimeterFilename; + std::string nodeFilename; + std::string recordFilename; + std::string mode; + bool stereoMode; + uint32_t maxLoop; + uint32_t startStreamTime; + + class Localization { + public: + std::fstream file; + std::string filename; + bool enabled; + }; + + Localization localization[LocalizationTypeMax]; + + TrackingData::GeoLocalization geoLocation; + bool geoLocationEnabled; + + uint8_t gpioControlBitMask; + bool gpioEnabled; + + class Enable { + public: + bool enabled; + bool outputEnabled; + uint16_t width; + uint16_t height; + uint16_t fps; + }; + + class ControllerBurn { + public: + ControllerBurn() : configure(ControllerBurnDisabled), state(ControllerBurnStateNotStarted) {} + ControllerBurnConfigure configure; + ControllerBurnState state; + }; + + class Controller { + public: + uint8_t macAddress[MAC_ADDRESS_SIZE]; + uint8_t controllerID; + ControllerBurn burn; + bool calibrate; + bool needed; + }; + + class Rssi { + public: + uint32_t time; + }; + + ProfileType findController(uint8_t* macAddress) + { + for (uint32_t i = Controller1; i < ProfileTypeMax; i++) + { + if (memcmp(macAddress, controllers[i].macAddress, MAC_ADDRESS_SIZE) == 0) + { + return (ProfileType)i; + } + } + + return ProfileTypeMax; + } + + Enable video[VideoProfileMax]; + uint8_t videoCount; + + Enable gyro[GyroProfileMax]; + uint8_t gyroCount; + + Enable velocimeter[VelocimeterProfileMax]; + uint8_t velocimeterCount; + + Enable accelerometer[AccelerometerProfileMax]; + uint8_t accelerometerCount; + + bool sixdof[SixDofProfileMax]; + uint8_t sixdofCount; + + std::string controllerFWFile; + std::string controllerFWFileType; + Controller controllers[ProfileTypeMax]; + uint8_t controllersCount; + + Rssi rssi[ProfileTypeMax]; + uint8_t rssiCount; + + class Temperature { + public: + class Threshold { + public: + float_t temperature; + bool set; + }; + + bool check; + Threshold threshold[TemperatureSensorMax]; + }; + + Temperature temperature; + + uint32_t resetLoop; + bool statistics; + bool jtag; + bool videoFile; + bool errorCheck; + bool errorExit; + uint8_t tumFormat; + + class LogConfiguration + { + public: + bool setLog; + bool logRolloverMode; + LogOutputMode logOutputMode; + LogVerbosityLevel logVerbosity; + }; + + LogConfiguration logConfiguration[LogSourceMax]; + + bool setExposure; + + TrackingData::Exposure exposure; + + bool verifyConfiguration; +}; +ArgumentConfigurarion gConfiguration; + +class SensorStatistics { +public: + SensorStatistics() : frames(0), frameRate(0), outputMode(0), enabled(0) {} + + void reset(void) + { + frames = 0; + frameRate = 0; + totalLatency = 0; + nanFrame = 0; + enabled = 0; + outputMode = 0; + frameDrops = 0; + frameReorder = 0; + prevFrameId = 0; + prevFrameTimeStamp = 0; + } + + uint64_t frames; /* Number of frames */ + uint64_t frameDrops; /* Number of frame drops */ + uint64_t frameReorder; /* Number of frame reorder */ + uint16_t frameRate; /* Frame per second */ + uint64_t totalLatency; /* total Latency (Motion->Host) in NanoSec - for average latency - divide with frames */ + uint64_t nanFrame; /* pose NAN frame received */ + uint8_t enabled; /* Enabled/Disabled on FW */ + uint8_t outputMode; /* For Video/Gyro/Accelerometer: 0x0 - send sensor outputs to the internal middlewares only */ + /* 0x1 - Send this sensor outputs also to the host over the USB interface */ + /* For Pose: 0x0 - no interrupts */ + /* 0x1 - interrupts on every fisheye camera update (30 interrupts per second for TM2) */ + /* 0x2 - interrupts on every IMU update (400-500 interrupts per second for TM2) - default value */ + uint32_t prevFrameId; /* Previous frame ID - for drop check */ + int64_t prevFrameTimeStamp; /* Previous frame TimeStamp - for drop check */ +}; + +class GyroStatistics : public SensorStatistics {}; +class VelocimeterStatistics : public SensorStatistics {}; +class VideoStatistics : public SensorStatistics {}; +class AccelerometerStatistics : public SensorStatistics {}; +class PoseStatistics : public SensorStatistics { +public: + void reset(void) + { + SensorStatistics::reset(); + trackerConfidence = 0; + } + + uint32_t trackerConfidence; +}; +class ControllerStatistics : public SensorStatistics { +public: + std::atomic isConnected; + std::atomic isCalibrated; + uint32_t FWProgress; +}; +class RssiStatistics : public SensorStatistics {}; +class LocalizationStatistics : public SensorStatistics { +public: + void reset(void) + { + SensorStatistics::reset(); + isCompleted = false; + fileSize = 0; + } + + bool isCompleted; + uint64_t fileSize; +}; + +class ControllerDiscoveryEventStatistics { +public: + ControllerDiscoveryEventStatistics() : frames(0) + { + memset(macAddress, 0, MAC_ADDRESS_SIZE); + } + + ControllerDiscoveryEventStatistics(uint8_t* _macAddress) : frames(0) + { + perc::copy(macAddress, _macAddress, MAC_ADDRESS_SIZE); + } + + uint8_t macAddress[MAC_ADDRESS_SIZE]; /* Controller Mac Address */ + uint64_t frames; /* Number of frames */ +}; + +class ErrorStatistics { +public: + ErrorStatistics() : count(0) { + } + + void reset(void) + { + count = 0; + } + + uint32_t count; /* Number of errors */ +}; + +class LedStatistics { +public: + LedStatistics() : count(0) { + } + + void reset(void) + { + count = 0; + } + + uint32_t count; /* Number of led events */ +}; + +class StatusStatistics { +public: + StatusStatistics() : count(0) { + } + + void reset(void) + { + count = 0; + } + + uint32_t count; /* Number of statuses */ +}; + +class RunTimeStatistics { +public: + RunTimeStatistics() : startTimeMsec(0), stopTimeMsec(0), diffMsec(0), startToken(0), stopToken(0) {} + + void reset(void) + { + startTimeMsec = 0; + stopTimeMsec = 0; + diffMsec = 0; + startToken = 0; + stopToken = 0; + } + + void start(void) + { + if (startToken == stopToken) + { + startTimeMsec = ns2ms(systemTime()); + stopTimeMsec = 0; + diffMsec = 0; + startToken++; + } + + /* Else already started, do nothing */ + + } + + void stop(void) + { + if (startToken == (stopToken + 1)) + { + stopTimeMsec = ns2ms(systemTime()); + diffMsec = (stopTimeMsec - startTimeMsec); + stopToken++; + } + + /* Else already stopped, do nothing */ + } + + uint32_t startTimeMsec; + uint32_t stopTimeMsec; + uint32_t diffMsec; + uint32_t startToken; + uint32_t stopToken; +}; + +class Statistics { +public: + Statistics() {} + ~Statistics() + { + this->reset(); + } + + /* Reset all Statistics fields */ + void reset(void) + { + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + video[i].reset(); + } + + for (uint8_t i = 0; i < GyroProfileMax; i++) + { + gyro[i].reset(); + } + + for (uint8_t i = 0; i < VelocimeterProfileMax; i++) + { + velocimeter[i].reset(); + } + + for (uint8_t i = 0; i < AccelerometerProfileMax; i++) + { + accelerometer[i].reset(); + } + + for (uint8_t i = 0; i < SixDofProfileMax; i++) + { + pose[i].reset(); + } + + for (uint8_t i = Controller1; i < ProfileTypeMax; i++) + { + controller[i].reset(); + } + + for (uint8_t i = Controller1; i < ProfileTypeMax; i++) + { + rssi[i].reset(); + } + + for (uint8_t i = LocalizationTypeGet; i < LocalizationTypeMax; i++) + { + localization[i].reset(); + } + + for (uint8_t i = 0; i < ProfileTypeMax; i++) + { + runTime[i].reset(); + } + + controllerDiscoveryEventVector.clear(); + + error.reset(); + status.reset(); + led.reset(); + } + + void print() + { + LOGD("Statistics"); + LOGD("--------------------------+-------------------+--------+---------+--------+------------+----------+----------+--------"); + LOGD(" Type | Info | Frames | Enabled | Output | Latency | Run Time | Expected | Actual "); + LOGD(" | | PerSec | | Mode | AVG (mSec) | (mSec) | Frames | Frames "); + LOGD("--------------------------+-------------------+--------+---------+--------+------------+----------+----------+--------"); + + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + LOGD(" Video[%01d] | %-17s | %-6d | 0x%01X | 0x%01X | %-10"PRId64" | %-8d | %-8d | %d", i, (i > VideoProfile1) ? "Low Exposure " : "High Exposure", + video[i].frameRate, video[i].enabled, video[i].outputMode, (video[i].frames > 0) ? (video[i].totalLatency / video[i].frames) : 0, runTime[HMD].diffMsec, (video[i].frameRate * video[i].enabled * video[i].outputMode * runTime[HMD].diffMsec) / 1000, video[i].frames); + } + + for (uint8_t i = 0; i < GyroProfileMax; i++) + { + LOGD(" Gyro[%01d] | %-17s | %-6d | 0x%01X | 0x%01X | %-10"PRId64" | %-8d | %-8d | %d", i, (i > GyroProfile0) ? ((i > GyroProfile1) ? "Controller 2" : "Controller 1") : "HMD", + gyro[i].frameRate, gyro[i].enabled, gyro[i].outputMode, (gyro[i].frames > 0) ? (gyro[i].totalLatency / gyro[i].frames) : 0, runTime[i].diffMsec, (gyro[i].frameRate * gyro[i].enabled * gyro[i].outputMode * runTime[i].diffMsec) / 1000, gyro[i].frames); + } + + for (uint8_t i = 0; i < AccelerometerProfileMax; i++) + { + LOGD(" Accelerometer[%01d] | %-17s | %-6d | 0x%01X | 0x%01X | %-10"PRId64" | %-8d | %-8d | %d", i, (i > AccelerometerProfile0) ? ((i > AccelerometerProfile1) ? "Controller 2" : "Controller 1") : "HMD", + accelerometer[i].frameRate, accelerometer[i].enabled, accelerometer[i].outputMode, (accelerometer[i].frames > 0) ? (accelerometer[i].totalLatency / accelerometer[i].frames) : 0, runTime[i].diffMsec, (accelerometer[i].frameRate * accelerometer[i].enabled * accelerometer[i].outputMode * runTime[i].diffMsec) / 1000, accelerometer[i].frames); + } + + for (uint8_t i = 0; i < VelocimeterProfileMax; i++) + { + LOGD(" Velocimeter[%01d] | External Sensor | | 0x%01X | 0x%01X | %-10"PRId64" | %-8d | | %d", i, + velocimeter[i].enabled, velocimeter[i].outputMode, (velocimeter[i].frames > 0) ? (velocimeter[i].totalLatency / velocimeter[i].frames) : 0, runTime[HMD].diffMsec, velocimeter[i].frames); + } + + for (uint8_t i = 0; i < SixDofProfileMax; i++) + { + uint32_t poseRunTimeMsec = 0; + + if (runTime[i].diffMsec > MAX_FIRST_POSE_DELAY_MSEC) + { + poseRunTimeMsec = (runTime[i].diffMsec - MAX_FIRST_POSE_DELAY_MSEC); + } + + LOGD(" Pose[%01d] | %-17s | %-6d | 0x%01X | 0x%01X | %-10"PRId64" | %-8d | %-8d | %d", i, (i > SixDofProfile0) ? ((i > SixDofProfile1) ? "Controller 2" : "Controller 1") : "HMD", + pose[i].frameRate, pose[i].enabled, pose[i].outputMode, (pose[i].frames > 0) ? (pose[i].totalLatency / pose[i].frames) : 0, runTime[i].diffMsec, (pose[i].frameRate * pose[i].enabled * pose[i].outputMode * poseRunTimeMsec) / 1000, pose[i].frames); + } + + for (auto it = controllerDiscoveryEventVector.begin(); it != controllerDiscoveryEventVector.end(); it++) + { + LOGD(" ControllerDiscoveryEvent | %02X:%02X:%02X:%02X:%02X:%02X | | | | | | | %d", + (*it).get()->macAddress[0], (*it).get()->macAddress[1], (*it).get()->macAddress[2], (*it).get()->macAddress[3], (*it).get()->macAddress[4], (*it).get()->macAddress[5], (*it).get()->frames); + } + + if (controllerDiscoveryEventVector.size() == 0) + { + LOGD(" ControllerDiscoveryEvent | All controllers | | | | | | | 0"); + } + + for (uint8_t i = Controller1; i < ProfileTypeMax; i++) + { + LOGD(" Controller[%01d] | Controller %01d | | | | | | | %d", i, i, controller[i].frames); + } + + for (uint8_t i = Controller1; i < ProfileTypeMax; i++) + { + LOGD(" Rssi[%01d] | Controller %01d | | | | | | | %d", i, i, rssi[i].frames); + } + + LOGD(" Errors | All error types | | | | | | | %d", error.count); + LOGD(" Statuses | All status types | | | | | | | %d", status.count); + LOGD(" Led Event | Led Event | | | | | | | %d", led.count); + LOGD("--------------------------+-------------------+--------+---------+--------+------------+----------+----------+--------"); + } + + /* Check frame count and latency for video/gyro/accelerometer/6dof and check error events */ + bool check() + { + bool checkerFailed = false; + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + /* Check if video sensor started */ + if ((video[i].enabled) && (video[i].outputMode) && (runTime[HMD].diffMsec == 0)) + { + LOGE("Error: Enabled video[%d] didn't start", HMD); + checkerFailed = true; + } + else + { + /* Check if received min frames of runtime - 1 sec */ + if (video[i].frames < (video[i].enabled * video[i].outputMode * video[i].frameRate * (runTime[HMD].diffMsec / 1000 - 1))) + { + LOGE("Error: Got too few video[%d] frames [Min Expected %d] [Actual %d]", i, (video[i].enabled * video[i].outputMode * video[i].frameRate * (runTime[HMD].diffMsec / 1000 - 1)), video[i].frames); + checkerFailed = true; + } + + if (video[i].frames > 0) + { + /* Check if received max frames of runtime + 1 sec */ + if (video[i].frames > (video[i].enabled * video[i].outputMode * video[i].frameRate * (runTime[HMD].diffMsec / 1000 + 1))) + { + LOGE("Error: Got too many video[%d] frames [Max Expected %d] [Actual %d]", i, (video[i].enabled * video[i].outputMode * video[i].frameRate * (runTime[HMD].diffMsec / 1000 + 1)), video[i].frames); + checkerFailed = true; + } + + /* Check latency less than 100 msec */ + if ((video[i].totalLatency / video[i].frames) > MAX_FRAME_LATENCY_MSEC) + { + LOGE("Error: video[%d] frame motion->host latency is too high (%d)", i, (video[i].totalLatency / video[i].frames)); + checkerFailed = true; + } + + /* Check frame drops */ + if (video[i].frameDrops > 0) + { + LOGE("Error: video[%d] frame drops occurred (%d)", i, video[i].frameDrops); + checkerFailed = true; + } + + /* Check frame reorder */ + if (video[i].frameReorder > 0) + { + LOGE("Error: video[%d] frame reorder occurred (%d)", i, video[i].frameReorder); + checkerFailed = true; + } + } + } + } + + for (uint8_t i = 0; i < GyroProfileMax; i++) + { + /* Check if gyro sensor started */ + if ((gyro[i].enabled) && (gyro[i].outputMode) && (runTime[i].diffMsec == 0)) + { + LOGE("Error: Enabled gyro[%d] didn't start", i); + checkerFailed = true; + } + else + { + /* Check if received min frames of runtime - 1 sec */ + if (gyro[i].frames < (gyro[i].enabled * gyro[i].outputMode * gyro[i].frameRate * (runTime[i].diffMsec / 1000 - 1))) + { + LOGE("Error: Got too few gyro[%d] frames [Min Expected %d] [Actual %d]", i, (gyro[i].enabled * gyro[i].outputMode * gyro[i].frameRate * (runTime[i].diffMsec / 1000 - 1)), gyro[i].frames); + checkerFailed = true; + } + + if (gyro[i].frames > 0) + { + /* Check if received max frames of runtime + 1 sec */ + if (gyro[i].frames > (gyro[i].enabled * gyro[i].outputMode * gyro[i].frameRate * (runTime[i].diffMsec / 1000 + 1))) + { + LOGE("Error: Got too many gyro[%d] frames [Max Expected %d] [Actual %d]", i, (gyro[i].enabled * gyro[i].outputMode * gyro[i].frameRate * (runTime[i].diffMsec / 1000 + 1)), gyro[i].frames); + checkerFailed = true; + } + + /* Check latency less than 100 msec */ + if ((gyro[i].totalLatency / gyro[i].frames) > MAX_FRAME_LATENCY_MSEC) + { + LOGE("Error: gyro[%d] frame motion->host latency is too high (%d)", i, (gyro[i].totalLatency / gyro[i].frames)); + checkerFailed = true; + } + + /* Check frame drops */ + if (gyro[i].frameDrops > 0) + { + LOGE("Error: gyro[%d] frame drops occurred (%d)", i, gyro[i].frameDrops); + checkerFailed = true; + } + + /* Check frame reorder */ + if (gyro[i].frameReorder > 0) + { + LOGE("Error: gyro[%d] frame reorder occurred (%d)", i, gyro[i].frameReorder); + checkerFailed = true; + } + } + } + } + + for (uint8_t i = 0; i < AccelerometerProfileMax; i++) + { + /* Check if accelerometer sensor started */ + if ((accelerometer[i].enabled) && (accelerometer[i].outputMode) && (runTime[i].diffMsec == 0)) + { + LOGE("Error: Enabled accelerometer[%d] didn't start", i); + checkerFailed = true; + } + else + { + /* Check if received min frames of runtime - 1 sec */ + if (accelerometer[i].frames < (accelerometer[i].enabled * accelerometer[i].outputMode * accelerometer[i].frameRate * (runTime[i].diffMsec / 1000 - 1))) + { + LOGE("Error: Got too few accelerometer[%d] frames [Min Expected %d] [Actual %d]", i, (accelerometer[i].enabled * accelerometer[i].outputMode * accelerometer[i].frameRate * (runTime[i].diffMsec / 1000 - 1)), accelerometer[i].frames); + checkerFailed = true; + } + + if (accelerometer[i].frames > 0) + { + /* Check if received max frames of runtime + 1 sec */ + if (accelerometer[i].frames > (accelerometer[i].enabled * accelerometer[i].outputMode * accelerometer[i].frameRate * (runTime[i].diffMsec / 1000 + 1))) + { + LOGE("Error: Got too many accelerometer[%d] frames [Max Expected %d] [Actual %d]", i, (accelerometer[i].enabled * accelerometer[i].outputMode * accelerometer[i].frameRate * (runTime[i].diffMsec / 1000 + 1)), accelerometer[i].frames); + checkerFailed = true; + } + + /* Check latency less than 100 msec */ + if ((accelerometer[i].totalLatency / accelerometer[i].frames) > MAX_FRAME_LATENCY_MSEC) + { + LOGE("Error: accelerometer[%d] frame motion->host latency is too high (%d)", i, (accelerometer[i].totalLatency / accelerometer[i].frames)); + checkerFailed = true; + } + + /* Check frame drops */ + if (accelerometer[i].frameDrops > 0) + { + LOGE("Error: accelerometer[%d] frame drops occurred (%d)", i, accelerometer[i].frameDrops); + checkerFailed = true; + } + + /* Check frame reorder */ + if (accelerometer[i].frameReorder > 0) + { + LOGE("Error: accelerometer[%d] frame reorder occurred (%d)", i, accelerometer[i].frameReorder); + checkerFailed = true; + } + } + } + } + + for (uint8_t i = 0; i < SixDofProfileMax; i++) + { + uint32_t poseRunTimeMsecMin = 0; + uint32_t poseRunTimeMsecMax = 0; + uint32_t poseRunTimeMsec = 0; + + if (runTime[i].diffMsec > MAX_FIRST_POSE_DELAY_MSEC) + { + poseRunTimeMsecMin = ((runTime[i].diffMsec - 500) - MAX_FIRST_POSE_DELAY_MSEC); + poseRunTimeMsecMax = ((runTime[i].diffMsec + 500) - MAX_FIRST_POSE_DELAY_MSEC); + poseRunTimeMsec = (runTime[i].diffMsec - MAX_FIRST_POSE_DELAY_MSEC); + } + + /* Check if pose started */ + if ((pose[i].enabled) && (pose[i].outputMode) && (runTime[i].diffMsec == 0)) + { + LOGE("Error: Enabled pose[%d] didn't start", i); + checkerFailed = true; + } + else + { + /* Check if received min frames of runtime - 500 msec */ + if (pose[i].frames < ((pose[i].enabled * pose[i].outputMode * (min(100, pose[i].frameRate)) * poseRunTimeMsecMin) / 1000)) + { + LOGE("Error: Got too few pose[%d] frames [Expected %d] [Actual %d]", i, ((pose[i].enabled * pose[i].outputMode * pose[i].frameRate * poseRunTimeMsec) / 1000), pose[i].frames); + checkerFailed = true; + } + + if (pose[i].frames > 0) + { + /* Check if received max frames of runtime + 500 msec */ + if (pose[i].frames > ((pose[i].enabled * pose[i].outputMode * (max(100, pose[i].frameRate)) * poseRunTimeMsecMax) / 1000)) + { + LOGE("Error: Got too many pose[%d] frames [Expected %d] [Actual %d]", i, ((pose[i].enabled * pose[i].outputMode * pose[i].frameRate * poseRunTimeMsec) / 1000), pose[i].frames); + checkerFailed = true; + } + + /* Check latency less than 100 msec */ + if ((pose[i].totalLatency / pose[i].frames) > MAX_FRAME_LATENCY_MSEC) + { + LOGE("Error: pose[%d] frame motion->host latency is too high (%d)", i, (pose[i].totalLatency / pose[i].frames)); + checkerFailed = true; + } + + /* Check frame drops */ + if (pose[i].frameDrops > 0) + { + LOGE("Error: pose[%d] frame drops occurred (%d)", i, pose[i].frameDrops); + checkerFailed = true; + } + + /* Check frame reorder */ + if (pose[i].frameReorder > 0) + { + LOGE("Error: pose[%d] frame reorder occurred (%d)", i, pose[i].frameReorder); + checkerFailed = true; + } + + /* Check NAN frame */ + if (pose[i].nanFrame > 0) + { + LOGE("Error: pose[%d] NAN frame received (%d)", i, pose[i].nanFrame); + checkerFailed = true; + } + } + } + } + + /* Check if got error events */ + if (error.count > 0) + { + LOGE("Error: Got %d error events", error.count); + checkerFailed = true; + } + + return checkerFailed; + } + + /* Statistics comparison overload */ + bool operator == (const Statistics &ref) const + { + for (uint8_t i = 0; i < VideoProfileMax; i++) + { + if (this->video[i].frames != ref.video[i].frames) + { + return false; + } + } + + for (uint8_t i = 0; i < GyroProfileMax; i++) + { + if (this->gyro[i].frames != ref.gyro[i].frames) + { + return false; + } + } + + for (uint8_t i = 0; i < VelocimeterProfileMax; i++) + { + if (this->velocimeter[i].frames != ref.velocimeter[i].frames) + { + return false; + } + } + + for (uint8_t i = 0; i < AccelerometerProfileMax; i++) + { + if (this->accelerometer[i].frames != ref.accelerometer[i].frames) + { + return false; + } + } + + for (uint8_t i = 0; i < SixDofProfileMax; i++) + { + if (this->pose[i].frames != ref.pose[i].frames) + { + return false; + } + } + + for (uint8_t i = Controller1; i < ProfileTypeMax; i++) + { + if (this->controller[i].frames != ref.controller[i].frames) + { + return false; + } + } + + for (uint8_t i = Controller1; i < ProfileTypeMax; i++) + { + if (this->rssi[i].frames != ref.rssi[i].frames) + { + return false; + } + } + + for (uint8_t i = LocalizationTypeGet; i < LocalizationTypeMax; i++) + { + if (this->localization[i].frames != ref.localization[i].frames) + { + return false; + } + } + + return true; + } + + /* Increase error count */ + void inc(void) + { + error.count++; + if (gConfiguration.errorExit == true) + { + LOGE("Error event occurred"); + exit(EXIT_FAILURE); + } + } + + /* Increase led count */ + void inc(TrackingData::ControllerLedEventFrame& ledFrame) + { + led.count++; + } + + /* Increase status count */ + void inc(TrackingData::StatusEventFrame& statusFrame) + { + status.count++; + } + + /* Increase gyro frame count */ + void inc(TrackingData::GyroFrame& gyroFrame) + { + gyro[gyroFrame.sensorIndex].frames++; + gyro[gyroFrame.sensorIndex].totalLatency += ns2ms(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - gyroFrame.systemTimestamp); + + int64_t offset = (int64_t)gyroFrame.frameId - (int64_t)gyro[gyroFrame.sensorIndex].prevFrameId; + if (gyro[gyroFrame.sensorIndex].prevFrameTimeStamp != 0) + { + if (offset > 1) + { + gyro[gyroFrame.sensorIndex].frameDrops++; + if (gConfiguration.errorExit == true) + { + LOGE("Gyro[%d] frame drops occurred - %lld missing frames [FrameId %d-%d], time diff = %d (msec)", gyroFrame.sensorIndex, offset-1, gyro[gyroFrame.sensorIndex].prevFrameId+1, gyroFrame.frameId-1, ns2ms(gyroFrame.timestamp - gyro[gyroFrame.sensorIndex].prevFrameTimeStamp)); + exit(EXIT_FAILURE); + } + } + else if (offset < 1) + { + gyro[gyroFrame.sensorIndex].frameReorder++; + if (gConfiguration.errorExit == true) + { + LOGE("Gyro[%d] frame reorder occurred - prev frameId = %d, new frameId = %d, time diff = %d (msec)", gyroFrame.sensorIndex, gyro[gyroFrame.sensorIndex].prevFrameId, gyroFrame.frameId, ns2ms(gyroFrame.timestamp - gyro[gyroFrame.sensorIndex].prevFrameTimeStamp)); + exit(EXIT_FAILURE); + } + } + } + + gyro[gyroFrame.sensorIndex].prevFrameTimeStamp = gyroFrame.timestamp; + gyro[gyroFrame.sensorIndex].prevFrameId = gyroFrame.frameId; + } + + /* Increase velocimeter frame count */ + void inc(TrackingData::VelocimeterFrame& velocimeterFrame) + { + velocimeter[velocimeterFrame.sensorIndex].frames++; + velocimeter[velocimeterFrame.sensorIndex].totalLatency += ns2ms(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - velocimeterFrame.systemTimestamp); + + int64_t offset = (int64_t)velocimeterFrame.frameId - (int64_t)velocimeter[velocimeterFrame.sensorIndex].prevFrameId; + if (velocimeter[velocimeterFrame.sensorIndex].prevFrameTimeStamp != 0) + { + if (offset > 1) + { + velocimeter[velocimeterFrame.sensorIndex].frameDrops++; + if (gConfiguration.errorExit == true) + { + LOGE("Velocimeter[%d] frame drops occurred - %lld missing frames [FrameId %d-%d], time diff = %d (msec)", velocimeterFrame.sensorIndex, offset-1, velocimeter[velocimeterFrame.sensorIndex].prevFrameId+1, velocimeterFrame.frameId-1, ns2ms(velocimeterFrame.timestamp - velocimeter[velocimeterFrame.sensorIndex].prevFrameTimeStamp)); + exit(EXIT_FAILURE); + } + } + else if (offset < 1) + { + velocimeter[velocimeterFrame.sensorIndex].frameReorder++; + if (gConfiguration.errorExit == true) + { + LOGE("Velocimeter[%d] frame reorder occurred - prev frameId = %d, new frameId = %d, time diff = %d (msec)", velocimeterFrame.sensorIndex, velocimeter[velocimeterFrame.sensorIndex].prevFrameId, velocimeterFrame.frameId, ns2ms(velocimeterFrame.timestamp - velocimeter[velocimeterFrame.sensorIndex].prevFrameTimeStamp)); + exit(EXIT_FAILURE); + } + } + } + + velocimeter[velocimeterFrame.sensorIndex].prevFrameTimeStamp = velocimeterFrame.timestamp; + velocimeter[velocimeterFrame.sensorIndex].prevFrameId = velocimeterFrame.frameId; + } + + /* Increase accelerometer frame count */ + void inc(TrackingData::AccelerometerFrame& accelerometerFrame) + { + accelerometer[accelerometerFrame.sensorIndex].frames++; + accelerometer[accelerometerFrame.sensorIndex].totalLatency += ns2ms(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - accelerometerFrame.systemTimestamp); + + int64_t offset = (int64_t)accelerometerFrame.frameId - (int64_t)accelerometer[accelerometerFrame.sensorIndex].prevFrameId; + if (accelerometer[accelerometerFrame.sensorIndex].prevFrameTimeStamp != 0) + { + if (offset > 1) + { + accelerometer[accelerometerFrame.sensorIndex].frameDrops++; + if (gConfiguration.errorExit == true) + { + LOGE("Accelerometer[%d] frame drops occurred - %lld missing frames [FrameId %d-%d], time diff = %d (msec)", accelerometerFrame.sensorIndex, offset-1, accelerometer[accelerometerFrame.sensorIndex].prevFrameId+1, accelerometerFrame.frameId-1, ns2ms(accelerometerFrame.timestamp - accelerometer[accelerometerFrame.sensorIndex].prevFrameTimeStamp)); + exit(EXIT_FAILURE); + } + + } + else if (offset < 1) + { + accelerometer[accelerometerFrame.sensorIndex].frameReorder++; + if (gConfiguration.errorExit == true) + { + LOGE("Accelerometer[%d] frame reorder occurred - prev frameId = %d, new frameId = %d, time diff = %d (msec)", accelerometerFrame.sensorIndex, accelerometer[accelerometerFrame.sensorIndex].prevFrameId, accelerometerFrame.frameId, ns2ms(accelerometerFrame.timestamp - accelerometer[accelerometerFrame.sensorIndex].prevFrameTimeStamp)); + exit(EXIT_FAILURE); + } + } + } + + accelerometer[accelerometerFrame.sensorIndex].prevFrameTimeStamp = accelerometerFrame.timestamp; + accelerometer[accelerometerFrame.sensorIndex].prevFrameId = accelerometerFrame.frameId; + } + + /* Increase pose frame count */ + void inc(TrackingData::PoseFrame& poseFrame) + { + pose[poseFrame.sourceIndex].frames++; + pose[poseFrame.sourceIndex].totalLatency += ns2ms(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - poseFrame.systemTimestamp); + + if ((gConfiguration.errorCheck == true) && isnan(poseFrame.translation.x) && (poseFrame.trackerConfidence != 0)) + { + pose[poseFrame.sourceIndex].nanFrame++; + if (gConfiguration.errorExit == true) + { + LOGE("Got NAN Pose[%u] (%" PRId64 "): Timestamp %" PRId64 ", Translation[%f, %f, %f], TrackerConfidence = 0x%X", poseFrame.sourceIndex, pose[poseFrame.sourceIndex].frames, poseFrame.timestamp, poseFrame.translation.x, poseFrame.translation.y, poseFrame.translation.z, poseFrame.trackerConfidence); + exit(EXIT_FAILURE); + } + } + + /* Pose must arrive every 5 msec */ + int64_t offset = (poseFrame.timestamp - pose[poseFrame.sourceIndex].prevFrameTimeStamp) / 5000000; + if (pose[poseFrame.sourceIndex].prevFrameTimeStamp != 0) + { + if (offset > 5) + { + pose[poseFrame.sourceIndex].frameDrops++; + if (gConfiguration.errorExit == true) + { + LOGE("Pose[%d] frame drops occurred - %lld missing frames, prev pose time = %lld, new pose time = %lld, time diff = %d (msec)", poseFrame.sourceIndex, offset-1, pose[poseFrame.sourceIndex].prevFrameTimeStamp, poseFrame.timestamp, ns2ms(poseFrame.timestamp - pose[poseFrame.sourceIndex].prevFrameTimeStamp)); + exit(EXIT_FAILURE); + } + } + else if (offset < 0) + { + pose[poseFrame.sourceIndex].frameReorder++; + if (gConfiguration.errorExit == true) + { + LOGE("Pose[%d] frame reorder occurred - prev pose time = %lld, new pose time = %lld, time diff = %d (msec)", poseFrame.sourceIndex, pose[poseFrame.sourceIndex].prevFrameTimeStamp, poseFrame.timestamp, ns2ms(poseFrame.timestamp - pose[poseFrame.sourceIndex].prevFrameTimeStamp)); + exit(EXIT_FAILURE); + } + } + } + + pose[poseFrame.sourceIndex].prevFrameTimeStamp = poseFrame.timestamp; + pose[poseFrame.sourceIndex].trackerConfidence = poseFrame.trackerConfidence; + } + + /* Increase video (left/right FishEye) frame count */ + void inc(TrackingData::VideoFrame& videoFrame) + { + video[videoFrame.sensorIndex].frames++; + video[videoFrame.sensorIndex].totalLatency += ns2ms(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - videoFrame.systemTimestamp); + + int64_t offset = (int64_t)videoFrame.frameId - (int64_t)video[videoFrame.sensorIndex].prevFrameId; + if (video[videoFrame.sensorIndex].prevFrameTimeStamp != 0) + { + if (offset > 1) + { + video[videoFrame.sensorIndex].frameDrops++; + if (gConfiguration.errorExit == true) + { + LOGE("Video[%d] frame drops occurred - %lld missing frames [FrameId %d-%d], time diff = %d (msec)", videoFrame.sensorIndex, offset-1, video[videoFrame.sensorIndex].prevFrameId+1, videoFrame.frameId-1, ns2ms(videoFrame.timestamp - video[videoFrame.sensorIndex].prevFrameTimeStamp)); + exit(EXIT_FAILURE); + } + } + else if (offset < 1) + { + video[videoFrame.sensorIndex].frameReorder++; + if (gConfiguration.errorExit == true) + { + LOGE("Video[%d] frame reorder occurred - prev frameId = %d, new frameId = %d, time diff = %d (msec)", videoFrame.sensorIndex, video[videoFrame.sensorIndex].prevFrameId, videoFrame.frameId, ns2ms(videoFrame.timestamp - video[videoFrame.sensorIndex].prevFrameTimeStamp)); + exit(EXIT_FAILURE); + } + } + } + + video[videoFrame.sensorIndex].prevFrameTimeStamp = videoFrame.timestamp; + video[videoFrame.sensorIndex].prevFrameId = videoFrame.frameId; + } + + /* Increase controller frame count */ + void inc(TrackingData::ControllerFrame& controllerFrame) + { + controller[controllerFrame.sensorIndex].frames++; + controller[controllerFrame.sensorIndex].totalLatency += ns2ms(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - controllerFrame.systemTimestamp); + } + + /* Add new controller discovery event statistics and increase controller discovery event frame count */ + void inc(TrackingData::ControllerDiscoveryEventFrame& frame) + { + auto it = std::find_if(controllerDiscoveryEventVector.begin(), controllerDiscoveryEventVector.end(), [&frame](std::shared_ptr v) -> bool + { return !memcmp(v->macAddress, frame.macAddress, MAC_ADDRESS_SIZE);}); + + if (it == controllerDiscoveryEventVector.end()) + { + std::shared_ptr statistics(new ControllerDiscoveryEventStatistics(frame.macAddress)); + statistics->frames++; + controllerDiscoveryEventVector.push_back(statistics); + } + else + { + (*it)->frames++; + } + } + + /* Increase Rssi frame count */ + void inc(TrackingData::RssiFrame& rssiFrame) + { + rssi[rssiFrame.sensorIndex].frames++; + rssi[rssiFrame.sensorIndex].totalLatency += ns2ms(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - rssiFrame.systemTimestamp); + } + + /* Increase Localization Get/Set frame count */ + void inc(TrackingData::LocalizationDataFrame& localizationFrame) + { + LocalizationType type = LocalizationTypeGet; + + if (localizationFrame.buffer == NULL) + { + type = LocalizationTypeSet; + } + + localization[type].frames++; + localization[type].fileSize += localizationFrame.length; + + if (localizationFrame.moreData == false) + { + localization[type].isCompleted = true; + } + + } + + /* Check if controller mac address is already connected */ + bool isConnected(uint8_t* macAddress) + { + auto it = std::find_if(controllerDiscoveryEventVector.begin(), controllerDiscoveryEventVector.end(), [macAddress](std::shared_ptr v) -> bool + { + return !memcmp(v->macAddress, macAddress, MAC_ADDRESS_SIZE); } + ); + + return (it != controllerDiscoveryEventVector.end()); + } + + /* Configure Video sensor rate and outpurMode for statistics calculations */ + void configure(TrackingData::VideoProfile profile, uint8_t enabled, uint8_t outputMode) + { + video[profile.sensorIndex].frameRate = profile.fps; + video[profile.sensorIndex].enabled = enabled; + video[profile.sensorIndex].outputMode = outputMode; + } + + /* Configure Gyro sensor rate and outpurMode for statistics calculations */ + void configure(TrackingData::GyroProfile profile, uint8_t enabled, uint8_t outputMode) + { + gyro[profile.sensorIndex].frameRate = profile.fps; + gyro[profile.sensorIndex].enabled = enabled; + gyro[profile.sensorIndex].outputMode = outputMode; + } + + /* Configure Velocimeter sensor rate and outpurMode for statistics calculations */ + void configure(TrackingData::VelocimeterProfile profile, uint8_t enabled, uint8_t outputMode) + { + velocimeter[profile.sensorIndex].frameRate = profile.fps; + velocimeter[profile.sensorIndex].enabled = enabled; + velocimeter[profile.sensorIndex].outputMode = outputMode; + } + + /* Configure Accelerometer sensor rate and outpurMode for statistics calculations */ + void configure(TrackingData::AccelerometerProfile profile, uint8_t enabled, uint8_t outputMode) + { + accelerometer[profile.sensorIndex].frameRate = profile.fps; + accelerometer[profile.sensorIndex].enabled = enabled; + accelerometer[profile.sensorIndex].outputMode = outputMode; + } + + /* Configure 6dof rate and outpurMode for statistics calculations */ + void configure(TrackingData::SixDofProfile profile, uint8_t enabled, uint16_t frameRate) + { + pose[profile.profileType].frameRate = frameRate; + pose[profile.profileType].enabled = enabled; + pose[profile.profileType].outputMode = enabled; + } + + RunTimeStatistics runTime[ProfileTypeMax]; + ErrorStatistics error; + StatusStatistics status; + LedStatistics led; + VideoStatistics video[VideoProfileMax]; + GyroStatistics gyro[GyroProfileMax]; + VelocimeterStatistics velocimeter[VelocimeterProfileMax]; + AccelerometerStatistics accelerometer[AccelerometerProfileMax]; + PoseStatistics pose[SixDofProfileMax]; + ControllerStatistics controller[ProfileTypeMax]; + RssiStatistics rssi[ProfileTypeMax]; + LocalizationStatistics localization[LocalizationTypeMax]; + std::vector> controllerDiscoveryEventVector; + + std::mutex videoFrameListMutex; + std::list videoList; +}; + + +HANDLE readyEvent; +HANDLE stopEvent; +TrackingDevice* gDevice; +Statistics gStatistics; +CalibrationInfo calibrationInfo; +uint32_t resetCount = 0; + +void updateControllerFW(TrackingData::ControllerDiscoveryEventFrame& message, ProfileType controllerId) +{ + struct FwFileHeader + { + uint32_t headerSize; + uint32_t dataSize; + uint32_t fileFormatVersion; + uint32_t versionSize; + uint8_t version[7]; /* 3 bytes for major,minor,patch, 4 byte for build number */ + }; + + std::ifstream file(gConfiguration.controllerFWFile, std::ios::binary); + if (!file) + { + LOGE("Error: controller fw file %s doesn't exists", gConfiguration.controllerFWFile.c_str()); + return; + } + + uint32_t headerSize = 0; + if (!file.read((char*)&headerSize, sizeof(headerSize))) + { + LOGE("Error: Failed to read controller file %s", gConfiguration.controllerFWFile.c_str()); + return; + } + + file.seekg(0); + + if (headerSize == 0) + { + LOGE("Error: Invalid controller fw file header size"); + return; + } + std::vector headerArr(headerSize, 0); + FwFileHeader* header = (FwFileHeader*)headerArr.data(); + if (!file.read((char*)header, headerSize)) + { + LOGE("Error: Failed to read header in controller file %s", gConfiguration.controllerFWFile.c_str()); + return; + } + + TrackingData::Version discoverdVersion; + if (gConfiguration.controllerFWFileType == "app") + { + discoverdVersion = message.app; + } + else if (gConfiguration.controllerFWFileType == "bl") + { + discoverdVersion = message.bootLoader; + } + else + { + LOGE("Invalid image type input for burning controller fw: %s", gConfiguration.controllerFWFileType); + return; + } + + if ((header->versionSize != 3) && (header->versionSize != 7)) + { + LOGE("Unsupported BLE image version size (%d)", header->versionSize); + return; + } + + /* Burning controller image only if image and discovery versions are different or configured to force burn */ + if (header->version[0] != discoverdVersion.major || + header->version[1] != discoverdVersion.minor || + header->version[2] != discoverdVersion.patch || + gConfiguration.controllers[controllerId].burn.configure == ControllerBurnForce) + { + LOGD("Updating controller FW %s application with new version %u.%u.%u", (gConfiguration.controllerFWFileType == "app")?"Application":"Boot Loader", header->version[0], header->version[1], header->version[2]); + + std::vector imageArr(header->dataSize, 0); + file.read((char*)imageArr.data(), header->dataSize); + + TrackingData::ControllerFW fw(message.macAddress, header->dataSize, imageArr.data(), message.addressType); + auto status = gDevice->ControllerFWUpdate(fw); + if (status != Status::SUCCESS) + { + LOGE("Failed to update controller %s firmware: %s (0x%X)", gConfiguration.controllerFWFileType.c_str(), statusToString(status).c_str(), status); + return; + } + + /* Burn succeeded, no need to burn this controller again */ + gConfiguration.controllers[controllerId].burn.state = ControllerBurnStateProcessing; + return; + } + + LOGD("Controller %d versions are equal [%u.%u.%u], skipping controller FW update", controllerId, header->version[0], header->version[1], header->version[2]); + + /* Versions are equal, no need to burn this controller */ + gConfiguration.controllers[controllerId].burn.state = ControllerBurnStateDone; + return; +} + +class CommonListener : public TrackingManager::Listener, public TrackingDevice::Listener +{ +public: + + // [TrackingManager::Listener] + virtual void onStateChanged(TrackingManager::EventType state, TrackingDevice* device, TrackingData::DeviceInfo deviceInfo) + { + switch (state) + { + case TrackingManager::ATTACH: + gDevice = device; + LOGD("Device (0x%p) Attached - Serial Number %llx", device, (deviceInfo.serialNumber >> 16)); + break; + + case TrackingManager::DETACH: + LOGD("Device (0x%p) Detached - Serial Number %llx", device, (deviceInfo.serialNumber >> 16)); + gDevice = NULL; + break; + } + }; + + virtual void onError(Status error, TrackingDevice* dev) override + { + gStatistics.inc(); + LOGE("Error occurred while connecting device: %p Error: %s (0x%X)", dev, statusToString(error).c_str(), error); + } + + virtual void onPoseFrame(TrackingData::PoseFrame& pose) + { + gStatistics.inc(pose); + + LOGV("Got Pose[%u] (%" PRId64 "): Timestamp %" PRId64 ", Translation[%f, %f, %f], TrackerConfidence = 0x%X", + pose.sourceIndex, gStatistics.pose[pose.sourceIndex].frames, pose.timestamp, pose.translation.x, pose.translation.y, pose.translation.z, pose.trackerConfidence); + + if (gConfiguration.statistics == true) + { + static UtilTimeStamps timeStamp(pose); + timeStamp.setTime(pose); + + poseCSV << std::fixed << (unsigned int)(pose.sourceIndex) << "," << gStatistics.pose[pose.sourceIndex].frames << "," + << timeStamp.hostCurrentSystemTime << "," << pose.systemTimestamp << "," << timeStamp.fwTimeStamp << "," << timeStamp.latency << "," + << pose.translation.x << "," << pose.translation.y << "," << pose.translation.z << "," + << pose.velocity.x << "," << pose.velocity.y << "," << pose.velocity.z << "," + << pose.acceleration.x << "," << pose.acceleration.y << "," << pose.acceleration.z << "," + << pose.rotation.i << "," << pose.rotation.j << "," << pose.rotation.k << "," << pose.rotation.r << "," + << pose.angularVelocity.x << "," << pose.angularVelocity.y << "," << pose.angularVelocity.z << "," + << pose.angularAcceleration.x << "," << pose.angularAcceleration.y << "," << pose.angularAcceleration.z << "," + << pose.trackerConfidence << "," << pose.mapperConfidence << "\n"; + } + + switch (gConfiguration.tumFormat) + { + case 1: + /* TUM */ + poseTUM << std::fixed << static_cast(pose.timestamp)/1000000000 << " " << pose.translation.x << " " << pose.translation.y << " " << pose.translation.z << " " << pose.rotation.i << " " << pose.rotation.j << " " << pose.rotation.k << " " << pose.rotation.r << "\n"; + break; + case 2: + /* TUMX */ + poseTUM << std::fixed << static_cast(pose.timestamp)/1000000000 << " " << pose.translation.x << " " << pose.translation.y << " " << pose.translation.z << " " << pose.rotation.i << " " << pose.rotation.j << " " << pose.rotation.k << " " << pose.rotation.r << " " << (unsigned int)(pose.sourceIndex) << "\n"; + break; + default: + break; + } + }; + + virtual void onVideoFrame(TrackingData::VideoFrame& frame) + { + gStatistics.inc(frame); + static UtilTimeStamps timeStamp(frame); + timeStamp.setTime(frame); + + LOGV("Got Video[%u] frame (%" PRId64 "): Timestamp %" PRId64 ", FrameId = %u, Exposure = %u, Gain = %f, FrameLength = %u, size %dx%d pixels", frame.sensorIndex, gStatistics.video[frame.sensorIndex].frames, frame.timestamp, + frame.frameId, frame.exposuretime, frame.gain, frame.frameLength, frame.profile.width, frame.profile.height); + + if (((frame.frameLength < (uint32_t)(frame.profile.height * frame.profile.stride))) || (((frame.frameLength < (uint32_t)(frame.profile.height * frame.profile.width))))) + { + LOGE("Error: Video[%u] frame (%" PRId64 ") size mismatch: FrameId = %u, FrameLength = %u, Width = %u, Height = %u, Stride = %u", + frame.sensorIndex, gStatistics.video[frame.sensorIndex].frames, frame.frameId, frame.frameLength, frame.profile.width, frame.profile.height, frame.profile.stride); + } + + if (gConfiguration.statistics == true) + { + videoCSV << std::fixed << (unsigned int)(frame.sensorIndex) << "," << gStatistics.video[frame.sensorIndex].frames << "," + << timeStamp.hostCurrentSystemTime << "," << frame.systemTimestamp << "," << timeStamp.fwTimeStamp << "," << timeStamp.arrivalTimeStamp << "," << timeStamp.latency << "," + << frame.frameId << "," << frame.frameLength << "," << frame.exposuretime << "," << frame.gain << "," << frame.profile.stride << "," << frame.profile.width << "," << frame.profile.height << "," << frame.profile.pixelFormat << "\n"; + } + + if ((gConfiguration.videoFile == true) && (frame.frameLength == (frame.profile.width * frame.profile.height))) + { + TrackingData::VideoFrame* newFrame = new TrackingData::VideoFrame(frame); + auto imageSize = (newFrame->profile.width * newFrame->profile.height); + uint8_t* data = (uint8_t*)malloc(frame.frameLength); + newFrame->data = data; + perc::copy((uint8_t*)newFrame->data, frame.data, frame.frameLength); + gStatistics.videoFrameListMutex.lock(); + gStatistics.videoList.push_back(newFrame); + gStatistics.videoFrameListMutex.unlock(); + } + }; + + virtual void onAccelerometerFrame(TrackingData::AccelerometerFrame& message) + { + gStatistics.inc(message); + + auto calibratedAccelerationX = ((message.acceleration.x * calibrationInfo.accelerometerScale[0][0]) - calibrationInfo.accelerometerBiasX); + auto calibratedAccelerationY = ((message.acceleration.y * calibrationInfo.accelerometerScale[1][1]) - calibrationInfo.accelerometerBiasY); + auto calibratedAccelerationZ = ((message.acceleration.z * calibrationInfo.accelerometerScale[2][2]) - calibrationInfo.accelerometerBiasZ); + auto magnitude = sqrt((calibratedAccelerationX * calibratedAccelerationX) + (calibratedAccelerationY * calibratedAccelerationY) + (calibratedAccelerationZ * calibratedAccelerationZ)); + + LOGV("Got Accelerometer[%u] frame (%" PRId64 "): Timestamp %" PRId64 ", FrameID = %d, Temperature = %.0f, Acceleration[%f, %f, %f], Calibrated Acceleration[%f, %f, %f], Magnitude = %f", + message.sensorIndex, gStatistics.accelerometer[message.sensorIndex].frames, message.timestamp, message.frameId, message.temperature, message.acceleration.x, message.acceleration.y, message.acceleration.z, + calibratedAccelerationX, calibratedAccelerationY, calibratedAccelerationZ, magnitude); + + if (gConfiguration.statistics == true) + { + static UtilTimeStamps timeStamp(message); + timeStamp.setTime(message); + + accelerometerCSV << std::fixed << unsigned(message.sensorIndex) << "," << gStatistics.accelerometer[message.sensorIndex].frames << "," + << timeStamp.hostCurrentSystemTime << "," << message.systemTimestamp << "," << timeStamp.fwTimeStamp << "," << timeStamp.arrivalTimeStamp << "," << timeStamp.latency << "," + << message.frameId << "," << message.temperature << "," << message.acceleration.x << "," << message.acceleration.y << "," << message.acceleration.z << "," + << calibratedAccelerationX << "," << calibratedAccelerationY << "," << calibratedAccelerationZ << "," << magnitude << "\n"; + } + }; + + virtual void onGyroFrame(TrackingData::GyroFrame& message) + { + gStatistics.inc(message); + + LOGV("Got Gyro[%u] frame (%" PRId64 "): Timestamp %" PRId64 ", FrameID = %d, Temperature = %.0f, AngularVelocity[%f, %f, %f]", message.sensorIndex, gStatistics.gyro[message.sensorIndex].frames, + message.timestamp, message.frameId, message.temperature, message.angularVelocity.x, message.angularVelocity.y, message.angularVelocity.z); + + if (gConfiguration.statistics == true) + { + static UtilTimeStamps timeStamp(message); + timeStamp.setTime(message); + + gyroCSV << std::fixed << unsigned(message.sensorIndex) << "," << gStatistics.gyro[message.sensorIndex].frames << "," + << timeStamp.hostCurrentSystemTime << "," << message.systemTimestamp << "," << timeStamp.fwTimeStamp << "," << timeStamp.arrivalTimeStamp << "," << timeStamp.latency << "," + << message.frameId << "," << message.temperature << "," << message.angularVelocity.x << "," << message.angularVelocity.y << "," << message.angularVelocity.z << "\n"; + } + }; + + virtual void onVelocimeterFrame(TrackingData::VelocimeterFrame& message) + { + gStatistics.inc(message); + + LOGV("Got Velocimeter[%u] frame (%" PRId64 "): Timestamp %" PRId64 ", FrameID = %d, Temperature = %.0f, AngularVelocity[%f, %f, %f]", message.sensorIndex, gStatistics.velocimeter[message.sensorIndex].frames, + message.timestamp, message.frameId, message.temperature, message.angularVelocity.x, message.angularVelocity.y, message.angularVelocity.z); + + if (gConfiguration.statistics == true) + { + static UtilTimeStamps timeStamp(message); + timeStamp.setTime(message); + + velocimeterCSV << std::fixed << unsigned(message.sensorIndex) << "," << gStatistics.velocimeter[message.sensorIndex].frames << "," + << timeStamp.hostCurrentSystemTime << "," << message.systemTimestamp << "," << timeStamp.fwTimeStamp << "," << timeStamp.arrivalTimeStamp << "," << timeStamp.latency << "," + << message.frameId << "," << message.temperature << "," << message.angularVelocity.x << "," << message.angularVelocity.y << "," << message.angularVelocity.z << "\n"; + } + }; + + virtual void onControllerDiscoveryEventFrame(TrackingData::ControllerDiscoveryEventFrame& message) + { + //LOGD("Got Controller Discovery Event: on MacAddress [%02X:%02X:%02X:%02X:%02X:%02X], AddressType [0x%X], Manufacturer ID [0x%X], Vendor Data [0x%X], App Version [%u.%u.%u], Boot Loader Version [%u.%u.%u], Soft Device Version [%u], Protocol Version [%u]", + // message.macAddress[0], message.macAddress[1], message.macAddress[2], message.macAddress[3], message.macAddress[4], message.macAddress[5], + // message.addressType, + // message.manufacturerId, + // message.vendorData, + // message.app.major, message.app.minor, message.app.patch, + // message.bootLoader.major, message.bootLoader.minor, message.bootLoader.patch, + // message.softDevice.major, + // message.protocol.major); + + if (gConfiguration.controllersCount > 0) + { + ProfileType controllerId = ProfileTypeMax; + + /* Check if controller is in argument list and we didn't send connect request yet */ + if (((controllerId = gConfiguration.findController(message.macAddress)) != ProfileTypeMax) && (gStatistics.isConnected(message.macAddress) == 0)) + { + /* Burning controller FW */ + if (gConfiguration.controllers[controllerId].burn.configure > ControllerBurnDisabled) + { + + switch (gConfiguration.controllers[controllerId].burn.state) + { + case ControllerBurnStateNotStarted: + /* Burn not started yet, initiating burn process */ + LOGD("Start updating FW of controller %d MacAddress %02X:%02X:%02X:%02X:%02X:%02X", controllerId, message.macAddress[0], message.macAddress[1], message.macAddress[2], message.macAddress[3], message.macAddress[4], message.macAddress[5]); + updateControllerFW(message, controllerId); + + /* If update controller FW succeeded, skipping connection attempt until next discovery event */ + /* If update controller FW failed, return and try to burn again on next discovery */ + return; + break; + + case ControllerBurnStateProcessing: + /* Burn is already in progress, exiting */ + LOGD("FW Update is already in progress on controller %d MacAddress %02X:%02X:%02X:%02X:%02X:%02X", controllerId, message.macAddress[0], message.macAddress[1], message.macAddress[2], message.macAddress[3], message.macAddress[4], message.macAddress[5]); + return; + break; + + case ControllerBurnStateDone: + /* Burn is done, continue to controller connect */ + LOGD("Controller FW is updated on controller %d MacAddress %02X:%02X:%02X:%02X:%02X:%02X", controllerId, message.macAddress[0], message.macAddress[1], message.macAddress[2], message.macAddress[3], message.macAddress[4], message.macAddress[5]); + break; + } + } + + Status status = Status::SUCCESS; + uint8_t controllerId = ProfileTypeMax; + + /* Found our controller, trying to connect */ + LOGD("Connecting to Controller with MacAddress = %02X:%02X:%02X:%02X:%02X:%02X, AddressType [0x%X]", message.macAddress[0], message.macAddress[1], message.macAddress[2], message.macAddress[3], message.macAddress[4], message.macAddress[5], message.addressType); + + TrackingData::ControllerDeviceConnect device(message.macAddress, CONTROLLER_CONNECT_TIMEOUT_MSEC, message.addressType); + status = gDevice->ControllerConnect(device, controllerId); + if (status != Status::SUCCESS) + { + LOGE("Error: Failed connecting controller MacAddress = %02X:%02X:%02X:%02X:%02X:%02X , Status %s (0x%X)", + message.macAddress[0], message.macAddress[1], message.macAddress[2], message.macAddress[3], message.macAddress[4], message.macAddress[5], statusToString(status).c_str(), status); + } + else + { + gStatistics.inc(message); + } + } + } + else + { + LOGE("Error: Got Controller DiscoveryEvent on MacAddress = %02X:%02X:%02X:%02X:%02X:%02X while controllers are disabled", + message.macAddress[0], message.macAddress[1], message.macAddress[2], message.macAddress[3], message.macAddress[4], message.macAddress[5]); + } + }; + + virtual void onControllerConnectedEventFrame(TrackingData::ControllerConnectedEventFrame& message) + { + if ((message.controllerId >= ProfileTypeMax) || (message.controllerId < Controller1)) + { + LOGE("Error: Got Bad Controller Connected Event: Status = %s (0x%X), ControllerID = %d, ManufactorId = 0x%X, Protocol = %u, App = %u.%u.%u, SoftDevice = %u, BootLoader = %u.%u.%u", + statusToString(message.status).c_str(), message.status, message.controllerId, message.manufacturerId, message.protocol.major, message.app.major, message.app.minor, message.app.build, message.softDevice.major, message.bootLoader.major, message.bootLoader.minor, message.bootLoader.build); + return; + } + + LOGD("Got Controller[%d] Connected Event with status = %s (0x%X)", message.controllerId, statusToString(message.status).c_str(), message.status); + + if (message.status == Status::SUCCESS) + { + gStatistics.controller[message.controllerId].isConnected = true; + + if (gConfiguration.controllers[message.controllerId].calibrate == true) + { + LOGD("Start calibrating controller %d", message.controllerId); + Status status = gDevice->ControllerStartCalibration(message.controllerId); + if (status != Status::SUCCESS) + { + LOGE("Error: Failed calibrating controller %d, Status %s (0x%X)", message.controllerId, statusToString(status).c_str(), status); + gStatistics.runTime[message.controllerId].start(); + } + } + else + { + gStatistics.runTime[message.controllerId].start(); + } + } + else + { + LOGE("Error: Failed connecting to Controller[%d] - status = %s (0x%X)", message.controllerId, statusToString(message.status).c_str(), message.status); + } + }; + + virtual void onControllerDisconnectedEventFrame(TrackingData::ControllerDisconnectedEventFrame& message) + { + if ((message.controllerId >= ProfileTypeMax) || (message.controllerId < Controller1)) + { + LOGE("Error: Got Bad Controller Disconnected Event on ControllerID = %d", message.controllerId); + return; + } + + LOGD("Got Controller Disconnected Event on ControllerID = %d", message.controllerId); + gStatistics.controller[message.controllerId].isConnected = false; + gStatistics.controller[message.controllerId].isCalibrated = false; + gStatistics.runTime[message.controllerId].stop(); + }; + + + virtual void onControllerCalibrationEventFrame(TrackingData::ControllerCalibrationEventFrame& message) + { + if ((message.controllerId >= ProfileTypeMax) || (message.controllerId < Controller1)) + { + LOGE("Error: Got Bad Controller Calibration Event on ControllerID = %d", message.controllerId); + return; + } + + if (message.status != Status::SUCCESS) + { + LOGE("Error: Got Controller Calibration Event on ControllerID = %d, Status %s (0x%X)", message.controllerId, statusToString(message.status).c_str(), message.status); + return; + } + + LOGD("Got Controller[%d] Calibration Event with status %s (0x%X)", message.controllerId, statusToString(message.status).c_str(), message.status); + + gStatistics.controller[message.controllerId].isCalibrated = true; + gStatistics.runTime[message.controllerId].start(); + }; + + virtual void onControllerFrame(TrackingData::ControllerFrame& message) + { + gStatistics.inc(message); + + if (gStatistics.controller[message.sensorIndex].isConnected == true) + { + LOGD("Got Controller[%u] frame: Timestamp %" PRId64 ", EventId = 0x%X, InstanceId = 0x%X, SensorData = %02X:%02X:%02X:%02X:%02X:%02X", message.sensorIndex, message.timestamp, + message.eventId, message.instanceId, message.sensorData[0], message.sensorData[1], message.sensorData[2], message.sensorData[3], message.sensorData[4], message.sensorData[5]); + } + else + { + LOGE("Error: Got Controller[%u] frame on disconnected controller: Timestamp %" PRId64 ", EventId = 0x%X, InstanceId = 0x%X, SensorData = %02X:%02X:%02X:%02X:%02X:%02X", message.sensorIndex, message.timestamp, + message.eventId, message.instanceId, message.sensorData[0], message.sensorData[1], message.sensorData[2], message.sensorData[3], message.sensorData[4], message.sensorData[5]); + } + + if (gConfiguration.statistics == true) + { + static UtilTimeStamps timeStamp(message); + timeStamp.setTime(message); + + controllerCSV << std::fixed << unsigned(message.sensorIndex) << "," << gStatistics.controller[message.sensorIndex].frames << "," + << timeStamp.hostCurrentSystemTime << "," << message.systemTimestamp << "," << message.timestamp << "," << timeStamp.fwTimeStamp << "," << message.arrivalTimeStamp << "," << timeStamp.arrivalTimeStamp << "," << timeStamp.latency << "," + << unsigned(message.eventId) << "," << unsigned(message.instanceId) << "," + << unsigned(message.sensorData[0]) << "," << unsigned(message.sensorData[1]) << "," << unsigned(message.sensorData[2]) << "," + << unsigned(message.sensorData[3]) << "," << unsigned(message.sensorData[4]) << "," << unsigned(message.sensorData[5]) << "\n"; + } + }; + + virtual void onRssiFrame(TrackingData::RssiFrame& message) + { + gStatistics.inc(message); + + LOGV("Got Rssi[%u] frame (%" PRId64 "): Timestamp %" PRId64 ", FrameID = %d, SignalStrength = %.0f", message.sensorIndex, gStatistics.rssi[message.sensorIndex].frames, message.timestamp, message.frameId, message.signalStrength); + + if (gConfiguration.statistics == true) + { + static UtilTimeStamps timeStamp(message); + timeStamp.setTime(message); + + rssiCSV << std::fixed << unsigned(message.sensorIndex) << "," << gStatistics.rssi[message.sensorIndex].frames << "," + << timeStamp.hostCurrentSystemTime << "," << message.systemTimestamp << "," << timeStamp.fwTimeStamp << "," << timeStamp.arrivalTimeStamp << "," << timeStamp.latency << "," + << message.frameId << "," << message.signalStrength << "\n"; + } + }; + + virtual void onLocalizationDataEventFrame(TrackingData::LocalizationDataFrame& message) + { + gStatistics.inc(message); + + /* On Get localization map, saving to output file */ + if (message.length > 0) + { + LOGD("Got Localization Data frame: status = %s (0x%X), moredata = %s, chunkIndex = %d, length = %d", statusToString(message.status).c_str(), message.status, (message.moreData) ? "True" : "False", message.chunkIndex, message.length); + + std::fstream & mapFile = gConfiguration.localization[LocalizationTypeGet].file; + string filename = gConfiguration.localization[LocalizationTypeGet].filename; + if (mapFile.is_open()) + { + mapFile.write((const char*)message.buffer, message.length); + } + else + { + LOGE("Error: Can't write localization data to file %s", filename.c_str()); + } + } + else + { + LOGD("Got Set Localization Data frame complete: status = %s (0x%X)", statusToString(message.status).c_str(), message.status); + } + }; + + virtual void onFWUpdateEvent(OUT TrackingData::ControllerFWEventFrame& frame) override + { + ProfileType controllerId = gConfiguration.findController(frame.macAddress); + if (controllerId != ProfileTypeMax) + { + gStatistics.controller[controllerId].FWProgress = frame.progress; + LOGV("Got controller %d FW update Mac Address [%02X:%02X:%02X:%02X:%02X:%02X], progress %u%%, status 0x%X", controllerId, + frame.macAddress[0], frame.macAddress[1], frame.macAddress[2], frame.macAddress[3], frame.macAddress[4], frame.macAddress[5], frame.progress, frame.status); + + if (frame.progress == 100) + { + gConfiguration.controllers[controllerId].burn.state = ControllerBurnStateDone; + } + } + } + + virtual void onStatusEvent(OUT TrackingData::StatusEventFrame& frame) override + { + gStatistics.inc(frame); + LOGD("Got status = %s (0x%X)", statusToString(frame.status).c_str(), frame.status); + } + + virtual void onControllerLedEvent(OUT TrackingData::ControllerLedEventFrame& frame) override + { + gStatistics.inc(frame); + LOGD("Got Controller[%d] Led[%d] intensity %" PRId64 " Event", frame.controllerId, frame.ledId, frame.intensity); + } +}; + +class ReplayListener : public TrackingDevice::Listener +{ +public: + virtual void onVideoFrame(TrackingData::VideoFrame& frame) + { + Status status = gDevice->SendFrame(frame); + if (status != Status::SUCCESS) + { + LOGE("Failed to send video frame with status (0x%X)", status); + } + } + virtual void onAccelerometerFrame(TrackingData::AccelerometerFrame& message) + { + Status status = gDevice->SendFrame(message); + if (status != Status::SUCCESS) + { + LOGE("Failed to send accelerometer frame with status (0x%X)", status); + } + } + virtual void onGyroFrame(TrackingData::GyroFrame& message) + { + Status status = gDevice->SendFrame(message); + if (status != Status::SUCCESS) + { + LOGE("Failed to send gyro frame with status (0x%X)", status); + } + } +}; + +void handleThreadFunction() +{ + HANDLE objects[2]; + + objects[0] = readyEvent; + objects[1] = stopEvent; + + while (true) + { + auto res = WaitForMultipleObjects(2, objects, FALSE, INFINITE); + + if (res == WAIT_OBJECT_0) // Object 0 = we have some event ready to be handled + { + gManagerInstance->handleEvents(); + } + else // Stop event - exit thread + { + return; + } + } // end of while +} + + +void saveImage(TrackingData::VideoFrame* frame) +{ + std::ostringstream fwTimeStamp; + fwTimeStamp << std::setw(16) << std::setfill('0') << frame->timestamp; + std::string fileHeaderName(gFileHeaderName); + std::ofstream save_file("Image_" + fileHeaderName + "_Libtm_" + std::to_string(LIBTM_VERSION_MAJOR) + "_" + std::to_string(LIBTM_VERSION_MINOR) + "_" + std::to_string(LIBTM_VERSION_PATCH) + + "_FW_" + FW_VERSION + + "_FW_TimeStamp_" + fwTimeStamp.str() + + "_FE" + std::to_string(frame->sensorIndex) + + "_FrameID_" + std::to_string(frame->frameId) + + ".pgm", std::ofstream::binary); + + std::string pgm_header = "P5 " + std::to_string(frame->profile.width) + " " + std::to_string(frame->profile.height) + " 255\n"; + save_file.write(pgm_header.c_str(), pgm_header.size()); + save_file.write((char*)frame->data, frame->profile.height * frame->profile.stride); + save_file.close(); +} + +void imageThreadFunction() +{ + HANDLE objects[1]; + uint32_t frameCount = 0; + + objects[0] = stopEvent; + + LOGD("Starting Image thread"); + + while (true) + { + auto res = WaitForMultipleObjects(1, objects, FALSE, MAX_WAIT_FOR_IMAGE_MSEC); + + while (gStatistics.videoList.size()) + { + gStatistics.videoFrameListMutex.lock(); + auto frame = gStatistics.videoList.front(); + gStatistics.videoList.pop_front(); + gStatistics.videoFrameListMutex.unlock(); + saveImage(frame); + + frameCount++; + free((void*)frame->data); + } + + if (res == WAIT_OBJECT_0) // Object 0 = Stop event + { + while (gStatistics.videoList.size()) + { + gStatistics.videoFrameListMutex.lock(); + auto frame = gStatistics.videoList.front(); + gStatistics.videoList.pop_front(); + gStatistics.videoFrameListMutex.unlock(); + + saveImage(frame); + + frameCount++; + free((void*)frame->data); + } + + LOGD("Closing Image thread - saved %d images (PGM)", frameCount); + return; + } + } // end of while +} + +void hostLogThreadFunction() +{ + TrackingData::Log log; + HANDLE objects[1]; + FILE *logThreadStream = stdout; + uint32_t waitForLogMsec = MAX_WAIT_FOR_LOG_MSEC; + + objects[0] = stopEvent; + + std::string fileHeaderName(gFileHeaderName); + std::string fileName = "Host_Log_" + fileHeaderName + "_Libtm_" + std::to_string(LIBTM_VERSION_MAJOR) + "_" + std::to_string(LIBTM_VERSION_MINOR) + "_" + std::to_string(LIBTM_VERSION_PATCH) + "_FW_" + FW_VERSION + ".log"; + + auto rc = fopen_s(&logThreadStream, fileName.c_str(), "w"); + if (rc != 0) + { + LOGE("Error while opening file %s", fileName.c_str()); + return; + } + + LOGD("Starting Host Log thread"); + fprintf(logThreadStream, "Host log: Libtm %u.%u.%u.%u, FW %s\n\n", LIBTM_VERSION_MAJOR, LIBTM_VERSION_MINOR, LIBTM_VERSION_PATCH, LIBTM_VERSION_BUILD, FW_VERSION); + + while (true) + { + auto res = WaitForMultipleObjects(1, objects, FALSE, waitForLogMsec); + + gManagerInstance.get()->getHostLog(&log); + + if (log.entries > 0) + { + fprintf(logThreadStream, "Received %d Host log entries from last %d msec (max entries %d)\n", log.entries, waitForLogMsec, log.maxEntries); + } + + for (uint32_t i = 0; i < log.entries; i++) + { + char deviceId[30] = { 0 }; + short device = ((uintptr_t)log.entry[i].deviceID & 0xFFFF); + if (device != 0) + { + snprintf(deviceId, sizeof(deviceId), "-%04hX", device); + } + + fprintf(logThreadStream, "%02d:%02d:%02d:%03d [%06lu] [%s] %s%s(%d): %s\n", + log.entry[i].localTimeStamp.hour, log.entry[i].localTimeStamp.minute, log.entry[i].localTimeStamp.second, log.entry[i].localTimeStamp.milliseconds, + log.entry[i].threadID, + fwLogVerbosityLevel(log.entry[i].verbosity), + log.entry[i].moduleID, + deviceId, + log.entry[i].lineNumber, + log.entry[i].payload); + + } + if (log.entries > 0) + { + fprintf(logThreadStream, "\n"); + } + + if ((log.entries > ((log.maxEntries * 2) / 5)) && (waitForLogMsec > MIN_WAIT_FOR_LOG_MSEC)) + { + waitForLogMsec = waitForLogMsec / WAIT_FOR_LOG_MULTIPLIER_STEP; + } + else if ((log.entries < ((log.maxEntries * 1) / 5)) && (waitForLogMsec < MAX_WAIT_FOR_LOG_MSEC)) + { + waitForLogMsec = waitForLogMsec * WAIT_FOR_LOG_MULTIPLIER_STEP; + } + + if (res == WAIT_OBJECT_0) // Object 0 = Stop event + { + if (logThreadStream != NULL) + { + fclose(logThreadStream); + LOGD("Host logs saved to %s", fileName.c_str()); + } + + LOGD("Closing Host Log thread"); + return; + } + } // end of while +} + +void fwLogThreadFunction() +{ + TrackingData::Log log; + HANDLE objects[1]; + FILE *logThreadStream = stdout; + uint32_t waitForLogMsec = MAX_WAIT_FOR_LOG_MSEC; + Status status = Status::SUCCESS; + char deviceBuf[30] = { 0 }; + SYSTEMTIME lt; + + objects[0] = stopEvent; + + std::string fileHeaderName(gFileHeaderName); + std::string fileName = "FW_Log_" + fileHeaderName + "_Libtm_" + std::to_string(LIBTM_VERSION_MAJOR) + "_" + std::to_string(LIBTM_VERSION_MINOR) + "_" + std::to_string(LIBTM_VERSION_PATCH) + "_FW_" + FW_VERSION + ".log"; + + if (gConfiguration.logConfiguration[LogSourceFW].logOutputMode == LogOutputModeBuffer) + { + auto rc = fopen_s(&logThreadStream, fileName.c_str(), "w"); + if (rc != 0) + { + LOGE("Error while opening file %s", fileName.c_str()); + return; + } + } + + LOGD("Starting Log thread"); + if (logThreadStream != stdout) + { + fprintf(logThreadStream, "FW log: Libtm %u.%u.%u.%u, FW %s\n\n", LIBTM_VERSION_MAJOR, LIBTM_VERSION_MINOR, LIBTM_VERSION_PATCH, LIBTM_VERSION_BUILD, FW_VERSION); + } + + while (true) + { + auto res = WaitForMultipleObjects(1, objects, FALSE, waitForLogMsec); + GetLocalTime(<); + + if (gDevice == NULL) + { + continue; + } + + short device = ((uintptr_t)gDevice & 0xFFFF); + + status = gDevice->GetFWLog(log); + if (status == Status::SUCCESS) + { + if (logThreadStream != stdout) + { + fprintf(logThreadStream, "%02d:%02d:%02d:%03d Device-%04hX: Received %d FW log entries from last %d msec (max entries %d)\n", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds, device, log.entries, waitForLogMsec, log.maxEntries); + if (log.entries > 0) + { + fprintf(logThreadStream, "%02d:%02d:%02d:%03d : [Verbosity] [Thread] [Function] [Module](Line): \n", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds); + } + + for (uint32_t i = 0; i < log.entries; i++) + { + short device = ((uintptr_t)log.entry[i].deviceID & 0xFFFF); + + fprintf(logThreadStream, "%02d:%02d:%02d:%03d Device-%04hX: %012llu [%s] [%02d] [0x%X] [%s](%d): %s\n", + log.entry[i].localTimeStamp.hour, log.entry[i].localTimeStamp.minute, log.entry[i].localTimeStamp.second, log.entry[i].localTimeStamp.milliseconds, + device, + log.entry[i].timeStamp, + fwLogVerbosityLevel(log.entry[i].verbosity), + log.entry[i].threadID, + log.entry[i].functionSymbol, + log.entry[i].moduleID, + log.entry[i].lineNumber, + log.entry[i].payload + ); + } + if (log.entries > 0) + { + fprintf(logThreadStream, "\n"); + } + } + + if ((log.entries > ((log.maxEntries * 2) / 5)) && (waitForLogMsec > MIN_WAIT_FOR_LOG_MSEC)) + { + waitForLogMsec = waitForLogMsec / WAIT_FOR_LOG_MULTIPLIER_STEP; + } + else if ((log.entries < ((log.maxEntries * 1) / 5)) && (waitForLogMsec < MAX_WAIT_FOR_LOG_MSEC)) + { + waitForLogMsec = waitForLogMsec * WAIT_FOR_LOG_MULTIPLIER_STEP; + } + } + else + { + fprintf(logThreadStream, "%02d:%02d:%02d:%03d Device-%04hX: Failed to receive FW log entries from last %d (msec)\n", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds, device, waitForLogMsec); + log.entries = 0; + } + + if (res == WAIT_OBJECT_0) // Object 0 = Stop event + { + if ((logThreadStream != NULL) && (gConfiguration.logConfiguration[LogSourceFW].logOutputMode == LogOutputModeBuffer)) + { + fclose(logThreadStream); + LOGD("FW logs saved to %s", fileName.c_str()); + } + + LOGD("Closing Log thread"); + return; + } + } // end of while +} + +void showArguments() +{ + printf("libtm_util version %d.%d\n", LIBTM_UTIL_VERSION_MAJOR, LIBTM_UTIL_VERSION_MINOR); + printf("Usage: libtm_util [-h] [-y] [-reset (optional)] [-time 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%0D_%nu#Zb z2f8w(sGIEhP`qM$7m;2Sjo*j8OVrMBgp15WIQ;Mk5oCg*s5+70a+Se~{N3t?XFFlH zcnUmOrDN?T(l~y9Ra0-YYRU3Y0F)u%#Rz+zHiTR%QgM2&1eqli;>%u?#r~8JMu%rle73 zkN8_Xv`KX%iUS`xe&#GIKP1+t1v5$6Nn}upz4XOEW5@hFBu*}bU(NrfM5G0Tm@{0hA~a02z~H5qolh7{@&?dRa; zRWB*~AKtf%u!{X-N*CZ4f6zO#5PF34pJwE+=m~*Q#EcsAfwu-aKd+sxWlI$VWQfEf z2jUJZS_XtIQQ&aFJUe-d)}fP3F%7CHdJwaz>r&8S&sk#w4FVoer#a}G_4S4M2}Vsl z`J3(Tla>bLl&@Di3sgv(NGS)hCi;etQV*X4R^j88zV>c;^Ulu3zRo{`msPJ8z>sR%x^Qde{Tu<^vOAV%!Do2N!fZbh)d%mKaw-)RZWKT6A3YWU7 zNiEtbpE;}-ips+aIhdJXd-Nh5x8mVfV&aE2ej2fJue-gnj)>55!h+Fnxct1HQh5dr z^nf|9ZE8??Ia5+ZgPLnh*y?-v^x4+kdn$bq{BzZJ!DVWz3SS1vV=x95b(25>IjWZHDO%k1)V5bMf9dNyztq8wsi}fL_nG(%J?LsZZq-c9q zYm<@otkZ@kZAxkxYBVot=LzGBCkCgk|6ZGj+(Aw+57rkHeL`@S~G%D z*zE7fc00#3PDj5q%rOgRdWYIXrVVVyI4Y}-)H==DwINmSO?#l5eM;LxOsIuU9Mb56 zJ+kmm>Gx%KEi%rlkp{)_&(_)jPu4XS2DL((`jncvpVq^Es z+LTo6pjcAPK)ddEl!#IW!9q$fs`ujOm(eswQpzHilvJoN?31;0c-k?kmoY+nN|?fe zGR@?4w{VvtYNKJBCBXbG;TSB6K$I1cC~z>YNTQrnHY(hg`xKL~((u~2pe21o+fc(W zb{?3*^yI*d7w?b#=3NpWFW!p*W#Ej^3eYX3u8E)@aR-;=8(N8{G8&xVSk9cTA`kiG z?Kno~<(Pt^M1XLKrwdp^gPp@TD3Z-r^59NFYtux;Q1gKdW-dB1RKkH^MjYmialdbC zDFE!seqYm(qRg1C9+o*3w{VRbdt~XDvL)AJTcQUMJhn4#`x?_*Bvpg)xK?CFN+9P{ z2YrY^fw`IpC^3cO!ZJ!AzWDShCQjhy_NVo=s8~ghtaH9iBqI7%b=x6jxTh)!Dge9Q zG$TVPig>pjsTo}xl({5p!kY|^MtC>y`qj0Lxltb}r4!geMd6S;MCpI5cL#G)_+ehu zO>0yyy&s>pWAbV=GtG{LmAgH9HlCFTbF4eX0rmlAj-Fb&w5}(D4pS{Sn`Dc8{QblT zNF>8XLZAkU^=NIakMSr}PGaL~=~+-aObRFWr9ZUR!BBi#ZvnQ^EUn$W4yc_FQ||o{ z>G(X;*ryeiBRUyO4n9`ZpzBHw)#iL`>NN9I5`3huEd(ghA>VbeVZ7J5&1$AZiT zWT23{eUN^rR58|+IW-pj+V_bPdV0^6a8P`7AM2@;xs0ooDtMeRHnsTuXRmZj`fV&m$D_sY#!4~~RxElEA`+VU)DRgAUPnQI15t0vG`;Q9=?9%m-^ zR*k+tbi9)Gx%ulb>aiotFLG+#l3HcA5pB6lAw0g0v*-aqBM8X4kPZc9_6SuH*A;52 zN6%RWu#P|WBcD&FNJz$IR&faEAj%W2I_A+m{fa9_Ck!~ zIcdBvZLf0M3$G6^8mA;HavRiepq1wfH$OP{1HtP33yUmo@BDJDqVC47i0frV?#d#X zcf}83bMyxFfbbjIE@ZJMuy0XblZuN9p^ri4#ogVt6;?z6;UWn05)kvmb%;O^m}c<~ zR?L}L?0Svv8>l^8*|1)$F{s4rjuP=IrsN&kG35=mM+tej!qz?Z%uo_YF6&hL_m`16 zb6Zf;f|x#OC5YwbTe(5w4XJNMi+LI>bSpic?cbk~l&ho!0mth_I`1haHZfLkNrWH? zw)}j7Cr6JohqV&>QsOQT!3{i3JX8RaoXhc#i({y;W(xS_$K(8cY~2bj3}J;47Xqdo zkF-YFj#8#cTcEC!ddmt_@@@RH6Iol4$H>Q_4a6~MrZoX^$4TozVoD4Bw#|)rk&d!a3Hg&f?SddphCDm=#3(fi>U1?R^(C}oeSW;8R|_cpk+c2_ z@;ohiw=;Jb@&Gm!^|2>zTJAN}#8=mpnrQLr8vHKZ>>zkjL}8EzJjUnGHBbm=;ig%T zMtK}lYuy}%J_?mONE;Y9A$NhKTx#MHyw)3imFLu?2~IXagi49gO8N7lDVz333G!CI zPDZ5o6MktteHp5^98a56ZPqo=E#j|2c_7nrNs`wsCm51cApqtFEYOnseliNC>JDBC zCpL*}<#EB|xsl^Ff_@Yvyic3Dxw~r`B)_g}%mHA-oet81I17>^nMsng&qm!t&6a*eAad#;%q0AH6Fgk;((rc{topEIGHfIoqNwTB2y6c$4PE8!i$^u zM%s`|E}ZjgyV%miKyD7^tr6@yi zo%@h+Rf+!>aqj^hS8=@$-#e1!wPml3D29N+HpWyN_d-al)fVk)D=IF;zieblwuNO$ zXeHZ_Ktu^G^gu`>6$qgOQh*Rb4G;_j2%(culMq5e3y@F(-}}zoGIz9+E&e>uN1j}- zcJFzoojG&n%$ajyxb}sTw0%FhbI5y1+#1I{5*fy;wYReS&!>}c{z%oeG}RSj@w%2^MscW@JZDZ}_uy);suSfDH~SL7wli3m zQn(F8ogEVd3sJdF$K~i0Y|gm+srI693T^M$M7Psm$0Ox>ERjSI7X&AixmSmzn9gIP z5Q)cmoUOkT>@mEAn6JpyK|gahj%{z}QN4lU;JcHIz?zM#s5lz9ua@X9nSGMZ zBK!jGMSG0YT%_eH*eCE&s?Q-L8jj?hh+k?K8hk4UCrKKA$v{oGD*~67z&Hh=Fo<9LL`y4swk%#)aIUom>j%9oio1Ei{s;~?j@GdEjpiT{oL+0{Tq||wAXFg%?>Mt&#Px^?`al@I@A zDGFQoog|aUvVQ!^E1y=k9PY6|>yj0a^b%bvU3B9>zz-fnEb3U7@wd^RRxilnfuT)&5w_ zRmICz7xTR`luHw$ZM3fMsKw2#<;5Z+G<6kA{q3DSMVRHg``2Olm+#fqE~{N;E#E#_ z3HwWficXo@L(Pm zBH2=BB^%>SEt%$GqA{N>u1w_?%(qgx6}UMP<6nuGHBz&U9|MiEirkW7!dS)4rEaIY zy7-6wS&?*E%JGcX0Qw;@N;0+3&@Yid(&FtZth;?3GIn*T$Ux}b?G`mmSt2&U*1Gm0 zHb+p=P`YFF_y6X$g5fwUsY*NhW~;O0QWs(LwH(D=9%Ev}d1O}#e(5Tu-naezIC?5> z#D2V^tEdd|?r(t@WA2yA(%=Py@V@4j&W{!2f9-A%$|**2MpG8b0Z06`z`JFA*oOly>%EIB`@_B; zKqW$%MISF_#f;vlK~J!Y)~`A+$+mA?#>it21o#~il5NiyWeB9c-_UtKM(U0 zov+=ykU9z6dN4Ht-vk+s)uE$7nL*IL_W09#9X>l7bl4r`SptlLW##MgZji>$#nw_= zXQy8Jj`FH=Rmvp|9Kn|T()Bm%XfOXWMAgROesdJt@`TQh+pb%vXkefeVcLvfQB$jp#Y_XHzr;78FC&vNN0rp>$-zU0xT@|Hb=? z`*8PXes(4umdXAN#mx|;y`5g4J3`XE`s?(a2Zfte#*`frp4cr(ouLNjH&0ci1!1Js zi7zR^V~7;~(B83jV7*%ezwitojD*e=oHqva7gURiaHV6=O~0e?D*CFm%bWLH?3rZQ zBUX8$%wci=R(GJJh{s~&5_!L}Zc2R!{CW&^NoVN1Uc~{V_hr!-PyIKIVCX=QPKK0{ z`$dbIlFAk1Yed|xxzdN-LLJT3&@4wgvr1AkvpCObFmi76@$b$egtAnYK&Squ0^u@* z{3h_a+R~}|$X}(IMv-EKaYLc6r?tJUwbZYD2l38O;@UTIHYs`%ktf^PTJ4p3+GH=}@E>5r|!I~-h>Ky56*5Z*VO zADBw+3;a_PVOUHsW4&LUso<^4E>%#=aKCj`k@1HfIxXaZ%+nPCZ)@)t8a7`n| zueH>{pPZj(&zm!Mjawo7i&;OXXYEneCkcy<8JG+M_K6`Yzq1lI)Vyf(iQzIB;wJ~@ ztlOU$pj;Y1=_Ysfd}8(6pv3qmhUc$#pBN-#9ZInAhq{x=nMv-PTd*WJ=jP3wyLj>9 zY10^JIdB}Exo*ApLlW;?ZM;`&O|F&i zt?%;Qo4&-h-WJ}U@`LwYXSesm?QzT6?XV!A_g=?V@4dlGDy+x9Jj{EgW4!lrQ*CSI z%rV~I{NUlWul2q^W2|Le8maRBcHcvX&gK#XzlpxXWC$>+$`o{}jy z+pO8`Jtb@Qk)77;mDWOQc78qnzjnSgyZ#t!c07rnvd#FT$(o($>__s71OjxspJTW# zhtECR6}+R9bsEquI8InZHKJ^oE(3oa`MLC z5qi@$c4A$dH+M~EcU#v$dj~$ELi4at;@>p`ZGHV~+BlMPHdvGGrAN)UvOV|BZ%q5$ z9fv;ff7kWbObG1=`qDY`JowTE*iPElti_VqUuu`AuP)zm`C*qnGXL({3$iDlcglw~ z=Z)JD)RF59p@h2A^w|b|r6dcw^rCf*zpVV%pKt!rtdG8R!D&CL*?asaR?rqGIM|KK za%P`@_{}e*fA!{+564~lpC?W~u;!fq_XcV3OOB7c_0-?q{p{s?{PML2XM8VI^Nk%E zV|^3`HgTeL_si<;I^)hyi}MaV_Qd8%x1DhGa|>(EjO<9a=y=Yk0#kCag#GQoQ*Zg> z&tCuUh3lXA&Ouj4&#PHJeYCKun&cIU`04*XIQzc!Fa7nFxwFSEyx@yF)f{}UuttmN zR2BW%3Rk~w3H+zKRz35-((PN~?`H1$$tm-ntaS{Mn!Hy!eeys$QgTH*s=3^_K`(e$#kK8=xkd_(m z)nwn7%|M8l5%w6)lBoWX)!gsAw$1Oo^2CB?Pn&U^u77u5yw3(NBxjSjmwzB`nO5iiK^sYr`Zo{w0Ye27~Wy0T(chuP>aoUjplU3VqaN7QK5FnOFGu#>CpJ$8pFpxOzD~^{-Ie=$$ z09-P_Rn}}e0}o~e5R>lJ#Z*kMLyf9K#;L<|It1chR}qiZgwQc|Rv0Jkq?qr8 zAO@m2A!7;daSg%Hn_^)6Rl``COD7N*02z!T?VE8Iccf|al15ioS6iRS!R-!x2ye$D z20HqVS-}K6@vVBvJE__ioE~^tM}U)E4t~l7M71#y^bRmO@s%J193>05-jyE$q1VEI zcvXXdQ_HYEzmg3ms1`IjEqGm{%^?QNunwP+*qDHC28c)S;4DY{wnn@(SwNU3Cc8^Z zUx|@M?LQjbc3i+@v4*&MS0l&FYe+=m4H5VP23^SX0W)Ua`$2#d9MBL{4t*E!LDPrJ z0QgV?SQJmg4>gWhZHS>4Xmynur>mTCvQG8aM^rl>%{4?2c^21-no_X@_J2)sL!m8U zMyWyvz;&ehSUMKX@m^8@KySFg?3F9VvIwWLPNw5E|stc=~F09s3>T5HxI)wDg zIprHDI?QegX0-z|F$j}JOuP(omY1!J-WLYUFPO!H^$&$CGMHw5bX$=IAMV2GK^5jl3M%-Wpo8u67i8s3h8SRkyU$(rG%{s50V6 zv>6Jm=(Kc35N#Ccf6Aflr=iu2D!+5Ne%oJ18(sch3AeXu>H$tu4-CSMCUJ2&xI=U} z+!;d*WriDdr$HVr5)d+2-MxD>62%ZSb99t?rm04NJvfOwlPQ3r&q-ry&ke#P5(xU6 zlac(H=`4Vv&qxDhfrb)=in8Mwnq<=q)>@>ajVk4dMB7)5-VYqMUlK$cMJ|+bXou-& zb+w~NVJFdyDS5b#hE3QGXZR}DZJ!N-jUvZbIkbKa4I9$YhWwL|#?Es zjf+1#Y^84k0tOakAT56(GS>F*>Ig^#fpiWrXu}f0_r#%hPiL2Ouy(ex2D|=1gBg8* zN!he{ipkz{M8u^!Vgm!KjVf%Igj}W|zv%4KFVi4D&aGXE`8kF8B?t3z4Kp_Cz@-v# zu0rhFMQyFF&=E%;^-Mw@rXc4!kk4z7SPPH@3hv;z5=u51GRQ@oA9yz>C7Q7!zv83K z8=W)$$utEyJDKCqfdc<~%}1LH7DjYYWcEPtPTZ%Mk2@IiUAvGIUUERwsSH+rT|K)$RO9RCoA|1^lB3>qGj0e1mf>(DW)Tb2G z4;>9~ss@GoTOT9qfkZt}q1HMTT&AOrI=s0=JXj(A%2_iG(-7+rBLwLNq77+3-MNYq z?=@H#HG3$eV;rQ3HCv+2QC;wwGoO1j)DiBH zOVVmZdW6$^M{A_8`HVKwrG#9fAkT6{U`d0-b^Fn#c$1KZhH;t(S(m9rR+%`iZ>gK| z%a=JGs<^D_39rDtO^LXts^no#yHD2;vFRWF_Nyc|3?^r2#F%8G%2-*OrlODOwBf0R*M>wFtw^ahpc8Fj|+7#iO|1hk}-BG)6&pXf*Q> z=DaQrMY48@Mj*ZqYyQuj*8f&}eF^rtFc#v}fJGD=bsKv~b%8u7wQ zvPU(t`3NkOgOZ-C%{6OXZ}iAxIz%ub{%A!~$Z<#tB4~u z{-fcJ7+^vY8*=|$jW`-Xd@+P6N8-<5xJEbr;fS@GrfRjDQ>*ti;OzvNDK}XE)5x(N zjXI~1#57i zNdRO;jg4WAh6yutU0o`JTt9V)F^;$e#SLxpGATX(eJ@C@f`+YfR~-z|BM?bG8DUrg zXZKN%DMex{;yK($1`995C282;<14g35!WZ0))iJ`zqFfHMl^>wQ=?4QB0FKWVxZNI z2AI|`k>qTYK~E*(a)o%4qs3)3L@4M-X@K!j2Y!`^wF>b!PUo%A5E0O6RN3~fawsAyN$vU`L~&y(okd<~Xq>_6VInb?5jX(>vK-aOFfX2`0VDX; zD1(T~swULHY;#!W>lz|#0XRsH;!wX_P{s`dYu%_D!SEXr*02kFLxYWtJ`}Ws{H&^J zzr&Q@(jYO^A1nE=#5D%`+dA&YOtU9(4^s8L*{SdMHC)8x9c?sU2^m$8FFKGv(jb{& zW^{RCB;tI9IKj~bF4YhbEP0fHoh4#KAztnvUa28seM~0PscaUZMeE7?1Kk_Dd$u5m zA+5s<^CRz3E4I>2YsNBpm4=DvGoL_aDp~VGRn50JGU_3%l=(>N0(UYQ;)Bb#wuBpc z`5!c@#fa=5t3&j1H+-_MZ<^Z;jS3Me(L))8!$OpG#GpW!VaEn4Gf%ov=?e=UL4a}1o%(5JkSV4+H2~iPtqR4=VJc{L%-SMh63!DyoN`pa2 zvEf67$)*^}?w>T`x>Ri(p%3d|mI!FzuCA+URz?c#t|~O%>7HkFOl(fjKlwT=kn&C< z!#*mYMGQy*Da?T4%yQ>6+}nv6wjApWO{@HvIlp8N7?yWi!$l8d8ycVhAem)a zi$oVpMI+<+3>hQCxoaN{6zL{MnZi+4^AJ_dWezV*(-0A*KNpQQAS;nfKvQJ6Dc=_8 zPkO`SYK8`ZI7p)nc`j=eQ?(j!R)m=vB({ik(MY^LgXnHrZ4wAkBm?;+a^UeUUPVQ^ zCoq*t+h8)gv4z@CD*&UDhJ_y+h%yC0e6xTZ8l7KipbJv)+C@O-TrFY|X*_LMtgh2Y z7UkdpgtP~+vf_m@^$1I5F$G@GSLkh(ULTLbP7@Aa!o}V4Kl;hcZ)`b zJ^CmEpUbgLsI-M1FiY{ymmF#JsE_&oU&uFk-s)(@uYsv~ z-ui@JvAO8K(Wd<_*MCoGkT?S$brMa9X{^OhYnVF}gHVDPPNUCjAh1M?GT)R$H0H>Q z8Y1(2e4K0@64%&R{9VWW_?ZkObXsxZJB}24Rfqn#*%>5oN&)}J0e(#bhLHmP8Tmp( z4iRM;Lj-XinCG>-V~fAnkRjOJWUms@FjBmsA%c-cmpxVj8eH_Y1_&)=^hu~CsG*3w ztAoOxH_Eil64Ef|ReaUgXwjAM5RT(OFdopowQNzoa)JDBj{GsYGOQ!k)n+oWBn=DH zW!1>YR6QDy%8%h_dR?oeGO?h%5oD5%je7%WPl1vQg zE(`TJFUY;GKKk$l{qoWEJL!6hR%Gk z2FcTzbOHI03&HVJS&DqwW#AOmf>VW%|a zGsMe_8sNuB!X)chP@TBD(}{o4AaMrKP@hjGV#q6@PY#>0fs?2as{j}~gj&R=ApeUV3nLSR{&XCUUXpoT3 z4bdc$$0Gxn-hHZe?&#|0yoPkP&hoXSIPwRNGIyG+pRrf5HApOMF<6U`N-;RR%$Dvk zpXuFhcz;xDc#Cmqtbj9x0_|Lgm#uZYernnx%;-ZqG6gi?pW&iN^6xySy?ktVudiT`Gzh3O8?}0 z!54)m^Qt7&P^}mA+U=y#EKcQ<$TEa{0?jFuqIZJcaWxvrvTUjz=>w71Cyuz~IfH6E zTO`jc5I{hqTEgLp!9cbm6){Nky6vKoEKTFyFcP8WYa>nBW`juYxm|UlSbZjuN@Z|+ zbm$oAM7!xkaU>y%BrvB}j7&5|Cu)f2p#2~ncQ(e#>5h7OLAz@-i_!&Xz}Wt-NPp@;V{8LW`M|(_iB!vjgQ#d|~aYkuND^GsrzduNQQ<+$;D*dS}eki1OG|kB}fl z);G)oWxNJq?WfUVqXUK00WG8egRmVPhSSMe324w&0jH)59l?n@h8&vuYY!>#H@)9B&K_m)uA8fw8r| zMkD?hN$e!#TBQ^0<>&<0YLHPVq)0&xQxvqmk~BdnFGRPg(g-oo-N_J=Ot$AV(XdDS zOoIe-6_7|SUw~6argLqYff`nTnrX9rHkqvIgUg$@AyF`bO2kxJ6gmpVR`DDHC!gOsV@v-V9RUWWhC(u% zfXTr@h$_vl*@>I0je@boxK+o&^|&;W>BVr!@8Bf521*-7uE`x5!bnNiBwtQYJkaQD z*YDD3v6aJp(tI8XSKZRVMv+pfr>zrt>e^EXT&!~G`ki8IvUEC=(BPs2 z$mG-AAK~?_ZlE?vTc5+>#=WNdG&J1JPhnEgNlYa>-KfzcI!b+Q z0!g%x`$X^b0$nqPz!>)#AJ<`U*j$&0<&iYUtxO6o&7FuWcI*fSpr>>Q2`Vj0+OUHPqvv*XbazOUHJ^#LF52 zW<(;9h}T0ObP>=CqnQ!^)X*>^;)zTQJ}{ox0@{x7-)lMwwv@=unM}e$>$Hj}JHmf& z>M(V+X(a2bgF~(hvyv%;jqWvM=sP+BPO75ynFs>~>D{aNPn(j46UVzc1|o(e;Mt9z57>D+OeTM~E?{-qXPfyo6eaA{}RK0ym3gzHA(*dcY&$8hgCG&i1#V zIOC~Loh=Y-7^@6XaY&rw>a4=m526l zhF@drev}4=gV9V~7D=v=pidnQv?^79!Dfyt&xy)Xq2;?a~2pDi6;{ z>`jLOOx5S|jq##`>i{lO&2{f;ck6&it{Fk1-Ue99^cr?_s|rS-2(Ls18=F&+OtH}c zG;S&O>VWau9O7n03(%O#0=6{gi_uIx(pYS2MvNFWAPXH8&++`ZRs)2Z6HDYGSSE0j z$h^3WOMUvj&zPsb)?gsSn2gNvM=l-+f^^G)W?iId}j=Ty*G;&0NB>Cnfd&nfH@ zJ&p;cr_#iL;l6Ua1~e~;Tx7VZN6*Zp%)RCm9=Jw#k4Aw#88U>%5d{O63A7~joa@dS z8gRFC@C;|(Z5kq6mD0(09l{L;yQ$nPFXN+VsL~H=$msXFY$l(=;wlrPE?*|kDkz?` zzQ^8(vZJr#=z-2Yr@DtL=5QVOAJRavtY*`&hSb3?ROdG4O>qhc!aIx z;zy*B5jD`KDv7jSSGApRd%;0AoZ27Pkh$`uakmsnA8X6CUE%pks-b>a=lH@H-fGEM zES{~ycxeVkXSE1y?2UED)=}bcyh=5s5G7FFkC&OS{o@&pYmME_avh|;7ANY-406nZ z{prnvD42mxH``?_UbPw+ZR4;WWnk5Hr&2kS^SXfFuwfdHYk0Wz3TJ0n$uO3!-O|lS zt_-e3SDN9X9+haj-x)(g8l^Nq+?a+n2njDUStKu&R&Z~xw8-H=mm{?PGxYJb8X}sU zXvoGI;@Dqn6JgT;y>;mm<*~}T&OUtS^M}#yHXRf~6H&O585|FK9a-k+js6Pd8Mi}6 z!w_fTkOvrBI&t|tkd1?y*=m& z<-*wt<)X*IH2gzP(=ag)@(s9OmdpF8kR8qmocL(nXUMA4H8l8% z5GGfEMIu(fasyp;6fo!OFo=rUP*=clegc6Noejl2U-=z@nT`FzQHB895d;*!45f9GeWi$V+R1*zJ_Y)lhiIri&Ox35Dxf>!tD)=L`(U84& zYgmXc6iX!$SO9$N#D#~uVhOQ`z;tVgat%gX4_;@xStItXYSfo4I zS{(kU$j~6R=|Bmjw?@>|96T(2TaZ>GvYXClMl)a0v7*?g)!_Khp0P^D~dmicj`acy7>IdY+nKt(-~iDNOVl}`SQSma+q)j*8J{~{d( z#)XDR28S20*?ZN%f{P5~8iG-cZv_z=3I!NRa1`kyG&MKk;Ilc4XK=i4qaD(D(RQs7%yOBHhlR%)Dd{EBa_2nbXr3wBv&`#5K&j-4f%A5hLM$< z6P{A)+MGYZ4D;s$8Y!+`!dMx_MfL58Bi`g#>t4Q`r^JyP66`?@5mtGGI%@!8&gTr3 zS3~O&!{qd^4iK%4<&*J39d<-Y2GW>8i=LcT6hZ@dG-zms1bIwHgAYEc(*O@s^p4{t zD9>h`It}9}Xy{x|=#bIce5wvQ4T99VAS(0vTwX$#i@V?XM%|v)fe>N>$EvXI!vwAw zTezYwUnDEp*ujLQ?qP&3e2sPV84VZt)MF7ul8wSaW`s3n5mJgTqAuZ`7v$8ip6{IB zJf~qIBoSNy;dK(P-PBp~u8C>wm!l(N+AZpIMX4h|jUE4s8t$@e0_>O1B9_$(q~?ab z09~jbU>Y~+s?XEJ7)~PdFm5Jctdc?q3r*3fkv{+itL?4Wub42k|4RS$!eI}3V zHnCdFL5=H;dqCG}B+z;g>?ey89z^})A1EYgpN#%qxvQ0F2;s{CBX!0x9ZE*Z28 zhJNqU5b?hZf}|5U3{t*aMf5P62FMLMNCXlgjok=B>bR}Jzx4xw?l8vrMjZh23p?^$ zz9C}*=o1BZ?{_IbF(;WQ{v9lcow&Rs#ABrX@E^9UIy zaT3Tn9sttXW#Ify$4S-3a7q=)LRl-1qc_adVuL_YNQF!TV$_re(pzQ#-LC`TUP>m1 z(|p8QaeFA++C8wYwXGl4+`b_J!i^>40UZlh9B^N*p$-;+;j#1<8g}7_f?%i@1~2RL z;?Roetu(;?sDptmacQv7fY>T-&2UEDKG4=bq=5!oKB~hcamtg!A*$9{0T{h)1{*vc z#ECb=VVKecR{%$EmVxt>j)V0ij{R~TOM~02rj9ZLc*0DE2oL-1%MSV$zX$wI{n6!Ov5UR}H!J{~V>#E5x_ zaA@%73DD}zF!bKP28iqGBJp?<_i6Q#^?DoBf7hs4W_^nH`klEBvk-cAf&OmxiM}iR&F#r)L z&0!ZHB|2IuGBinOELLym0I>Ar8W3awSNtWv`2c!jjHTw^L4;IYq`n~rPfY_sZ-jyH zj*bAcVl16V*TJSB5x`p8ZNz_d05l?+ihw_Tf#(2>(1`bf2&sHsDh~6eS%ncA@jo3Q zL-!j5Bh2`9C~P>U)QM!aWql|N0sfJWg@e6B9nR6=tL1jcP%OP^#=N!9*YsAb1`Vlv z0SAR6fa$F?z$$gHI8t3CvgrmKVz@PvGf66}y2aPfu&Q(r94=(?c?6d7wE+!8ZLB&Cg-#A4n=Q~ zv8+uB!sHQjAr@_LhTW))-XsI2Mu)+kxS@dCY6)arV4I|=2)-NxBglkZbO0PHW#P6P zac8s-ptr=B(Ypl^@)3A#CtRuGBj}AV5cbd!VBx~G4H#QwBjj=;Gx`{7j=Q^^0 z{+?b3(}<~5(lBxDB!|c&*m=i!m1}$2oTt$5rFb)Kd z(@H=brhHuU?>?Wpu;s=5Y-X$CV9rnRKikn+VQj zp!$_^u0={re{hZC%JA0$+DIO55cz0aHFEn$=7u)1rv?UItgmm#B$Iib&Xy3g3%o}! z4LMhEaL$`wO`ZL)MR#rVZX0P5+)&M1H6Un6 z4e)M@MYD-v&QPTd)15Fbch7YgWnaaBZnBkj9T{%1@FOWi>g$F%RxOLn8R~!=_g6P) z;MguslQThcD@Lwt#qpL-B^Wo@<=t^yqLn zluv?x>+2bE+YNPB)`KCi9SXlS)+!cTnW36Tvkd&wg~-dWGZ&$;tn1uromMd>G|fUB z|H+p12YzTDZ&_COw#l{?{s_cv8>B~9+lGzBF6$I~OeNpB2`@j)4-={_Yt_tqCRd2t ztL(SN+TpKFvUqM4X`}C5$5QJ=dyLH&9|pxA`Qbx8jtg1V_}k{9h;OL(b3KWU?l$B7 zNBHUm)A6{`d;fcrEO{)`*wNL|S|Y*NF&5u@90fee4+qV_<82U(yKSzp!k0P(ehC`E zV+I#)JU<1G zUY%YFw!xryffU69@t8TmZN)39QjM)!g7e@-zWX=6+ktnh!+h6(e#Dnp8!9%ttJ2R$ri@`U4?Y$?u&|@ac`HMOSnBXP8 zy6_M@ig;C3d7=;Obsibu?|k<`zWZ$tw#I`s#^zlkRn~8)>uaJE2Iy@>6Bdb<+{34RWp5xLr zZ{V)3x2LDeBi$dQ|0L4gsg-Gfi=&kST8R+$h4+8PONW4CV2i@mu zZkt?QAJ}fKUWDqyDwsow8A7QM^uk~!uzDC-g zllG^eRkbb}w1vLT9-KfQ;}tp=gs+qEj(R+rrnrP{CLu3bmilp{%_C)f-yms25|8hJ zRMmGqNnvb-+JB7O(d7o{P0~I|+6O^fCENP-X%gS|s$MDP%H1;74;2=A`PgVMcn@LVK+xBm(V z5q@bXPzgT3<#DbcPU{9dLRhmLoPS81@4vm4wL#SQg1y9kC`)c>N6$v-3E29L47r;_wT}E5*8`H?29Dzw_kVa zty&$d_D@Or$O(8%Jj5Yof7$1Q$PWKBNNU#l8WNv$5+45r@g8imMqGMBmRsr`+F=Ky z$=8zj%=7VxFR-jx_e>t6I&3+}?C^;uv0nB8ApaT3Z~Yn`p@o*U|2>nd6#3^Q`SlLD zTXdV&V=VmVq`v#>cuZMrSWx?hs+ns4GUZmDJMg5I)OkR5){C{AxG3%rhm3%-NLam)0|W+ZjZo2r}LLUraK z_4TAa?7MhuJ4~nEAgO0L)NL-gU*|a>zJbJ%@8hxS;gYyQAEYHFv0gUGe?{^sKg8q3 z0J$o=?cYw#O~zaT(i=&7)vxi`1#-dXts_BXhqnYt^&TVfuSwkg8$3P&@tz)ID&n&o z;_fYG!H0qNCeqHh1&@C;Sk|Y=8j`6M?Kcfty<}4VhSYoAipRNW%i7DJKJ{(c;&nqL zNED8l2mdk1O%{ck)z+olVTgF$77j|$RyO3-Rh{+5)p-p0djcV;1rvcm%;+FWwV3((w2 z8tWZA&H&ARo@`g8o#2!*Gn{-;wxF@8Pi?E21ymnkDhQ4zb&_deH??-$UvbKfvP`Sk8ROS}&>pJo}w~_XPkMQ^_7Bji0l&hfXsSgcWz22n0m(&m2HcH;8Q>%Jc{a0~>TXL^AMr%NO zA8Fqbpk0E6FnrHsVY4_pyt6~*HdkK?*je|J@bU@}o&Z9>vs9Jmle(nZD$Dx<-oADh z%X)iXeE4S-$d2l^tQn_GuCU1n*gQ)LosCHH(;CbAJxlsQ7$h6}ENkD>umF(h?C`vI zRm;p7wieZUfJ7gD3Pb~2^;vQNXzXx2K&g);X&)r*^?QN#3&-eUP|@Z{ySbzaEqR^U z0m6q!c=c2e{sn|P!8BmABs;v8G>XLU4XDF+%>=>E_5s1)k9B&3jk3e5-UorQ>=fI2 z2a5eY#jXx+ctiz$um(R5?e8s?cECIkr%%KPNUOzd|MTgPC!#B8BZjV80cxzkAb*?W zE0=)$YbRUQr*E4avcmR9lDtG?dM8y|_4)u_e}}KniR1P4U!`hG8_7O=9XAp>;c_Y? z>#l=xSrK9hTS0Qy419RH0VL<1X<1XjTVkI_p{|MynWm>ieRZ=3bT+!_ zZg$fLX#l+epj~9yV@^cfoI4Ofiix239Vu?ifMV~nEGr}_#*+eskFqSV&@4IaWdn05CPA+-_+2cCmZ_glaa4~(_vn~(qS@h%_l^YI}c z)~R?1J}UVb%f~oAs`;46$7DWg_}GPyDSYg~$DVxb&Bru8X7I5eAN%uhARmYDF^7-2 zd@SH&5g$wVV8(jEMDv0P;zhU)Ppf0AJ5cV7K8hle1GTJ#fvUO4vX*0*sw(EFF_Oc> zk*Q#hSFoQe!s`h|ODfO0-R$k&(Ekfu_aG539suJ1fY_XzJSOzz{p^D=(d{nGADhE$ zbC_)ouFa(Lf@$JKc+kc6OxGwy=5_a&@?{k85DWOiRur)98~8NEMiG<0ykEsmWJ;D( z#d0eBa>!0L*-5a(3YKUyt^8Ofg%`}@E-vGPsnP|rn~U%bc*8ZV zV0v=F%;F;aHlDK5t*|7Q`g{6tB3fb?sA6Zkb&624^KSIu!>sRNUqyZ2{f=ejlas|5 zDU)kCKAcK6Cy-6yV24AlRykJH0}lL5DobLcaY?6mseJ-HMNSvf6&7+P-Xpl+{Z1 zhu08=_u>&jwzYGU2XYKr{}@4zy%-=j{s5mIKS9$VyV`+hK|d zrr;LLm|e--*MixqMR*IIsvvVgc^RGZIE(tmk5E+YkMQXt%H`@Wu`Bj-%M*TiTx4y( zJm#(zOgCM@w9z7bCX4DU6}Q6HsLSvtSjg!=Mj?k@g3TO6gShknJA!(P@P(wI7;wkX z2^Z!qdz39t65!^`0Wbi7!3QyP*3>JeiYE>dCr1Qzy~-(rX!Zp3LM3ZqGwZZq8fkGe zA58TuZsUWwm<2N|3+77}%z`YK)>tr4v0&C=!9>D>nSlip{0gS)6->S>m`_(QIj&$@ zTk&T;m`zrMpU2bVLaFS?_D@3)K6LU^tp0J=q56+qhEF@A`u5b^exh-I`^f$6A0J@f zu*CKz_m~(vxxfe8WGDDY^6_a-s?YGj`LFnhEw(4(Q6V{i3#m;ZZL?o(rmGdqH!GMN zwu(t!1rxH0|M9^*s3JTDiK{GxWg0G{s5g=!tj15XC41kDmh5~vK7Ez!GM4J7m2 z2xdYROkG;Rl%s+bc5){fx;gi$V zlY>f)VwOF&RiFmvV_FE7JK-sg&Y!&@;&4cTAt*R)_NdJU+e{fLnB8$A(=-aE zR20mDD44TQFw3A|NNd9IGqSQx2C+F%HapK|x+}p1R)Pts1QSdNri&8H@U$DVGYKYC63l=kn6pSQ z%aCARAi+dFf=P1()7%K=r4h_4BbW+CFmH=sh84kVD1r%01e1;kCJ7Nt>mitlLoj8A z2=9WY*U?lM72l$FDbHVE??(0)Fi_lt0f#gXmlKCl2ujf>rJm#<$~w8)DKSS0XNM2c zOVftczd!8_?r$-;FS7V^4?^*${{|)iaFAL_!%IvYIC`w>To2{3)YjM83q{Do*#?}y z5a+goffKqJ`)f=-2PeHUTHb{zkal=K*ei*g}(lbA%<(?C>TXlfI{3Rc-<8OQfw_2-=F+j!VfWHRrh{P2QqvTy{ZsK zx(2+t3^2a{Ob=kBFiHFsE6AJ}mS=yf{ikfaD6 z8mss(qCK`8Xw!ddS^N0C^&1_-_jmD#p8)ZzB>r11h))O-->wtu0V6zeGR$u}ZqXGd4D9d?N!j1%GqbOd;KwYF_2 z27m$xZxCT13WSn}@Q9AU?cj(AZ^}9l;YJVPi6DYJgO%IYHfLF$W)0Fx^KV&t474AC zc09X*RvtT?#0ua^MKuJTwMrUl=s95lg{t|5M6u&Q+5axfnzsQnP_Cv@32){iL)G36 zzrIxER3U-Z6G+X1w>2Q$uPT7^HgWb!0jKTmVQ?xC zdf%;(;VI|HPp3}r5bN?Zux9+uvW_TMr^&=}w+2ol-7fP+obCG$u|y77&jYIoSXI;t zZ1#@Lmf5V9&3s3KNs2^xh7PUbyEwHCH2A*+o^}L)zkCl2@c?$Z>)@gEZq0lX7OVCy z5!xDo@Yf*1LLH%vdu=C&1DxsujeCy>mo)()z0I;BsKXf9IJU-SJ8V|QX3iuL&IXul zND2V}kEyhP_ZIBu=Meay6##w|zz1#cL~B6@p?F@`4nxJ55DDJ};n^fSekBN-aaJ~I z1A0ZWQ+TyW*yDx8g;}>W(4|})i1Pt(&LqyVRlvCfIG^5tB?|iM7iZbwUNpwiJf4+Z z)!kOrKoxwSw6l%`?V9^sG0|^QN!`Px+P1T0eL$+qNOdu&#?;dCe5^?oSPpkZS^p!| z^v{9nE>P7_@XHZkrYOP8P9l7g2@+flrvT+cqC8EMiTA^z@1ZcUlVBz$5k7keic~>x z+0jpPI(|BK-I;phJbZgLw)uZzAS10ZgXc z5loUJ!p{wZSzfR^xTg|#U@dU>dr4d9IA;*@N9}-o50E}5T8IQJ znCeA@LwH))NN<7_)zx)}o6}z<+}aMn9r=)Djjf#wFSk9uqEqAWa<6fcqD zMoz&rP63Ib`p0^JK-^#rU0=s|J;=iW=;@Hbr5380>LB$f@uK+ zL;DNH-xrLZFBt7!Fkro4w0XgR@q&@x1>>~~hFKSkk}eqDTrfJhU~F;0=-`4uy#+&Y z3r5oxjEF55)LJkWwO}Y|!9dW0v77~iF$)G!7U8}0Ru7ToXR+0Xo`_a|@iEKV8|z{P z+RTPlu%UK%x=ubssGm*pmXkn!4af&?fGt6cgSjVg${=+kY4enjjKM4zFIj|V>EJ_! z{W(P6b{f#9KY>qg5xo-I+#q0Ocy<70JJSAKf*yYkKu>rQpBBQ>QZb(LeoPRyicy&b zBPmxg05tf7VSm64LP+;xT__UekxZMw!0Bg1_4P#iYrBzPK z@(?W^V|wvWI+E9IW8?_{&q8bl6BY~wT*0Wl;}~&QFo>>T+*`pAwSpmK1%to}#%mSf zPwSN(c}X~*)!p+asBZpQ9O!a240enNzX42XQj^~JWz0<6f_#lQuU!e8=I3zGilI{) zmk-AHuY%!QMR**Zuqiru4Y;*;gHtLY0jT3(*1v$5J=Xy9;O8x?L(b}2y=l%GFrsL^ zkRV_BIY1740iTWp0|=a1sgnVzf*nYW{s=y}Nf6Qu=vSLeEO_`}vmb1Rjui~~TFE%A zBE?5I6{r-HKfi7M{W?$nac%!PYxVd|sMTlwf=?%c*DIw}u59s)w<;JPwSr+yMK~8I zugq;UuPiQv7qP70--@!%`71tcWe@D+^uU-hJ)jN?gFR5eA+s3;RWNL6)#?1i=%9kJ zJq06kig1fwtzql(hW1_bn1Hc>u)kfOzmK3M5rm*=yw(h3sK7+NSs&>$*+W zwOuc8SS9G&EbWblQCjyu@ac9A(?m+DO1XDEQt~Fbl;s+ew2F~01>;tZVc1E*0Fi>B z97TAeUifxa zTkZ{}cmyLD7D4tE(0`ZozkU|<{{j8rS(Yqv-vEMI`-hn99E9%?;jkBhu=!v3w2boI z767orkpPVH(kKUW7Ao?6!Yq0PFweef+XJr$m;+ZT*2kTj#iBcpBexuQ-zyQg&&c600)tS zSH6Kym!lbDCy|H7en88(>=#)l4wB7&w%JQILvsp7*94 z#!VEAbSN05P%xyRU<^RPfPEtT4xXxXZlF|021>t_rN1x%rSJMSJ{`i+!>V*yaPTBd zmTt3Pn+4k}*k*AyWBi2}r%y1lo(La`rz#l6&Qb|Q%iF_V*|(tOm$B?kyQ1vf|08YD z;{|)mW~H$^=QLEOG_dWv`)g{VKPKJs-9dL69VhR?h>N&%*s*_uL94kPk18H)RNc$R z1AIKf$5VVf$H!m!_y-@a^YIQJ9PlcEQmaCIOyFZ@K6ay`$LM~7@%jV?wnxgIKjYgf>GN9L#_$NOB37pV4O0+0AqrI!2|<(35MemjHM+Q2um=6m0;*8 z!H`jcp`QdpI0;5#5{#lG7ywByc9CHGA;Azrf>D43Bl!ph;}MLfBNzxrFo2C<;2FWd zF@kYl1jDunhFuX1nj#qTL@;cLVB`_O;30zHKLq1^00hC11tNT$+n}L3_!Vr?g$cBXPO0=yfd;PWrm`;KlYIcVV))vQ$JaDATuG>! z6rlbEH`OB{$CQE5U*aVgHCCG;4{#dh!j7!6UPa(%(*VA!(zX%+)>cDR!3$iZ9Wu0B zO~AQ10A4)Cwhn~oS4TGzi-8mbV;{gQCrv!aH5749OZabFKdS_{mmTP)B zz02NzmMrk9+k+%`*@PUU7vjrTP!)Uevs&xrF_4{*LZ(Y6)? zr$W+GE~=gmU+RJkqq_c_5YHYBh_m1geJuE{nnP(Su0Epd4%rjbUTsDzsAS9m5x(9l zs+_sGqLN1%G?V;-MV>lg~tN! zz@6X(E6<5;3DOLajMtH7*Dr$RD9|YL?{9-NL*(N1q&b%~=YwYO3M$SOzCQy6svW*3 z2r^U_-5^n*@eV#y1C)ym6iP8W{Gf?4yg>RD@h0K@!RlRXYaY7NIhc4Nh_OR5<3@sQ z#!@_xWKs5OqP&V-{@@d7N@T+A@7}n@|GYfbNl!MK?Z?6ExL4Z4Euz+Fa^DV@E{vzNWzK1PMA7v{p=Pm;x zi10^2+~K#Kw-Wl~ZvlEb?m(o_Cql|l#Uk&OgDEfmZX?i^?*QnxWq>Ndf7*N>R`}gc zl%u{2l#V^Y8s~B*Nq0DhXRCua+u0@FL9{Ra5NKb+C5;T`k=D3s#3@Lw%*BdDy!1C5 zSXEo+R%HsAe6Ai})KuYiDm^jPwS`TH1--R6(2d)&8?ic7R?5>dSd)G_orKA8v6cD8 zn4;Y`WL*bSP+ck)Q_=N{vHZ$hCLf6vBF#x_@F95Honl*+VrNjES5=UtC~^jh3|Tfz zEl4PnZAvk1S942KG1eGKG~?cmONLd-S`9mxDnPzc7e{y$x0Gl;i!d(nTyv8(_=rn1 z7G<3^HQ$85;Z3Q04(TNG@l_F&dI^MgwRI&9v5R!kFGjPBjan?UG7D#M9WGlJ`|(-= zuZ64)Fi#@!OT36OvM2!ym0LgvDA!dLi;SWn?-b*z0F%q${xirYO=cY;f|JV1kTr;^41NW@|0~)10yeWE=#V_EDISS7qkSNK z3Z&K6)uCuI(r8(|A!{z&x}3xV@y5n{V=&i+ zP~N^@6&9d!NT7+TApt}t-i&Ny*}8Zm-1FQwF|0IySk4s<_yFUCn+L2_m2=55>%eEf z+81r2i;8z|M}>NFW6tn^STWcpH1THH#2FYL_pDq+kVC{E{}u=g3vw7=c@;g;*@NJ1 zC~If*dC2Mua!R2wziM?1RYy00(R~J)hR*svNvyMNo$14LJov z=%F)$J+ydXT?&~38}s#z@g{u=Ek!A}nDazspDdUKu)!9fcf&^c3TK&y^bs`EK<99`qp1@OfM+dqiWNm)m ziFimhME}KaPuJrmN(U?N=yUc2>9|Ve-8A|2Q@a9GwbA_zygX7qk&o{^N z(PB#@8sbT?T|jmdIN7;?*4xw7wXVI06fqs`#kRE=%O8V-L)J7DeMC#VadojVo#t|;21zGc?xXXRFGL!Aef4=NHay#X327&e~+`fb5lpDx1$a2=eXi08T-wrY%7{i z;o24gELJ8|#dB4{TB+mvU|UC4c-n8~%gx?rqp+tj-f-8^2d371@=vHPjzW=`< zyES1bFcTZ&=pYImPhwXi!UFGE%nV3m>$FOk7DBv7LXnKd)t#g!ts|GcnM)Dhn2pF; z9A8yjF~69|MCzN+fA`86bSJn!2bL}Q@n=|C@ zafogu#NibhjI|B5kD==*OsyH!)&*Ezqbrb@5OwI53&KxBGJhuQ7$NH@2!B`d7ZJcN zlZvLABZx>=*Pzw`$;p-I!OMfJS%@?>arM>svdXF)U$mkdTKighdpr78V%Zok9b6V* zcvZjpclPZ$?M-M}FuuI=MM(g{&2IRKNi#aNp8`~5iZilz$1Ts$7bEUd$H z#-`upOGpRaSsl_6? zijc@;N*Q~_nhKA1tO!WrQ5VVN3(duNvbZ9W!PLMjZ_3V@t_c&UA<%*#&^>Wdg|_|$ z#}0r-GnrdRE-S@+OLL(GO}-za=7_{M(s+L)OS$&t!Vq8?E2S& z^A-}#2^Q|p+w;hp?|IUxGbo-O0!+xdT8XDdtc{#Ho`{->-@fIqCXQTEod|_A7mLW5 z=C+kk{Xup`7VdNknQ`Sp=83Zzc=6ic#0{<`VZ3l*5D7~~ym18>c9|v@>u_ zm)Ba=X-Vz!dCS$`-Zj_S+GVxNEM%n;l?PbUahiz5&< zW9hvdj@B(jN2<$yQ=DH5x$&^<{+p|~7IEptBKc#ZK||FXE^j4_SOE+fD@K};ZmX`P zS+es*=);gT$y=72qRDtRqAki?`xjO*ofC`OxAN;)2@z|K|obeS*%17jo&h%ym zRGnx9a2Qx`W{k5cZ&+eQaRIx(qp4$4Yww1hzK)0|87~8S{|@`5YHK3&WcNlnG}Df) zD17i5xsdOnDJ}I#q>K^7<|bQAq%v`CZi+3~cTm|v!JZ0J1I#Z$U2}F;+~!nUr+Z6s zW5;?#_wGAp1!L~Ux32WrI45V^i9w!_?Siy!Zp>r&JocK4-I_amIR5LzqU=du4&I~a zp~{Q(4lMfO`A6u&$K>d~1u{Xl{FZZE65bB z+t|P&chb#yC>q%GXuU;k?c&pg)wH^)xH6SnFduQ4TybHu`?1q&T#02Sf<`ad#TiH_ zhMib|yzQE!GIVUle%fKVX^^)V_mgK*04(%5Lv2fQ`Q~CG-;x8Uw>y0wyVJvlzF5Sz zNqg(X0TK?~XwoL)O=Wu*oNCSGCT!^ zv=TrV?AK9+6EN7-*8bz981xK9VXZ#y49=$6ht0^Q3<2j!Q5taW_Bq>ENwouZoDG3L zD?2AxSQWeJ0&yDl3bZ>M3kmn6lyDb9h^hTyW4u|a<^TJ&X>pPo7pQ1*3R&k4)dnd0 zsjD}p3e9;)vG?3*!TI+o42H9QV9VCg4j@XoYO7h3k}DV&5B`!|Tc%^ctF4-G29q}6 z^@1_~_m;eIe2^sz3kt;~_kE~|$C8HtG7JPq)N$k7v!$c2(9_k~c8o0cC}36cHlMYj zzjZC_DWwg#0bg0k{`wHJ`F+@Fs;zUTW(sA@X5W)?zl&9rS}cU0G&aC8q&BadXd!ET zImTQWX@mg|(#~T|?$2ihHOBgQF5XBD#Zwq7QJZryde~A?UzKBbCMqDy*K}P}{}S0v z?XH+a+WO`Q6f$KR^Q9#>QRlzSRDIW`;NhkP}}7D(Z6TN8L=1xP;iV+Fj}-Us zsR(>+C?7(tc8nbkoyLmeP`gvc)wjs)X01dx$w$DRssQ5w_UBk1KXBQ15FN74U@m7W zMl;xEQt)^jM&ri?7!S;&md2c8y_ylSjxzxA*vUc~!t|jL9}MEJVL$9_hm}}}tm@c3}r8|!C1!%wI&FNU3+Kihg>cSBA+Ae3fyG2J% zd+X?URBu+O#WM))TW#GC$`n?@ppB{>4c@N&@c~-MNh&>vBavbZ2{popV1)01K3Tc! zAJAIY^>u9RwN4wOq|tZr+gCCAA?vJQUwMlQj3!<`ABMWTzPT}8-xSG8rhgKBG7`rW z9izhFow6f-KbqG=*6$pJC7*#naaE6Ed{wH626azC;j(jTu=Y(&nU+<>Tx3OECLc`) zG@(US_1*a ztAuRAi3T|1&r;(*)E6&JEzzj7S9&@F`*$C0eSw)Z+1!lP%Ue@+#~a8&4iEE7$l795 z%+WJ(CKAUsBb~#UidM7zP_yUjQJ+F4vRbWgMeOi$8JGu(*x^F&%+)J}CzG z%Ku3k_jOxMfeZ1=^LV|ob`GbJwrPNcPjSRF%#z>8HE24P1_)pO_yN#d7DCoIZ7{N_ ztfT13#l;%{E-nXoNRO~oXbIp4TCA$eTeBTlj^zX6{-clq|z=Mu~lP$@cE;X>2y z@!>j*;0v`Df#VT6tCrihkc_&TS%v*J-hB|UAteUIuoaE;e1-_0nK{ zG~EM;DS!rFY=IS1o&2E1IMhacch$s_yO$|)QKTJPTN}b4Uj(KZ zX*CDvZE_|ZvSTlXtjA&6Y+emBOH>ZB+JaWPOd)k`NCn@D3;7%C@WMd&CKl?wMQBP8 zy)bg+dfGdh8Ky;<8=%|kdqXP5)LFH?A@`fd2B)cO8kLI}Iwo6yy}?^u4?uZ0L(7Uf zYE}hTk1x*R`Y^;08 zqpyHvLRM=KQ@R;6t%ijUJAAp^Yqg(*_p9AAGrBIM&>7y5C+F+hAR2gmI4oLTcG7qV zdLay_(dHsg_g6p{qjo|8_F|7GgH1T&RfVyywyp_!AE^8u18eav zhV)wWHQXS!_e#k6&gq^(2w^sTgAk_-@8?*Kg@P8NL<|T2aP*jKoq!2FxD4~9a*k`I z44h6WrC=#K{-2n9!lDZrPX-u;thg4D`f<)CvFyQV_ys2cgMY-x4}KprFwXmfHm}p! zycB2i@|w-7XEv{b*}P(9^D>gn%Re@6!f76MjQt^Nva?P|(_5;rVxct{^5q_`!}HvPq3Kj46zR>R%ix)~&`3&D4FVlGyI(y}L&)_VONcV1aMA+4{ zUTWU8oc5^2l@Jsm>jyrI;3yBdnP3txy3C@~OIa*8IX}fhEram7dOeU%knP!m@d#KM z)#Sn>)=o;gQRw_GX*0x=0^V* z3PVwcSj0)??&TavjhAXxZ;+c~DX>5@4>Po7d2bZ_qQ{i}fp{txJP+sWc*<9t3OE9Q zH7}A=vW9a$FItBHK4DZ><%8=#QiP3%V@=QU9Ia&V7HwiFRHD#hiuBq9${Bo zQ^vt;)&$QUgc=B0=j$UP-DTtyQ@j1420(fwPJ}=-&hzG@8Wf5l>nGRY#G)~CB%CFS z@_347feF663|YHra*ehV&ykJi$I|Jf=$Jq>RfK08X>1S*W%q{04q-heQOG(Vz!{Le znyp%64S0uI3{>lh=W>2b+C-rv6VP{oS^{kZx_47?DWa6Pyt@{^y}X+&s-@J?msKfX z5hl^?f(o>x-5*UzAOsiR+|h*|r0LqWcSJVneIv0p5SKsj!O45IGiZoQ68FCTOUZzHpDpJfMiFY=tG+;FC1qT zv9;~)@wT-aATr*9$gnMjCvexSVLBsZtqM+B$ZFTQ$Jn`Z`*9fB#LbbriE2VMo|UaR z9KYu z=_T8eljqNqU4|M9juVc#W;HkdP~qQlWd~h%TCv@d&Ny(dHJpZUuFAlS?iO~S8>#sm zpBc|0#SmR*%Q`&fi!!e7Fd-o3om&k+P3BG+(K>pO<9Epsvc)thc5XqssmR(sJ21-mzFZ#=WniTA?t}?-3=KG z?UoTK)uJn%qTOC_EmZUs`d}K7DpX%*3oEfXjq05})~bBsAnq)kgShzCh&M4Wg4p&iikDT%*-tws6z-k9 z#So`!E%JmKrh*9d8gEV92~=-j1>Q0=qX4Qffm+l7>cm#J5FLwNcvud?BENykDpl9g z1fK`bAoU&$Qdfq8CrPKmp6wic(VT5tm-*}*UAKPI`o3Y=8A`G{3BAL1?_7w3xxke? zrcer56TR+G)-|sW=<&BNm=Tf|4)%^ko&NPWoxaIb+i{kfNx`qdyYWEf^(JWHt`fc& z)4baH7n&qb4*j!2sN2eo76E|J>}u;-$18j($7@>1s_`laYm{Tb@G3bO_Wc=-hg71o zOLLC84`6?kgHb}?&|e+miFT!8tQk53J=P_6@j!KBaL(}F2j&a=L!EEyhtzvuWSIQS zT3*bnq-#xxMB2K(L;AA?le4KjB2I2L<=ln{G@>FjZMucyx#Ox@oMW%)xaoneQQ`*r zyIgw%2ZTqUIU(y_$KD%XMQ6QC0-aZna{36ey#tu=}Z)>^k(txKzQ->SA&dur8c z)oK^lPwUodTeWIitJbB}TKzs>?>Xzu1-yCu9>2fJgPghVnfJWsHSaTL*7wY85-Tt+ z@MgvrD^VTYfiaUAg*7wL<)&|Au39p#!(==aQfK!anX0%g8slVBhL?956DSq+Jy&== zZas{bIwj8C?9M|w*6gvHR!>-cOzP1ER_2E*f#|C%nnbDK(?cSc98s3Gl?_LQj&Q7y z=}v{t{I1Y}!WBB~wnAsnR_FlP3Z4F1p%Ysxbi`_fPDrh=Ew4JSv_fZ%R_Kt>3LWZM zp;I|4bh>7R&d99LF_#rOsj@tAiCmZE*qK8d{H! ztTFp`uIn2I>W9bOm0Fb*=B7HawRCwCx9gn=4 zx`@A+Jx`sf`u1WPC$H*guDfmPbd8YRHyEJ24xLB`(>EvC3hVsEmqcFl)$%8n;<_ z)Hc!?K}9A;q3r^YKPEE+y`yS1t%8M=RUF$Vm9Vqln==;^iP(Biqo)VM?@2SS1-5O? zIB2%gj6WZZ{Ktp=xeXf}gW8ak9x^`1!h3FX8rPRvLM3oWzWzSBA`dnHPW)=w75&{3mKsUmK&-o>2l z+PI~SY>?x2R@&?+s%Ya5VO-g&yex^Dss+}=i`yyHLKkQA7uyyve2>e)OmehOYOI#p zeR71ofeZSxu4hgCNR+G1VbRofygN+q_1|W>QP8#pHphiUiq|;ETBg_E^&2_-_Ih%dF6qti-59HS+FX0RsXmQH z?AO-V9z;z@?Np@;9((up<4YzJtJ#r!CZF)lS9m%VBCb0QjP=WC*C#u6L{f}>}+^s#SJ{^G8n4ED5D51ky$%LpfJ z26~^lka`LuJ&p<^`sK9XReMEK^RoZOdQN8ZRaL5rn~8anB{4BGdSbj3-nrVef}M`- zW?F4SsMH8b+dNMiwy1;=nf6}t9qNl}TRLx%4GEm_RtU4k+b%T&VfH1diH6OId@9?C^>O zt6SPTb+B!+bf$sc$Gzuc!-V6Gow9#jY8HlXjV?T6_GB7HU)S;??KSaAwB2N&cOG9> zdjBjvJ`iR{T7}ltZ7nMf>R!pMxW(W>9}s;b%6tJ1v^t^tm((+bUDqJmaK8lU8pvzMp=H1aRm zxxLd0PGzs2GvhOK7pqs__7~&MK{%yP%@a-MhsWAYw+oa~GrLEbZl6Lcq2)qsZHnSY z0TH9nZ2sfQb+=TG_kr&f`crN`Jehh7w>_pd<1mEOEu6ioop2{r{2j{ZW37FMlQRV| z-XWSQg4>Y#HMd0Dq|N+i4y&OHnbk~9I;+P%>P!qsZ2iS1E!@v$7V=eT#O9ak{jM%* zlp^!F)DVCUou_+|KTVh3%{@o=Ef@FcYrOAg;hXJ(>(iuipbveIj5aiXSZZc@ORq{d z(4?p9GwZ&Zu0O@Jdwtws#8xe-CPR(uN!j^zP^%ZO88-zng$>ptqXh!be%OAL&M$SL~ND=#A{| ziK^COpf{W$1j=5v6d$c;v#L?7Jk9hpTlYBb@BN$pUR`Pydf`E-UST{REooKsvM}ai z#;TWP5L1Vq)@w&-syW<;Bg-ttC|VxeT5tKs>W5bDpvBuA^~kwp-07(WWSL}?xG2)&tfm~{9Dou?Nyu&Vlq=H zmG2Gs(wn0(k|gn z@`2rcL$Ap@dIH%>Gn^{2@*P*1S}uAfY2KM%STyT^IW(o4r)FY*@F6%B9l0_xkPJt^ zvZp-5)Yy?#X%Qhm9h>A76nzhYb0vYS}0w*gU2{PnWAW|l1O;_p|@$wMJB z)~PpSYOI0VXK2QwN-kbivx_A-+{BveZ!=ar)E$^W6ThBrsZ`Q4$*}ox1r_fWWKEV+ z2yBiv**B=EyEoO5jCWzNm3tpP`mk>hqoE4w>h46o`q=}$V`%v!#Ko2@%m5qas@i#x zt@aR`ihak%QPBVjn@U+Bv*5~|?k4p5TyxWW<{hWuvjf%7K=035op)yG`%q-CMWCKx zSJt6S#!_GI36pi&WKXds>E~fED3c=X!idcUwcPbmOsUQ&>1qZC3=)=1Z(Y{bPJ80@Y>QsmaYwG> zb;d>ZN{Yrr+rl+C{>W{&a{41z->f@ZeQ7+36&t%eY+Fc)xv8BvM|VR_O=8-hrsgc_ z$O>=R0mo~wjMKM*l?6HhbeWmth%7aJ3lKm z?h)*nk~PJv(w%5=-5FYF+HrT$4iI%SPooj9@xGF&($HP1K5GEg%3`gB`-h7646Q_K z6`ro8j1vy~()MaA?A7)gM0mwuJqCLE9o6ppYzGv~{X())_p7Cgy*& z5X`1{_O>QfGt1k$W!wwzC3oGV9u#XAlb=KK?0ribyXYXzPMOE?T0d{C(w(_kVKu|9 zH>;@YQ|U+D`>B}2rBc?Z979uVwy#y2TcV2DEt$lPh}z-XbF-o*(T*+KhQz8qx3hO> zOIufS$La#QSPR;Q0kQs_=B)qnoBPLo?=hy4ltcR}W^(H_&@9(~n3Q zqF6gLo9eosr)wrgaMYifr@Gw{t_xlfb-{H9MZeAU&_xveFt*8#>J~OFnm7Bf8FSKu zJ2gMs@vc{|iFOsJ^{(-b%arldh%|1wHHsgdsjTS&rJ$##1vG+uFUewDsax)3a#!K$ z(#F`tCiA_dw51OQQT4e+={dGu8z5`Ehoep&*7~+tk$OMf`P8EFtcE%BqGFOgBw~hX zV25Oks*uFQ?nai+<6rFex8hDHK?ZJ zcHD-?T6WopUbMS$gtHKmuO;ppE?K;2napJu*ynPKnLnCI*BQyH&Ca~pTb{pA_VMw& zn{s(=GMvzcf~GliSf}w((89--TY4|2Ca<_?meOAI80!#Sbdx@!PI6@gpmHwZc5?hBsf!nm%MK89-yTr+jcd5mVoKEz(E4+MesM>bhpkWQUYrI3U0y4(~7Fiipc~ukB^WNcUR#jCL z?;f3;aXtltBfL^IRcP|IXu*s*v~#g-QmSs#+}pY=2G=)=H)NC=z3oAk?xFvk+Wt`E zUFdX~SYyP>R0VobHj%hNr9T+YJk8*f);+oLCv9pkY22FnzGdACo>Tg}g_|5LLN+b7 zKoqoGj=k;r*BQrxu`>QJ>2`DUB88T&Nopvl>Gy_A?WHZ#sX9yZL?0%_hHl=5hIgvO zIF#q?tf9iZ*{MlNb;6dXx34fGRr9@u53}FHfN8mtjZvSIj+^xQx!DXM@PI2GWHaAix zIS!0QKJK^5ZgO6_1f=aInY~Nez%RPDzLL+s=$Xh({(1`yFS#4}!t#uHXbsoNJuzBTkySr9(cQI}u%@pErMQvc}`*gORm~*&I zGb~1=2cv3lo=WmyDvlNexGa37ZJe_++gRzYihq^o4L0 z`nz~@^j5e~%Lf{DmQrVVilZ%QP7{Oe=`*G-WHul^lRl(9%w4@r zndZ)A+kd}S$qb<#H$n>_x+copuQC&54c6KajV|8(9Y^s^6 z9<=l7%S8n%y;~Y11|7K=e`KTK|7OzgMn`;UyF#jS^&u^Z&72INB#x>7`>fzimBL?d2MgoFw0S zU7$wm0xv7e72CPhfG*Usu%LB7O%D%9)tnmIFDl3oCOhq`i4WcOUcy{WYqYlAN=v|; zNj9gH&05tyrj9k?*=;FZtVI_WlaYbmZE<-F5B#)^QAzo0wkl>Z8Z+aec&#XP`jy)3 zYvT6DmbjAUM`@he-ww+3FcEyNs0oNG7MdEf0k?9r0KHYcspZT?T<5%+`@%Zt%5x}m z+zZ~03|vA6s&x00D}_3&Lf>UHN?*#Ca|hogJ29XVitP{hkK>FDE-NHi|LfNqiu*Zfl(JFYqOA2gp3xDeOq>( zVYX((;^pHyhPK)PI$}9<7rm>B<5X3=s*KbPR8=uZk4^O$tVf+5^?D4^W2hdR>9M&U zTj;T+9$V?LwI18(!2(+UHA^^)R$mkq&Y39R`cKizwBwqrZ0iFa758RYm$69!N`Ksy z)iDLfXO*1UR6c)(JH#&5_s&G9PU+j7a)*tYM$Pnh;`t>h?^(W@a3zvejG5A1ylH}g zqV8yj+SMaD62x8b9^QG%7&+cUADOf4$+!4jN*s3esd2lNpsbaTmR=Sz36fc8+==pE z^=P%%)zPg}eEIxcS5kL9KaDkZ)ty$WMoWjOT2(E~MWSDO=u{N=sPq&)$q}oN9!?%`XORddGxXw~hPf7bF=EKZf zx_TS3w_I2Nor-kTH0`x<_o*$y?Ci+aCCgiv9KDQX2<#25#p!`n^W#_8`~Nl7h;0&B zPqlThVLd46VMTLRNIA^Ld(Y70vnvE_v^miGIrDkZmcnVA2{F5!g-d=9iF(@7c$*F6petB_y^_OHGi`S^DaGl2|;o z9vUemD@QVZ+(| zsQYRw5*^!fZgaFG&m8FfqF7JU!e!i)b6*VNci?a58qCezNA(cWL?UgMUN zInfodf?O9U(oxjSdxVmIHaZ&Ujj{<|_cAuCy1K+cadaQuAy*CL=r^X=9piQ{G^PyC zq>GzAP4ZvJ=X_9ytHG(i+ga$~fJwWYBJLb+g&?~Qq_8l)3`FbXw7l8Erh^w!-{T}W zhM$=+1!NytuPZ(z=!E2h#E>A`;~y_a42Na!q!x8HWpfwI(gBsRP1I$dRCtZn$tl{k zk^1(uZ39`K&*EOFLT5)8Oy?vP23~Pvuc5l`RE!(1sEd)za9N90jpSPs=9-sG7?hT|^bxM;CNNihnJWm;M&c?7Z z*_3((pThIjJ3BEwGFb0%n5nNw=V1c$JsR%J0mV&I3v=gi7(1OHCdc2W`ikq2ztGXr z%uzTY6B=4gL{QbWR;Mp*Ted7(-VXN;uS0b=k`WH}RHwssn%mmHPCTz_R_!TsTdOvU z7qM@l3as!pWf_gRjP@nntL^rs?oPG>3RGlT!7M?h<5hetly$#0qgAZ&CS~gWXktE9 z^Xu@(o#Q&!X3ninNewmz(cenuaxtGug`!{`LZ9LPy)nIC=;C?XX<`bgNuG@N2Tz^qLS9Oi; zB~Nc8U5o$kt;hQt1f&{Aja4(#?MZgr==I8M*>;C)r(0ZRdN15yiAaVVDiJqRA}YLl zRe^_kbX_^!pOwmpF3>`0lhV`LsBC*wNnIE(ilKzmDGI6h*2k&vbVYI86Y9MSs5k;9 zX4Ej6;@a@;O!c!_y#*u3FS%*P*;IJ(y7cBA7r%Pb?C9m$9aP`RaVL*iPq%;EVurVB zg%|e3w!vC@n!#ED3kC7P1XpEWMc50{U@84RG=X$(h-tSJCTzifK!-G{DQt$8hGtpY zk?Cohs3*T4W%K*IDs~vSBlQZ)yStWltZ5I=)7ya;QFXLhhLs&lm&NVE&6LthtX=pz z(Pl4Q(Hh0WSU35aIFKe*K(B6szU*_eHF4bY*ge&%>YYAK>aT~ae_vIeb5 z@!P>e>}x$FtoP%oI5kIg>56N*R&e7=tAekfef={*VQ;a-#`!kNcXT&I2~*s|thu;} zrZa3%!ro7&P0{pv)gIOdik36twSW`(#BZhCaL7#Y=qEZAk)ERV(#4~ok3XrJKZ3z} z%hJrq{yw1mfhw!UD`X1;qX~Ci8s|)N&RnWQNNF#k6?*5qt%I35O-5$%>1(q7`e?_D z$yU)8mkMv1swY=V+VnGLzeFo??yS5XtlTs=Pl6-k4iuBMp>4wqmctgzXIK`GTnE6p zBR5;Iyft&qR5k=n<7iH8Q0Lk|F8grnX^WaUm6lzZ|3tyo4^@Wl%zpK{?QS+UPf1rK z?pbJFeNk3d(M(DyTN{o0zmjFo%pCekE!dk;^}9v z=V%5M*kL8Tz-WkM(`MlwXj#RZrgI4W+z_|vi@B5nm!Gi5VIkFpGl5H;lk4?7ZC}Y^ z*Hlc$iV>4`Nd?}Y2pWL-Tvay>^H@G249TKjZvC*LUsX@OiOglg3sY^NB%tie#YNi{sR6E z=I@PaH)E`*>jr=AZABXNb3Gqd)OGxA!JndD&fg>a_2=&%{+_A!?0gPI-TaexK2W5F z@9TN_EP@4iB!4~pNz}FcUC-Zm{+_M2vxB{gN>(0hii1-<^;5@WhQ8Y09{TEY3w>q9 z9{S2$C;WL>@>7dKp>J#QQ}&vnuk5_SpBg5HKToos21f@`guYs}3x8gdFzCv~&{wBa zhChFt{8aBH^wsfaA#@#|5&Ei^7XFmkMEFxi4&hI2mk)pHyu--{*={k_)~Me;ZL*luE4DO6aH1(CQ7ezz{2*WwgqCT zS95H&4M)9%N2i;Gxt0AdYhQZSn3XS%?0mhNYR}xtYVBhwOy#Z1Hq@)EaZSoNJSY8uGjIN~^smGs4YRLjJF}w`4q>ksTdoRqZ`%{`H~k z&|h+%FBKq3FId3PFh;8XTcPnbl$Vqvw>IcNC1YCc1H9oSOsy>1~x zi$j#e-%1B5g@0Jkp!5=XwfB~JZkqCxlz;Z}m4Nrmw>COn7oUHbPhHU&8P(ng=3k%n zm*D@D3XtrNEug=hat`Tn%e_}wUpy;Y?e(*OAw>ZaT~#tfva2nmHtI;=Q)@n3BrDs= zCKf^mTIiOgLY2^_>kn3H^_k$Tu^PccEqJR?cs5wlH{U?G#BXKc+n0zRuF7}9w2d(h z+W?c2+HRvZ;Vq(5C3$mN`GVq<@Az=Ay<1l8`lnD z+9kP^?&yuPDa|p)7L||e_Je9~tc6D9BNM7L#;reCvL{+_FD>x}W)Jy=b$4UksaT=C zSe5FYpORM)8jUx4rR$%*rMr*;yIxKK*!R=1D*Y~Foj9$dd&SaRdq-EUV{unYTYJup zb#mIxpVQIsIc#B}xJw~7vb!-iQboJl;tn~|dYi|Jb&*&J`HqEbsgQX2fF3E5S195Y z7CgRJqA~A!lf2quPF@njZCRqD&OjD^axUhmrQIu69zVJi=xa>E;j3169Lb>tIrar| zjx*EiIW3yEF3sh0%e%T(HSW6W%Fd4guv8-z>Hg&tUw&%M$cctmphi&Q0%7uA^a5W6m~- zz~?d=!%&&s$2dl`cf^_M3?aCe7?nR0x1WPBYIW=ASZCQBbaf<{9fWp^ zGgimZ=WXQ;J*U-Wg_&IFh@o6)!V9hri#AZv7Fjg>jxoPIH&DitL+?>jdsl8vODA7{ z`RzMzA3`mz+URF-;3TN~jq0~2KYwmNYmhf- z(fkQ`Jm|v2_D~dQyaFCJs%p~M@eq$h(Z)}3(SBtfmE(o#*Kxevp&mDC(D?B=s?QbF z0Q&fzFwg2ecFQkr>w#NeRX`5-X1WA7dQI$cVYr5YvCnZ}fuYRHv*!Xx%f{u`Czy z;L8kAt`h?|wtZmgQ0;xN%0^O=6M zA0u}Wh%JpPpP;4HY+ao@WcKu3=gpq3tVSS~acpI7=aHQ|6YBtrwOwsC zl~mQ1OSgZL5x%fFCTE)^Em(>5Hd7S7xyG?qcRP0`nK4ONZJEy4D=0Ubr0r+o(cOM@J4;znhu^L!Cfg);lQHc&inl98jL8X-`?a)n zRo8O&Qx&m(jc$yJ{^J&p$`%<<*zam*TMqrC(VLqAGlfXK(Oew(lnW%Y_!b8~Yk>ns zc8=;KsgbIzo-?ma)qB(l9JYjC?fu?-w@zMGSXst}?geAmj-7o}gG&*W)0X{Q%aM$G zI=h@`kJFC5%5m#gb^7y*x7&cJ!Xj?n38vUP83VykWeih76J32v);&#ndeH~UhcvQt zY;IvE#xcB|Wi4EIpX0o>?pD`dtTsWD;)sWjTzW*VWrd_2PgC8+NQTaO$7)4q5r_i4 zVukynLiN>}fX%W3_Hp^&(Za^8KmTEc6jnewXoE=yi!@Yum$6VRn4sYnG=2T@R<<0M zV}dm4_H{}x_JUNaDg$wiHo}A@ti5uMQmufDG{$reyN}3)IlSG%s@$aPkBsQATjD2z z_6|qR4#_)>teM?3KlHfAJZ7ygH)!6~E-O3K0pc1ERrh>Gt({|XokzE=TBUc=wY-%q zc6Ov~l})#WZ#~r#J#10;N}^hMQ|e(& z)3!8;BQN_14oi@N@0Z0gdL_Sc8IPKbRJUMtE2nC6e@aeONZc;*vO`?@#1a%?p?%V% z&_Qe^kMt{0nZ6F~6O8(8u0DyYqo*u}@4C6UHEkN+kS2*R*djDg zZ5uRIwMlKFR4n(6aMl^;&Jkzaz^#wJ)A)B^--iM4Pyt~xqK+$P6Em>T$gay`$HPnY z9`W6Q>jAvVZK7^VJgcJC-!+Uo8b@52axTwG%uM5rEY;q$EJN|F@U&L4Ch4b(2-qdd ziZ9D?UQJa$-GiV7KH;kArwb7XW+}IyZa=_B_a7+0Il31CN*5*I=&A%9-Hw2x`w?(- zLjsPjN5Iil2{^hZ0Y^6_IPpp%`s=5=6A0DC2{^h$0Y^6|;OGto9NnUTqx&0hbb~_( zQ`b5uLp43KHlgC;pMG;|M)Z4LwP%^GtO*C)==%gy);`_s@t`}01xQX6ma9PYUUtrn zQc+yeab&w*Ui4aBRig6$Rg*F#C52;t?R}nRAq}aJBKb>nSC-1@>Bc!U#mR&hVFaCP zL32_;#X`c7R%pe?4_o+* zRCrNZbmih++eTA+#H3M)B}FS8pY)zEjssI1Md^jr+T!jNN5^&BM$>)Lq^3W%MWy7B z{&U8;fiEhICyS$~YTan^&zsQ97ga(GslQ~5^ER4$2I-m(|MUo6Ho+rOf+GbKivq>p zjk!6+T=WjC*;S<=5F5??8zyesl(zblBbcteJG+xYthyilb8>E3G`-F%IsyRUI{`!$a4zsAuG*f_ca8%MWbhSyjA4xw_)z#Y|u~!wL(`DlL={j*7T_}$8uyb^qH^n-AjYH}7aU9(>j-z|WaRwSE`L6_t?*GQmN`;WfPgktV&pEng9ZDCiKxs@4i&x~-N_EM$T_-=9ZI*b+uNxkRo@}8G8iGz4ejvh zj&>Z~(vG8h+HpdioZ=|m){ducZpYDO?Krxr9Y^=KXeKxg};XZQ|MiA@t0mC8@;ld!(4ok;I|KYmbxHgZX&*K)nCO3&r z;2jpYYl*;7qiv?ejp;Ruz)f$Ri%r|a3oM?!-Oj~}nxYf#2>GdnY$Zh!GR<`Y7DRXd z&^yhZ=QtbiDC)K$rgnEpM`FjNJyVB%WAa+4-JE7$3uH!K4(EuUe~j| zt=juTZ?@Ud7^)Cm&5N0*u|=;aJ|mHIUb2w-5W;+B#_hbuYVR+ZDD%~I4?|wJkVwCz zvVQ&k-B=_2!kW>}n-&^rN1 zhwrWCyKiE|0o9N4mK5&DnqE5oDvQ5c@AwPZAv=x33GGfj880yPRt*1Ei<~jh z!Wc%q)Sp<$@Z_CFv)a)Y!>bG@dhaq;O^{i`P`u5*+Plvf`h6L(58KaO60s^$ZjLUy zL}zeq)#VY3GAxObEnOjq$Bb+5#Infda3d?KaXDDEeb>Zka{Ho$18jLfHaX02RWgwuB zGWw?6T0n=Ic(*p?mV(^+Vn@5$qS+RCyJ^`rEwqpf^S{_3N${=gW>T6}l}IDY+E#bU zdS;0+HpGzEGb;sWt8x0&mFuZ=vj|Q%_UVaHg01K#pA6-ObqXtd3CcV(mzXWsx%s(h z98KEmOxUm(rJFR)%&x$7hH;IGmX0%Drz@LDO7_0avIs-897#IU~C7iuc)(*r4O7McIB!Sth+A7F1mfma;(8Sx;s}KKZd1$Sv5J+ zbgf*9n|%Gz#O}GWtuv}nVvHEp$><&h3F#QE7rGnvA}_UmFIoHn(cyS9ZW?&5ijvbI zm40|M906NffS=^lY8A_>J4rj2$?>+vK7Ru_R>dVNsZW_@v3J>m&-YDq6~zEpw;Hh- zhc`^ukT2PQPS_7DZ1dh>O$6S)zCdkixYGjbqnNmMGz6nL3m8%qAbEF{43V&VEM&_l zPUe6Jf`4YgTa^r!;QKcaF7Xeh!h0P+LHI8$d~1{6D_jXYvLObg@M~k(E<)J*Xj6C| zHJgrh9y`;-sG)wSrEjXpFRIt)`dIRAS|D1)#nJICM1r}h& z6KAG3j}m%eV@yizPsX$E1`fP{TmGDO1u zYa#Iqo7emJP;9u3Cw^hQd6Y!Od8*nrz@*fwj47@&(&wR6d;KkNi>Q#N0wup@y`d63 zFcpd(Gq(@0579zhp)U%R=(_a>OZHF;b}uhE5rBZrEx^6JBtR0kEEytU+gQlZsBoH9 zvF>6dXgdp1Wt%k1o|Zw3(pV<@6ZB>4p{NGbb6JS@&LIB1@9?-27b+ z&2bP5#z{xF9>2JQTVS>MVkK>Hs~G@wtv+5oZVe^L1Ies5p-qWUS%OBp78pfHd{@UR z8oS7XGB+wJL`jRK7P-?Tjp$^!g+XK6%I=l)e$f&=03EiMv#tvgZESF?F{d_rStmk? zt)d^$vbvLwg!KaA#4>G<(mT#Xr0mEnngL$YvZ`fq+X_Y(txV`d?_4E85s$Zs*ligv zQ$AtO!|Y43uw^vQ{w)|!FviVuVfP@H={}uofm7G_q8oGCPsAWhI}2@ZQIrf;a5-Mb z(qz(Lt%(|*L&=fd5@Ky{UxLmV&)w!AsZ`ilGchax@9!*dvfe;46iaL$Y^pa|=F}8R zDsW#-q&#gBhDkh965%Ne3b+QJZ?%zexv^r2LHKsYlwR+4uf~n5QN}Ud7=R+}Fk{>% zC9Qbm*{Bmo&frd!xVH0stNn79r1IwkTuZAvWcd8bNI z!Q$@ND>KDqX|eI`(2Fl^%K^*|W7{HOOHU5e9p1B)vt9o8?|&Njp9cP?f&XdXe;W9o z2L7jk|7qZV8u`%yFPw953L#nW->q)W|Fe3|+#&Khk0Ag3>foQ%*R8$2Va&RrUFt+xfb+H#Ce}xAx2&j5jrSO?o!IR}JeoVO`K~LB(jOp)(hF3+%wS zPmA-r#74-)I07ZI;C}+n|5oS!Q=R`HLVG0v!G8_Vq>36iuL@&ajZ4PvHdsABn_QX z>X2NP>IMnP6~xB*4vthOvf%HA>Lx@=`yO7wgqn40|3UdUc_(koX;X)LO*|TRmjIPO zOCGBHRZek-uWk3K# z7W`%aOpgHEgI6$}LUa!Rl>ZcfnF4Sx3OMeD>$OmC-6lj|1!arJXHC~|BcuPc}>aUiY)l8B)=p|{_l7NOESrK=?m;? zT&CpTAlQmMhWFty3{d4` z_?XxT`A})&iY)l6!LTaA@CjbQDyzwReT1C?z_w2Z0IGZdHN-|p6^;OiEcnL(;MfR2 zEndN~R73anu7-{mfB`U=2B69ZFqGH`8LWhHMHc+80^lnVfX(mNhrs|q zl@DNhVk2a0C5$Vw;GY73lOq7b@Cr^YT@jxu06V~70HDeTunVyfva=G#6o0QsPa)vBsN0EDRo?t1^--7oE@Rq4X@zr5Ctt^ zui|)~Q0xwup@1qM#ook5$YiCCE3)8U1d4A(DE7fC_*QYPc-pkJHB<~d8^0|S`@&@? zpvppVZtdKP(Q_+yBK6S){|~jz`R8l%&(`LjuFe0p*7fCf37)UL?&K?S6ZPFQ-m;jblA7`{GT4UcrS$>_gY3X{?r zoLe!Ocl0Wn*~_$|4qa9&iF({CmwG}4pp}2*!q*yWXm8V9nCRS!eXIyl7|Je!&2uZJ z@V1%@{yP-Gw`=on*5==+&HpVdf-{4+Gez)2@-Pej*-85E;#}PJ>1%K7x=6w6$ z{EET(qXvhnd&%*=?3`De^B3p5>YUe{^H&_dYp~^IDEI#(4RjjSTQ)t(^1i{ zITP^G+}z(3vD+eg6HXp#^L2lh-?4*(PH|g@Z2~1p+;1pk&ETM`SI9pIaS?2iZ1|)T z1}kfCCgLk1@o%NY*JR}Ly(RLqLH|?&YX$?vDvsQwuUZ1{IAQXus1H%n8S$v!zd9HI z@9B5t=o9q&U*cWul=KcKv`m3NXdg_j7xEVzE8$lASTg!G$2y9UuKrE@pj-Slzh{*~ zZqkW{;{zuesh_CSKT>Q9wqkUQ{Qj$O%|bzyUyQ0ZwPI9(BVCIu_@_~f zP8pm(d2qfbDn_-AZ-8?KI%iWH|7%Ve?}p0o&rpfEa!_z`tcsrK7bV7(fx(Knu1Jfg z#9BNfiV%D)`V~j5BWkct-d3NVE)~?{XDWayUj=nLnhFNuNChGb{`qyy`E%>SZ)k|) z*bK+Npe{JKZr$348pi!#$b$_NP9CyKg?e*>f(z=Pn)qw{W*vT8;Ag0y%11TKrMV4` zP>EzH0IF|AsCIN54LJVAb-}lKqZ&?7aB+<4+d{PyeufIFd{kpxnmgkNmB@nsT~J+C zm%k(eHWuID65u@C5L~9zC!9Cr7Y$O=u7osRE*RtZWiX)1hcTJh2-yusFhmyotATN4 zUH*y)#$NaaS2!3~W?<}1NaHnvu@Aot22}YlW)K@8O*n!flC}#N*G3p-I*!>m{*86P zwV?TB!?@cU9%-1s6pB=`KS9Bbbx<~`a)VGEfS;j)Dj!wBr8yTzs6-b0?}O^52-QNz zaVU;|Yh7?tZ&Zg76xTLM5``{|Hn+tjpgP0b7c1a2s%b z-4OgxsZaQ3!>_F5v=Y*IhhQw@m%)H4A4UhU5poocV2CXEcL5`)%ikHnI0oO~P6s2% zz*tR4<4*;nlV1h{s(ctFS88zDF22!_al|65=@6=C?Hw;%`qk4d#;P)}A=Y{G) z{0tRT`KTUsX+DA@R3Z!hpFs6uUH%Udu*dNY{s5c@8iE&<`h<&4`ni>yCkSc$vtT^Q zFM|P9K8)vyjga5r2!_al|0*zEsms3IZ0V*X7@g(DlPNcoR$yhnRx5tyK?ERTA9zuCP?`%dkL|k7W?C z5mJLAEFug3zrpf;1Z1$|sK@a?tP9=;(k}(+T-2mCD{R?^5FUIOx1k>h+EDxq8dUkv zwsQ$@i6dwt3%*z1od38k|51c)7{0+rVET24Dfrl$un^S_1UFXH6MaX185XGWvFuE2 zgzSVPEFug3CSa+o4|~m{9mi*I{ObCkvR*A1YB~=!se%rhu`z@P)%8&`)?d)__!%^) z@}ceF5+08uXd(;#ra&85pRbM3O~N;*1=IZ@reL5jM5rbc+&EZR8u?{dpvuQGmDmW` z7e`n`7W|=LsgHn6cO1<){^s>TJ&+z3r1MdeY9s#i(aj`0*t|Zfvo{m8S@;<=sPds5 z>=K@XBWNNE{Ugsy;ZuqBut2r&g)3qyoz0l|&i3Clu$85XGWv9u5yAxGc{ zi^zh%BUpw-K$hUesO)aOcCY8DdQc4m)o+E0>WLxeLk)ArFs$~Pcr@k&X(>?*5>)w+ zRuCH@%LN5jWWgT^q@5y2?RYUHOF`Oc1CV?{>L7|if+`=<8e$`)Q&4b47W~fuX>)w+))E^bUlkNwkp+JukjB^Nca2b;iZ7!?x0LgY-re|m z&4B1#F60sH3drY@SNU**)?0c`BUm3fG_Y?hdc#RhhlVLSAU_SwOy=yvkp@kMvQZHGV-D zALEx{geo87GsH&7Q#isXk_8|zE~(GAL>QmLm(h9(pKpsWzKSoMvJ}Rnvl!b-VtkEgjqSqtI=>7fRQVX+CN@Ie zz!64~1-}!F$3y_%!Hc;D3@C?sJ+JCT9|NM-^=9ds@sU2tM;eND;s?VPpVWfKZ zm>5Z6JS&Uw%#s*)B3k1)!Z?Coh7qcKjCo=sWE74tiY)jSg7Jd-{P_{aUGZf|kivLD z7UTIPF^(fz<3++ao?nI$s(g%-iH(rmafDH1!M_ZQmqY;f!i#C0yXk9tJ#XtpUjm|c z^kx|t@R2^wN6I3ITh;!KAni>Qg9KGRr0K*)$P_`r6q=WHdG?jvM?FJy-AV~9xVvwN9hjcix5psy2 z;EF8xw*l#v`uxoisw41an#mQe_x0{>(Xyhg1KP6i&4B!O@+w&z)<^mUqBZ_N7#H!& zFhZ4&aXGOOvIIvMMHc)3jCa=Oe-vRn3SZ`+QW)>dV*F7_j7Jl#@h8H#f?tLas(g&y z#74*}9AOm6$~qYDtbVO)bR1JV@6d$SntE{X9tqBY(xjK}lKFhZ4&@oi!w(w!k<0sXhy?wS$sx^=asjk~bQDt6LJU@%I9` z6~7E5RQVut#74+=I07lM;J*aOKh@`7j6e>@H~X#rsR-nYB|+{)yvCOWas((l$;2@rU$4)<8iCvu-|WZwdJ)K1OM)CnyvDx^fMH`Wnm~M$;iX5;GMs zBa;F#H3#lC#awRPfy>QAwdY&J4<)-QXTEZEA-S)Lp`m&81L+ro2RmS+?^mnJ-?o1F8rJmcWGEao}o>&ElV zEYBo%uDP6hQO+;Qa_kMqcdXo1Fx7m43Hf%GWr`ER>zx$|xeN|_funPMc!5_Fn>=5E zqZcT$;P1{0oS4gxkKW-m_y*(Scj(?wFcFWQ;qfcF7AC{7M_+Kt{?=H@i`F zHbU;i5nhqZ$$_^im)|b}`%`>_{bI1LBn3@)oG!4gDBTT{;h5eR91jqi^zT#pxFQSw zOmNJIKs<<7Frx?rpV#TbKZ!9s1dm~u)fWuEAvS6MQfcFgEcge4;eZIkqj&`e#28Ya zf@-dcX?t45MyYo-!eg)-oP+uT=V@XC<0-+w60zQN%!4p$WgN90rw#g);UFd2?7_65hk ziB0nP2)n^q+7~$e4m3EQgm6R_{3C(W8o{Z> zD`+)1sxSsXT5`+#lH6`Cxp7JcVIr9x$u)<~(bxP^QW4CU8e&fZ=< z8_&rRcMo_C2vqq^_P)eMNTUGYiY)lwih)dl?gfNR(+ zPXNKVru^6l#7p?nE;|sT1Y#T@dWHj{XLvBS2++%L8=#4O0rUp30rHxF;EH7XH-IKJ z<@adH?-pVD2LZIv4%26ZX;PC~*M)indw}U=;X8SFuv-znH;HNZ_Ua41_lXUzcZ3U9 zWWnDTe0xX8{)HDUQ0kTKU4-D@a2SI9`hvjQ-Vl5gA`n^dr-NW>Q+`SWq5@yq9S35p z)H4+jYX_l{wS$5wMS%LjZGdL<1<)V|s763giAY8`0Gid5Z;k+MiZ2zv12kTMW?>z8 z!Eh#rFBl#)7Xca!w*fk!FMzfpHb6EP5L}T3{~!R(iO_6~7uA329chnC`m@>w4nr`v zF9>qPCgovD8CN8m*g-HaLePL0HGT@gydng{;V=Y+G7)^ei6nz%BuOPtnrB&>!C4wj zG9h1MRjk*L@^C+`=KbUo&l~9KV2<%)ur#&1$Md|NK^Ak`RJJQ2qVb$2gLY1t&_46a ze$vaIkcjgFj?^QPrAWe9jN}>CeSgMVNGc{BhNl=`!p9%5DBQfXn@W-R*R8!{IMwsp zHsEyImF^hccnBfHd6_s1DjPowH9C^>isX#N2T6Af_j)F$CGm;eFdu0qM>8&-2WGjz_43cL;MzSd6E*wav?aYY%tM z$lgM(IP2ElBOR>GX%2pEE|7>KKX*dNA_9=|9#T-t5~$IUWbN(pC*tE|H>G7G^`7Ce zRHHKT{$=t;OCHkyElMh~)cT^L$_i5vrO`4ivfwXAdUJjm&-_vzp^CIg-l=E>o>I{! z`1l{DRirpY>IfvF$j@b|jR2(dM+z!h2{k&BTrJ6y@JXpClI>I!NlmFJl&4MnY}b)I zq}Pg)iY&A*D$)jg1l8jx(<0evg>?2>@eH-IIo?h?op_3IOMLv{d%7G<*Gm?ANlhF> z05P^A2Ab%C8XZZ|#(aM-e4Lz_X*sF9hZ43i38N(edE1JztwEyulJ^&pfS~7blqHcY z-6D;}Tb}uD9-#vMNSIT=33!V8Px$yZ4sgKj3sRgS1q2dN zf-AD%AB*(nd>7CBY91l3n+WqK{}oMw#q0U*<`h%$XgfZ-dv{PE=H6J>;EU*+iEdbp z?hDpik$|9Egb`O{!9M}%&G|3$%pcDq#CjWH{y&9PD(m^4u!=|M_zT1u{-y+>(68cw z%pVZXKpNc_kUv5Kf^HW$T#*HTEz+CwC-Kam$Rh-K2VwpJu|lsFNb&IQ?N9-+3S{=x zpMo!f?UMHc*@AiX*NW1b<-db|zi+6botd&9cR!5RX)!7{opu(m`3aJIk^ERhBOeuMS1 z1lCq~8?2v4uoT!E*3TWRtzkD%V8;%43H3$%{NES*B*b_YSkMRiK=KRwu^e1PbU4=|u-=EUX5}=)OSO4GBP*C@8oh3;yc{ z>D2_%?syAQi$PLAZ%BW2koJJpAQ{~kNP8mzD0>MCu1F5tG)VtUAnk*(D1kH`Z!1p^MCD0=yV&PSaXm7 zoC5?0S7gE8^6x6t0|XWGMkk6Yo3+=GZ~K#lGT(1VeH zpm_p_E3)A4fb{14_B`|3@(6*>C#<+%C!VBVC+6N*cf=Rb1)>{Pqx*vOFeD)8P+`Os z$*M8ZSvTgHAHgHU`dPyKY1yhyJZW@+_|qOvwWIMx`sa{faEHv z4(ZMLu{`r*c!Y4jKv*$c@ublO;!k@xxa09f`XVG4T%-E}cQFzW)FPO;A`AW`q&MgH z;F;fzM+kQbVZ|L%@lcAbLn_wZNGIcq;H5-1q(=7z>Cs3)&{0B&E0S|Ik?hB+ZBLOI15)@pK1;520E#jF!f=8&TuQ=8daQr2B`$xxL&iCmg_)GC)#M{KB-hv!fq*FRL7{c?MzD27Eti1H_TeOd#&Ghx@|5gfFtcMtlQnbYH;gVig4GW*dPOS@17G zdUO8UJVTrp;B7chk8momH>^t?tZ%_?u#D~ttZyR$I2Q>HuE>IaxxxBQ0_$SD1?%W3 zKnWDk8`2dH(j~AOB%}KR={raO%4LFrE0V)F4APYer0?P_NJkhX1@wk=je~SKtOm*G zzCgMX2|)Rtpx}yRxM`5COCVi^x4pbGqnD?^-mq?Tu&##PU>V&PSl1x|IM)geuE>Ia zi@~}%fptCJg4Jo7Qb2D=w>n5Sz-o|;E(_8h|Dg|Mc6e|eM;k(i3)TD2tvJAc1)9nE zD+e{cWSny>9sAvfbmq^kIG8NYXWx604jv2qzsBjjX6Y=nbnJIo>AYyMdv;|$57qiO zQn5(Bk2m$%euroNQavu#<02m6S3C|b|0HNPomeB2ZE~r$ zeh9T}7~Pj_%s~QzrU^c-$b$bvq&MepmU-ed>Y-Ce10AY2wI^0;fmzg8KiUY4A1JR zml)$LreBvLC$CFVieA@G@gmhPAk?y9bYHTu5(x-eD)_h}3;sh$Z_Yo!Gk?DxKhxtL z9${YE;qotpPQ5O1$9V}9EqPsvpS&&!`n0d>7w`hBg9I&~M)xJ3I?@n9oys4s$b$bU z(wpDQ&m$?KAoqSy5pUZi>gLMD&397H>)D>_ zMNJrI>rU;SL!}Z`9M2E~BxjP4WzFcmWbIo>K+xIB60S(jD@Hme7W2$M%OlLyg@hI7 zN<49{(l1Bx%FEIV!~nrXq+(ezx-VI}3JD0hR9V3lS@8dY^yd7_JoA6nvf%$6>CO4S^31=+Bh1z9 zgcavXJaMklZ%gsY%hDUf0Ktz)#j<2{U$XRbBp~Q6Wd&Cx#|$IAIsXpN{M&lG$s^3y z127a<5n_z<6(~mXwiLCzti4YRkUU62mNldMlC{T?fS^Z|C0vmO|364?&VRr&|1Tb4 zuAU&QI9K9{bCrHuidSBiJ|YGPo+K5^lF@z1(hEpH(C?HLT#*I8>MdodAJ4p}$Hz&& z!gX)0X~V9CY#S*2K?4Qt%1|VGSL?hpa(*=wy}!$HwvSH>)_#AyiYpzl#fnKpSXC+j zv2Fq(Ng4%gbGtNbw^eTmL8+X1f(i3mgC^>D^yvzHnPdYc3pchyU5%UY8~=EP+vvV1 z?=7c=*Kwpgkp;gF>CO2~dFBW52+PCUj`g3;c?Tz}H}P9G*LmmU{Gm{Z-rj}VAA(o0 z-ozH`EnT2uX%ZO-MsKCPC#jN9QfcoK8Ys<3Y4!XjkN?8oN}SPsQCcNyrU?2Ef}}K& z1%DXQoAcZ8%x|N|Ry;z@RXP4moYNmCt2sqVN?Z;q(Ofm${*HJRSDa#tH5bGpti%aG zQsRW9xAJNvSVBrFua?k2$wtcCf#2kH0RE;tqx+(~t&o7AdK@WFB$LNTXY!b5eguzD zZ(BRoZJe_$PF8Q?PfFZppc1`p2e-cqUd4J7TdcQqiHoIKi4%<8O50viC84C!h7lSl z%}8mZ`Ar^oz~7W+bYGM<1_=lnfg`1fEckmMy*a-d&-?^E#_mv{VQowF-WR&$D! zl(@-IiRQ+^?N7q1xbhcUthpc-VI@ugk`gB*y_Gj!f+eJ+@+J@(DA`DPd-9vSPQ>4o zXLMhbw=WV9v?q>~C$iu-A-y@jAJ6N}En-pfn?;P31RvoPob7&FH=;Z5|R3G#f`s6Un9^ zq_ZuEXMQ%1P-zD{*7?pU;AE91{-m(YgG!XP0B(OSUd0WA*kYvxu?Q<{N;@uW8ylNM zE$l+2B&j78c?h9_a72nch~MPhN^Owaz(b;AC|u)}$o1LnS)=8r=R$yoz-wwpfShk{C-XrzD=KWF)er z0>4gZAP|uPSMZzso`t_D(CEG>@M0t&=zJV0P-MYhgY@QnH_v=0k5G%3IMz#@a~V!n zi{ej8;+LQjEq(`X|9HHL8xpa_S`1Clg3Y z(*3eg_EmV1>K_qm*)X~<*?0p92zpuYaYb@$Khin4pJ)CCJ+9N^8XjR@&~UHUCGI#c zfubd^OYxJ}B|#g0U3Mo5YhYYsV!6GA7=U}5WG%Nw_a(QVAOS)DQZ8{t7W_Mq-kiUk zXZ{Cz+{z=&sK-jDX(RR0#29BZ{n8XUd1;bT^wRFci&QJ3=)Po0 zrl$yMP*!k7vaEx2mUVdMf2qgAJi>hK0z+}36JwmOKrxcHrKsg)?J;72M5e5kHKY5I zwMj@o(0FADS7gEeKcqM3pX8Z;oJW|e$%GZ>N<49{(r-)g%FEJki2;H}Qn4%<-IpxQ zLIQ%OC@Z)kS>i!DOFTUD&+73sk1$`e9smB$ku_(wE1*cOCe)nmAEA=2fNVbf7x5}C z#$t;V6T~9y;}n2cH-V5O)l4*^l)sGLKMihdWl8KEf%>$E@DOZ`rgt+rL32dNW(dzv5M_H?hTf zOBbkEnnVVI(OYSq|&}fXrMGBrM=E?khkD(N;A4IN|R|Jf?9E;G?4}WeWW+% z-{qPAryg(d2sPL4_+>~4yX*+Fnp32t#C-sjXinyZ{=e}m{uIR)Yc7aIScwyWq{InH zZ{^7t(U*{t%IhRFP_mKo{>5*=cj0f!GrBLzI|&I0`Vx+mC$ivI&{Js6e?l7h|MCd+ zcCuq#>zq??vU(GLQsSzRk?8GIxcy4JiuES8Sa0bP7fZ7eCm6kzcABJ0LP@2`Vlhyf zk<$9{n>?<=-;`!_Uz8^MK?HpRM@kdPRRu`rvI3s@S{|X&WIG7Uh0c)yU{-13PYPQ- zRH8JQ3Ho(-6@R8;i~tgIw(bo#D*W~*zofVbtua_LS=Y|6B*j|#LIeOO$y#NP>Bv@Yv*r`SFsMo z7V9uw@M38a`Dqn+qmq%xk_x z=W{&sJMaj#7&z9SIOi^$Y)KSia%em#aawv5mpkFcG)HIZlxrt zB^7xOp@DEjirk6cBv7gWJ#JRjfm?#X3xv#8_H6CGl}3BatN)_ynPWKtu{0!*BBYB>tvAqx+)3 z9H%28XiFR^P$VaqBb_tMdFDIxSgFU+dbIHfmD&KS-wo~W$HR>jbFy?)yjU@T1i134 z84O8sn$`@4L|%9D6*&!g1Bs4r=Pha3nHnEYxTfh!yZUq_C<397hu*4Z=ly*W`%Rl_ zr+aiYcELmreIl63jNY)ys(Y@-24T22_`adOS2wp`X!bf85l9+~Bb|vX_$MR2Ij_Nb z{;PU?MUOA(ahx8<@(2~z2)};{w0}4cr#N4VQ{+T(l9DJ+;u6J4Zmc-Tj|ru-VJ+JH zLae`i0TT#JvHl{zSbu9_BDeby!SrWzpY(SSB9Js4NBR@Vp)QE$SQno8Gxaz_kJI%y zl}D($xv-k1oz_N6Yl;$UE&UxzjHyhIK=MpWdIS=jP)fm+riYt*IR`JW%_Bj}t??Hn9HV@SyJS8kLC2$D z`A0q_N>&k>@*}G=qt()u1F@8kj|tM&-@ScxK)qu z>v0p0Fn6cJ>i-zJxaLs2IE#Sv5MJ0gq5mm%wEjO_c(1$0|yfc1cJ| zyF?}BTQZBv_x*SQK|fN?a77mUXOP~U z{~gc&$J%+o>rqti|E32BE%ch3o3P{!EW`l%<|3K^zLbRKo2V3}qf(@c6)q&er3grq z-b-jAAmB=oBE2ghy(7|lZ~yOeW_Gr`n{wa(FP~4&%$Yg!%sJ1Tt?%yM_+g2ENj!+~ z!Tkd(qan9;vd)6juq-%@55eg`_qE%C1z8I0ahmYsCT6g}Om8Q!`#=EbHU&f}mA}{s zI)Aef89y!YB*MpZKL+KPWEf(~=c5K!1th#uKrhh5{0{(c0h!)TKu?1J(8CIZQff1L z6ZA&>Ix>D$;uVRP5k9JCuqvmatSh4uLWn96RF;FnEJgMfO*rx_Q&?oCw-ec0AOQ4| zBBGSqj6MUs5r2Y=Ka%)B;ysCX5I(%OF^#@JkES48Hri>L#)TLY0c1N{Az7IA*)2ha zovolPN*z}!Pt%Gg81FNGqWQ}^+q5n0Y(K|{*gjx@k!5;2WDOW(WPK)*0F=sKOMFjc z4M4{IB)(?0_)CQ6X&_d{Xl{d*b)YDp+(O9GKu}p42(y$1gE7aEK}=zhnchxh%e%-H zRYU+vHmBjYZK zPJ|C?Z49C@=;aeGS%=6H4a*i^jVp(Y3bO`#A&Qk&5#pf}=`k?~lBCub^sMxI;oRduc{o4`wc#WgOh zxVutVl_sW`28{V^dOJRE2LeD_$X80K&FEX8H{vyr@oEy^KzPTu#~_-3Uf!f-9lTF8 zEL&BL|G!z)pE7-->C3B{wuM!#V?;ndV}LPadOHm50|G$1QHUX_&FH(JH{!LB@tO!v z&c5^+dAU`UP2eTJsv1{X)rmAQ#V>&|pG|Ma=jk8-^jrB#DYY4`4|*eRAmeo<)<$^8 zXE^(VU2zD7w!Q4GsRp^_)DLTu(XxA0hhiOVNRyqOy2!^XpJf&zPXdH+5;+ipPvl9q z+e&XV_X=1bu-5v++F|r2nl`;VwE=ZvJ)CwU&-8Z4I|&4Uj;0WKQk&5aL2tyG`$@@F989d^C-lpRG!=fohLVu@lOz*#Y>&(Wv=)$ zMUh3>yB&#MF4+++WASpVqaA1}=b_93i}@`upb0XO8?B`MMW{lkB<%`%6U~{Dwmo%X zypnb!&GdFi`v(XBT}vU-r1D8p(D@`OGTv2UXM|_&CTD-MD{i4EGN(Zqb9d~{F7NFa{m`#$<)YfV?GrvBlB&nqf=-qpV!MQkeLJs z+nD8rt&Z_2cQQWZS1X_IXc|FQ^7$^kiJVCJJefM-zel_AX?i<+b__N?KLJR5O65}^ zpf}=kk@49QXCge6{hWP&R}7#iQmH{%Ud}|zs2qrObRkV;DrFX^%x}>F&3KeA8IN+R zmB&GvLwJ=u4yHGe2PuyiP$%pmv>T76x5MM|AON%&g?N57pQMKU!q%geu^Wn^|?9bHRP`9ecxfy^X8SYFDDEHC9w#;5#h<#Uv#5o9Hw zqv=iLM9Swi)CqqD+Ko@s+u?IO2mpkGklBtndUOs}BkvSRb z=wX`52OpUQGLrydc_}Zlyp%f`pYp4f&ncQlkd=IHL~kM|Qa=Apo$xoN-S{-U9X_`N z0iY&@_>|g=o&~)TKaGr^lz1HBsocuh|JW5jp(s+RL0MkDfR<6YHP+GdG?l58S)ekX zmjTW4Qodw7%BfZ!x6vHJtK@N8dJ}n&^7tHe!rqQ{wou(+%3o6?;<@$<)X!FW*DU$lMR>=v|u1KZ(gKkeLJs%S(BY<)z%o z_>^C*eEvq$2(psT-_o1NiImTGs1yG0Xg5AhZ->vrKmh1K3h^nG^CIrjHR8{hBmP9< zBZQ~&aA$vnD~_ZnQmH}qdth(J^v>ujw2aDQu#Uc@sZ6EJ0+so^3}}{@@`XD*?zai~ zl>NGxUMn7_DFjzZFv;X9tZ%PLLnNZ@^shxqH#WCJdeZ> zi9r$r5T4NU-N*}EaUn&KPz}#=b0M^h(3x0A3({2nSz2a+&?G=uZpxJ`H|0~7n{qDG z=h& zs2PP`N$o%AP2^8X?Q+y%;wIXSTGQL1_TL}?bT5UdmC8%_pz{(wG9Hcagg)#{A92N_ z6h%U1pQYFqG}G6(=YOChGDHluHV-iTL4dXoO@tX^@&|0s$i$u!Hl)zLDNUd1|Ejixe5 zG7BW-vo4?sGBEU-a5Scpq1Wk6G)v0Rc>FqG|2?zkaOCg4&@~1yR=Z}9P z;|T~))2Gh#Ggo|0QKU)sSuRaP%V_!n>*%{QmG864EYOq$2+JkS-I_~Z3QEH&nfi*} zL=&YQXFx5LmcK>%ns3Na*= zzcC3qe`OLGZ!Pg-i7gSHu>G)#wm^5cpx4kXXjzAN6Ag2DrKbWl-Vw-SOzz=sdLS*z_jW(YZ90_g^y0XbTYHE|CMq zjYJ|(f)-AE-V(4tDi2m~)0=48^k8)kbz*&ob|cU9cF6kz1b{xI5P4FY(WRg_;){{- zOoZp{OK19(E54>E@+Nzi0{VIDa`e-*W^sBTa6!lGabCLZ~FI zKfQ_OOi8k_mZd8W5R-U=W9v^<5#lgfMBp!41~GQJt%d5fIsimn(#QRGeb7MNWJ--(v-R>L~F zgQhZXG7G%rGcKSBGLajtq>UA-5GqMqiQYtWrlj3Yofuc9-AFUN9n!uH0zj)#h%~9q z=n>Ex@xPJrLlO^2+=uY=t>p%N#}(hDDAK2qS=v2@meIEk*3o}xvSX+_)UT-%HJJta zk^o_8CockLiQLKV9mucH7m~1{X#`ox=X&%eaw6sPQR;-hKJCV*>Fw}24FrHTq7a`_ z`NTZvjrcia{EWm?2v6k?oc#}7@gs^Nl^SHxSth=WmQlGC*3nBem8q0jpfaC{0ZovJ ze93r}Q>{F1tvQ5O$>TQkCh{QV@kQ!{y)Es=qv`GNxH|{{?L;9Sr8cAYKySqFAmg_r z-jH|=;mO>?4cgNcdr=h0)W|F^KSayO+#BoY1Dfm%CzqEp3uGn%!tzpH1kMtWFlWO9_3UkkG+~hc$GXJ zMsFeyQXV^~6ZYY>8;_>>JPO|fc&O|`=;&$)!1LyQ-7J9M8Eq%qz;xxUT zIQ5+{fMzH@N~ydT20HJBA>$(?4nz15_5Cp8!G*Z4LX=?$F~8gzZ1G7XLdjlCw@a8~ zXkyl*0JShoZzl}@rLkju7*dFvbnzZrI}kJEiK>>SV=@ma|D42jbaK16*z%y`HxmkdLQ z`Q_5!Y`KI|TCQ_xA`E?t%)&6eoiMHk0idgV7*dVY`v|QKF#H{xMYGIh(P8g4Y0MLVSpHgZwx)Jn7d_6M$ zyTsoRK8(jPDDNa>7{W+2!d6Nm(a;b{)prD1{>;KZ7=b5GFp-63dOM-L3<5y*O*KlX z&FG(?H{x56@l6OH)qg9Zl1+#zzc?D#w~%gQ1RlJShGcp>A#ofP(CZ3?Qff205A;TS z4>G<>;tqt5i$iA1K0<~eu0$iU)zZ+ug?2w9@C4~r%k*|a>(9IQ0DbO5lggi$1-B7D zgp40R_^1ZZSKjlIpK*oD<~97smQiq$oLhBmk~a$ zRh<2*t{6vAJiXQ+TPyoB3vZ!iS1S6{S@b4NJ_R~opF+m(Av|yTCK@J_ zT(LGqkvG{}n7ti0cSfJ0WxTC}b@T~MW!_{Kc*`eLKoevlH(E(sSExd$B<*|jCYm!P z?PKZ$(4gH&Grb+segpzQn^K50sm*Afk3`xKWIRY>0Kzl3xwF^z&@k7h&x*`xP?mAS z&@$%qg|uisn#wy)nFZ#O0AU#?2eOQlC#~e^n`@CkD#_C~))Gydk~c4PoZp&uBhU19 z$kSh41!zYKktekoEed)g9*&F`LU`VGbEdg3o)vkMeU@=cpk=)2!)VdsG?jUiS>P?7 zaRJRTPHwc4W*<-!LM3T?F=wJVQ_>crPK^5gnUQ9CJEZyVoc$Ugk>9B>}=RP7Y)lCr?_*J4C<&sU*+7ccy7m z@ryBF$oZsiJH65nW*otMMAA4QlDi@rA7C>gO>c+DCqV${5egA0 zmA6Pi=PgpC=kO_K^|ULVp(t`F(<~FWK+8CM7VBtpn#vr?EO405#DLb9Ongq$2(psE z=jl!4L`vX~s1xrCv>Sn@w?p9DAOQ3~3K1xkhrmGRAuwdTHNvy_jx&AN74K0LS(JU2 zi94ZXEWVF*v?ERB1BuK6i%Ed6Ow`Q|BYRh$u^mf9S1Oh;H zxlbvTpF%+6rx0X(qQvnM$0B?zYhzVjE?I|I5)I3iOXJ<G3yTiwJ=O?CyX6H0B9?@Pbsw-T>^R|z6cp#C~-c*hp{6D<=m5D2qV!5TQA#V zYiNk1>Lje?&n#TZ2t3(|i7YhJ+X-z?5CGa)AyGE zY_&ABZ=qes2s}BEi7YhJ+X?L`5CA$n(MvRyH$|BR=8^zm z87Bv_jFTs=EoD* z=1fU@i#jptbC^b&>FtpAItT#0L?P0oHlt5LZ^R!X;|~#@v^SjTo37ATEsLbdK1;SQ z&@$5O!e=Hb|cdC zc8L4}1b{xG5Rp=wQGfo5S0nB~dJeyIR$sZ|YlmbNK=_ZnFS8> zi5JiWIkyD%(=>vtB(OidiJV9Y96+6T2heT=n%)k93xEL75DF0}wHYk{dLtf&jORso z78i7;3%O!piXw}$&oXggw2Z~!SVs%dRNj1L7FbLIgk_@U?sFzCqM3wRN#vsRCh{XC zazX0Ey%_CAr0MMtIT8ebmZ1=lQk&5dpf}>hke5kF7ZQ&X$Vj1&X`8qqDLzs+?C>bdeTwj0x^jITz={fhL9Z7x+E5)J=?0c zx&%FQ`kkFn|4#Mydvn*vIq2c?mj-cDrtBq2b@Dk4g$&1hfH8}Z)Acu$GlC3Zvj$n496`=OWfKtsym>5Z>M zFj*1^FC>9*=bQxP@3@|YqlsK7Cqdd4lHivZ5!u-cup}_O9jYz|0iX*+5v9~-bRg)B z_yA=5dx_sl{2Jk@(kK7QNg(UMQleqDwb-s$<3k=O`~A1q1zF00gJ{B!zcPabW_mk; z-3$Uii2|aO+Kdhdy%F~!<3l87Abd>r(LU`ka)~X&5K}&}HMlAu;gte9f+pq*&$`U~id z_|M4rQi+QtW=dRu@Zk-?G`bQ!nu^G!fyRXx69HstAfzk}1R2sm(0xgR`ItY^{N*%A z+d>*#ff2C{V}OxmdOKt-4FW(5Q;007&FDJN8*ze+ua&r3;wpsaX&J1H(OeqHI#85P z10iH-AgC-2gjq_1>uJJ~WtqYvGrgV2Rt5neeHT%YNo_`VgWibmM8>yE{8QoK#KPZ}~FNyIDoJr(NIWf|Kg0U&*OQ1nS{M*ji55kG>A|1I&5!~+udAv|+yV`@Y@(l!%m8WKp$ zXNO?2>=0g-9RkiY%}~mf$7sU0b(qkCHNBl+{Rj4%K3J*E=q1n_@e9cKIf-W^o|1S1 z;e*vT`O3j+NM*2s$$}ML7M_4JO*52&eVHbL)o1-ISkv1H_Lm?4^a}+^DU~})pmRqF z8NVv=3c`nI-@bbry?ojy>kwz6VcC|T@nK8QH5eCKxli}c=Dfdr%4c7_)AZ%sPuoK7 zzl9M2h0o!c-VQ^DI)-{YLsFa3hoCp&_mT0t2v1HgeMX*3_NhvC*#utl$*yr>{oNV; zM>OHHzPM+rYI-|9>+5v@{XxD`N^M48f!>I}K*pa*e1hetLkK&LXm7&5&bhWy9%&J;5MN^M4iaiI|pM8^FQo}3Hm zGxBn)Dx1JdepNNDw5mgBVhVkk&wMt$9iOiO0ia9eE2Y$CvpcwuC`pv3$V^GVEu z@F84_X|x!6d8^g95Jw__Y<-0k);AHP?UHoSF6h1-vaVzPMDv#yIBg3HyeLM*rqA*j zS*Eu`*4-cgbTfs>lG==x2fYz5i;S0+SW;qfgy-oVtc+2&)ONWp>p)R{sf7@hIuTU1 z%NAy7mpy_e9J!Y%EHcyEiA*251L#3TL@Biytq6J}UI7`8k{F5bLFvsa}rc55;VtRw+KIEfs{h><5573n?f7X&O27;XK{ z?nQbNO`G2M8bh5JUZUN|Grb-1-T?t1eNs>4No_{sL2tz4knt)A&zrtihsk@cc%P!k zo9x~8(Q$KUv<6zn+s9Z(tJ75GO=f|&d^QC%K_+sem9)= z7tcsDy&cjPbzh~?cjrW!)MhjV^hUfPGG1SzA+avP)3=x#w74slpeWL(ky+Ypf|k*@ zB-YW!G?lL}Wftg50)(ZVyvWi{?qu7e{A%TMDNQ5DN|SHu)WDm5s}#I4XWDp$ce+LER+l`;!d z<})#%StiPtj7K@u%Hyhc+KEkOXNNg+O^HltsH-iY@>#(PQZ zf$&srFMo}eQMonN(XVJKQz^4RWj-$hn&qW@$#|4gtvqg{IfPfq zFw~i2M7TDltMg8ZAOQH-iT))#b_$@r9Ct$gZhmXRPU`TQ-tiJVCJ zJd`@&|BiO!)AV-uJRSsqj-(KuQk&6npf}<_BIBba{($gQp5W|HbcMb(S)@{fvb;PI zEu->etfLcXvR^>l8P66vXbQXokliG~V0=*HRfs9X+I0fO!yU5vJ?21b$isWffM&7w-8F`ms9i2my z{VTtfPBrDpM)5KqUlBAJ)MiB<)2*^zll@%tKLRS0l50eG4sPcV4WcH)$%fE3?3EK3@Zx<*OXZ$dqT9CKp?& zoli3gy^`8t^d|BrrS=W#L^VI{My=`XP}>OtKub}GTB*(GW6&G%hsgMSgeP>QGwpK4 zD2gJXvd_}>GqjA*(O5^H(p0`DE3-go5+E#HHFrqY@X0!TNHfx~N~R)u6HSyd^$B$X zTak8S%Jg=a`UVI9>D!lLN@_Fe_*6`N#T=fb@y<#gu*7I}iXure&9ZI)!!wfBz&h$r zQ<)^01(Nbv7tjP582YAgG^UcFZ_%4*mXx7>)bX`WyD?;XI}GVtngH2HJ1M0$qhX*o z;(3wrP=u#xU1$0|S2QS!G|4{8r3KM4n%2WQT7ahVonV;-nvwuvxum&Ub4efAj5Ms0 zsSW5&G*Qab{L~3-L)wig)7xQcDhL2=Orczo+Kd(hy%8^h^d$L@cjmsyStLoOSuQP! zmXV}ScScLlWOvft>EDla?kuy6qySM!pv^T{hLyBzL2n}Hl$OP*6UCOa8!e`{LyNvE z3D9m7qD3lyzXNpseg`s+C03HCAv{a_VHJ%*cR_BWAZ48esbMae^z4(yJJxsr&OOqj z`{+Up&6OYa&OT{A9$WnT4KrK7rneLDAs_&>zXGL{+KjqEZ^U(E{7s3~5kA&KF)%`0 ztlKMA8HQN%I|mK6h$IrB1kL@%wI)r>s&7|X7^b%q#t9$*bfny;l-i8G2YMr32N_S2 z_^!lS2p`LdSe4gJ)*+Te!?JbL_^?3Px(P8fSFT&`p~Xq0Y@GH!3QjJY?kj`pIdyyeL(FqZ@f%Q!g@=t|^C&_c$&DPVzAlJ^$9 ziKb1-+mkx6zD>K4XL>v2eE|YMA5w@ssm?>@6_M zxc$*G-UiG=c)zE~_GoU8wUdrFGRt@i5W-0`cPmqa1*Kt?Obwwo(L^azzoSlILuogr zOmBy&r9l8_VG1!Nl?Q1-=RsOze2ByhiGvWHvt`_%WnHlxMUgX&%yR7yXc=cCu#S$T zseD|MS;kogq4N=W;TAy;J^>jYC-Fywr+B=xU(FS(QxqxIAQyJW&7ILH zXc@)d#5y{erZUAc3lu|u?j+f>?irf+l`nx``P2t~yEV7wspNM}dJ}n;@_Q0>==e76 z#;@t^@Vg-h08OG0zfzmgg`hX$^O5nn5@$=CiSQ&(c7vw4Vk3$o$r_pE@x^Ev$(vvu zT|`rvWSIq$lMF(6EH46?iQEZ1%dOB=na8oyPox`{%5No__y0=*Ic02xn}_&&mWcsmBsTIlWwIYSQ0FgTcKL|7w@w7)DW zuibh4<}@+W9RQi@ruWJ94u|keKXd&_3b`(ouSoI{V!k4Y^bS8yb95pjpp>V5dJd7t z>JJ`^(szb+FGd?MFJfKlwSH%)KH&38cn=xw=2DV3+xK<6nnr1$Ux znxiWneMgM!=s8+0s=q+r5hJ@tIKMu^-uyDX9lyQ+0e?P~Ka^6NQJ4AEnfdiK&Cx9- zztms$Yn1b=U;2@2)7$ZD2nhHy(0}AwYBP$=uhE%bLurogGrta8q@(9p(WUO0(~r{z ze8fmr@xVp8S9D&@gSD|`dOKb%2m)RWlQ)!7o6%VFYE0(ULNrJJE_$Wzl2!9UAOKXCgOpO6(FUM5 z;`Na6_YmIEwdjlPC^{;e%uyMa9Nmy6aKFP?bJX-cIl7TUIESMpKxgalOevMaG<_VV zk>1C1oz(@dxR4_HF}M zZYiZUqv__{fynp(iQh~7R^rzZ`yo7;w_zV0jGm}byyblzFJE<)uJ>sZnW@30;z8IZ zf+N|+^Iv9nl+F6BXl9x^XLe`VtUrl;_AuKPZXlyNdbVDe`Q$a)G-dac@zgZMUFER@ zE5|DAma%u2#}2L>E8beh>P28nqZ#z}?c;%d^Zy700(g%?X(Y87%>=CxUx19ylQ>7> zEQvEDPDA*W`WXA@V)R7avs|f9FiSKnO~Vehj_0}(!Szmmpo=Aaz?SsWvf(_oR@#Gf zr0e-v*=F8Wo6pNO!&W?G`iH|3#dOHp`;O44>~xv~ImK9)@0o!sw5o<;z8ipq(vyF%SSs-=>wy=i)%)b8*P{dWpYF z{7vE-iN8u*De)JC&z!~Ov-DC*$gYqQOOy>=N+d$fZN5vE&9bcMpJvWdWgDLr@;sLn zdPTV4n$HRaql5@qu}s;}B|xIk$=LM%i{A7t;Q4hFSA}(4u59SnQG6EGad`}F9dD+m zt#w=j1lDmJg|>UCeC7=_KJ$i*AC$OX;$DfnCGM2CUE-e-w;=ptev{#r1veZtWI#vH z4%$%)22!W-KM|TDV@4*@OWTX=3fs#jWkbKc2sO98Y+5$U z2pf{VBBO z_Of);@s-y6D=_0P5Pm@qbfyQ%H0qCuJ7Xch$=!ocYXcCWqu(0psHr27FV&r+kRj+uKPpv$UsxMAEw&z#P z;`{W@G>_q7m|NXNQES5yp~J7PIuaRcRzG`JeGwX%M(<4<4+Vexn)*SrTGMgRW2cM@ zioR~mT@$P=>cxsRAEVoR?K(6O@r+S=^c(0eNJS$KUBWm^2GiR~hFd@Y=q3s!gVbiU z4Csw`DP+6^!Y9M6&h$=-M195e?xF>NyXeiY_p&tCe4k!?j+Ub#9bQ=PdxfiBF|TQ> z)_WA~%yM7VEWYmn;pE5Xxy-k_b+fImVjHye!j<6Dz_7Am6%-uaJjjf;3Z}QS3U7e` z&>Iw51*yD+2|90KBI9WiO^MAUHbMABc^lJcb#!-^?-!gM*+!^8NMR>&8c!H2bKTN0 z4Uq^-hXgxYvrWR91C z_;2BMKx?8&=9(8dQ{v@kG!e#7d^285?-MUy+s}}DY|?Lc_}HTv_4`^}d=0LPd(T95 z?&SI#EWOj;?+Dc>6p~(UngmcHf9HGD7JySG3nIlnBP>433H`J zzco6B;$k~=9I#2h-$Ta%&1ilQ8u5I{cpijLP;Ebn^kV#p?D8D+99TB=JfwzMm<8^7 zrfb|Rj54Nz3u)9nGpfh68XkW8hf-+smfDOK2CWe3pXi4L2ab&zG!V`B4eTfPuq*(jT$zUW$6nHyo$of-4RWBQw&9L?2&0 zuDo6;a^U8K@;IO9`3Q1r7RH@eJub}W7pP^vlNe_^P+ei{hydn$!iOlmV40a_zo4jC_l@GEvYebGpCJLbHzbVtWTT}`dV8P@gJmIJd-=`0o;v^)wdyZ#-oCsMB6S}D2%9)yOgdpuc(C>s; zM44Rkrf22Hb4EXXSa+ub(N9rEkm+q4&3ZoWX9Veq0TCp%8QlVUBfbggx%q_VM0)S^ zue$hw;jCetZyq#i{r-{@`-d*KKh&LFU6I+Dw=9MYREIl*!}(jC^LnVjoHxBs&e!79R;=0tk$^uz6hek*Q*KQfvy4jQXpa}FBYJ(ykq zp2N~SGQAy-UIYQ47bxVBR4(((qoJ8cFVP$gLpNv1DIDopU@_d3k=!JM4j(mz3_576 zuKHefu&+=g8mIPj7jX30=wHxb)Jyc7g_Zv4@)m@H4dH(@6oxM8ZosolU#B-;U&V$) zCivO}I;^>SA=KIeh|u9L+SHNA7+cwQcGVX|Q`oPSC+p-?;F7VabvUOVlU{*qH!*i{ zyxv#ng<9i$1B|HyyA@PTF5A`MTX|rY74y+YG;hni|I~gp)7#04_do#XT?)}7l@E7; z&WF2@K2P7LIa&<8B`ai9krf{}*pD=>v?mcvnj{NB?&+P{CHvizGU{UnB`L1bre?nk zjZ06lEdxf^mj@@wgY``0mmK{A^wP0_>i2B+NFAMKtMERdw;3(Y$XpsNjapk05juPt zsUwjwDKtBWy6Q_|1pKGK&o0~w4^#Y*cte$VIByqZ?64-w~78Cv-2%R5eWszRu>YEA-H)b8{m^mVH|y zBc$@&dK6fVcqGzuq0?$6JC-6*a~A!G`dCfeuSciO;bs_*l#Zsk=KFM-YhQcQjhl%* zS1ek9(G~0=)6a^u@Ixf*@tScL3_o+U-JzLwNXI_8S@Y(D`({3miZZ8g#I7H;)~6!P+SVO67S=up05o zNT0#yY3_08V!eai3-1Gyj9ZOAXW{ERCx;$AcvIceKlH3p8Z#jDcr?5?-#K-GD=yUJ zt7~$mz5%uq&GhPpc<+r9)X|DOcONBPxqxA@TB8UNreaH=+ zQ*{2{uYWq#<(f)EXjRXE(e;UFVT_TI<)b(WH%GS`QJ++{%_ZX;ZF8GN$#%kb!L7A` z=Tf&YVAP2E+H>njmF=t?88yN=BJi4Mf&afim<|wEh-+IpSR0tqd8JxKXQD28>~{{# zucCt!7{!Hlwaiwdu4V73xU^j%yR_snkXx0_KJwS5}Sc$OIkS5DIZk}uD zYe^R0rfJPGfo=jT zOp%&}7^3@!xZeuyUSxJ=;|^MQ4}Ykcw9k~4(Rh?=;XUN*&9lW114RfM%DQ;Ppj&1; zNVcO4zvk_4#h#NdMPE~3cJg&w9>nc2wHE;XcA4wS$n?IS1l`McVD6z1g;JYQ1MG(1 zS>8uWZ9^KG(FL4$Ma$K>SpL(Pj-F*Waz}U2VY#CV0hPWe)$= z%He-7b1`ecEI8CS4^M$Dx|w zaC6_&F+r5o6c-#mBo4Qu-i)?KmO1=KD~Hcu=3>@>0*80mwtN@cvc~n!xNB_vS!}wu zMbuP_D)648eeUs|4|u9o;l0o&ycYwWYL3H~d@aYJn&5C7-_tQcl+_d$96l@#e@>l0 zZY92o``wr6u=vvbu53ixg#Xf`8FTwxH8IameNV>(!Pb6;V4g=b&#u&)(Qe2j1@imd z!&}$t70evB8c;J>1dly7awWtE`Rodqs@3nxZS{2^weZqSq z;Hl=;>P=tEt(BVKaA)7sF+r5o6c-#mDh~Ii-i-D^mN`7CmBY6%b1`c`fy0N4!-wEd z<9cU2G`9XWHr;z6YN|yQc<<0Y_jvCHJk_f3-s=6jqO zYKjXE9}|baq27$XExwBR{vjO}Uz+c-v3&oCo>IQ6iFtnIdpafvw)SfT^E|G3eows_ z?T<`SAfNA(TG#4h%pA8GP*|&nZLJ<=tu&4Y`o`8j!KV9nh?;6q1>UE$&pqB}0Z+9m zywCfD_eH={P0V%Fzx1`-TB!*Rf1BD&5M?#R1&2?H!x_|@(HgC5^%Wh3wUUjk)z|ct z)=EvxbCB=pm>}5NbOiG}rFjme-i&&YNeblGs=IZqIu1>_)quiUJ!Wh57;B|*y)zyg zTknT;_aPY8REsL``u7QMK)_S23U6Sa@CF4u)!cS9*w=Dvr6xE$m_TejCy26|;(|l{ zB3C<#I&U7fuGJ8R7S>8OwpK&^Jh|hDnwaOvFppqse{l2YC%)PrZJuM1NeblGYQxsG znkSt*(SX8QJ!xz8Bx|K{y)&L1Tb~!}?qe{lsTNh>&DSTqVF6FID!lpogttJzQ_Zc_ zg1(kpD>cF4(FEewN|e?MpPEz~OVo;d5}PalJF1 z8(Uu(>+U~cSW_*kz#HBtyhQ?@YE^iP_6cvXfTx<{aB*MDai}IZJdr>ghoY>exZv=4 zad;+mzOaofbNHiH4wnGVai{?W4qrA7Uxq`C>z(oP*!q%Kcb|b_O|_^3Z>c`vEgkSw ztHN8RPk74)Jk=bB%lTT4Lp8zS=>+096lFEV1&8|WymkR~J~x9bbGU6Qhsy)!IMjdw zhp!ojufd_l_0D*0Y<&dQ-REOiQ!T2%>+BQW$bhF>6<$}L@J0nZ)f|VTeJ#hKn&9v} z0&yIQvYO(8Lp^s;yNr4>`ZKc3;f}2wt^l0lPy-4aUOqGZ3mg7)OaCjwwI1WWwO>x2D%i{1l>dokSWSPTXwQ{%`aE?O_C~)|$ariDAYFzJ( zcgNOO$GZFP7}ivaD)83m6W%uio@!Ni-|7=yJ>aS4IGo^XIS$nXhY5i=4n0z*Eg}IMLT~9I6QpZz2%Kp(v{r%Mjgj)L^f{FFyQf))(&2a5s-&|^Ongg}q;Xd_byRlQ6Mx&T!eF`^? zP-`0?nC5LwvoZB%xNNO&A+TTA>H;eR?Ah-o4l z|He?q$od{=LK&nk+1o4f=|zrzv~^1!LsT4tdFItW+C}g;*Ah*oUv$yZUx`&6tD7S3 zIoH8?T%Kg&=Q)Y5N7VOZEE5mGz&Nta@MkpEe9usNAWo?X=wyAylP=7-W~SDw6txEne(xdIM~nw%G=-R!+KhU@YQ*~^ zS`RO!wcD6&T@rbsjFy(B zbQj+B1iN{mX71OH4PCeJrlCR@rQ<1MJCX+LVzz6 z`7fkH$P*SRbvVB;XELi&VJ*2Zs~yQ%&du-0ru~IE8x&iPXl@q9&pAn<&;7!zE%P+a z(!#7mU&X?#OEXI$BH&>?0i@U5j_RFtWYKO{-akDyn&IMLzpnehh3)c{vAH!#-N@5@ z4r4|4229gR_isAu=Xrw-Wzas?=Q|&7=$sp_@n;Itbsxtd>KjofQ{@TT#?l6KpNm#Y zz2Bt5xeNL@G*V6M<@*bgMa{ej&P6W140xlAZ0**rm+GZmu&IydQvmq?e|mXY-;P`91E? z^bBDO+011MhS+tYkcNk=b4852&`+2SQoIw}DB#rj z!cKELdTKr`x0lu2?+JF0dAbRf_5?f9SFtD9iDo-vuw8cFPkMJ@JY4>qLYzu%M%O}3 zBfc6LUnOyc#N`r~NnC>PrwzNh;k&uw7p~ZYBGJI?wBczq;P+IIFxowBkS8st4IX@N zfy>MMX~Q$<3T&TZXFiF;Y3{zVsW@#&F>P0x24<%XX(;*K<+FaqUxL=UyY#2I&tZgD z`^l!b5zi@wJ+GzH+~>iwtvz?zpk>ORHuyPzqbMtKvLY6pHvCrRX8(?X#2phXGwS)4W;NPpF7gN6_wrwxGs_8x_FaGtnDN*&HG%nLv$ z6%LXMvpQ{f(YZNYHtjD=cG@sE3*+ZJM4`|9!W=5|G|tk(^wL+cFo)61Qiuq6I8p$! z-)CRvSf9mdL$)TV8+p24@^l}?G_7=V+VEd*aI_5C=Q=xWmap`A^Y|MY6Lks&Lvccd?xcUdrZa)JZQ*#Y|@u-N^58!)e2rvYE>iOw!nO+K`6F z*3N0e>=W^S&Y!cGs}y-(_O;pR!dwtj^K+h;=b-+oUxV}I$^9td#xS=v?%}o6ecb(^ z;XO-r@J9rQQ)yw9*@wt|hkNXd@dC{!WHq>bLTCLoKkbE@_8}BQP0!qBs`X!L%A1>QoOe-sUsX8B_Gd%poMaS@to{e>VCn#3soMASVdw&`ItYHRcJz+?eEeC z)Rb&!*9#@xTHJZ6KT6+7PtETK{~@cnKlI!r^K_r@(k$Hix|u!~dSSL(XtwiIo4xPn zsdq7+aPFkg+DmOl9};6DejgdXi}2^9cRSO2Tyd`}?sLWcu6U3l(KvsG+OMnL(QAA? zM2~GBlgO&>k1(nEo`2KRj6SC&-G26+-2IgdzCienwfE&!TR!4iQ%53Wv~;Gf`j<4w z{f9Mw`oy}s5!!IuzT4arg@Xgy2SguJx9!HgBiLE*=UnBAvh1qwd6b?=5Q)Zuk#5%i zp@Vl0B?ELh1DvlXWqetf+uWeVl-|b$7c}7lZh#Fjeji?WB6Fu8Hk?(n4 z6MdqIChF!fOmy_@uZ<{0^0UyP4e14$m)`EqS?c9wtX`BW0%cUl653y)-BMgx)5gu) z*BB4Xs}#x+sm*98AvWT{$ao;ar}*p6^bJ?M>58{)z%ZhN-USA8Km<fsZy>3l6L>YFhRjoyj>~Zt{9y{1>ZW;Z#C%s!?SXDi4r0GYye-h@!%gS`qGig zFRi`ZnRl@aDT8PkfNWEUmQ8Eoq544fQ8|?ix7NR=rJOp5#?RC^uvI)jnA?C@jFqXr5uEY2eL(Mk+VW-(f+s{H#xV8@SQD>&XhcsZX2t|&Lo z{v!S0UR}H88f_4J_2^k4(y4E#K&Q5J{c)B=IOw)Lb=K=>Vq|{vt?W`U z(u$(*YzwvO&VTuR-&yceeE-*d-hY|)JYl2OU-!_u^KQB4dfB<6XE{&Lhk1IIr;#-m z67+QT)u}}}h;gDgi@4KwZuR$r`14ZCVaww_i1ba=+UkhV;g7)TNMsD@vqH9)d%Nmu zV5Ma1>Z{+m4Ktl8>>k`7gUN@KKc0KXf_2sLJpKOlDBPOOHqb^Q+VWR>9M0-&^ZG8# z%=P9q8b3KIYcXn~)Q-yfTTOG`=Ejtncb}lb&x7rWlZhOP4qR#6%o#l^xnD1Pbc!B! z1gvm+9K&0_6~mf;t@qH~M_Bz1zb5~5k1noP*M;$bkE^};cxCC-v zj?f>DN@T2`4EfKt-9!UTe-_F}v5oe7G}e62w=}^9G}r|GV+343aZ$PlS9AMDkI$F8 z`bvruGdgI}od*+IJ@7qi%7gW>iY^%Dr<%d)8gCq$(hI!+T$)sW zpsF^7;fXM^nLFznp=+Vm!h#ps(G;WqklJRbwM`JA!|ylLk;oWZ`;=Rv#YG9*YR_3* z>FQs44!&gR9K5T(DMK{%cNE*}^SPWoR8i^UaC1h~uHrEAeR}UosV`yrq5f-G{@Je( zw#x+zaYTKZci_8npme>Ot`GzK_3A{d(s@FL;Ql1qrw*`lW823_hp7p5?w+rBge9LO!cg>P>~SH4ydzEVckX zZq;+7_HVPmg~bQDwAEd0cXYq6@!M$pAnwpGK777#7UShe&M)BdA2)0YZd-+=fP4MN zBh&Ntsp{R1j%-tcs~^ysh$J?XHFuyCfj^|N_Hz{KKcY^tIYsyOu4xN(@9gSZQWxM? zmV7^Uja$)3c6f755efR+*lC#T2dpK0)dpH^3Ug>!%@6vC2Cb({2~0YAKGQRT3-EX` zA)o5LX}UEQiK4~8Lh7pTf+qjBp{r?3Scf~^_$i&W9TB0!?>5wt2jb4dP9A7GfWX^c zQ)!(_r~Q8RuziQq{?ai}UneSD>3hOQ)OV9Vo&3E77v{h9^oU2*L2&kYWWhNMMZE)F zmQOYU7WV;u!CcVC)z|K6&G@gpp}nI*R%$C(m3BS4{?tD_tEPRiHM@+*M%B1O2oee-s|yoN?E!KLep#3yWJnr_SC{?4#{0ZBVyz zqb38}JMN+hbLrJNqP`b>B6?T4N<>F{(^o;Re<-yS6d1b+Bi$N5Pq)!u$diFJO@z(v z(gt+zidIWgdoSAlf7WFm2fcSYFd3aS53`SXSd)kNyDwU_;q12A{WNX5b4uAwga4H} zMG32x-<$kelj!JRdzZeS{QZvcc=uZht)A3ov>)>{;(d|+nDBd=qhF)jM|;^9bAi=! zwD#pP(&u_v-mh%u{l}GbA7pyhOF7_}TzG+b8rkw1tW$N=1#u5q*7Va~uJ5%MfHX$8+AZzXN*@>XR zAU*5uePphuR^ay@uCd(1^>cFR@YIoLPA=8t5`q5#Z`hBCYqX=J4X7zs(2ka;v)xM^ zBYSP|ztV`lZ}7)6o{5g5P;N?XMn{5`zE5-lEzwal*m9-&pOa{{hWa1r(#Vr(3EzZB z@82CKr+C&P-{+3rQ+1UfSk~`@VbS?x{9dGsqq=|mb8^iOKb7I^+mFS%mQHsXZNaU6 zsavO;Wlfe$d#0w<94w#hB>&*r&ytlmGrjMeoy&OO&!JG{Qk&5UV5gj&=M2uLh)$tB z+&a9${^02auKPk)%yh-Y6k@dJGR_+&YL}X(@;=&2+E?UtN|8P&Ye^aX33M3r7dV%I zVNKdCWv>H-vFWd$m&)!je&W>W;z?Qw;jkM%VI`rRu+o$Xm(!r&F8e=l>(8@r>v+Y( zztpYE=j_&Ba0{>gf@SxK)N8)s3L4mK5LtJi#QPx`%fEfInra43Q7cX~%Zddf24&z$fe(B%V;dCy2 z#07(Jqa$HtGde9lRptn!@w58{bk@$3h0Jt2gA;ZA?5>VPZD^Wno{{zQ(d5wIf$e*y zoy6!Y)2>rcYMMi)U7xpe&Zl0btA2w_W=wL)c#cjYL>4@xnQrXZWXyjix%pT6iQ`5F zb}ROpZ@5WF%49i}F_li(Rlk}(F>rIMVQQ3mbxYpFEwDHXxq2)8Q-{=q2;gn9K9l;a z*O;^0sx`iyk*D#&;^Hxme@7 z@^;So)T@-uyJb>XW093xV`C@3#@EnK9QQD=Td~)C!+k<3GF6`JevJ_kjDLVSKeifz zn(W8chh(6w0fT?ZV6aOgHQ5iZ|5oF`nl^Gw9zhFJW@#mQ7aUo?7NgLcEnTix>+z^C z&dQtG_kgwcE1o@ST)&a&@$5euwnR^EhT{AK=c$-_SUUPKMkg8#=9-r>EUOX&QY!&CRgG+_3G%>EwCuA7C~lQO$LnB6c7v!`TsV``R&2z-kE5zM2T zLUo~d%}?D-cW5F{k%rshXL#H0`+t)l_Q&W)$eybFVbTiIKjHs0{lJRE_7eP?HuIpfJELK` zvF9Gm?0y)#nvddLMUgtwqd$?PfA**9^_R@+k6}x&@6C0vtr*#bSC}U~g$wfpo4~fC z@QJw$>2ZbRef^*iQp;5JCtXM%&V5KN7_)Ns>A7_GDY@(YAcJO~Mr4H~Wy?D#riDioUnL-Bc0fHCkr*Q!q*`dh|wBhn%ixnMR+bk-3&z8;=gY)jJ zo4zjI{{DoaieQnxNAM{_`x3#Tnqp%nr}Iena~8vTR?9zq3tlCS9q`e=xU=Q7DPx*OuOci&409+ox z{4o?CzVJSbsF>@^YHWX-x^=}ng^_MBAEX=1NLst!015}XsB?7Fq-ZP1e%;yK0v;oa z1}AVOW>&IxsK^}zjnvNiS70kYR+dSLyszo&O9I4Tq^_?*9oGd9`Zv&;(TgZHt;O2? zHfrrnMCjmJl-qtabtE$OYd@%~{uUZIDTMS3=3rhhhKrY;o= zs~}(8^AMA}iWI{SaMhLBI(pvM2|844twgW&X^aC`V!LOW}v~p`<*TU_4%u;(t0i>jR zZq-rZ_Fd=EcjVFBaXSWXFA=vFdu}iGcPLUeHFHh4#^!g0yf)8vm(p^g+ShMQ*GXz$ zOT^m)X<-9jTkV_9ZUa~c3pf@vF!MdM5^4QiPeVvyAF~h)Pprri$EruY2@<5b4uLX$!nA(cNdNT~dY zL8L!GPop^+ir(^zxQr?uj{SiK%tK$28t6tz)$^}E#FB|OrzqIEzt^UO?Uz2bwS{ct zMf!Vh&zM2}+*3b`=nQsvYP)5NZLMIN$K9&McET)ee_Xb;2eIAqrgLvCn|$24X9Lm; zf`te}gl~gwdFQz>eQxJDJgpfIoQZJ!7pukN|F&Sr;0sHP!Sn;X9gXC(=0V#*(+r_L z%LUZ>&3p8j<;bxx7D z+#X#781Pj1gwF{3YvJR$!q7(Z)0Q#g3uEM6dCbok!`=Nw>8ho3 z|6Gw3k6yF%-L9AxXLQBtaO<#ob#`uXv=E%A=-FjCZbbONc4@IM1ZJ6#+U{xr)f5nv z`~pqg9r--iT_*Yk!t$c;JBWQ55B5G3S}Li{XgMZq#LFP#r6iU>_+7;>o&A0k(MXyT z^(mQVwFCH-EW0pjNzHU*DHnh3X84UOe&>qc3nc<2`~Y&5`#IQkbEGJGQuXH`N7TOu z06}v6Fe2U4Gt1Lh^F7mrBWKgpF8(z}Tg-~TKUDe>iS|@ObQ(!W$WqTX;}9PGnz*98`a@8N*TDvrj?@-#B$LO_{5Y7&herq5m z*VLg@py;TLlah$|-sxBI8?}C)@2sz<*78$lsSzjD+0*eYYsT{*8qGVIt5BGG6EsO} zRRpshDIVl``by3$IJC>{Q4==6GEry*Wni84_0ei-2kj4_b+4=jwbF1j4bcedTBF6& zo65(~YHM5g#Z1TZeG(aS!Ib`l;C?Jyfkx8S|5$dcK-HveKzCu!8CFOlJ+7Mmtk0#_ z_=DW5amVF{xmU!@i5?Dm>9OYcyv-oPkrXkz{vh%Ix3Bg?PScQUB6ni+_xy>`xnrVEM-`N$Ov0wIWdv5Y z8V?H%?6UVG6YYaUwqtz4hNvv%>CCD;Jwvk>_waML0#CGO)1{xgM>g4c$o2S?w5Iv_ ztJ&J)YbRGc9-pa+!}jF<{BXCnq^bJLm}k*Tc6%q!K}$pdL8qa1PS}`+kT44v$K&8K z@H=l;eIrb2vQvoXwuV@WVG}H&XRc#7Z?-Xa7p4l%iy4IIHz+cKkXR#N!qHI^OC%Cd~Ggo_qtIDG)_6UD1+sxfw;TneZ zZLbg(xMj~!?3wM};rnh?u4Q)R#^2;cF*o|QcWCx8brMZH%e}+j=_Pf%lh@0+F-#ER z2dbXirMqNmli;;1r=g*+iu~tp~8{c-Xq$)I)LiCk@w}9(x9P9T3nWa;Hk36X6O&-9+V!4zP zsEImZu)NV(oGR{gZq`ABMIy`jbDOii3YZh0=DzL1?B?@>xW!vuI(pP}Dr#?eJtT(w zd8=K{>GYlC`j?D#IcIv`FXtX-JTU*E&}J>Q8SMymBi_uxz=&OxR!a2NX zVAm5A$ZU2bxXn9v9jdwYE3HcZ5g6)S*%DGb-9$2KsD_ z`}OG#&i@ku@e33`KJA+*?>PI_4^i9}cb7gN$U6;0_nsVy2IPnIbnipG<{LiN9KWC; zdP4ViwUiw_cWM$p-hSaKa>Oz7aC`gy6V0Q+`2_rfo9R={r08nC;q#(#HD#UkqZp## zzmQ|4Wesuj*{`Fqt60?PUHF%pyLGv8zUSRZul~N03&-ktW4j%GjDCkmqjfo~TWMco zWt39y7)X)~t|3vQ$o%1D!!hPiaYZ`o z$2x8n!P?fyjzzW~_C1UFp5nvAb}ZE`l<;V!yD#!{;8-?)Uu1Flkms%o zz4zXG?*T#$0p9O#%HHnYo^+=q@BjVl^U>^-r~c-d+1c5>U5yGV7o`%+o&BN2cywDC zVo#18KXrvC#+vO(@p6tm9Y*;$Mzm=kqqGxeRN{mz!?492 zNq1`DY{xh*C??G>2Zn2{AA764ihBdPlf`m!kXP>7O|$K8HW-hV)ck8V#M8W~J9C<> z{KWSgaVOQ-8*{NWbH~%0=U4)cEZo%s;tA1QrzKAhmHqno(XQak{0_=Lr!Or77J|cW zj6S0^OQpZrevBs@JBYk;N-S4R@U#`rf?vV&+kEs4mG^j=EJocaI8(GmXo&!cNBRVD zy61aEw)M#3*-r7YG^wh_31BwAZD&saJHcf~xCT%syzz`Qo1c-EQ>_SlnUR)H(ka?1 z(~sXmpO_@k)|Gv&Za*2^ywWnpaPBYLMdu14Zfu5&-AT=Gajn5+(;73}ITqR#(W(wy zN!rt+zqb=eC0KOEIL|^`_YAiR3R$+D3tK2#V#tTZI9YtaX*>`W&xc5scF9sdr_E!= zl^kvHVfH&CI23s(>9DM-s{Wy3hSKU1(95(mIjKa8rSrKqVGA5ZHr#&YJU^;R44t605R{sP?6N3!H(?2rPg878bG!muJ6}RPkC@PFEnZ{|Unj@# ziyJcB2`Woax>I766Q&_iJvRlS@Dd6s7sv6VqMz+25!lE+W#pvN!PdC-M1E;)a<=r7 z6DXt{9K|!CumcNZ7H2ZQ|K4Cn^BkQ z85?{?upeBTLt`G$u_ukedW1ZEV$j68g^?agI;E||^LqUBQw^SNM3o*xiPI4?OzQM% zFLUA$GiS%-*)y}0uR42XE;p*$GusiS8?VHjoNmlNyKav=Le4%7;TC7V7cSa9Gv&s& z&;9^EfbPUW<4Hw6-GEtqx`DKCgOcl%T%+VF61RP}3#8^k#4MtiyDB@7@j@!jaM{9} zA)bPJy{S!?1VKd3pA26cOWkW@Cr`4{bVpi$6U{{l{#qd{#JYd6EkM>}MmJ|*qk`I-N^&uw1*f6?cWUS@7Tk7Cm&<@4~Y z&!ats@%h<|&qV(ob};N|4}00e-W(zg1O3?Aj6bGv^Mm|-)S^-1TkM^21no}?Ur?pV zG3fWE_fQP)B5@7z=As%R5xd_awzL=Trlj%s7pd@HPqh=|Zmg<<+h}Lvu!}$Q&T?~o zs-fT|#kV}-`13EL(oFW+SbO6QA-q-n?_Qi4HoZ$o532DRPtqT0qR@v^kjiJbG-K#B zI@})ck1`aWqYl6RDu6g1$g%G_&1h~dzF=h@RKKhy^ZcIJ;}@pYX@$kk%-NH*>(Ja! z)0laapY7`Dv+p`7bQT>dFM}DFmk5IgD2DfuxP~|k)KIq-dj2qyWMU@nX=BECEQnWe zO>G@7_EoHGZswjW_xEETGs27aBRrG_b>?A>c0fH2v=(1NJkqECccja!2}_t>a)J*> z@RWfibxA0H%tI|lNLzaCPN`-zV`f`%sQc{>C$W7kzQR2osGxH#`=JCgF6+s zcKoT=@%LDRQ7DWk9j8+Nk|yR*)2_K&c>I`HlS9s_b7#F%WY+%axC!uMpe&zYe~-0L@lhBp2hFA^_Dq6q9( z_Gh$H`8O)LhbOI+u94DzkQbf(^*6QGQd69+^5x<`9vV`<>O zUeZ<^fMWH~9g?LXj(q~We`|3j*K}8oCq96U^+i?KiOyS`v_m^k^MdxX79jq@0kzF5P~udm%qhLGnaJ8;qW^W z5O_d%w1ny@N)J)W#+4@Ic&1DB#$cH2`70Gy`MvQlqWj=WU5PNdt{TJWIl_L` zjIl0Sjh@G|)}(pVr+IJX&-oQhcbnT}@OOza4|gA7ynmu+eJLA<+PHZV7TIBc`=X&1 zx|XMCA-?1bt~}%D>@J}jlFy1nYYS6ud~NX}{K$TugWyuJ91KTcS7A2N!YoRLk+{Xg zOP2m+4#8YhS1l&Qkh7S06^2L*eif1KhZHUgK_+8flaln(A!wz|4x2Wg6L#83WU(Ma z(~pW~v%NSkymIk9)TRWu zbDEPK&k8-A(?%joMt5J#xSc4A6l8XS$xF8CLY%8I5CL`fZU$`t9>97P*s<2YX5Gn%dtH(w|?zYKEJ6 zX32H=Kd3a5uPHY^`F;yOvcKV=h*hy1jKQO>!f4V$K;q`z?=0!}9D)MXcIa#SRcq%T zRK1w0wKu}qkh>i>QcT|Mh}+d`wBJ;#UC(#XesisMefWg-TQcpK@uSRGoO)}VzP zTocUD{l*z%%sGYJ>toEhkKCEqyyJCIn%B8(jwFikk_30kx(mM5pYKU|?q@-J){o9LsQc@>w0B7)f z^NSdULH8Hg?$e^9!s<>g>bW-Sjn3ladg!_0{g9w(Lo}77HgpvmNwq@71ihdBH>P&V z;F8=3MLlH@yqi#2u>uE179XIEmQ5wqj3!sHnfM2Wf`{g<#MslvjX|Tk+io)OObQQ* zcf#m9j_!+>J9Dy&VPAJGGXKcygHQ{`t+B?9KnNM4b4?9nGl)Q9k_vpzZ4SKRO^1Ua2AWvUOHJ z6{{sl7BnPrj7M6TCmxA-oMI%I2$F66+lr4-3RN#_{w$9&SoVB-HtrU?IsF8J8VQqq zQ0T$kmprqE48oNCR2moxI!b?)VM~*DP}?2)162E4R~tVOb0D=I?FswSD$ly944<^Z z4kBOL&*rD9{wbTEMm`WansVYnW-b+wa0snEc993xbis0nso6_|d+eo-r>!}MqEMzE zCbQC=l0p3$7;;c|FWE?+m$Vv~UM(?rwm}((BZJ;@wjL>olMxZ^GL?v%_W2l2ua9zi z{Uh@;eKx#%zNUU=zA{5X{+pik8Mey^?OJimIqp$&?Qj%oBB@b@Imi9lR(y`Ka5`Ex z`0iWVciS-%}Xg)V-#z0pj5mtN}%Sf0@-3=w2`^cpf5h2e-vGPBPlC_B^wNvM5xBpn(_a`Hx9AAh2V0O372%It$<)_d(Wk@K6lunhtHOb44I(z6_ zmT_<-k0&rzSa)@Qol!nx44+uhuM=L)2O)ni@Lc&gI8->{<9R9#3N=P3olnWnd>4=p zU$gZWl8>j?`2Oid3F?bQ{ko;Tlsv*NAFtEo1W&EfY1rMiF|y%J`4h( zce4oIqd@)^@*HlphloSzP}_2wsNT2v+sS9IZe{lj>dV2h5Xl6-L8LvABPoW5lemU> z(?Si=L=s&W`w8j@$};K>sra3^GC#?ebmS7qd*!S>72ol%^Y27PBpE(pT8kf0Li$~_ zl%y(j757M;Y^nNnzT=nR>dQ`hJ%2CET$aekP(saR$^BBE_3lGj<=q2Pm!6$6@%a&y zIq{i44?T!DADj=#m_ea5q397QT9JI!y*h6iN|F!$JO+c05OpH2zx){XNHYBGyE+~M zYHl-Z^7V080gqwYVUBMlAzQrISmF02%WI!$tPWH8BzQMR@wZbB&+9xiR^FM7eXAMQ-&_4bOte969?K$B41wQ z*>^ig29eWK^t>!H#GD)kpHh|~uVig(Eq?BrUX#)uW9SPgb7H9GZ2JZheIL9@o>QBY zVaD4zx?WuqgC? z|CpoE`(07-FT)v%6NNviG&odmQ2Mt@gF?j~rC%w z_U=lHbW2Y1?#lO0b$=;IkEGy7+K^HAWykxaM0W{*iIH*c8CzP@&gHcyP zo$vSwxhx@sl=4xUk5FZ z%G7=6*5Uw+(HyaYtQjY3_Ubn4ERoTEU}DDH=(9O;^=)eXEsJD~J^( zUNThCG0?iX5|y=vt0P%Od|q%YAQsz2Grchr@Q|K#zpoJStMSezh<9Q7l%3awP4Q}@K&AP;pJm))JAq z{7S`$+3Vj1DnAsr75_kA(rP=aMI+-O=p__8I zlh~QeE=RZtJm!~s+pDsFX#>jPhN^6VYey<84|v%}&`Q^cYuhS5z7qI%l8@&L|P(uqG3f^sl7B^Rzt zRHasm;X)*FL-W!M+DV}rA`w#(J-@nMm=cPPk|Oi@HrE^nX6hPAXSmVqNcH&nHfate z*SrMwXi3ibeA_Xq&-r}Yv8vDceA{uV@A30($3p>hf(DOH6n(E>Vyo_qlf>6chn*}< z`f-q=qz|7&Jq>Zd~xjH6t2r$I#Vic!nMspkIl znJ}n>({F3k#f0g1-BmXk^R~uFlKn#`P?cp*Gkz}&$H*W}uJex*B=IxIoixZ*5zRTef`u5vVMJhwra)so{oEYwj z>^$m^L3O2mX%~ONsbfXRdZSI*77tl}E~&gAe+AhffmnB?v(CN-*;uD$TCS!gDE$}I zG9M?uN^z~^PLNy)&-n-iTbxaiI+09!Jw*0v+xFK5*ExUEu^xxG$wbnQaI=}(3;w-f z$|zt=DB99v;(F|e6pmG6;zqSN3rrN5Q?rl<^qXijW5bji9~%+;h`EJ>#)gXJU=p&s z3agP8ey3y=B`cA*VR0K&!5WmShJ}c7hQ;5lmfP*&4i1q7SK^ha$9~u0PPIvM6%2Q) zMM`@Q{^ACZ;mtj;Xe8NKolIruTJEEz9IQo++U=fFX^IHeC9w_h?~FnjRNkEqnYB)~&KW)`ZCQJL;CC?6N4PMAhc<3au`{D^sm zgZf3qa`^svs9pPLMxuwY*>tFL8*(?!(o-O*r*S zRi1a-5eiIt8S0ALTT>ajmRD&Bwxhy~N_%PixbF4Hat#tD6rN+*mJ+##+H2@EJ5L$I ziF}=nUz4TMK-r9j_To;IXn8%+V*1e-<{ICWVM5OHn!8SsYRo+BB&KSU6YpE_OI?o> zuajmbUb*PzOlDAZS9%70Th=;;$~-BPapfHvO$eEC<014u{D^svgF;Bfa_~oFcNKOf zbuse+)xn=ASH+BorXTzyZ<0^ll%>m(k9d42{#_xBYS4>a`_KQ@w%$ql-O5R3L+@i4 zBPsULX17=&IwXF;aCe$z`zKP8xwE$iZP>y_)1E%3zR}r_-*3yJ^e<07MTQ9M@3(iM z9l@Vb$qHI}D1FhRNe9g_9yg|q<2%j=kF!zTq75JKH^u-K8thk{P4qc6|BxEZ2BzG2 zSAGpY_I<@caHv=g_QvS0!d|4VEB{G#urKARt`t%4UHL8ik@$0afQH$!yk#?`<_4d9R!6;`B~($q3WFf3oQx)djdZY63Bn0k}wlDrRISN<{C=Jpl_^7bm;UFftB!HkvbzYe<6&=QMzR6$;+0~2V@%6J_w0-&spp1UGA91Kt7d&0k1<2ho_N%`A86GNX48if^`Ge zEa&Ze2N&^ulM<9VH6Ap_s$h1|SSI4`vGzRFt$QX8V(oN9zRNO`vhV9fWvL>d63^#Y zA4Yrq!KnBZf-ksH#t1`nO*MA3gig?ygbEYqCs_q!sW)+M%8keQV(^1?frH{)#d2^l zvbzcwk`~TaaxRGr_QfFxE~T6b_VKE{jM}PT7i;gqz6AW?6_A!ANy!AeYyhgdg8do^ z$p(8LWu}9@aUq#tmuUq1P_sM za^tbT9Q;7v$w9HNVmY`8*G?2oDu`qc*a;3b&CKZY6PebkP>vLAeSK z5%rG8@8FNbUjvUV!Q%);>ESL)2P;Zt1uFn^CF}L%;V}a`99R`41`kti9FIxxBW86D z!b8P!a5u8M3U`t^C?-=K+)KF%3K8`V#TxKO;;#Y44nR?TmE>jTxqlDBuy;)a8~07Q zarf7SA2I82kozi@gNKmaRd|53a36`opiGP;Mk&6BeX{+de((2!cN~7PD8`r-n{OH|8dO50C4xTgmpOOBjD*890t)l;V ztABGUjeb*ZT>n<^qjyW`<)~sgc**F0p`voyeFUz*Z zZP^}v#B3)UII36UFw)~#z;C0HuJ(b6jkJVyIMAHxcrJZp7h2H>i2itsZ zdPgx#(L+qe_@Nr);#a#iI&@d#sBtj8FxotWuoLv*TM(2}WV({hEPCG)QFa}W6rJEn zpXaNIGUsuL<~;re2YViuLE)PuAoB-#w71rJ#-mTPPnULH-SJ1LOn1B{uEx7#SNIY0 zCl2Zk70bbg$m%M*PwGbHZd3;!Q;t+A?wwSh#~tMiTVsdTyg_#FT~-_-je#I+>z)SQ|qq8 z%`rO59PQ3BGb;d7cB+T%29#gy?}Ow>lKq;8A74;H=e~&+GZB77edrqZmqEW*sKdv{ zW#_OnMjGzH>1})|H^09h+NNx?lK$F39XD;n>|BG7m^~8GmC>U2b&iL~tliX^={>sx zut75H?uV?y>I11Xv1rPT$Kq7@5pxg+#iEMk;9F#O75+);V)0XC^S6N}$L6}rZwWl&x$CcSJt7KL<;SX5`GWAQj_sKlbI z1F7SwG_h#PjmP3i@FV6#4vIw;%Rvi>bQPLOT`ZnVbufT(q{?1m@e~mc7O^<`9*d_+ z&JaXY#iBPN6N{&bVW=3gu~?rWnOHm>aX@mWqz;oi5?vz}<9B}K#$?Lg>XwF|tB1<~ZeF2pwvP`-0$hsJQ z#9YKdk)>iem<`!og;_{lWL-jaFr0Fv%3dPtGKeD)`=A<R$RInrrM^O(^V$5BwXW3@;!YZAR=77ii|vNJ>&9ECs_X^A`$ha4=&B%;goYt}5OI;j)q?Q2o*?x0=0P|Pq4@j|3Jip%+&F}f!jG6o zI0!-&d8i)QU4k-t!KQtGdK&=sFUyZYF%$`Cw;4=ff*hG82}p z7wVlrJ)oITp2?o3PIZH^tW&Tm*Dubs#JT>R0!uTYgoaM!t5Tqu&?sy?2H$`mk*{-5 zSg2SI)-W3-U8pOpY;>CG@J-r+)hI`*>?IK267d?)RgGmy@X=ZKboe&nB8jUtmQSmU z<>PdPe5|`do>f=K@`Ss?xPRIR{eOzNz=m6mzlo?D-xp7S2haWNTaS)~|mV>no zrfZ_Xjq4An4%VSuwZImUtTQH7HlIF(AreDnWvSh!6|~z_TAO-weFTj$-IN>t?K zX$}Iz3_r8?;57hwZ8bDrI67s6l%_vkxg|Q}(N{G+`r2Bwl`Qhq|4dQ`sqgT4N4_y^ zp<$CZ==fGu8v7fo+<0d%G01fGZ#c+g70bb{$m%NWLR$C(iR3ZUkL%m)@ji8A;7xeN6ad+k)w*`;230e z6^q0HgZq)) zRk)Y5a5sswd`sH0mOn(Zwfte9>(%lHphVSHh%=U(a^se73qNAEk;NQUq=hd?oaOt{mbLs_ znyuyE`CPA-{}W17?T0vHxhXep`Cs8j%wJ?NM-|J#&&cj7{6t##QOWlt&in%)$eQ1D zv^T%m=Xy2&7eu1(K*SmIO}TONr^1hzgJd>G70baOtmrDtL|W*tq#ubh|6mAYo4u7e zmQrOf6eKkb~TTqIasJTWBS3Tj&AB+U{0#$;0?$ocyx(+>S%Gy0cj*sRaj}Q z#lKOOd2>omdha^8k~SNFSNWV!a&JJF6X|;;bJehAu4-H6$+P8ZXUn`fwp`;a2vWAV zHxgCbGCwk8tbNpfEZjJ`0F^u_EIpJKRH>zO1*Oo#uuzWbYx7m-Tu&f$VF_3Qj$q6TrQ26##CdRHr)qFz%`>0JpsIN1pupYumH&4aCH&@_>BNqn|uX;yC~HOKpX0=$wt6E z@Yev~n$eocuNgh5bT>qyG}QorDTg;7H$DVp(Y2WfkS+jNlY<37{)TIj0KgIgU_J5` z0Pdw!Cjg^ScViZS`{Azvzzt>y+`tgH528?-Y5*{69|4d>H)Q~jE&wQVumH&4a0&?k zEGYmsCSL*I0ZMfOFdlVL7J!G~uK~cV27p@u;6aE&X{rIhoP7j97TuNsK)L{6Lk<=I z`5SIT0su=1fGx;Z0C<>EodD>`);qEQJPLme0A?5fW&pq=5QWlI1Av+O2!JfQGXsEh z0l;P)ECBL1+?)gemKFfplCJ>p7^OM^n25T&vH&~*e+>XmGyt3k0FOfyN>dF0+WH89 zEV?@bfOG-C)*LJV@;BUu1OR?30Cpr_0pLkWbpo&|>h8}1@HG520JzTpa326X1yLwX zH2|2Wj{wM`2QmOi7XbX8g9SkThC7e|z%l}07xEPVo}pAH0BfV}kt_hu!CwP_hYbJ^ z1HiKoh0;_50Q1SJKAZ()(W4mvqzeH4z`+6_f5ShL06_NT)!1I*q1YkqdJ&^_A zMfhs~@VEisaR7J$qEMP@05EeOBS02CnE^n$0AM!`76ADh?oI*#9Rgq<@)ZDHqEsgU zTjoW;EAZC!3Bj9!TYXI<^iGb&bfY%@jrKtu0v-c4IS@c3C0;CH7_UB*$kiX$yNC04h0GLX? z0>B%T>I7g{)V-Dk;4S!T0Pv~-;8g&46QWR>Y5*`-9|4d>uV(;|E&%A}U;&W7;Xxz- zu&e+$jC=)vw<*;Lz&@yZI}5{j=0dN%g3IOj>suO_ZW6V{T#UCK21`w5tqW2*RrLhJO^Y;;mq-EKQBk2T? zBRE(<EY1ARi;AS3o|3D3rz;fXvZXKoXW^0g+As zIhKP3ME-}zkpRf@0^}6(6+k|rR4*WjM;1TG4wcW4Qv;Cq%}{xtq4Ft2p)}S2WbVEK zlCbQ<%#e{z06B?+1w{UbCzAlkL;-Rp`3fMPQ>qt`#B*PtW&!yJa%uqbi2>vj0Qmx< zP#S9hGN`YBBrN+Z1Bi42$mtv`Ao4#vg9JcU5FqD~uK@BTrFsE5uQFt+CeyExQv;C7 zWcn3Ep)}S2WWK%vlCUg0sY)k+oWsEaBLBm4NdRO;0P(wv9sjg-D+#2F$yY%7hElyC zT~-09YV+kgd+0BKlXK}uMb-Gq@&K)R5F1xfyg7b$V5tO6=^LSPr% z_1Ja0GHzqC^y_-(<_=T~PjOn3F2kglz^)D?eM>{OHII46LC%(hB7EPx2&4YMjt&p`PL=OU*a^v?A%kX2%x*QY$Dwcx<&3(k5(BN)i zPN6!IRh652{Cw{i8#L?Dn7)tUDd|&gLg_xn`l3;_zt2d6>NlcZK6PKku$*XKP)&)6 zm)i|IG}=8|KRaCYPCethT1=cHTdy=n#>U9t28hg|w5gM5@3#JIQQAx-LdldHM`;`Q zk-aqsp`>Ct7-3LqL2aR##6h(!ZNW^GBegW}Yvk?3I1rwy$EIrt0)gbOiMJR3An}j3 zpQO8qZ#f^OU$lFE7@jo0Xurw4IC%#caArq)*qK8(GwkI^-lzlk|FyL^uSBXUl5zea zZ2O%c-Rm=>KQvV4){o<^ee0)khp%HkBudTCCZq>Y3ROjK4s|U}9kMALMuM%oU}QNM z;@j-atJy>_3rXDIZeFP&60viJkHE}INj>-{>-4T1Qf{@e?7w1+XNrA@-&xf}``K+g z-t51E(^i~cT9VN8CSwpXj@A2*^|nL3IAH3*=ZzYF!SHiOnHNC{kCs*=R+jFDiNRoW z1oyOa3tnGOdyG!CL7ry=`M4MYQ*M*--w*iru+T} z57X$|LlDY7RT!&?nFSood&>CC8KIXC{ks0r&o~-FHD#f`GPEEz7+Zsdd`>{P+p!v< z>E{dflC7!duP4mo-1sw1;ldQ~cW?5NV_znzkfm}T5wzO;zK)<0(-7<@f;LOAza#iH z4tfawLM5N6nA?&VTcn-ORJ4mt8vAw11my;FN|$~QBDT;m{Z%4n3WYr_2T+PM9dJj( z#F@hGi*Z$G}L-q&D zow^u`V6%#~X2+ zpC&^k$xLL5=ClUq{?R29jkTIWYY zWd@z&jt%VT#JCL-Ws_ZptxhRa6@9&~Yia6`O<|b?NAAMNaQQV?Sxnv|7)v(wIL1ja<*}= z81oj!fHiBi7r)luyy2d=6lxuRkJQ%(a(80G_XC~Q-gm#a-A>PoDi+KsO&9QN;Uum} zBDv?^il`ve>B^q|I<}>V8TZMI^c92WGI*JvcW-A^uyeG%&k3fS{i4u`eE_i@42x^W z4zMP}vAMPQd#Xa!W5|PWl+^Q~YN>cwmaR$N^oak!@FS}IZ@?zWF>K}mF6psPa`gb) zj@G!xWJYhW?POKd1Dg~3#Q)Yi6CJ*L&8spqL9w&-?x}jb`$PTSIkC@3tnblg-=oj} zZ+#wMuDyHc(LY+BU#!RHUF!GQiGBHh>m3`XJbBoIcYm_py;_fVyVmcW6Z;xsxjHq& zR`aU4I(>smGiJ^8woU|$<~K#D6TK-nes%f|{D^s*gC=hkd4Y#H(eH(Z!X8R?BXKAC zcOeM&p&Y42C-%&F+N|Z6xw9Y3cu$P`!c(YreUJ9r z?4MF^B$=39d!TdQ+R88Wl`4^6WO-SL3Mr%Knhy zGbuYnjWEVL^q*TxzOaXX*uz&G!lNK9N9s|hh~>+5lmbecKNb5rBGEYUyg7sO&zi+JdyHHQ^T)q9XC@Mss;5w!X)49&u5HpYNq8U z8CD-wiQ8EAqIwOjJ`O{UIgQ2!n9}5qTWOH%|CYgxcy0Fz>~&xqu|Lbs>4;4482UFh z{30c4W;UE~g46sfm0?5yE1HMEpFUp)y=;uSZZY9~8&dF#Gx^_r&W{GZfTL+9y|>v9 zbt15vmI53bTBrPKWv`;_yL-+lj zw&D!cW?yRSiTII4<)gG!6UCIlp}6_z?Ao)RKFBjF|w*vDcdJD1ysQlS5GzX=*}B$6XvV=(ZN`q%u?sh7E(d zbdoAmOU10RRPF_fal;Kuv8?+zzPTP;GR&2xaR(u|8xq;pmJol?9R0Z+@#$FVzZ^E% zv}&v0&cn0GE{~?2+FOhFSf#Ve3dKt3D&~+Cqlej{xj*||g0%ZmGkH%P8H=$h%qO#w zryF+_m7R*>t0+xH?mmol!JB2}neJ;X-itko(>Y~}CZzxNJrv*f{)@bo+ahxdtIQl& zywBM)kL>wh%yPvCoQC)27nk(*3f`_uz;e z%X*%_eW;jeLWV!wpyBPtj8JA^)JT;Eg^F-Wqf{Db$DgKxDKk;Ih-_?qzS+Is`!9NN znxBCyz6Mamlg%UWYQQWi)(r3&_x$Ni3w|aF9^>!}jy|2CMx#-qwzAy8jF3w9nPnBF zF)9rx9dAoI#RfXH>K57U9K}6%r_QgsF(%^Bk&YE(|L|7JIF3?7acma=kyaKO3~?M6 zB(|k=BlV$HR|0Nv>dYD>M}wB#tYdrGvRy*vd0l26Yb!p6^h%vuP(3QWYLVV0rB{Jm zm)^wqj=fn*68vRHkC?NfhNw1zeNryMXDFt+u?D zK^_711Z<%L04RDJ?7e>g%S_oFi}4_S_9_ zdu~ZqD>)tDF|1zjEAoHhL&8M8Cz7$vE#vfYGqYjiBh^kQ9Zy>;JF!w>xg5uaHJ-_( zF&ZnEhs(?7^qu*u(IW*DX$qgQN>?B+^D0*r5UePIXDz`>jv%!PUReasS%Os@!N1H- zfmNh-C3n%SV6Oey2Ej|MZCL(f|;>%)&>8*&KWLrgi6HNYS!*l+k) zqE!`1iQk&Z?l%}JGetP=cwqOm-|z_%rN+kE8ksHg`x5-CB zD6@qiIFS`Aj<*o(xio#7dJ`l?lB7{@I=!v-m7!WtzbQ=}9v=bholray9sWDMP&A51@_=)lKF;z`j({tkj4g$Hs_ET~ux24h86VIXN? zfRYvxHwdR%`e}x4DD?(&`*_u?6vLTG0^i_*PYsdkD#14kB`G`vL6MYXqRvrQM_sb7 z5XW}yJd)m55pHUp8yTSlVbY`sTM~Ct`tE$B0AitS?t8Zu z=c9zeBc;$GXX1Vc^`UD#ipD83mE?LT3#SZ#<7id(w>_e;F@xb)Dl3CQGN3$8lo|}C z-1uNP5q`v+z(LrkSPmwd1=fOSD9lgd#=%Ln1+A1Lwe<2v@smY7QpDou{SD&gBAfXn zdoS%2$r*)+suNm$a#;~1@06V?af?V?_8W5ECm537kULEbff%xn=-1E>fAZGe>Ed4$ z{uB=5@zg!7DQxD9-zGMWr%~8_nL2V%i@!^AiKL5#OD9>?-85TM z)2QOIR`;c}s@A^L-`MI}E|;pYP??36-Qj+g-8E6`^>uruOweiGeObkICluu|4L`RT_79tUCULp zfVgy_>Kds^FSWc;=IU;F$dJ#irinf$MfuCFYb9(9bdjPwb4qfq#(~n~MZxuwG!7xb zI{FZu>E@>H{RMA32<00^+316YD_sUdM!4=h@^T?i+`mU6>YJEmL*iB1AiL6Skd4#ryc~;hTc0zqai^~YxJko2?2??i6H~F7YWl%> z-mo|X#!o%;avnh-iM1q_W6%Rs>~wrQ z(gTZ)58pLDq8>GhLn+3L$5d(w6)=>Zq?C7dkxgd8sZ1VEWl6`I;pb;cvmfdoAbSQ{ z!C|lS9URX}vzAt--1yS!1^6-fc@Bzl70bbDW@)uH<`&jeGFi#$ByN%QB2+<{a-nSvCzn#Mz>iz6aR@g; zOgWMpfN0{XwYZr?t16O`T8k1xWyZeaj?XncElM^=qSV+$$-0z6RS_o}xR$04*%WR_ zf{|}vWF*j?dELLV$2zLGg>8M4R!OrHqmkxX-lip3AF4>mW=@XkKe%FpdptS5BMT%+ zGlmJZw&GS29M`FVhiUThG zVS5rc*1w`H*qL&qmR>HXz83MHL@bWJUr_y1a&|>T)fJF80+f?iK;MXAH!)tKkxy2n|3uq6E%Nex`$)aotcj9@aaYf5~ByPtmJVXjGKvnEc! zPhA%c^=8PA$`4B=jIKNaE+8HTk!tL9k%>#!GK*`e*RtY~NK|mlD!u8`QL3jZm+s~| z^k~>};wHb7W|Qna;jky7UPs_Qbl=-L0lno;KwIicKz$=nbe-U?U9cd1C6F9ENsD<3 zKG~GGFhB&C1j)wqpo`U3T^jfiH#^>O*>e=;g&xh}jiGr=BWTXiAe!Chu^`gl|F2M; z)1Vp(RibllN|7Rnpzk=Aqe6Bb0a5Fc?082suZYrd-PL|_*+@(aOB zQ;BENLur|6HQpezsBs?UtzuF(4{_kSzCCQnA-oIW?=pxZMH=5cz?iW|0s%>QPKB_|1E4((9=*%4Y0Bv@pKIn6@*3F>`q4XTO88Sp* z=TNzW;LWMTAt|G@MYS4lRp#DSN@f0(xs`<7j^IdPnAyH_zQ+B~qqA0+(twy4^ne$;-8|jbjP@(>?GpFm1?Wv?cWCm93;7IJu0)2_ab`bz*xw%h${~CSq2)+Ux#7mF@sSj%Dw4`tvxcEEVduEx zXzgj%_!xp*xp{nTA7hFqIhinR8B*Elvj4TJw`8NAx)2kwQnIvv-59FvJ60vip zt-bgPC3WOM2nwE6yMWJ(H^<(BpWUYD{_tDZ$2xo5=@i^GgaUa1+TbLrTjVqhH~&cB|~jH^IeMJ+az&= z>r6F7B1Up55_OHivwzu6i=X$Zdb;Wzgh>C9ay)&WXh?A4K{9Vx95U&Lt<3r0Q`Fii z?=zp1-}&c*qhwos!zq6jPma~z5UR@&vt^qO=#I~E1R#&D2C|p=|9^?|7arXpg|j== zXGTcqTsR&V@ZTuL@oXUJL~LXbx`$*`u|E7%SwPb@&BnV{<5Jib}pPE z7wQ{QJzq=yU%~w=i~FhnPvQP`L%2H^PR9j)KB_rA)YNN!K6-{K2ZTBm5M5`g)W4*2 zc&Ib9d5H8Jxn5^~CEjlCesWwX!L9|^&$;@14`%x?DlDH-C2v+#%;YBe#G3wLAI*Mqua;nS@|KP&ca)$4rZZj^!tm3v(zFTp&`sgY*%?9_^LseqKuG^9Z*riQLnnTxqJMCV%>B^Zc-wf&v zJf_?@Ja@s5`a3xY9u>>MJjm`U%tcz5L&@wU4yd~={k-%8u^&c{6?<=jls5-8g(yaY3qZ=h4Ply+!gMnE%%2x+^*?kHF?_? zh~%4pPo;bu*uS5@U-y|PN7EiGO2W?$&K#wm9h^Bz&VH`O&^IdJ2KR&H>Odq{2O{}G zBxBMfG2uZ45gt?!;g=#Dn@1RvpKIivYvg_dc_h1Jizv+7^-a7D_Ji-+EKOxn?B{8| z%hK5SA`&=s={R~Lo)o^%(pDzOcmVy7rKwDkG0l%znw*L9Us;;UWNB8!-u|Dmbc5nS zy@aRvSv2Y=Sf7wjCSF$ zNRh7yr^Krj{Aj0i#2E8QlOfXUtJO7$N#M8eYJM6j8jXzU2cMhizJVTxVOg3=_YLqg zGiPZk-Pe5(6Sk+DB}-T7!Wj2ivo!tfnK$2+c>UGf;!m)%Wd&3^ZiqKzc4*9$5PNUm zz9pOnYNAO_2*V*}2`f?1zq`6$*_A z^UlVJ{-kft^{Tcf{iF_a(syT*FL=9%IrZzDn?CiAaB}Ubf0Q++ol0}+H|54p{Q>;= zJ&J>dmWt(I1!Q*>mM1NADw&|9gT$Tx7lkTViL$+r<&hd5H*^+{8x&5qfmznBD;9$>k}zqLzn> z%8-X%z|z!}gD$EpAgGvBT3r;YN~&*go5*U2ROc*dX)mruNtXN;LEgwn9pRTq>1DU) zjxP-3WwRL^t;H!$-ZFAQo~4A%&y+R2P$i_hiR%FU=_)fup(qhT!Cc4Mx&8>A5T7}pN;l!OB?l*sc|EO|j`f=UBQf-)3=`Eo!fmEHr%`W!7sa%6fGgFyz<1^LT@FQj|4jK|Fmd!e& ztFS2=3L7igki<<=>sb1A?V-#e*os!;zarWX-1XEln3lwPMTEF>;jmy!a&(D*X=fw_ z8ho_670U+V--7BI3&{NL`wzxh@{VdCO?Jji~ zU@s7RGa!zpUF#6peZIG$Zn3)~HWIG>kv7YLGw&HZr2)J zsoBK0`K5Un*tf_RLI;>ly@u=9Z z4zYXHC3cVxw7=9LviszEqM}9+m=j-emG8C`52#1+L0+-Y-mRBTRC^yf(X&-`;(=Jm z0z*MemmWkZySD`R^oj3ULaC?A0YB9+hCj@S%=6z#CatMYQ_1{^(a0*8RvWS(ur#CLnnH+s!lux3yE_DF)@4`rBs~H zTiKD_8xqC&33d2XU!2D~F(=g_vJ&Te_5zXG`v8$=tAgm1I;`tMoS#;Q*aqYLj5qlKF)hvUfB7sH>!ATb2iEeVZ|rm$(2~I({T4)-V_)vfFaT<(JzoA ztq3{gj4JM9MP8Vs@U8Z8&{13jMfEZ{vwxJ-VV22SV{)Mq?;mNEnqDkl;^f-J@=n&A zOQ|%AWm9f^v3v#m_ zMKZ2(m1p*hrc;F}*UGF@Z2r0&vreU{ZnLgOKqPQ5yyG-0^acqXN&Y`GHFV-`M4WK3 zyB^Moo2U#=xB70DzVw5J!5K8w?Zhn-utIF`G1>#_30vo{x%F8a=UZjsnbyQ8$HcQ5 zG4VD8#5kXAh5lVa`^9GKHbswS*Dy77;%-M=70xrL49~Ip?vTC=&gVAb#GMF;ojA`5 zy-PyVdl!9j!il>ZaaB&-LuGir)pxJ-Wt_O65hw0LK=kivjIL?N7i@$dpG01#TJ!Tr z^MaxDpwzSx57EiR3$01_JDcsSrT`CJ%LA_E?Bw`;kV>{`54X&kc>>Ki%Q8e%C-0j( zHSH+ZuTvvkxt2~17v)BDDk_z0?^M?+H?mXs=*`j+A3EU=OLzvXi=eA}WIciaiT@_) zz@t=}3EPb2#wYA2;m6R&IcS+T;tx(eM{Oh>{`WOS9wc>d^YahLJx()5J}&_o zB%h$kBKf4xNqo-yA37;7N@3DS-A8C@?RXmgs`~CG9*ol8sNz$!hawVQFIAh8;^lh~ zBZFtWML}=2A@OA-%5pnH-wZ32uTaS{M5-yhCd<`3kWILoqbzxy%J5lB@CNzdR`L?m zeL?e3P`22=NlWQ2OZApGp0oM4QyeEGINnKcyep39EysJ}NOyv_$vi^-0%Y}d2*$sU zuqvlNppvoatd-M&oYmNr4We)El*h`objlNrIkAO~>0>e7;z+u`oIif*X2rdDKOvtb znq6N7(Ja!`{k=X5qR0g*Bby!UsUuBJ89wcy{cLV{oHtB6F`pnhl4w=f0BtS4NSU@z zX%nM;Nrd_^bShS zkDK!j48Jq}m28O+95=Y2`bG^m2X_9xjV~%inl%tH8WS!YZFtD>y0|pFu*V1*bbD=A zOv7c)2AY5>^MA+76oLL*Y|(J|PBztgL%}Q1is*Y0rP1x>#{j{ru+|Oi50anx@Qm+0 zQlVglt9F;$32`8FaG}62+uwueroKpJ> zG^g}eDQyWIl>KH#sruUu2)>>78YKbOLQ@vFH)zX2M*I2aR9;Yd(n#ZTf64Zfubhu} z$Oq8EsDB-4p;tGPs6BAzzDhi5;;{F_r>*!Vtk^iP+D1>KA<~;XGmPG<+o#ZJ9V}BF zXy*2%k7-jFReT$XLU4$z^C!U{{KLINt8EV6&9@=v%!b%u2;k}Tci;)Vs+p6j;?GRY zVkkpV!Z#bGxIfbN`(Dyoti5)V`Ghhgnj2@AG&h}@&TTEeXY~xHMKwL%a!hK})SK$6 zX5Coi?vIaXH;zj0%Vno!&i_bDuKZB)acwR{Gei`gl;)w-`v9L8sz`l}<7VyVhTD9S zrAOXPx$(8zLhxh60vr@lDwc!q(9>1;hP3dtk}s8fq2x0qpD6iA$p<8E?Y1zS!4H(< z2RQ6Gu2uCvQ6EW4?qY6)NPZTpdR42WNGd3(qS^GmLL+`)awH6q?CD{hH{^JO?kLI5 zOwW~d-cX3ld?Ot>qak!s0x3+|@78%U!=I{;&sj2#%5WA-P#_=t3jzu1J~^>qTa1=cv!z;G z9JAW|_!P(S369?+IF=B{Y?fn5aisD0Hf5H0bvV5g!m6BJno1^nXRVwLvO)Cy zI_0r)EuHd2OFXfKj%gV&WsGzYm{|$cVPpwm$@t}zH#<5}IYDYP6qe*{$P+p&r$g7& zsU|ae)D=04%tb1T%tN)vlo+jIh_&phayc0wS;8fF4>jPblETqq#q^Y@XZdUlaWbm$hU^t!48((28hn z5v7M*Z_DO+VZ}pP8h&FnO8AdM}d!*OE?^25aUh`)f^M2)Wh%;4i&7S_;BX~`lw zf8CIUPRizz(kpITKqc*Vhs=4gC6#6=V#r1{imSQHt#udpwn)Yx!eFr1|l|(2k`fP`)`$yxd+@ zh}s1Pvq-a-%O`8QQ(i)7_P_;KJnkdmrgax#&jq;v)Ut`v@na()8IR;h0C*XKveF_< zGj}hH2hN~fb3G_74Oh~G=2laM2fN7wZ4v%|?tyH?pgk}s-8)q7Nu`Ns`MOr)5xqbB zh}n;WB3i|Au&en!X%UHq-;%gU{R?ejC*^W*JA2u|LPfTeP95FUy#QZ(4z8xbOnurr zI(77mnFf5ze>+U@;s7F8Vj;=gS(}B;$kzV zPdf+QBc%IU$c*kt3{pzz)X|g2O`mp#ep^I3DH5M@#p|FcEl(0EmnZLf>g$(xLo83Z zJb5=ZB=4qJo^nF2xp=e7*_R&~3{?e&%ej`O4nY>mFEBhn82*lYIarl6i{bs%7~TSz z^;rxl3_lnv3_lng$(xTP#nxJb8a_NZ#$SJmoBgGh8l# zp{l@e71z?#A;?1c1%?L-!!^m5gSAMr7(P;s;T@1!pT&^E@N5%@XA_6wn?CLALh(*$ zN^6jW$^|*{?xMbad3VS1l*^NMPebzVjpZq4F}%;^5*Vrq3@5vmrVc?C$}ceN7KZDQ zF9++BW-)xC8pHb`vp$OrjUfn1v&B_q`rQ655@A7%aiwT zL-HPp~aYVRRxAyxt695K^Dp{Fg#co?nIs!PLO6Xe5)G6S0J-Ki=hUF z;xib&3QcK8l2ExIN8W4H*Dvq&Se|lu^4@4j-kY&Ju+mKnG#gM}A0yCB`U@VJo`m_rQ#dn}7?Mf0V7v#u$m-_nU zy%)<GV34axf;mZzM>@I#kNV5llE{F7^G>JVh1`~t&6gyBBq%fY^+Sqwj^#_%J^ ztj}UdVfcQibISYSq)2?q6+eciv^PnpT#zI06YA@i_h~FoxjcEFH6-uzSe|kg!!KMe zfuX9v@XxNLsY8&3@(Tzg;eY zp{l@enrmt55M-hJ0>kOT@M!Yo;26>@hI(cybFKF?WY%Xf)WFa`YgPOOn$l4up>jcv zykDuWUtZG*UY>G!@|vlyUtYgho^lq$7MDw4s46f#5?e*n)FH@1`2~iD3&WGhmxGf@ zvl#Z{Vm^amf5cfE#ZUu7@tHUr0Cnj^2t(z99Cjcv zyqO!4H%lx}xjcEZHY9JhSe|kg!`WRffuX9v@N^up7z(mbeu3eU!tes}<={fnEQZ6X zF&vIKi=h}&7*?+J#AmMc=773%K7^riL5{pR8A#%IIqhk zFjN&7o`)kALqQhGFEBhx7+y}k9Q=(mi{TvA7|w?{i=h}&7+!AHhnKTH6yNk|mlulj zLtVNI!ce&&N8SPr$y+d%r(B-Ag&LB#a4b(bi(#wFB`{PK7+#7a7DGW6$}ccH1`LC_ z4|VK*`}|mH4Xt@Jp=3#qqtfi2nsVd2rzgUX#uGSb_f*Alum@mw71}V-ZJeG&b+`!S za@?QAMO6%17PFhnkF$#zx6zyIT~0EatnsZGc2v3HBQE?N$-(+9so^LRw4Z{SiihpC z@>D8~ho;=PhiAi&?6Ww?Llw)xKE}f_Xef*(ac-VNTTq}JsilFpZJ#T~#o(!Wd3Fs! zAe8)Y$$8>mT>ROO%z1tok{<^+9|oMcKzzS}FGktE(Bneee33m|EUKkF)vxSATdGTJ z^JVsMxjkGV;$;o7UiUHm;N!*w+fW>*_j_=q!L7kbal2jf{1Go5eanv`ta_y0ruHnv|cr4 zy!|;czH*oS_mHXguakF~JEA+#X6Bz@N1u}!`r*zwBO`@F`t8@(BSQpsG|Nr|-#{gn zN)M%*D3yaE1ryF@1eg8~b?*UYMUk}u-|l-S5JgnX620qHP>ef(s2DM$gL!ovh5<&0 z8JvJ%L`21$bIxhiH9O|4>zZ@Ut~uxYzwfDV>-Mec0l)p9|M~Ud&gptjom8i)tE;QK zH^O0I9DXc-?Fo3ZNP$2XnQ=sZyi$Ba9MRreAP9eM#h(&a_1l+qCHvkG`+jOR+_{4g z0=$!j&5b)`dr^xUuI^;8agS#h57Tcf#7h*1jl8J;`NQnPyt#oQmX_WI6D7&=_Yct< zbsxtLQ>7i@Rj<(FUAGN*dz*f8XD?nkNb|t(&`QNJl!GyIuiOY<7@HlxrAc`!Mgsdt80=e*(3X;I4P&;^WbL#tx=k7g*2C8+f{8D=PkCp3fFl%OR%iNcB1qqkNpVhgpe z3&&ZF8so7vfmFp<>f$aZRx9XAPf%FF)Low&B=5GRc)S1e8A_}yFjXP( zSxV%YBJp{F#)sy+aI2z*op#21&Do~N4e1)oeCU#iHm^d)=n1F_fQ$&vK8xa{F2f~gSI>7b9`l&7|5cL_W67B zbkJ9B4S8u)X^nNtYJBb1kXP!K)>yZ!#y6-@n;%895D@kF*XW&bSlAIi7SMAT0$pSV zucXxAnw@aO*8P^M6CIorOv7$xFV*roY@T)GEF&Q)V-mARVnoKHYMnSoW;*k;AqrJv0;Ao#el%Q?@eU=yfjt#ht=$-1M&lFtc3%X`}UzG9(LlOJD%G( zyNO8YKGnPOc7{e6%7?mCeJU7)fiMO?HeU6@T<#H>gsIX6LJfC&?XM2Ele8;_4aEw+Gy)VoitAoZd>*P;OnD5&(cpLR%7FN-XhrLJR^GWg{J4h(DH zW&E^x$^U_>k&tDt;n9QUsa9KxqRplnbw1!j{yhEISE7|&lzJCk4p~gSLDYI^>vP0I zXTv+BOJ}WHjy{aZqAgiw0i8_KVs*<`Hq^-gpW4{6Kt%}Tato-Vyv>Sl=`2m>zlYjv z`Tv2cWSbqXyjX#v%_g4zS(}N63(#gqu+6k3gBH-qVw;s~3APy(q0NRYppNo3D}F_y zw9RTlZ8r3OpeosB^OYCFDB5fq`k%F#c(?#3VL|#-95E21sg2H>^%T9LrMd0Y)~-Y^*edc_TKpJ@&!2>Y zE(RpA#+B5OSmVM20(&)^_QavQk1{lovYVlP;d0b3-o3dN zM+~!CalHsf1s0*_U#gMJDAp)kh95Y!1w?ewghV3IL5L|&Bxh+~UBC{kxlW1N5~8SD z-H;L08rg+0 zP?A}W7k(t&4fP>rSNvfz^RPC5v?TK_&@kdA{@j2coy`9LL%s{gWywrVPcrY$3b(UD zGMP<<^kl9>{lZO> zQIjC5n9QPDBa^9wfk`BDQc3s8JgDX&rL3`BnMS7mHD!{_#XHPRTE0psI)h;Cg+fUv zIbQgLnvVJq(~Lh%C?3}4PmzRr92!PE#-B&|^DusNR<(dD{}hh9hhzNt3OAmKl9a}Bk?Ey8DvR@n)AfbC@p-6(qfh%Po38^eV8#g)1W_$2uil_PInNq^WJtjAH? zH(;$Tso;6-Tu*4MO|DhBN}M_fg4%b$1uTzXHOD}Z@P-xKOQL8v^A*fsC^DmHP~mI* zz{2B`&TJsTc13{e1aRd)MU9g7U0m+?a^cf{M5Q`w9z;Khf>{MiFlreFub}6D!)kiL zAP7Af9L3Q?;;xZXsqSOc&3}!lf|}Pphqqdgo3n(D5;HjQDa~?lFgs482^524@q9>4 zZ-yDr>D2I#`9}DDTq`odMKk)_cfG9|96d2a?gn5W>!Zv+Lz}OrZeqSh&IE#hQx{a7 zO;x3fyT&M)4Xuywy{fUVFDeLSOeue-)j1UQAsd37KXEqIuG;yhJ)81EMo$fq73w16 zJWBeAG2RY&d)NHRkvHLq-u-3KOr_VnkBYCh`KR5UIC6Gb^u)5t|Yy4d&Gxs?dBfM^%|_Utw0H*l~8F9v&|pAvdWva0&# ziM=T?<_}%zQdU}2Rk{qcj1TheV3lNd!hOke_=acU-NDQmgqY6N>fIwH2J=>kx?ye& zR<8((#65uOzAueonXSpWXz~|1!6J! zcho>&uVjq+s~BBnV|38brmsAWj+p-hyb$4Z5i<~ad*aoQ)5WnECewJnJO&!rj{l?z zjuVn#X7sp*71F{_b!zRIra)xLD-EwTb}ONPIUZbxQe6I0Gmid+BjfY+pbLwu@*6;> zHET&^6L-G`)-0jjx)DlVeWE&{pvk~}GhmE^KHe=n3La&l>9)_umTV)hg?bUz^D z$pfGZ%cv?3vPz;6mj#4Ag7?vs4D7*1e8^R68}VUB<0GK4Vxv1MN4-2pHonS=cF^3) zu^iq=RgYySCe>psQ=3%Jv21iwy~eT) zWF3h!_XWMpw5;S*@{Z^qJqTI~M%BA0%TMIAi62>-)64V(r1*e7pyd0I|g})sA81*6MBm7}g@vt_3o?MO&h2{~1@uM$CKLM7{u4l1iZu#<#}AbL1QA_qgYK^~e@?P`ui<{7P3uBnZHZXt zx>8;A4XW9ZOv!6e*A%Bt>9uG%lY1eFYf*}Z%cJo}0yc^U6~^EPF8>A#^J{?4_rUx| zon;O}-zrI}9@14g72W~=x|l4y3_{D@ymA+to|9f2%`MJue`kCs7vrC3#fLI+#4hs( zRVGI0BkbgBXK+E3TAtR6QOxqv$=9EdL9yzRhAj)hnMGvSq8=Q@I7+B72Jsp&;GkSZ z$Ti}Tev|ww<~b@`4T^1WnB^RJzZh<`F4#3<1tpE7B)9t%TcCKEGY1#;_GM{RthOqu zE#ReP7ggxCk+-?Ve4DGBy6J}RTC;~yLD$qB0q0tC7t})3dxD7#qh4kyT}~r?fPkSd z{ut#oyQ7j#Ad2-|HK{;ycctj%kKNK$^A89ph!pM5s9hIYWa)k~5m2C97`q5vUXv2t z#wf1A(9Q8EU z1FQ=0+yeKs-9Q{Pw>O3A~D@(7ljNx-5&vt z!gyK~&POh)4ytJ))BhWDtI7gJV|i;+{9$j)jG{q>&G3U*t_llTsva8Cla$p^h)Eek z36_yO%8s7GTBZrsM41^nNA0Ef)aYiS>6t$()%EY+;h3hB7Fg>ao(Lo+}%LQkX?W&@Z@`+b1%6n--NvM7QvS*YMs%WQaP`v z<#^$*s5eJ_SUnzpm?%8N%OK>6dLL*T(TX2^MZE>Edrej3 zE=r0mUJX#&j^L60FG)ZzP`DEcB?;ts;geuj)Q6Z|@P|pj!`l3Hk_2<1VZHeFn*ySD zk&~l=94~w#G^0Mm?2SK+5D#nfmr5d>1`Q)l;m=9<(b+H^4EZy0+(m?~&yjTyM!as; zy;@lDEYvAWBO@r$y<1t~Y*t7vbeReX&8Y9LY|TJ@c(jlD(~dud3m~nSh8N+en1*NI zsK6qm;dvU#jAD(#`S^jPP8xJB=;YcB17#L#=wWfOhUS+q*35*UE(+2ani*1aF^(Aj zeW@cc)w|5Dj9daPcv-So!?q}0teFKF^h+()&@Tv{jY4=vJviFGT#YeE7HhOeb{?KD zuFjO$;J@TMfYQ%_rY;i=wv=9-(S9#jH=6@VzHUR|#Zhw9r8s2t4k>r5yDutShN=?M ze2HUv4pJW6L!pAfRqr;?!JUUf1ea#Oio-RF+LonRjvx#BsT?nSKRp`tA^Rx&p{G2o z&EF#Z^lGRbaV38)$B*u($ABS!Esnd0baU!HmdrPhnKe9T-b7F1AursMYum?B&W#XJ zHU%(7Ak^*@;CNQJ2^C~qGfT@Su=Hk{L)Y*iF{YlKJQbbS0Mnlx}(~K92Yoi~AWd&p2 zMgJp1pk+Z!xL8am&;&&H5*}3)m@_!K6h|H>+}i};x{P&l1*Ra5uE0^@PSCX_HA^A{ z_i=Uo2$_->oNmaRI;D3m%9&h~U_V8}q}%aF8h;cGD%^n|Sa>Dv%@)|baC9|&G@Y8m z!ARLCXFAa$NC|e>f0B)cw9k*5>aP{ZABC-;Uy<>Zg?IJ5VU9 z<#^#$-;MfEc^6gUFAr<;4~Xh#p<%?+Mg8{x%fEo*vKbUPJ##lCzdM7vm-Sx+E1me& z3@rDd6p9}p_%cDXzPddjz6zTp)|_j&7ZU$%z@AG`Ha2o=88+5sWZ|5zi0H6la*{+S z`VMnp=y(V^YV$8?3)DxpM!$x`!YlYu04841+W6VBK%k4vW(AQdG5dcNMGV!$kk-X; zm_1{hwO+k)NfkRSS#=N1WKLY86cW?krkNzc1{p?;-8jfp+ca^k%Lz;wW;f*hf zw2sNm94b&-mkL_RV^A{sb*1n>bmcuoKW>z>nX~*!2!Oj!ICY<7-S<`9r=0SqL1SB( z*1)F7GjiO)L2eq99n0{M%8jLV?%-gxg0A!oMdU`a=?iRAxhQ%Lg=H!7Hd)@pPclUa zl(e_#9a8fa%9$e1(=H};2~%n8coVMX16|<-R$v#&KZ^xf8>2ZFNaANQ<`oiL)1Nnd znavb^bQx*_`AQ$vi(k{w(xLVg$JLn&>=3E;mtf~hY#hzxDdqr9NOmjPlx zr4*LTNYgHx`Q{+doe2<@GiV!GEk?fF~A5hK| z`H^-p8%vln}14%pZF9EKN;SYEwI$YClF-H`759Zv>x+9|XhER*zy*oB94~y_`=LI>EP_9bI}dB~Z%N!2hlUaT z`O^IjA-^P!%R)}h?nAy9>UXhzBIH!IAR*t`o8G#%gxT1k!n`o5d|@5{yAYQp zV77!=ju$@6OQAl*EQvo1GY@O??@O4Mfrb%F;YUY!X<+%~aok0un}xh($UKn$RwQU< zPECK54MiDzzIoUJ#$Cq;0(hBQfY>gI#W7D=*zsM1BRaD(HPV56wSAKBQI0ALQ3*ICNJDEz$tqaMDcMv;T)?^IDMEJW}JY3e+*eDHHi_=wB#uIUz(rm!2^VO zw1dd$e>s#FG&lwz5{90GbzL4|TeLdb07td?zu`dIQZ$WTjg?2>#{&A6E`csG>$Z=F z)U1jlHuN|V#FlUbt%1YBNc`BW)Pe|@JRmlVMwI2)iw?a3 zH7`sKsdr7wbeDB$;>Hl0mek|Gx4=og351Pij39VZ(0IEc_26i;Zq#hJFlxqA*#FP+ zeGAy^=KFU8Yc^7$*%F8Zt)L6Ga#(E#Kn%A=IbJl*guu}@JgQU`nQs#-P`E7%bEBE> z1!l8c^=ya2L=)#X2Iy&4eT$N3`_h|7+d~GETiyRbHpb2(Q}j?Y4sL3?jq979AzgmZ$oC*`0I0&ohfHqh$!1o5Ri))y4#0#Vf_iL zpWI&%sLu;gE*bk@y0k0AA|oT2GNanArv;)w?4IuCg44!K>F ztA{}B8Q30292WMV%F=UPzW9JgEx06mm=Zc?82!?mse_DS&TI}Gg7-urhHUD=(Ii@6 zj5D#o^0_vu{4AI_Y0dDStW3hdM4YS^MttIACB07@KADCy+2wfQlYI*6BiX-@WZx0) zkC=!bo$ONq=6Aty7m;p~eHxi}Co^mO^<;0NoIN0-EZGBc6Un|8>({Yw_r)Iun1{9b?5ec7IlAW`$(ZnhK_6{8@Sc>cgXhC}ujvB(jw5VFg{rVA@qoODZi-%Yz}Pi-L-2 z*@7cRZ4PxL(z3X3s})?yv}6QJ)AA6=D5hl-a0s4@LZl`2;AkE#Fvc0@k(S!1Zqss4 zG|N%EU^r4=F!bOBgBu4ES!s%eQsqlg?Zn}9f+;G;3!kD#qCUKzk3S4H4{P&(N{Y52 zFe7H*M`!0z!1A3q?jq97=zKJpXM?Y7zmlmeJ+=5ws$*DTKUPR)Bvp1lyhEJ3E^-{i zbx|C1&1p`WWVSdJw;#)5MYl~nXTHAm=pBJd{Q5tu_BDA2Y)2cN0N13+<#^$n{AARJ zn3M2_P0qvG{4dhv2SUS${qdul{1jmMIXLbj(oK_}O6GasD{FF7Inm^&vBF`jkZf|Q zT<9i01LC?UF45#P=`T0=329AU;@AIKwXew!hV5vhv*4OExg0Nilb?(F5OWUxu*rE? zo6q6%bDNHs4-F#@$B%CE^MK`##&KDrkkfNcN_4#Qm8uKWp9|HWi}9y6e=G!W6xR1( zPsCy2cxn=$cn_997t7pxu(-i?0*YY$B@mWBF3Dqiuf4UXzjZ+k1y+@-z{`A=oI;k9 z@RPKJK$#_2**F>H4CLj?y({pii!n=Np6i3PFLXOe-Yt^&%v^S0%^5Udp=B3yi2=uk z7m9(wSK8S9A4FPLY^e2y7p>$fD8c1716p9DB=G-4VOa{CPL@;glMFn8vcMw+PD42Z ze~tCS8)Gp{7I@Co6|QB4W2he&)L4(%=#BLH=3Dz5UlpD=}6zHi0?j{sUud$WEc{4eA+NZF;aIf_? z)Q6Z`@rU8$VQs#b3<&2#;)rwkb2fhTsL%z5{6#qKBGS!?;C3=!2EMWrfvKE05!}HF zm$O3hL_n1beImFE;<_j<(H&{hUp^6>Q9Kcxz-iv`dYZQgr+JJSw(a7<)=nEB@$vs` zMzJGHx=9XP2yJMzd*G(zfE*W33R`ER@2du8qNq<4Mc-6q>b7casBdnZ(Aw0}(b(3W z!QGB*t~wKCWHjg#NAV9}nmBt#MB})6meX>x{Lo4VB1+h3#S#2CBQvlQa3Ss~O1#3fr zip(0-nQ4wlUKsa@VZs>8Q4`=8XF1j*$KkPYkBKKTHnSWX3x^HB-G*ap%dtguW`@s> zTFWsZz_E+v*fGGdr{&l^z%j*gGz2)BEk{#;qup}MsLnK%H7sH}+j7jT&UAvshKxxp zAv@Tz98jHUc7)iF9cDS^LI_x_5F7WSEz5k`it$V*-n~Z6NtWYyX_<14GcCty0e|1@ zF)))wFZiGbdqFn)U-dWsQ2+gif8%WKV|ASSMEE0?C6sNoQrbr2H*;U?}tFN@pn2ZzbLP>zv{og>A%0*D0~emDN*>+!j1c15H%)KCpzW~slXN(iQaak2TBbVC5F@3vw=tu+cfgFj0F((BYXn@?#|XH* zPiDvZ=FY}qhwL9km-fkwn>@L(WwN9EepB~;TlXGQcRt5I=J{e#beE^@gQo62eW^Da zAJ|v*tb^3w_+a_V9@Nq|Q#-9`N=Ifc`%VOzgQ5s;MBlozxua=Db5lb-t#b6?rRxS` z94*50-psz4&S_0kr%h;Uo!r?V?WE3kL!%fk3GZxf-PZ@T_02TS>S(KP=xCg5ahRfA zj63>f+?o!8SDu&XQ_x_ldkRub0C}?nIC_2F@izT=fTw55tbbB_j^|sHaLfqRX{SFtf5_IoKcz07Yw*;4 z+}6dT92Qq6eGF81a>Uod3cw>31Nedh=MUMd#+-Gn0PMFa z7l2J@-3Zv&3c#kQaslgiD_{#NU^9=WZy7@;ShlUx*mksR*n3iz0#@1{mTfm<^*qLn zr<}o2gk2hbx8FDBOtAtQd;;pb70_Y@;I_7kn`=Q+w*oq>0Ng|_^Ymk5=zf+B_gcej zb1d6|X>9W?+hN9Pd#yPniVh7~{e>~-1S{ZJp8(t&^9SAvIKv9SO%)Y4*8<$K=|;eL zR>0XFPro&WUSip>P+t}|%XXDzyF88U2Fr%kP-V5f!o^JVnvm5$8FOy80g=1E0hbsIzcxA9<>4<@_71-G4x5x_MbGi=PlbaX>6}swpXmxc8xAuDVP5t z%YQQ_y>ErQ6B2@f!SB8m@|hKauBGFbu;jyTg>1RJIlYcwJ~Od?YH^_koo7=i+OWPI zSCkx!xGXqC7J#<5)wW$e(~!7o-g^1Wj1KoQm|@{1OzrZSnHo{^syUn@U~W|yG!TK~ zB~yM-W~2J%=1KJpdvDS*v#G7M#hLv7VdA%rZ8G@V_sR_KmIfaf34B8akS0 z%5u?0tyub9>q z1KTc;uwAXNod#t##d2A5(|#%jItQG{_m&{+ut0M6v~qVxezY~Uwl#IkF0P8qSwdv( zwm`BbTUqrB#6QjQH!cwW3E<}2+W=6v#H{`ZotZA7>f~jMM zyH>6G#j4@TV{GLbFiqiVRU+-z5upw4GM38swZc-@w^G*)Gi_p-HXN6k-s&z@sdU;P zTz5-bcl@|;TW=Z`u&ouaRhX&PGEE3God70vzv9?&nOVs(bH~TxjJhMUGbmXzi+9l! zhrU|SnX+ZRb&#rRH5lV$ofR?3ifC{|{G%vh4W~=4C+WUx5nMe1S_MtuQ8qSfvDtXK zrfB1oqKt9{lZ#+b!9L(o3fi^UY}}zKDrhOn2-&!w6>*>yagZZo-=c_e8#{|&(8j$! zrH!f~r-IJZ?VLHhLZAOOkbb(a!_pxvyEx`{$TXnMU-clT>i)WFQF;{3_E($dR6E2} zGq|oyMA1`HyiMoyNnBaBfEe+rQ~f33+d0Ydmc#P8 zpXC#WfFdjQGY2bb77!UZ8=lTp0-uaF5|CS zaH{4_RU5-78s@LIx>IdcPqpFxYNMQL>*#9swlq?GEq}GmoN62US+;UmHutkka#-qw z#qHVSq-XE$sT#!%Qo1QKN6l`>Me$@Q+uW25&Wyt-z8?zB__D7sl(t`|l$EvLIZm}R z{VW$cEa&-IE^}Be_Oo2=uw3D1`Ip0Tji2ScFN~Lc;;Q(49Mx?*gSSK5Ydc!o>Zi&b z)#_Yk=m&s!5%5t-g^$A(KG78}{L*NMqVqs!>c+KA-KweGojT8u20KSkwPC^;r+eUf zB8tuuaMRYd>Gf_+b?@#B0dZDtte@`GT-02NTnXH?Pfvf+|6H5YZG<|aqY^{HyE z6ExuJH39})y;{Jqt9Jr7u67A;$kkiZ)ZE;SnnxTp52mWQU(kT7_X-$r^=<*fu093a zxca#8hFpCtP0gd-sCn5@^Fpec=L8M7`iy{HSLO8frlaPyR5h=3Bk4m&()+2B-W4=p z@Y~&}`P@sQBk9{zNnZ;ZF!;-E)coeC`8ie1kKIVB_^OzBG3elas*Hf% zMvtODL`xvQqqtJ)Zr|9B`|!Lf*1JlgxJm#g*LflvoSDN>6eDy$z@}UDNz>m;z@Yv` z1@`FgFF;WL3V@CNWra1+UP}uY2+ooM28|miu*bMT0tAgK05-!qhg9s6uA;$Fv8S*GT--yz zpo^0Q_PE$6K+wfzz{bUDq7N2%yLw83Twc{b^(Jf&KB6?;(h`IT|5M^aq*yZ z6>~!>4oz2aj-%qtB&)h=m(e(iP8T!~fKvtZ1|W(~7BJAqFw9>9+_-t6@H!DGvElqQ zHRpDt<~m2sKU39QC1}9aD+COS8oMwT)jznL$2PCrsiMWsJYisb62XGI|L25 z+9jaZRhj5L?5KGlRn2|fNP5bV^mwYI#{>-+{Af37UU1Yro2usNZX~_wNP10p117x^ z7KDwv0tV70ir*60Gj6;sfRj3<^QMmhn=JdUuzK4tiryD6sQ(jzJ^DWtAgKRGz()Uf z!Wsz9Hv$HX`%1u|aX$&{G45vpg2u&P7mfQfP5Oesq^U12@urZ*U{fgSOdZ7 zA)q%noEj`5ut$GC0fPFM1Z)~-@pKhSg;Xq^u41U8Vvw)~TwFoGpo_x<_PDsB06`ar z12!%e(p8KIsaQ2#MTetepCoI^IjdFBKmeu-=nX&=?JeMjol~YXwk7t8&lxOBNlgxi zvs$F~dSlzLz{&W{!L}=2T^$P4L}`w2Isq+l;UGZ+E*v0Wz=iz;EZ^&{?VlxZnujwT z4v&ZP1P*z40#M`OvBDYh@Mu8;P8=y<(1{}iPIKZghoj61w8T}wjh$BrZ^+Ke1TC{O zE7@>~qb89r*kN;#gE~g+gG+5WncoWB7;%&EhK#sD&_Fu;OTaP<)#~Q80;YxPpALs( zrOD{q1P-56m zqvq38H6N#``Os05=wW(9`@rGw`THwy-%B+my z-vkczvdV9YE@VN6Z-YdF2CV$kvB4k9-yM!JE78jqa}4R1YDix}1F`I#W@S$S(_&fe zaFkgY#fu9ZiscGGO*$+qoS}4BTF^i&mlQC~$|W3*GAj|wzX3Of3=`gvAwvWW#Bz{; zUMo3gT}j|jFjfOQ$6xKk1uNN?2{&fPTnSYJL z;W7VKfj#EmCP2{q2LK!M?-ka7g?9^>X5pO6@kN`mop95?xd|Fro0e(`z zfccLLm}dTC4u{A5=LPnd|AGKP^WO$+%zr~z0~Wp}V48)mI2;}e-x1hj;kyC^E&K|w zvG8+Y4Fvd80R!fLEMS`XA37W!^S>6@WBxY+1kL{qurdD^VJ)*zU10wtpw~iNVE^Et zj&UZ8__=z9oQX1coat`ab~hEXA~*E4-#lXW;8@63hy zA_9B7>nA|ayX66!*e@fj0SA^6FzCPlfzupV+~M#zu!6uI2L=idbf5s(IFJ|CK<*C{ z(CZzi#w!c#(Z7lSLH%n0Hu_gfSFvVD#aihq)^k*h5!QfS-G39l28 z5*w!Z)u=&gieC*uQ8UX?)0wKKUC@B5`v@3twN=2dtA_wLt{x=3Ay*GbQ?p+;YL0W% z9G$A>NI?Ux9wA`B)x!i#8+Z-Sb4I50V_`wFwM&29S)zB z7Xdd`o-e#*c2vkJ^tl2C>^M73OB7!$uxEt0L;xo)r5B;s05-;5C9DDcR|ptL%qYHA zV2}Rm1PJQC1F+G5o3NGzNA3N+MZkb@HwhRt?oNR{#@!`A(71;I8{-}j)`0%|1Ptnb zL|~8pM+FG#e-W_J|D3P}g7b`k0pp$$FfD_ha5%~`IEr5q*kk_70tC%}53n)+ZD9>q z_@;nq7QXIqcr1KhV2_0#2oSXJTfoM`uY@%a;4cIWnE#o8Y36_8aCprBPGFDu-wO~l zKl5GD{NIH&VBxQhe*cQ`&kl#j!U&qYx9np9f)>vIzG&fLpu;QPhYA`9{~-dFIX6y* z&4UFD>~_E+;0eG@BOfcgj=3eXgQL^b9NCSUvmG^Oq^dbh(15F_2pDknBmu*&UJTs0 zdV%nUTs<#M%{kquxzx1r4}*xqtyzFBLHC>Mg*Ht2YX7$kpr9)Lhq%ntL2I zcc!YjUC@B5w+ZNVRi>5?Icn}tRda7QlAd%V{U=q@zXc5#{75%yo_Ewdld9&aZX~_o zNP0C@(#wJd41Td2HSalU-cD8XW;c>Pb0mE%yaAIw3=4{)4+IS4cNBjvu;-NUg#b>j zm(J&Y1Z=v+cf#sTI=sPFz@Yx01or6vS%9Gao<9`zSAh<$xl{-kFfJ=#(70X#dyMNX zK+w1)0Gr_S7uJCOegX#d4-nX+e@Ov?`iBBG`UeSXAahm_Fkswr0tSs6Ca}l26$JCCl3`g5P(Aj^acR$2oNyP z$2g}2ZrnUpc%6up*l={3nj^bWbGD=Aj8rwJ2^w(q6afRSo+Mz{)r)}}S1%CWkgMmV zsX3<`HUD(fT$!roazO*GUMis1Rhj7B;HbGSRn0ZsNV>z3bX%&VTLcXld{Z}S?sL@K zovP-}ZX`YCNP0wg113Eb78FGf2pCA0DE^PYo^j)G0i4t+oj1Jz*kswW!s>0qD0*7J zp#B#H_UL~}fS~^O02}>p3u_=aZweSN?sWl!#=S4F$G8s!2pabVU}M~;!Wz*3v4BDS zUkdEe|CIni{l5S<`hOJGKybbn&}$s02EPjI(f^wOLH*eui~2L5Gj%}~Ie|SYDg+3s zSj17$TUY}w_7pIvzn{P!{fi0^)V~y96XGS(RV*D+u}r#(JslOhCs|9*S-S}u2*54^ zdIJzeI|-P!6t{!J;Vg8Gmzz9MTrY4kerDtU-at*1rU|DL&=MD>2pX_*vVdt;PI5TP zti<_$CU9d&yYPk#*+JI!{-DIIPr{tK_{LPIL(PC z9F8(4&=Ma2H+H@&ydgW^7Bpbzn*s*yd|lu)J70A;e0F{T+}Qc4@P_RCSkOSmd?=vT z2~Nen6gbqTKL9m0d@GzG8@^6cgQ)-MNct^R(l3Gry!k0jO%!K-^4%UYvxX|5#+#gQ zhP;Ud4FnC|Ebd5JG*!|ff(E?l6IK&Ny#!3lpdJoKSq4S%5(0;sb|6p_jOBzg6pUpA z^#&t~mJ%?{$^i~XnU#p;O2CaFD++HQT0;d4*fBUwOB5FbPR0Vy2COWA<9g}k=$e3y zaU+E_pnp{XRUFE^jN-Kf_UK<*fS~@Z0UP~W2x}lX;{^;Dx2b?>jkK}D;W2+3fj#DL zD?rfvT>%^OcM{fsg*ynCW?`+v;jwTxfjt)fLx7-#djU2UP8HTbfExu2nBO2^n)&q( zhsXTA1@@TVEI`oweE}QuJA^f0VVi(y7S3=uJQmIp*kj>r0fH7D0oYhLPgny1o-1I$ z{5b-qnSYSO;W2-{z#j9D6d-8+DS(anCkku8!s7)@v+!7l!(-v80(&exO@N?<7XmgG zo+qpU3(pZS&BC)B4v&Qw3GA`(VgZ5{-VNATc!#hCQlLw~fcduym}dUX4u{A5dj$5F zf3E;R^Zx_bnE$A-1}uD7z%&aVbT~W~J}$7w!Y2d>TKEQFW8tg98VK;q0tU=~QNZAg z_IZKRynEK+@Oby8z#i}35+LZ^XMl}&9}8>1fe!@?I`DzOX%4*SaCjW}Twsp_UkDI% z;3vSwf$xPi&^+G?==F|s^`8ay=>J84p#JFRqW(Y9Rm5QR?k~#<5LD5}QPERa11?qz z7<93(z#bPD5g@340ALg1#nM$Q8B(!Sx{5U%6{{s#OV%7m2pR~$Dgt@~5Jd$6Rf;+Z z$o+M4*J=!K<7SQUIuR+cVVyKJYjvY$Q%B84scOav8gO-e0Ryg%6)^1Tw!n?6TM2K- z)h*K0jPFLx&W@TLgg4+!ZJL(t(zM{wUV%MXy{iC@W2ILg4S-Dy_7v7YF!vBJ5KO#N zTVRj=MgfBQ_W^A5w+L$>IL!hEjN41VpmA*idyH!rAZXkHfQ@mpg*BjmUjc*q4;0v= z{~!T^`i}-|^v@U8KyVHhFksv~0hMw7mH)X8N7<4Pjomu7M>}v$HKD&2wHdrU}NE>!Wszh#R3M*zfiz5^UrrUJmz01u*dwX1PGdc zGhk!>4Z<3*@LvL^S$M6(;j!=*fjt)9DnQV}?S3g*xDDt`-NdHp?pJaSG3T9K9L^m> zoKZAUAbz}i^Ol`j+a|XcKMk#)!Q5Url&_8_>KRNd1}*Vx(WL&M!zL~3a4uyz@%a+- z3FJ8k$gcDO7Q@#|N-#O24s^sUXIUN7Yz&4voP$z1|K@NGQ=IOb@bP&&bv9qc;ae$% zZ#9Q+xZ*2qr_+tZwH?kigj0Qr%zX7kRzjm_q!NPWu^+QI&xlyh5iv&bl`3OEqG*&r zs;M`e-O*Uy)PEO_vZhuyc+u1wJBr4I6|JuzZveM&L~Irou}O-ET^tcRl5bYVe!_7M zKIw;+isIcxK*#hMnRx^7>hCynNM?R!&X)K_5d3KUy_mAipfmV%c1t^6c;`fSYxyJ* zm^&QieY#_wC(Pn*3rgls#S~wUx)CAbPWpblqIm!=|a`llr>k-QLXZfV< z8~5pK#AhGe&y3F&WV(hX&YmIP>b74Y-&bIHm7n9;AbI&(6dy*ky|JTHKA^>EOq7|k ztBTN-!1yVyY~I*?nfop`0X`B+|O6kS4~v%RscZgPD`y`xKg*yM#^OODDY3Ypcv>tz1cQmi z`@-yr#k&N;v3Q$sFcxnT^~B~Z)90%3>0 zBOG-28=@_p)9aerTkB@v+m?+T?ZwX#;*&;Sk--y@FIXOk$Y;d75&48jN<=;)o)(el z4;7JYHVdkECW^D!9H?Fx?XuYlP#6(q-1~!~jN#6(_gkupD2^c`)8u@7qE8mxuop@P zX0p)HAC%I&7$~K8aZuEojh6tWKA<;1QyhbdUz|vD;!9b^ z{XtgP5J%YZtkRB)q2g5#-m$YROZ^}F9ZaZ=JtwPs&Kyp8khTv1G3jwHxje~#kg=w6 zZq_nqL33Pl8to{<*GuzdvwcCSp<@wHYUt<($_!c6puJMiu?e|zzIX{Vb=-%=rTx86 z4vs92a;8)^w-hMRnOho^=*%qxN`|ZKvIfmAXVC2O28~w$wL&JA;dD{Z0o#@!RBqb< z7AI}%lNI|4z)Ya?&F8oTWi$rgk<>c0Z&!mNAKzG?4olO+q4W zm=(9Oh*Kwv!KRMWfT~JWydp4as>6Jz3E!TbTbWrYmzgtbu}mfpC_=bb!(VlF7zIi- z@n}%0iN}CaXNUDbso{5hq3G2Hsw~dN<5U@_4K1~iLeY>LYl;K*aL^dAafwpAvK*@~ zqShpa?8dmPaD9=$FD1qCNI_d$0k<_yX_`fE_>rXxVd0#OB!2PgOrOTA;M_bm0krT%NF zk1X|xr9QLN=a%}?QeRu@TT6XssUIx$lcj#K)Nhvh-BN#ADmuc%FKej^OI2E`+EP6& z)!R~iEwzZH7PZu3mRiD611z##(B9OKqSjeruhBXO!IrZIF@ST@T+&E{+ozfE_}> zKSDr*AE2=5ekjUz_yN{AC=AUHLnry6D0^lIxF7^v9s;fn0XK($J43(&J^&~BjnGgs zuZlMYrA}y@fKn&4O+l#>+IWj@ZqY3)x|Kz@w&*q%onX=JEV{i#Ct7p|i|%O9?9QOn zfVYdKcD2-QmimXLbi(C$zIOk?)oEZ+=k6J{+CGMg+YFU$*A(X^ji597qRm}uY+!k3 z`=q*t=K6MbIh>Q0D5@vSRe`3KDXq!X$lYa9Qs=Tbvz}$;if4DC4IQ%_;1rpDvopb$ zLx3w^P9|q#TU%Y@tfmfL&&Ow>M9s<0HdOW;L*>pjRD8anvKJUCzQ|G+8!CH=q2kLe zb)}{lqk};EVsrq@InQ*QP!rnyh@^*SmdoM@P~n+L)EAy8o?EQg(bzVnzM)aapVPuY zm=9$96!h$P_ba&Tsd&MuW_s#R_Sn`)KESfQj< z-av_Jl{XEQeald}w+$7)YpCpdhKk>})CY#j{?}0PhnD)tQXd;C`-!1)pBbv+b4z`p zDYoOypfgjNnh`cT)h^i@aw8c$o#A?xdmH*XBIyl%jmuKj&{q@nH*{CAVmA$ayN5H- z(07ovn}&|EPm*ie-i@>C;9CuSoWA~(@%#|zAPU9N*-K( zEAOIAXB7X)q8asVSR0b2&`Nb|;`kSZSaJNunb)#=uG2`lG`ddmNm4t zOo1Qm4Q@Z^uWlvwA_MZQ$e@Fh2Ll#X3GZF9q}tQeaFTKbVeUg@uM+0o!t4~u@W%A1 z>QmjP2conhIMm=a9F&?Vi~yx(3af&WnL>6gP%4INgHkbE2b4;%8c>p8*)fKS*R|Ao znqriOfX>u6cVI=hgR2zot*b%AnmD+Z6tBQy4mW|t7kq_-95dRQWL--KP%S*ItVG)I zmqa%&pc5TD7{a2G24f9;z>Qw-{cpfmL|WDzTi^H`L@y7dMuoQ$B9 z+ ztP$@GO2w(!P!-dKLfy(4rYzfLsSZssio1el6!n1a+ydB{h&>zB;Xc%)lAw05w?sRV z%d--z?)FFVp2WRbT2G{*vrR@ktH;TvD6S)m>Ht_0E6Mb=Wim5~3~mNS@g5?!EL(MI zaumR18qF{ZeCldp9w^n14>MHma7!JbDF&+(bf(GUty*`E;{C|r;<~>u8c{skMUxJl zC=MM=mNbtKB17r8pmoUo`*;p0tOE9B*sO$|wA)m81sBXljc>!_fi#QrlhkY~k%_U>#c~+MPJd5DI&KN1H;p$YO&dNM5=lWD2!QKV0I$?aq zT1}Wm*{i|a=O|KR;}@XRIpj-Fl5@GQEc&%YzcFb1Jt&p+KUwM*LuG%n)E}109BFDq zLLr0VSeJ1ycnh4~`FtGzmr#4-^tzUM>?M@b#^P$OwlUABQT%~ssGl*dwM}*#>P{@% zd*b&9yQhyRexF5&eyu{7Q>z0AUL~wkXO@1}_Qfoz4XOupaHm2P@cqfgl8ZF&2-V-9kRQC_6j$V$oR5`mYo1NBiUQ3 zmd*gxpwvVJ*DkmW$NHp7{GlrGUyzKON`~S^RLMBIS4ri%nQLU`a&K!tRsU6~-$EW< z9_Zmx3*c}W#WM(&UdK;kk(=RIUyI`DqNyyMqqv!XzU=pQ&sH)}KgU>e_UDQnue8_; zie~|y2`ccvn&94siZWNr-U~`y#@+`?jiL90l3q~xpg}7iGHB()2F?E4pxMU^n*EPK zD;_s!#S;dtc+#M`=Rrwd%DrHyiWd!4`I4gIY}L!Atl~98RlcDqX5-DEGwm1}d9_zO zW2p;}8wvV=>j`kC9A3D-&Sh~VtCiVnh&l^)namuua2>^W62cONX%DR5+`*!@#;Ij1 zWAQyiN|z|^W>M)0{w@~D0+d=w>Wt$qDQa!qn;VK{jyDoKY9jFtl56CpvGIFQa%Ro`07}lR*&jivGwYwARLjOk zk>Xl?Rw!O!R2V8-X{cPaq2iuGaW3CWm$8jMhD6)O?)u1w1d^BeAFwcdE`3kK#l=j! z3|F!W{Ep^xnqK;K^cDf9>9LR7H2oKZygl_xBKAVojDUKwBqz1cg~Lf3-Fa0LvGa2B zvlzmnnrKN-s)?2Yr8@4?prnc7Ce`f9$i zc=RUVkH->(yzy9^h@0f%tt2D7Tm`6FsoV*a@^WWTDiL=9C0ML#exPLP zP`N)SnL1P+Zp)4^Wz|Q5Qc{irrKB7UN=Z2bl#+5LD3MZizA3A^z?Aj41e8eWaVaRt zgC3WG5-HVJff6azSA!BM*=s@QhTj~x& zRo-c-yM#iQuDsWl-Djw(`z`f=p?W-IsH%qzRr!de{%xuM2nESikDIcpClrMf$5WtW zYE$*JrJk{T&l{@xMML#?$x^Qvs_J!6O3E9c-x;dv2SZi=XsMqJRrL!f z<=d~e>^CVxKdSoOlvV#>%Bueq3fihON9#POjzEcT)v+n7%7IdnD{NV%p{lD4RaI@N z9-vfod)l&Iii)$nWC~Z#6GqZ@}^`uru8!r6(cZH&OZL;Xor@EJfi>nP-SiIN7 z;l5;ATSL5Ml0*PYQFfLS$7>Q%fsqMG2K#XFBpCK5MguaL>L{)yg9Ym-it0R5djvO_`NH*|N_uR(Acc7cjzvK*%8@Eb^@ZKO4O<^x)Z zx-GXZ`RZF5@Kji0)odLiW(w-Ae~c)K#^r|&B<>KXg`AN^)SP%H2X5DHD;kQ|!2RQR zRTeorGZ^tfD%uBO#}T5v1v%99mW{RK;TQs#bK0hh;?XQBy;fO`MTsf0QgR{p7R;33 zO(0XSW#V{i;tagrgIl+6W*&u1P!rV{d4Vgq@#IQOQsa0mOJyqJy{Q?+Taf{Gr%hxr z5#!D1DBgsCm>#&rx}g#+cNe@Ja+G4gmKQflYq2!3&Ze%jxJX(2XQ&=1%o-3P-6+l1plaz>?$u!ex^=-dR`$h}kZ_61k?ewqwz@WpYzJ zZnW9;MRm27Cqv-Lq+!JL_>fqX98Sp7icntzNM_6N2B1_B##w4ZMakJ>ZP2O4tw{!b zdr%pGXBAdwVZh?mi1Eg0(^Newx$7Mxi2D5*?NsEW7V5^D9*fjU*(kDjT*gO5h)7o| zj)4foRw!1N4cmiKlfPO}YVtP`l**NzKuMT#J8O!k(#=7q24^!el*M7ZoM<-@Y$FLo zXIp&-c15U%ZryNfL?(YYwjreB7zoGKWbuY$TO#&ZB)tr&u6YraNkCP4G=Wm-v6rUk zcO7e|W$f-`NG8Q@EG!GgAh{#D3o*Z+4TQW-OeUfuV0KL~9dITQsc|D2+;IuMwV+I` z_qTzfZ}ezyazcOkLNf0Vay6}$99}bK5YaOxosDwc%duz{F!Ll-XSX_L2XsKt!6FDV z%=i#c#_R)FK}Dc!)Yy+$s!Mao;c-bu(IWt=I2{X0<-l>8V#MaLvR3UbJlBo!d$6a2Qrk|>040~L@tL61wv%%~DJ##@6scP|DEjKq*82r70SEG3d~4mkWuN?{+z##i>qQ zK@P7ImlN?gaSO`LdFe*-l&ig-#i?p;rzUYrbs!CMcE4Z-G*&{5B|+%I||RL(!|CQ@44%K?bwU!?%$`_oI)%rv{#H z9PuA?C;nqsyfgDvyGXty>Pg8c{=%*2Gb@U}B8%Hu@O3{Hm5=#o?;n6D^E1a3qw_8K z(>B5UNCp#~GV^~Y>NWp2x1QhpKgd#Q{;w?ZnxBKw%KSc#`4Q+;^DD>@G`|;7ulYUQ zdVceJlcm)B9xU>jzbFtja~S}tzwF&v5|motTMCpcjbxV=3d=d!WkB&6%xrd9i!NuW zM*TKPA^MWcY<1ht#eM0H=G#@|@iE6eK&hERou&4)RK2D+ zd)OItrb)Y9ynGxYbL$;7*w)zI*(}e9YpWDIk?@Sx=H~iI&5d-^%c=|=q3DdO*P#NST70+Ks5%Z2C=FN05ucwIl z#1Zphx|k1A#C+?B`6^w^7b#-?aK!v7VoFaESgnnspVQ_4m?FQ&vBg}j1nuc6ITojM zm6&*-tE?j8c@|w4lv<}63rek1tp`fhsdDRslKFaW15h$w&y52m^Yz@u7Tv_6n_6^p zi*8}jEiJl@MYpx+1dG;MbfQIfu;|Vf-NmB2T6A}d?qSh7i%zm=gGDD>bgD(CS+vQb z%@&<*(H4vDW6?H?wp(IO^QY^aJ`Ep?lvZnxAOmb%MQcU$USOWkLw2Q2lVr5?7_BbNHNr5>}?UB%KX{om?^^T?9wbc8T`oK~jTIwT9ePXFkE%mvj zzOd9+mipRIxo-_s@tvicVVJ<1dCd*=03BrZr`RB+%7EML&UA#;e_42{_0%?YR|a-<(|O>{vXr&-yxHe zoot*rPW2YX@K72s4?v%Ns6b(r3u)H+NbP--2fFDP|RSOk~_2&T_p@Aax75CmBkcypl!bYX)-pqVH9rl~x3o zv{G(mQ0m^{DxlQ8!{MORd3JS+u3^zN4H}OIrN+ZCmRi?RV+~cYo}nt&7Yebj+`v#3 z;|!JE&{7*|in%isbXx2O6ZgmdZ-l(DUx`Ry=Wo7jiv^^-5(Y2sdMo$E#GI|G4vIjl8BPf;8JAsnuXD5MD+1p^K++<5lwbV34;TeS{ zTei2Qrdz62Q;h0((4~_%?m~<7e1hsEXuf^Hs0O5? zK&j?C8kB0jV?e2i=82$W$jO}qN`{=ulR-(7R-OV%nzZs%P|~DTr-2fARi}dzc~xhC z5_whUSoB`n6m7xw(K@jR?!7Y z2EvLvL2>xYR^MgN>bnh9b&sX)RTOr<-EYetFjVz}mU_rgJs!5yBZjJY%uw0?Sn6>_ zA+4VBgH*A zcz9wK3(L=Uc;U9FZS8ECT&G_4;TYwf4P{>q_tu!*H$BWaju9QBI);NcAxaGg?}Adp z!F!<8aPU4THJX16N_ECh3>ANBsm~N8+f7~v?McK}S)7uHFBA7C;@gD0iTD(ld(@UAM%B3~M^KKPr?>Wf6N4|m_31{?5woE^wEE!B3 zG{Og-7^B|AN}ng`#iE4oGFw4ruK-MC_6SfavsVSBGJ7>pDzis}Qkgx*Q1QB!8f&QR zdWMR}S!zR}xTRwwQQkz<8GehOZYl`t&4s`iCArh0z@=>%5VSkSpNXT8!*HdS8 zr*s%m6GV-ur^bd8*_Ss6I0B8tI5j6TXB2M{#H$brzLsDxpG{oR)ta0>s^|FgWJxrz zwm{7%f9Qe66nCyNGdeC`-%7N$Cfiy{Z{3H+XChPL&fo zfl@iKGboi4yMR(Tu{$W06MGmcuCvshma112o|T-e%h;|HKzr8UWfHm#v63}-Dc*|3 z!4V>gwjl1$g&hcaTYDmrw8q$x3~8ey_BE3w6k9}eFW@So`+`ytodrrobT%jz(RrX$ zL=Q7me7L2KveeO*I>u7RTj~TuWlz);<2?;@N ze#FL(XSiDZ{b(Puq%~c;SdiL}I>^FTO1qC@wX!hOo_g-UU2kWCPqpj$pj5kF07|v% zg`iZsUJOdL>m`PYFSXPanqn)R4%(g_N?x3FDv^}rKbg3{0nZ`iZKCstq_@M_WJwG1 zxnxN1A7_c0vIkt%*n>D;3*6iw`zLwB_l2$^oNyD_DK}Pc6fwM2e>W)A9QS}y&2cX% z)g1SOQqA#zq2dQE^^l@4Ts^AG*f7_FE;q}bhOQ%=FbiqSGxAM@g5Bp1qTaT-i%5Fg z-0q6@_mM8Lq&452WJqtm+r*M^^F0Y%Eq1&JO2z#pP%7>(gHmyS1(cdMz6wfBuwDbD zCRnchE6QMBesQTQLRefp8zBXm?w}#4o zr%|@|J+hfH24gL%vZ<9Vt#6KcFO(?CMo|C$%W7!L2DV_U0&mwp37sXGpC@{FG zMJz64Rmq(HSMZvS`Ll>By_?8KqM{!Ka5G%bN!1MPd%}rGnI7Bc1SLNkF9J$(Bkl)^ z&qA^ldV=QHYMjk``yi{>t@;vjJ;OM%6w1`v^Kzh=5EQb62$9bW*ar@VGB=~eh?rMV zXrpCeHCj}Vl6Ro=)HCd*1yYZ(^vC27&%#W?MJ&`01ESQg1xoTUUfWXZSnBVVs?ik9 z91L3T+)tP}kcijJu3a(<`Ath=>m=0=!T<6mY5_ID9( zaI2~J%B{@uX>R!;nB4M=fk1BvP|mx#@u1Y|_2!`D5!&1qpwy-41W>9Owo?=a_3dq0 zt)VI=8Y;Vkq2e7awUegUNb7_4H_})l-bNZjL_Y%J?jRo}HN1_t387@uL>YW1W+RO_ zTPK;Zal9D;^`V2hX7!~LS!a<~(%qm|SvDn)+h_D#1|1$z-2qXjekdhQp>w1gHrnoPqWk+pv1-OnYQd~OPy=c^K97#w(LS%cCjvFC=LVd z55=KGyrDRRh$pKLX1RI+tn82TGQn|v>e+Vuom@w&?9`WZ9S*s9M` zLuJR}b;Q-fUWrr2HAFh((=NJ(EImY5v)tRBR}x7q$z9H}l%}~W84 z2+A3!2JxI5-zIum8#1;ydN441;B9@eRS0wD^j+H!Z#(;z^6oSnf@W zPlzPa;v<%&q{V+lnl~+eBIHYp-w8~wpH-*F{=_4UQTz)Tn*4e5E3tjsnmQVjnW57F z(*{X_kqEI$fi)%AT$EhPQfq694KM<9T8vjA?u~JQh$qJX4|`u8-(>Op|KtD)Jw!Y} z1xcDVv`vECa)@o3rfq1N#HQs?G?unNrL?sNh-Z;Q@IX*N1Vj*T4pH%_isF6WsCXkP z-iU}7D*oQH`#hU{(zJg1`+WcWloxsSd3I-ZW_EUVc6OhA2GH}kGES!)y(t9-gTY46 z*@@bk!DL}DD?M&dzfUSV6#b=)wnw)1qudT!?Ig;NXJ@gSg(GI#$aR4qLxeERU@1#e zR5cS_ZZvYayvHELXNIRCMV7#19E%j285&O)x!Ek2=I}Wp*DG@KMQ(w}^^4qMksA=X zVUb%Za?3>SnIiWrkvm!BmW$jeBDX^1P8Ye=BKJIzTPJesMeg|`cb3SVEpjgqxtECC zOGWNwB6q&XT_AE7irlM2?$sjq8j*XQ$h}_VE*H5sirm#Aca6whFLF1C+*?HM9U}Km zk$abxYq|$1?y}q~QumA010waHNIfi4kBHP`BDF)Lo)D=gMe1phdPbz46R8(O>LrnS zS)^VSsnEObrY%XBGp5rl0+&+ zqNF66qy+x{zNS!EBeMRbIk?OCe$g++RJEZFXM{#GM2QLv7)_iWK*1r)eqkc`OCjr^^iOS=X6b zDxnrB=IJKXiBvsO%+ocUuaz@3iBvOB!AqX4J?nCzmg;&DOM&067i-VDUn)|UX{l~4 zS}I|#mNLy3sRbgnP^7L9sYN2SSW6kNR8!RMSx9#j%*~+ucs<`}JdLuLW5ZU1`|uNX z9!A(9!Dt*qnK%lmx)SHa&|)!7{9A6t!nE0797vgQ!@#FdPDiocp;VxvyuN%&bq&50 zQq{;~9yvVbv6Uhv#=#tY6;5hu#AHcxEQtoIw)JNas|Zu;@+N!%b4qg)FtMPJ8a>&> zFb$(p{KGA%Q~oH>?JR#3rTcSkz!rK zyjn|jS;JC1=6ns(9VyFIlpjx7492CD6}QQkQBFrqejOF)XqVRYR6rIvO=;Nfq+5`~ zt=4TwaVv8>Qrv3YffTn|Tan_n;2x3MrlzQutVcS|s{1;6+`+=*TFQ?vk6S4#t~@qU zPW$p;p`C|OhV%6(Qk<{Hkm7vpK#KGAG*XL;(Wb@6zA)0q&Q!@MCu(iMSQ)2bbC7}UZOnR4hlA- ze1RT!!1HsIAD`#fDJy;#Md$knxtymTkm5Y;M~d_GBT|ef;{lQTlgK?Na{ok%hw%PF zibp{I)}EP;h-cleS0!WWp{0z8B9)}2Ovzfxc$Ahh^+bw|Z|0*#s+UL|qm?rsE1n%E zo*gezy+v*xkvc)-o`@75FZ*h_rheKpYOulhEz4jBRA~`J@XSI-v@ePeK=cj_rD^>s9j(Q+ zeFRcm+fPM`Yx_u~xVDcLxno4`XE!X7Na!tirt|=gL%aCHW!8Be= z87GL;B#}Ckr6|mGmUuRqr?3`wwn&{Law8&ju9h-Q5vd9-WvmjZsanc3O-psD)>5YP zw3K;8_2~-@m_HWQoL7OiWDE$E<=iRm)iV% zq&sNyI?9gU=1r8Tq__muBgG|f3sT(Q*(g%CsVUNxl}N|m8@HTtI&f>Q zqx|@CSVLKHjl)fp)3JnBQ<07&w3ZcUPeKOcjjWFLcUEmdKA+P(f)o>-=~1MZ=uD3x z#Rr<5Nb%v|NiAi5N=tQlTBM#8spqtm`FS-(BHo0wI9~<3hsCincd-n)yQ#~He2>o^ zlpSBdTPaHh)cGV2P;NyXzVjAm$qye+xL<~j-$}Pq5xv>Fk711`Rz7XSw?fsq(<>-R z0w(5PlAv^f!51ha9(Pdg*Chn6c2I5?D~EyD^gdF&qkn)D@8};Q#e+Q`A;nx^^T$YW z7x)vTIK-z&afm%gF^DdoA;lcpE_;z;TF~Wl@$3tvm@8`jQsnN_QpT^;6tVvn(j9f8 zHz+@z6b#09C@XF!dXIACPXHN>yXjH+EPQ{M4&vc)6FQJNL1}r8M+@Gi5^{^G!;LoK z{T})3>wKmINO8&hgcO&|L8Q1We@2SS@(@y7mcJmyA$~=QL;Qvmm*wwBaasO>6qn^; z@$65exGewDa*cnhDU#r~NOvT`uPHyi1b?8cxDwn?IeH2HNRK*@U{qXCQ1kMrh8vOA z&kEUsqC$^5FrP=-r@IS*#-SS(r$`kiQL(lM?y%Z>P@Zn8${r`s;|_@GO8N1Hc@$;E zC+c`)Y5o0JD%Q5*Ui7$wihELi{EB;1R{VAd&KgIvXJmoKYPsgqwN%$^E!8bY zE7#4VJ|o>e5$O&bwLX*`Unu=3tK9)0m2%^V%wV*$8ntiViUsF1R#R0IefCD2OwV6@ zu~8OndDMN}7ST|?r7(lCWwU~h)Y7AR`m(+3d|Mfw_`?U;AU+{v5lQH|52d+-SArBt zo#a(OINEGuM|7AEm|n_`FRyWw)vmlklp9xGXHZVZ{z{OFbmXrD7>+ocgU^Y`<9wDQ zrSUnQAlm1%l(OUVIf=5`x9K{Od%ehAE^=3h+?68t1})dT3MqDKr};*fGGNtiwVEO` zvk2*qly4#B#~002loglxYbZzWax9@oF)l|EUke+Eeeqc0xtdDI))i(_7)!M02IOn) zc{8gZ&Z`7Bu?$(GbkmGAlpUX&TPRBg)J-$)pxn5;-Y&z%ZRjMWhr&0jCQdo-xT;CE z<+mR_KW}^Lxt5;09}Cj{WzXozWsNxEXY!OObv2FCt7cAaYM4{k+|)R^28(WuQzl<9 zV%X%8s+y|u#wvQGK6VWrI7mtktgoD+{M1M3cHC(#mg_C^E!SGEv|eLfW!+%C$-3UU z$-2$@xAlh9S5kMUzLvTx?VZ%`Qr}PgDD}tGgQa<(Z)~4N=c5B+Uw9RST(^h8Om~l(ST^ZXmS~H%>cqwCN z=Dy6iSyyMx%epITan}7=+p`|YdNAvN^U=9Z32q=}^)yNiCMSmU)oP0?QSaMV7^ut01MNmSvFF3d>5%D$5$nO_rN2 z>n$5Bw^%k>ZnNBOxzn=Ave~lLa<65(mPag)S$0^SudB^0sA{s{8n(PEpeTdZ5rZueR5w{EvSV13Z~u=P>v z4(pTFr>sv~pRqn`ea`y4^#$vT)|ad=TVJuhYJJW6y7dj~Th{liA6Y-M?z4VpJz)I> z?S91Cl6raSg48Qg7pGpGx+3+4)K#fBrmjw1le#wbrqp$*8&Ypcy*2fY)Vou+q;5;S zKlQ=Xhf^O-eLQt%>eH#urM{5*GW6zE=*{b?Z>H`_eFr*3y7Niup487$KTq8UUHTz) zKXmB;wCU&6Us8Wh{Ui0y)W4uvM^gWRcFnUbur0J*VOs?Kx)NG;t!=4o8T4$qZ6!2q zwQZeky={Z-7TZSKt+qRCciT4EHbV#Rw>@Ng*w$)$)b_Y-r|oImbG8?4uh?F-y>5HM z_Ll8!+dH;*ZM$t>*}k@YWBb^jF(&wm)o#ZGYPSvi)sal(snS%CxJ}u1>oy z?fSHpX{*!LK#SL<-JG@_`n(a^d^>dduC%+;HbJYmrQMr$A9VYHw1?6jPHRnjB<+c` zooP>|J)QPU+DmCKr@fN)M%tTcZ>6F4q`jZEH|^WBAJPt_{gL)p+Jf|})2~fmn!YN1 zefoyGz~>OTRb$zVwIEA5MQF{l)ZG)89ycD}7h`yXm{rKS=*5 z{gd=P>3h?^NZ*(Kb^5pI-=`l)KbU?f{nzy0(+{Wrm3}0>#eTVczI~y6k^M^h)%GR! zrS|3amG;&4wf1%Po9*lE8|=5(Z@1rJztg_ee!qRE{b~C%_UG*{*gv%&wEt%R-TvT! z=Lftn;Kczi4cI&2(161OuFqJRaRd79>WnoR>oV47Y(OvGm~jVs^5%>!8Cx^%$=HTI z{XoV;tY1Hip8Zb7yBY6ie44Q*V{gV68T&F0X8fFSDC3um-!cwo{F!kiq) zWZs&2N9Ns`TQaw0-kbSQ=8nvrnNMcElKE=p2bmvcexCUy?8A?lhcf@j{44VaEXC!p z6boP}u7u6FCTmI7wOLEEuFG1HwKD65tW{a7vu?^-m$g1?W7h4kC!4Z1XKl&a%B;!* zuquyaJ({%xcIC;er?Q^TdM@ilSeTcyUdwtjYgg7gS-Z17gthrNYY*(r-mEWSalXp> zHtPr2pr5i1!3zBjEA(g9Us-==9m)D9tHm+bG0!pIvCwgaW07OA<4VU>j;kHlIF>lB zbu4u(W0q=#;|9kn*sHaUn_#nUc5HCm0?T!~;||B2j=LOpJ2pACIJP>rIqrouyC3$f z)$xeqQO9GjYmcjz?K#Kuju#v+I$n0X;&{#Ry5mj9TdK8t&#@a8?<2=2j!zwX9G}7B z?Q?wP_{Qj{S}w9X~k^Iu1F0as2A|&GCohu;VYs-;RGAEzY^l%boL_^PLNv z3!PUu7daO@uXJAJyxMt=Thf&&g-4aohzIxoi{jFIoCMXI&X5Wb8dh=z1?|- z^G;aR&CV^(tuwjKJ09DKH}WreBAkjbEor3=QGY{ozFR6biU+#+4-vT zHRtQF!f!fvIp1-<>wM3-+xemMBj?A?J`;9Bjv z3AX)a*DbDFVc&0e-342}6?Xn%SF7t$*JG~7T~D~4bUo#I#`Uc0IoI>97hEs7UU9wV zdc*aW>urn%?_fN5-}RyEQ`cv%&s|@*_PM@t9pCaPUSK*Zr=o6V{lMzV)^fAqpMNl7 zC^=KB%9{ob8nhG>EB<64&UC71npK5inY~+NGR&o4%l6x_~|D!pgMVO8DeIR;FMvSmn29Fpq zbl50@IJbwQ{PT}Nsa*K?D~3rUkKCVh+xwe}H?Mj3!Bl(rsV$3*$}vgZ6v|tSx14&a zRm%dWMj|WC5U7jv6{q(# zj827%h@%^)HI&b^bl881`R%y z0H)%cN38#q*GwMonOs^2?{ac+7|FrI1`ZxHaL8~fHl@0Dav{E|HF4w!=4e(78CY3U zlWIYaQCzfht+KjCaZUYqFI9C`>l!EHb@0l%*^QCfCcL9Fb7o!bzzW4xS=Xqz&a75k z6O>_!D>x1RrwmbC1#=Xaw-8yyrML+zuKenz;FR<7n`;5X(|P4hO_Z`+3laA?U!z^k``< zS5AU*_8FfH+tVkDEKr}8F3b}3aj-ilf0n?W_1e(2V)SI7V_HC4y)-RQd9%!@#*|_3 zpvl#>6*bM3RY(pTgmxY>4A;rc6%9?3XX2!=fzy?q2HPu(cJy0*##66FF86f(thTt< zmZbk0IhJ9nPzk=&2Ngua)0fLwWu~*29K1AS@0Kzv^zAXTk6qtu^Z#9CvsrDdnl#Uc zCw%nmg3l9vO}^*Hetv(iP5*s0QS7dBs&D$~@!dDS^2+PUuRpUi)N4_{|1*irrn3zh z55b4-41D^}7T=F|uUyq*Q~FK4uK4e35y2Jy{20J6YL`g4i*aAIK8}~s&eua%yXL>D;d`P*|YB) zecP@f&*dyHp1*A2kzU_f{}Zeo_5w{&XI9OusGk#QWEI_Z!_<=P<{v)ZvC;m|>g9_z z_L`mge^wEcv;ha+v3gwBowj3d(ZdIOAL)MEZ@U*~_WI_(XGlVS>ZR^OAME?%^gB=3 z{^RSZHz)Ku=|5^?xfPmck^!%6V&oo|`pg*PvdjxFFFk7K-1GMg>-EK?|3oY@KDAYW zli5Xv_Gw4I$KSs9!0#)j?OvO;#j~u}4Wl}XmXk@k!Z42c{dL#N)4uxh@j|G0oyx;4clYzFm!+?!C6!Hth zlD@2B*5U7V5B+pe>Lo_ynX`Ir>fJdk)eOx!-bu%J$1M8po1-o||IhhdAMZ7U28I8K zn0ChHo`!Q$AAkJXTfg1r{PL_9w{AUUi&0tnwNXjW&W?C;B7=teiu1k2p-^5${yZWUtR%@ zr`9zv^lA%aa5^tgdHhg9nH$Ur1;JzfxFS_(75FwzHMyI-HM2)j(>CHVd=_Yq-lloR zNuoHJSCOGs@sU);sQgeUcU&MJtwK^{Tu0gM!2yS=fO{oCPi~Gs7zld&U{{va@M;h; zwYjE7r4aPVas^>3-&T&hNNt}lWZXqLL0>3PJgy+96J=#{Jn{y&>@m^x^bulDj`M*syHXVxHwRd8yuH|BjV9(8q23u zxh2>bB{NH~ft);d&=(vR(!t8W$-Lrvwc@TiU{7(OJHOZylq;Sf)f@xdT?d@!9p@ca zSeO&4s+tjp>KNEW2{xGHDRjF7dE-1fV43U`PWE0^DUx;2dH!*}f&y>84qB!=2D+yN z4b$K&_T&X}VR=HBEtO6u(-4RLV>ku#=yXY#gvvDj(lBY`l z*cc>5p8Nm^YDDaxB z?n>S(Y?4?z;$1hNy}w>tF-PrPPEOY-m9AVFXI>5&zIsHS}C zR3dgx_R#F%Doz{6xk1J0k#Tx*ipGu0&C4$c|sdh>FM@HZ!^ZYphpFiJS;0xtU#r7_Yw!?|e+2z&1$wsCaS*tk5aGZ@QPQQfH zQ!QUj7nay z+glI{6^p)uFEuwc)XizrePSG$S_utWy09=d=Gx3bPP>dza*tt#XB55zpyyhBaI$t zUbVqxA_=of;Q)?tfU1d2GRA_O{5*_;<3gc-VVa+Lp<=Xij2Ejk&XO=vYZQfwiUPje z*xr49b2YfGohF&HnD$sIA;Jjh@p(f<9(ciRjCEU{Rmui(%6?SUd6|TeC=2BV{lzep znvK=9M*DOvm(Y0OJGhGrfYdAE1}FS%E=2B<^{$Tc!UhKh5tweJpC!< zy5Z_Xj_MY5K;JH-^5p~zeExvHFgK^Ns)_AECO5sZzOEW(8jc{)Nlns+qw1lG??D+A zj9-yAcbuo#C!uPrnps{yoed$>*RUr*!WLulTP19H`8i(r8G(Z0yqtW=lvdQ$V>W|V zew(VxF-p-(zuCiCi>q=Q&M9nA<@Sh#6C+c|6AI-A;9W3+p@MmKLCl`t3QdJ=1nlzWntNQCC!HEL)X8*yrzrbg=O zN$===^DYS%oWgM&mXz+IMD0oVxex0q=9Z;!!VUK&8B?~$B5ABB$41Y1IPNAKuMm!p zqc|P{j@o1;GHdE=wfY+D`Rrn*vVBnTCQ4-W#ZLm3>3YA=(`>va$q-C7q|DVpHAeJs zn+fiYe)#!Jf;$s%%XM(|F>qT5E)BcbTOPrZpH^QL;Hpy?m7G6)Wm{QgLjYtxN(I`U zWH>j)kaDpM6ieehtgc}I`xsy+5N|4&`zX}aSld=44v1tMfo*c*XEU~V*1a&{-t^iP%o}ehl0m?8RRoxB{@TS!jg&7Q(?F92R!Sp#%QI3T~3~K@Ng032w zEvo1N0!kf+pL!^lM&5~l+oOY%o0{MrB)D&i@N+BlOM@E$xI+xCqAiUB0rn7qZ7asl zZm1W)Oq%$+^rTi3U~+u~_%H#E58`L#$x%S6Z}8CsI6GFgP6bda0fnpZ^KoWWM>38! zqZ@$55wf)3ZGQ+Cf0JM*KtuM|;|-$DpUlKyqCK_Bes9Z^F+= zbU3Yp<^t?u8B7*FfjvQB{bu3k=%G=tVu0Zz(;zWc_5`z&Rkau}XN`$=>*8ZUp#&34 z;FGMTs{l3Zv}lVF6n11%uWloaaj5Glg1U7TeoW|~TBkk?s8KSMu#J-d^EAQSdn10R zQ?uSO@Hj(C-f@bkMOXeADu4cJ{G94hlyudWQXL^yM;pS%qmpL{=)yJlc@sdQl4&x8 zECOQYIf6N~7C(3AMSE5^&~}*&CbtoRJx^eduEWnKFg99;v;pi!Sq2fgx)9)AAh^rd z`&C9Z4Fm>Y!vQ3GO9=`|wu$d?3LQmG9`` zgzP5+>ScoZ^fvsI!cwKY(9>vm6(D5;}0)k#6fU-yNb7xspw|_g{kaDa9BexGhy-rXcK8ByU*oOA?oJgul?R)OtEN_8Al#h$Q0<#kt?PCCJa`UTX=hJ^k}?Sh zDyCz?R$W;!eTFnNJ_>-h2;dz8NQtOQ)W5$WU@@e8H$W*tB-34iscl2mNF1)+#XyDM z0@T}-c>YWL44sCpK|2SbnghIipf10vwj!qdE-HWaKK#^)^1lsGCeX>6>i%U^(5;I? z<=#UD?@;1r0@wtA?mGt=m6X3#fM^X8z`F!+;aB+iUH}*}WDW+8Q05!D7^&PxsNp?I z7{11j3*!Z6p(_VaD~JKuO#n9#K(zo!&d~7y2#tx)P{sQM;ra$Yw*i7Paf}KPtwVhN2|0eo&;*!wO3(`UMpp_alBLpfEHJIvo;-W3=r{D*P@LUL`P3jAD+_w0%_i z`~&#eB~YI&Q0tW7D=PXo6+Ny-Be^PySO||4;A<+o>L>i1fwG+9^90(Mw)ut%rya!4 zbpmli6mg7{zopVoQ0dnM>Xwm8X}u2tNW>s;EQ?1Cxp-@5RX5-x$6B+d9jz$e5%^bK zvHz(R4pBRVMGUA1uT<1F*B3O@ z;cY;z-YfwBK){2#19&hzxL!L4nUs{VoXKi# ziUBUcYU>2VtnzB2RJ#ECBf(yj2-r%%a;hJSfekfO*TJIV)1<1_4Fcc+0=y~-fZgFd za==Gp0qHZ+jZJibx&})0{zRb1Bm=Yzpq$<(2sDpJpfy3qYoLP!dkeuX0Bl#r>;92S z0h*^7JK$=Y@~fv*(dRd~HL#+(p9y4j3P7F$2)Zij{A!X?SmF(r`AZ}DCBfpz1Ycn2 z5TVcaVbuJMMosgvfNHq_?j5TrZ?6%DpRiYCA{F(`k)}F)qk=jZj;2vi{2bJFh-x#R z2)Kd^G4QdGa`P8aIP9aViBPC0qD6=x8xx>mJpuj;foEC(e(lAIGG=Gb1SRE%FBy1a z6I$Ll{$T$FCyoGJw>$ukjG*`GXK& zGzy4MTc{|-g+TjIZ$s~WIGJG(ehfDQ*t7vZhK=+?n<9*~k-m(p~{Gt@|5kf!>(155GdvZ0WJGriT1A zE|6`#6y-0fZK4;oOH6BYpDShxOh^V>%>z|AWO{qv`e-jbLaUf#rVr)wX3C6SX zS^cIIs|?O~VDWvy1hwiBf`5tNzX3c%hIT#ip+v*&V@>CrZn!Voa3I_8caC9+-!QV6 z$+0`>pm8GoOu~=(Z2Xua_%U2aKO{E;tq&V%J+}uf$Qo${)ksU8Mp~UT(t4ti*8hyO zVrQf!He*Tvzk*fWaurmh3zS=FA$I>Ea;r-~Zr`gEW%Tr(O848x5}83aqs~C&bRlw# zBy6RcZ6OHK^M!(47}@mli55Z@{uGG$>}{F(^6v zRihH}IR@sHW-yaFwYx7iDKl#b=9&H@Y?dqt`Tf?6O&iA2nLq}ydDMdBr41PCI7=!My08I zTAnxTDFQWs+YR)I%1Em}1io=kpR`U?E_lYML{Ko~E(v?xo>E^hfV>Y_*=|1(}MLnXn9lk$KXL!%30qI{KCcyf4Tn zK^drTV+I^43wuif?qaXh%Etrv3p52pFtONAWJgLTg}lJCm&qX9wHXT}Lx)6~pj+6N z32`w|flUv!o-Z7Mh~0o-LSsTnK>f;NI-M9XEZ0{W0Tl&s&16*=iL#eMxcOxPPh`BW zq_oWKS6aT%q(-b<`7E-%P^}Vg#GRKH3`C0ESWE(~q&54rqRA+Fp&&26#ODp<`6o$a z8NuqM7GD5#kOCZ&pg8qHDhUQl^Mam8SqVrG{oq)D-GP=`GxA%xf^gWX&}F2ZQ3l;;OGS z*B$mois7~TO5wh`^SpUV%eNW=s^BtBN+AfMt)!q|g+ZkT?qJ2t`U%j^9<0}o0ddbU zb^7IPgEGD};v0+!g@(5$@V>2AvoOR4{VMT-8B%r8X%oT35laZ0AqyA-$FTm>yT3o= z86Wgf536N8Gr<}z43?B4f|a1`K`+$0V8mA(^7}l#Qg&e68<}w@1yv(q^smBWWbZFeVgPTt^;|6sqi?ME$pZhpWhA$Kec)fYhms}_w zhJLM85)|`q$G0Jo#|#C15zzN4qwllW$}sP77sS*`t#|z)oMu!$t-v45b^C*%(ulV( zGT!Y+J41>GQG0@NgQUeU%orumv4tLY1(M4~g`lr!-WKIJf>^D$eqnDZSr~M2Vfi5O zvrbpeXs&9Q6RK*!rkt7Oi1vBvnroXdozoQc&0`g1Q5uG^5~0P_dNqE7aoc4VQ{xht zA5ssY%4wkGTYXD;g25u6Hv)~8^*9v;?Rss~clZ<(NYtAz^k;9$c(m0hsYB#K8ZB1p z5A>fIlqU31k0%1{^^h4mq_rqSuobTmD9z=wBWf3A%yCuAfdan;<`R{4UHLXBhOA!< zk!_847f`xDn7pv3&|B=53|=xY4(m#1bCUJ8nfslLs4i82{<}y$Rc-;)XcrUgYSHwt z=^i(bLA;{ryJPX2rc7yUddM6M;J{oap~cFNct>S_>=kB`#tM-pWjQ4N2Cl+0*xrBRG9Q$;P3aCHYLK)u}e#W5U?;#gB0^lO~A7&8mnOg zMNc8|z5^D`j|LR%B;v0Huno@e_qmmP0Lr(RlrJqRqDa&hY=n^BWes#5I&LIV?91~< zd}R&QkwJq74F-?DqM^;?B6I&sb#bGjEes6)>Gl@K?5Dly3yF`U|Jb-SN+cfOXmpe15 z`S&rxRXa>sh~cBQ5E7>Tm93hs(vq@pX=J>&#Fy{$xXEW?G%!C8!T}}Zpa^?LYWk68 zq|h0Cr5Kilw?{_h{HWpxp|KAgC@mglm#&exCq5$Nzm^6uj=;c6Ow)jN%vz%oo)lIC zKP|s&ozQ?h%jZVL@5_a}Bi@pdU`YhRj^qc+0$}wct>B;kz(TYw7=d#nmHOqML}l8@ zP-zGa=XD3lLXm=!AbvgWfXC~{1@C~kJw?!tF02P6wPRAyV}+1CAn{@dnN$nTQpj6U zIw?{Zgs_=Y#`KJ7#8D_XaU|8PR)gZcUTZA)crtZIlLgs(ikLQ`6nzBI2+#h~2bO z`*l;yPo>ePLu>g5H}M4t2=GGmzxP@HYiu(iVFMV-(2NAagCeQzt9mdIvNq&{l=woW zL3recwPq&8tFgjy&-0VNOJ9FTP!8#PL}+LzQb^ty07WY~0l38Kcb62v{|%I4hj|I$ z--QiHP{Q%#Ji%QOU|m8;n&u5_^&=Gc&uG8$ORJ zf#?IRZ+Y_ErEZLOd>Rw&V&q_5*hX^XMS3Slb2$>oB^bfrBal0;&YQ>=PgE|_DVsL{ z2Nb=2{7^978|q^|D__Ar`YM0*l!o$#InX<9SYH8!SHiF+D0R`6fd|EW2nbJU`Ach# z78Lp@M*6CPc>Hh&Nu@;-9E-v~#}p32yF_n+`z;l8fcE3zqpB_{HFBa7X5BUvgzCYK z0r`naG9myKS$U(K6Sd!sO6J%FIaQYNMrwnXJ<%rhtOi6(s|+WdDn zWoiRHC#fvz!gYEbBy$y$jHw;N07qT$I~q0k*i@{(sez5=3CdX28VCJQPqM}|07SeK zePNnx3+qcA@Nd1S!eM{e#7MwBK9`0ZotYm_F;^nB3h@Xt)C)0R2;pNIKPfgMA{W!J zvK;;HBR-Yb*jz&~3=Z=bxVxHhcT%)Ngv&f0Hv1EnmZZBvG7gx67M7O6d14?k-l!atlr>I}yuD!r__?pBP=O0iQsw&y}PU^Kt`9JmscS=?z4 zmqMxhu)l}2_8_zQm%j1UcBLtfd%WA{CpS!L{9%B2H3mWrgh51Pou$GwdRm^+!X!*B z%G_|I{4`35X)gs+Myov?wHHP5%ZNzPy{Wm*m36nVqM^FJsSZ{GyMWPdIFJ=6nmLoj zZcm%Z2^ol!^mKS9mX^3<;viHkU0*Cjo(UUsNEIZaDHby@+VO!b5YC5@LWd>d&p>9s`(ia zvUW6gm%4rmz<(b(0WmFiG3H1K$|;y%mQD)8kAWv!?B#x!S@XoHi@t;50G$J~mjK>6 zF6C0Du^>VDSaybJ3?;`ya)!vkI-u7NZIa5&2`DWN$&=L#)YltBiANnvOwif{klLu*!)d{1AJg=WK(Fwuf*$&8_-kK|L%g_^KX9EE#n4k99P)cm{k*f5T_ zhl~91E2JjC^aR0XPicfgtm838k?kf`5JrQ>(7zWj$-3+L>#tZ0SEbt(@=sKj>SMn2 z#zCaMc?w>YMYC^%WD=FD|E)-Z(knVW!1``qj($!yH`~W#aa8By{$BW_xO* zy-j;PBO*SJ0ZPcD!F){^z)~`k!TboEn9>M0sgeaGjq2JT5g_;?O$o|EwKp)Q-xnG` zOle7oDqn)KToP)2u*Abcq9qYHv1)`~5@~P6OlumtA$w}2nEL;L{@U-8NFUuf}xGx+L<*xvM6?aM*|Z5p{hR;ABWqHQqdib zW-|@j`6zpUsog~{M?()7z;p55Lm|DzRHX$WdN9McPv1^?K19k&X|^o&^pnw^L!x#r zglQ_`>D=TBQ%A$oB~W5D+%E!|v^K(QGDWr5p(R*Eh|Uw319~5W$P<+{@gJXWRd;4! zA55Y$52_v$OBn(Tr0!UwiZgns(^WBgkXrVC0kh>2|CxwAL|9mYJgOwQ$6PJ-7;-Mf zDhP$?SdclQ`iN53AzSD^B~&-Ns)634FD}2JdS>%X?0D2L)6u#i(5N~tP*dy|nN?K- z256FJzJ09Xsi|wM;!OmB@%hj|i05(UVqH86-9QedVoeLurnM-xjDYwoUyGs;T%z*z zmsA(_iA=`^OH|T8mGtYQM6e5_wodeEEEFn-n8v=Q*8?eP9pi=|uAbb!+JbVfh@`Z? zFw8cL5v6RYt1Q4mPCYfDVD^4a+eoi?zgkFvV!cb5<+(uhy(Uq>6vKh=y}F_S-qYq? zSW$}!XH(M;jA}3$BKR!WouiS0<&a(75blI=XvFC&V%l{#s$Pbw&HejB`RXgkNs7?h z=|Y4LfYIL>ql5Gn#Pkmu5a*~~RcHc6dXG#$EpfiAZ-Npp?~Ke@7V{NSqz$7YLCoL$ zSQ&x(Vv`VvG`~dTPoJiv{tjeZN7Ofm6n|ZLr8?bqgLv-3pNIn2Q-PpcDXnUlSzU`$ zB>1j2A^k5_@-=IsZ0ACoZG{q6IFfmo9?0JG#?zRd7X_4ec@Qpr*#a?v+Mr*y8 zB(xbK5hC^b5D*kg;WUyThY?R3@J3JN`AS3VuGXg<2r5{N$v?M5t1YK}c zRqssDMjq8S;EPsi(H+PyjpUWW*wH$zaHc=gs~j!sL*0l?9_Nz7SSKL7^iYzL9SQQ6 z-@=$QluxV5$sUa3dUP+z@!jg``)xTnqjE+mi?Aialwh$ch;>YAC=O%&qvad$m7wIP zlNIkoT6*#4V|}Xtvp?0}jF2}JEJIfnGZ=iLRiVved})Clmjiv)VqA}q5+o=W3E2q! zVyiYnKGYrOib8Bjfo=cOm+G#Vzf4uILeNwA`_KotmJ1RSk2UJjM`8%eF$S`hd1CO zgrZ%@#TSB%}bf>7k!*mp=UcmHF>sPfO2a<2yN`p<~w3%hXL{mF>!`z;Tro?RH;Im zNz}978%uE%e|ck*myT!@Ye`Nh>`wIV1m&o9XpmbiALXn<7#^H61gAu@wB%$Nta-E+ zPOI)Vb7q{eTD&wFU9w6?ga5;H`f|}JXw8lF3BJ=ube$fG`FOmO6YIEYFKtDU76WNZ zDzTOo$33IKKW%*hd%{H{t($ibx&>Ap@j3~>*TY1v=KP~i{&CK70Xy>vj zmPvja3s_3-*|9JgwDm2G@M1h*+qNa6L1A7{d5~{;K>PX-D3lh9laM)H7tt2WCxKu| zF-76U>Ivy%Qyc5A6%*E<>O1*`s56QPA7rKMOEl}y2jsAx_blHq=U8bL5=*5n3%h}-Tb znCZm?kdIZ&r?$I+LbM}=*XFTy?5m$T`4Ofl!)!yEI8mH!oqjQdPm}WzVIy@C zDR7DuoOV}Pr_uFX(P2f8>LOVLZq~TZ$1-ge_=3K9629x zWXWU@_p|{yjnl)=0mF%BG*^`RNN-dMasH;)F8w$Yj=<;zC+NnRQvhVuizNt)B?6Y+ zA&tRoYa-Zw5|v=@6ZJ!Pi8oM&7#rKNVQ%S)v9y-GVHUv|66G^-xP}DTWI_xI z?G8?3X9Y#DtO9g6X^Oc|h$|V$eM-U76ZZ5^tpvy@M&^ZRUu+{O4k0cXvy+6v!ND zojI}98fjuiRsLlOu@Q}L#A~s^S|eN-6Zt}HPDH*;ig`$yLMs9|8c+Cb*P=;Q!r?d5 zD*|%2(Oxdl1{LZ#S1@Kbk7_f~N>Fan+a7h&z?4f^9(>zUq0QcEOEy(k%xI*SMC!48 z0#}%~Wr7lTL5u)!dn1K78ck_AHaaOaD6~%-3%YGYEzNsK){1!))^MiH)K+oS?K-sJ zvzYaWFZKP_r>;cBbGZ6Jx^C6kD+{pg6Q#k3JC}CpMcut_XqYqDd}QzlGFjFHWvMg- zm%4Lv;n(Gs1SjzA9U^i|8-*_w9Sgf3u>DW!T4b|NQ)@?9v7@C*wkj@qy4Z$}6Q_WrEHLdXfqv1|GM8muKr;d5%clf}4H z9Ly`Dl`-MfWBNpI5Aa^vW`7i0c&}k+|A^s^*4}?-op$H2lB)U|yye#DscR^#YzQ>N z>8q~D!>i1-Rn)R4f^+OL5jw~=>&ZGu{@!=`4ny8579+5`g>4&%Goi96W?TEFG#bt& zQ+f>}^y_}qU1^Iih+5B7V|lb4xZj3J8Me7G1rZ|`DadSY^YcUbSSE-GhEXW?YjyiB zOs&}SND+q(iIFK))38UI`M}urPsb2yYkwazef`Z6_MM6FC|ln;tQP~DhGP1J)oNb= zTepgNq({suhoI`u^&kkgVK!gl4Z(hBvslUgp9G>VhPG2Ales5wg4ONYe#8MF8B+BG zrF%PL$*B^q9L0Pl%;3x!ni*6VvzI}(OCWM{5C_}D%v-cWb%Y{yL*Z*t7+pr{gtXs0 z?8X$DVu!S+ut@5IM=R#ty%o4>bXG4PZ(0D~ashO5DQoJVqt1f1l^<)?7@U-r$;@mV zHL6hU1lS>iW`Oe{)vh@yxmdW7>ZQHGi=<*~Cl~EA&^j<*lNGjtj9}8JLd|BecR@}- zW#;>`l{g2di8*QnWu9kq)c$%;4hzsB-$bupcIe1bOdlDMH=DKD|C6Acu9i@IoQO^`r3eMO^pH%OrG#=)rAbhuA2Pf%8~m+FCt7Ml|&>H1&0?ea6Na2`Z*U zrq-f?N?8MSXIpo%5`o3>sQuPY%9#F^l+lu`n37Rv?H3*9X;Inj$N1zyGr;1B*&a3d z0p4lRTWClW{k8|Pe&$(X1Bu9_we#e5`bMtQH}W9R6Qf+Rw?V-i?X59Cl&I*f0-O}S zjQJ@9eFJ=^1mzWduhzY_6SG8MR?K&OWQ6gKZeNbh*lBz1^{jiXlr&deQi+s${pkPk zw)&j_8jk2`;C5TAyT|ioMu6)+)|mD1AGs`}fodU4>QE`|2os~1Fmqm@?be?M!!6jz zJkqc32*6t+zv_v^fE=cHCf*}yKNvR<_0AF6j3JnI^kQ4)na~h1>7WVARJenDo>mZz zfk)kL3V~x^d(4zKoRuD`wjPjm>Z9xlkA2F%Ho{*yar_nr1lT#CRxr zn-^j6TM^@-QK+Hotnvu0PeB9e#VWiNB?dmW0SHQf7#3`-81$%$RN*i{Hf|Ul;V)27 zcq6&WY;}>|tgfnr`GTiSyCUI*?9%hoLA;R$|GAQC;)QT*0-@;>+me6)itK)oor+l5 zYcAlW7kDv0n4mqe`=0tFDE7MdBB-VY;5lttwj-Flnn z%`GdCw%$?<}C`(8#2XXPhl&Vd?y8M_@%wta4N`SrRB*4<);|Y$Q#xW z{iRovgyLrcV;S@MM+_scA3I?7cL2_}nn>_=!1qOe)D}Hxl^;7&^(e|9PPPycV^?|s zxJ%T8@qKq%qyl!Dxt@PUN80!W2EeamYx@|8D_O9mFB8M3dLp=n@O>xI^%WtA-XQxD zEV0v4IbSQGupEWOaV{ca;~wl;rxkYQ(UXV$RZOQuKjMp&B1R3LH=y6RC%sc}JBF%N zY^W+}fTt8)K{1Y&PcJ%uz!F2!loa2P+i ze1ggWig}4&+iZ%Brf&6I^y2bHdM%mnh}aFDZ)BGA5{V)HVh7e-$g6^#6sk>HFgO}r zf`Kd`ro_5G*-?w+ooO==$uUFkI9-aZNWFvlMle?t(`o8V8^ykBv+)Q+P;8ek}-sF+wnK+0nR=zS_KaOcMr-Ci)gEaZiXd zweAo2`Ia);C&xpl7_D7V??TL7;BVp;B(`)SyfoTpvbbNKQ|{FP`m*S2xtqDMPQaneqx@9Mu&#^(_!#AIWpP28Gyh z0^u@om;lA)5bD=!E^P*tbe3L&U!@O2<5l-IhX@d&jA(D8%>T9?1miLJlzWQ2{Tsc1uc9f25S?^%k;FA?ye9yh$BxTc1}!Zv-&aqm$?z%W{g z_Jd2IWa>*}OEY`TkD>|OfQr(H&vZ{!@Xa+E;>CJEJ@oocsFnG|IBVqE!M4#Q5Zcjp z7t=zczHPLHmB=7TP@_QLWoTW#9g>a7?DPY?w~8ei-T4RTFAw#`+e9(JqhFy)ceDCt z#D;g>x#6H6D^;-inA-gsb$QGK(eWy)r%u%ZVCvgLi$MD(;7U~fv`9-Sum?8@HD&WI z*67k8o`m%wZ91dd6t|jbZx;{-C-q` zcqp)QgXDwca%2Ib^HmMAsv4wi^hyiupjX{!?8}Dl0b@-Q9Cec$&ASto zdj6cE&uEbG7%C32QTGJB(D@6Ia__}%%c>6ejgFDT?-r)FY4WTMHT7jy@(=Fd`=D4P*^643LD3l2-4da3R=eL)=rNbE-C)$ zD7Qe;OcrufBaZQ2YN61|huZMYU${oN125pPFhTUaPzoI_mhfsJ5%yNTcF2X~d{ZRl zQmH)Kb&b7Qgr=N5mr-5nt3B9L$*cF(-H#5N}aP!Y1NM_2F)9a}emF zHGLQrVPr8;QbTE81PMw@Ft!SZfX3Dphv^C~78GVwSORvdvK8z1R=BP~+CXQx;@90_69LIR!(`39nQA|5h`JQ0ijBJsS?;-vS zn!T23_CY;j-E5E4qo4$DMoXg00x^H;VM1lOSoMK}5|5gpin*2T#?-y--G~Ohm)VFB zXkb-GsvEJ^US2T+?*;1gWCq$JQ8}SMy?hJruLmaJ=H}+=%7K8xHfb%zc8e(n6Z#vQ znkWXN8A;kEqdujL$O^f&-Pk+VYLZs#)g&F3sF=E*mEL@|1%+0z+hG&71QzR*CzG($fn(}Dp_>qhMJ@k6%|uII`z=PcF9`QZ%X1y zFNflLDS)LT7Zp>Ie2QZ?`Fis|6rIQvF6Z8(>;bcvwes<4-ukJyf>PPobkWOPn^;#H8It{X$LWeXeCMVmY zWMKgYi}*wyR9V}!G}@Bx8!@ceQg|V?3mxjHm~7FSr&Tps)KB19*jL(ctSmqpQI^hr zR7|NHHD9Q<(6{X@__Up+t`_7sR9S54q`q`Qq+&WLik5#_&cY7%Q_;}@l8Pyle}B%> zRA*7k=>EN*cwnW|CKZz>3W2p+l~a`*jbf+zxDp$^5emV^1nMR;%ja0i8>$n!nw4w=vg6A(cQF}STqVhw1oby2{q7yqE zX{ng{YY5vpLbIDfr(PP%SjK9lAw(T~ zoEDmyVH83QbS|f2I#FvfElL+X{Ii>~ofYY$eT54n1OMU}tSg2U;Gh#d(IZ6x@f|V7 z96lq4GIZdlVj7;$J`cuvB;LESsNV*&(2Kej$VL6AmqmQ%3;a=^r;|eE8brPZfiiTE zC^!*RkPP^ko8GL7X%{OpmJVR1p#f(HSTu$k(R-_yeVl`i-`HGFG0v(=vOTf=j1Cx8 zOno$wsaut3GEd)f10{6$sA4)n{p?m$Ui2mz=Z8)XCDW*WNXs&{63=-vsbA2tjJ2c> z8g>D&(E+29-l^58$C!3g=;%?!}6Mu3K}noj5gH|% z@znlzw;iY*AFx6gv~^_nMe`0NmC;Y@h&YsuAatl$G&8WeB>sT%2XTr)QlI}x!MiTBiPtpT`29oVW3a)e; zwikdTZ+DkRBk4grwI7whg>@T|F1q6b&H z4cn)Ju1bxlzwwB4sbwxSk?B)qceJLHT9hf38>!LLh?FBJ%1FUo=SeY{7%4_vh!naF z+iO8ejYf(YkBF4Z7#THj$fzSSx}X>%19zP#BLUDrLSGpoGUzsJZw48S-CgHvlyt{a zCX@x5P#SgAw4SfK^)ngGBI0@gk`afy&J%YOpn)WXh{Ba_!}d!-+{GGYJ@JT9HczAM z;y9#SMx-2#qKp*Wb)J;t0SzR_;XmLq|Dby>5WH>l&3XP=EouB3L>QsiZW7g z*LhM-0yL1EhzpTIw_*ELAZ4*eN?$y(zoeR>JE>&6tjX}@BHr28#35xJk&=m`j1=5;o)jmbfy6;1;7Ye)`z;`4y+(=) zkBF2y*4fv`A!8$vF%ZQV8Mx~_8N&b#BtxlsTs^hSVmu{k`mnayt)ry%UEMA3Lmv`h0YEXraMyXl&Hyx!goq$q={9WN3&QqvcYUG} zR)VK`uV;_s^?VYCvd@XKFu)jPxa&M+69Ek*6Nn;Q={9Wt9+Z9C-SxFb*(5x*cT-&q zW_rHWQPcXh?v{(~2O{oFKr-TR*LmX31~iaNCZceq+pzr*h&$NbbwDHT96Z$%cTh)7 z>jB*@C+-&_E&@nK9PT<#+!R0qNjVXPE8T|ee}TBe-Ce(H#8u#_-Z?ugoU`BK(DgUb zRS6(Q7w$Sw*9<@d$$3N%u5=r=_vn%DO6Z|F`Zah&J)ny9fP@}x9kaw9fSie9j11g$ zo{V}x14$iKk1O4V?MH!(WQ~mT@rcN%W@IGCA)_ae(STx%4BT~|jEevbBo|WkxYBLd zo(?i>JzQ3el8bqvOK`Cd=wUxa)%2|xWbGL}Fza|Ssnx2dZwQ1|1ca9Yrq$Mi70I9? zTE=CRF@T;hH_lG1wBQ+I1$Uii1!r*)D_7t`tk7-PJ{GKu>ERlsv9cIXnXTQb+1fEW z!dpk_Zh3Dwort>Fob)LB8fCiH5i6~s@Hf)c8xU)6NR^Sn%>pUqp0vbqG5ec}`ZP;E3Ql@C6ti~fEWd_r{DRIcCA~M#X7$XCBohM@*pn>Ej zsvcLm4cn_h#x#wLoAHRqIGd3%Ee;vy5gF@IjFEx6&Xchb&_HqvRgWv(VuA!RYBVx# z#Us`*_`D)_-)iEJQb(lRhN6rV+;yImI{^(OcMu7<(rwt@2vW}1NVyA-h?GVqk@Mq_ z(L`k2jbe-p+;xhKElELxFK8%4^}ewEo}_$k5yu4$MfUrW+>7RIOd7onW&8{~Qk}+L z5;U9v>?L9Qb`3jjOYIM-*dGA)GKL+gPGR2+$d*&^h7vAzTu6j;i`Gm=YbLwSOLk39 zc1=xItvo(atk{FeJ0MPM`NLkW)|%u+^A1oD#FdC7OqTWmpkfu(gi(n~2l&(b;- z>1C9jYk_=rkVJ5z1)%+d7F}*D!+%6K+)bnFl2QF&RKe9D`Tv6#d>@v0!6$MVFLWEW z&jK$^$*zWESG~r_>Bs<&oRJfVk)|jkLqT{^iQnx`>xHZs7YB>XF)jL5=Y z!1-XppU3%#3jazX7@src1mmvr1kVICkW><(xYBLdz8nN!m+V@a>{^oSx>}>T7J2q6 zS@_pQX>MH#;xyqe;Y2gxU(JdBSK+T9Ug}T<6F%-b&&x%C29ieN0av;W+t-4Z)yb|^ z$*z?eBNrnBJW9e}9c4ro{wmG~6aGrhM^yMX5y6)L6(bmTohNuPpn+sA5sE9_hV8e3 z;El^(>4Ci8D7TbSGIM?tOg z-cBrB1>}qc+;yIXm4F74Yl(DR={9WN1QzZ}cHN=Te*>QC>Ax#Vzbx=OIz|3wB7YTd zGxBlQdGc=sG?1(%vT>zb3^5@8zGT<7WY<=WgWHgy@2~eoIcVJ`wZPU+vG4$~a66DQ z7I4>j7VZZ$kZdN>ai!a^{SmP6aI)(`jsER;s;B?qDE)E^JlHAnA0_f10B%M;?mAEY z(|`t&M~Q4)={9VC8stBj?0O>EwL|0J8D!`??UPXsTAz?wU`MA|c!pSb7RVV3xa&L% zZvYxdUL?|SrQ5LmMX>OEvg=um{x|VdPyh2#`sEgQwo~N4MC88(+>CtOb)NiB01YJX z64|)YZP@-6$bTc*^;)v)6^(;WdAU9O@)<77z5STnTYR&<^$p$zi?1X*f;HUJZf$B4nQvvUidpumefT5c(ZT-;2+q zyf2W~x+@tanJ@J=iRMc@W1_)b=S6b>&_MDHE+iVd4ckA4Xg*AKy|2mTCtm0vzx<4g zUM?SUmKG^XE%rT!(62pO9F7 z!!srp+;v_oCaf(1$)7+%Vxilx{X2-|n`GBl$*wOo$(VV$F8tCJ7rkV@0Vm`$kjCtn zB-0Hg?LQ>z4c7jsWLi}~rZHb}@iNhT8Amh;glvC&(R4>%>kmRS-;-#1;29GQ?m91; z-hc*@p16=`=r(Ns6{0zm>^i8)r4KK30>7Myi(W2=I7>`p{*dHy5=z>CkCDqCQMt$( zqvBvQu{Z$@Bx$&iSm-uvH>c#gj43W9 z#dSoJjEk2W$S&AWF-4tqX7fTh z{Nlz%FPCJ_64RJol3a38(tdP|TzW<2B5RC_gNY>(_+m}3hj8_ZFP1#ywH__R(v!sE z#WN-r+;v_oAwUC(4;K;(-G=QaK`bYvxO!`1IfEA};g>Kjda;}!smsZdSV~dS-Y-Qz zB%K@;OREaV)TMV+T{`xuwK1xUu%8@XP~(x;+D{0oF9~V_o-sk;uJeK_2Q-kJg$oIa zZo~F82r5;R(iC2zf?q0e(Mu^+Ql0^ll&VnDZjX`DfT)yY<6TA&n*>U4v&#NVw~~kX8d4NUq0)ghaPtyB9+8q`2IgoYwF{YxxDQ>Bq>) zBWY8iB&T&KX)lN|*@aO#wW@$jo7|n%rke?SVSGWYM_y}z5L7-1Y6G4zLE*0Rf>O7o z-3BBiD84N%073aRDQ!j;a%hhkE?aTYYm;BnrZXfd(S9;}C`L+WM5QEalZxX%*QRZR z{EYZQqOEGJAt9t72}x{6!(Hcv^cpY%$+NhSkmwdG8W7U76jxP>t0Ki!u1V{4(x((aU z2QPIgu9+#W85$#>A_F{1A-K9IBXZl##` zUl5_V(rws&F$lge#dSf7YgUS@Nuzl`^7O*LFiLal1t3m~f6n4Wv-oEdCz{1S+X{au zBL4=g9cc0YONgVNP!Zz@cb(_xA3y`iZ^Q|%bQ`wM2S=BuxLQ(Nmul=NSnUC)QWIPr zWk+s;7S0W8f=f9!(I!|x1RJ!4BiwbKVA?spQe zzIdvqe_51%S>V@piu~n7em~%5fjYR%v;AZ6GuJhy<0vbqih-_Tx7Gd=NN85YHSy5!~|1*GMKxT$Xh82cE%ekwi zh@!(7Q5O@+>XlW@ab4Fnu3=Z#bp%8VYt9iPDrQ}^Rm>vh959P{O_;O%-cMCmhdWn4 zHm~pRkLlB=>YP*0d8)cP-0m9SU(w%A?Qbs)8Eok7Hu6GmWaUqnSNJ|~PgJhSoG$gw zmbtore)wIJCLhI)?xJF9ntgOB`;|9>dezHZQ~mU)7rLUWlhfl>qGk+bjvBP$)aYF& zg6MrLQ6sf%Zibqh`r8`QWxEt&~l4t(JM}l7PR8D=rtmO?1m66Qp@IkXt}q)y}Q4?GcdEa zx6=zlnCX3?JTv!_Nq7eLV1^mJMr3C6&mS%irp!dKqZ=`IdwLx;cUGZBFB_Q$t5frL z>Y^D5H4liI11NLUpcSX)2)qe$2tw3IEt@Bx=CS_vk-*D%Z#2P+iHJNekNK3`hxXsy(vVH;}N1o zYT3LDEwlRD7XmGMM~KQZywJ-&d0J+XMi?%yWoXgsK<3qo1L?JtmMC^~!{r6ft$6wM z^K*Eja<-zsR-GulP87YG5cP_PI+rp>6k2hjF2kE3dO=7;NiCbVA!>GDN$=WFqgQPZ z;q{vCAjSt?BI!G{C1M2DfoNUNxF)<=-q^*cSO?FlsS^n zij#CJ-UPWGA(Et)%^XPjq`&JhvN@&H0aNiCc2AnKdI(xcwuF)toR z65J^(`O015r#OmxCb#}EnJEG36S{FYJA567bx6Uk5=kH$H^!CzPb?MXoHR}8< zr90u(*blE^ji|eP{Du7BG=505D#aJ+jjYf5Re2*Tb6+;>vXK|J>F33zX*F86POAE+ zrd`{)aml*t)bKGMZ*Z+shpEBTk%9K>Wc2O6g5OWmj|#8S>u4izM=XyJ8&b>WkFc?6 zogH0gH>$H6)Y(yWcD+DI7ZzsoI#Y@XF(PEs3?ZX4glv={WP_BDQR(h_zsilfTTNJ5 zlcuB1d$8h55>_@7D{EouSV5ZyRyM_*DC;4_iqx{%4pz3Qv*YUQmUVV)oh=7S#!wAs zet?w3$2KV+*&}0|r$d7z+R}5NK@yG4?3X znnctM!{PDCzkQFm$0z?vN97^D6Mo@{&5z1sX)l`8V^sQ*WKWGKce&!*#@UIiMBK;ZD2>@?T*`N-dkq$l;PYdr_U8QfJQ(3EV{;>1F!hl1u^@ zWfGW@CZLnSTz@2;UkQJz;NMMD2Oq6C{KxPn$OD3nl*&ugz`wfAPOGz51niGfk;i^@ ziak4ird5T0MJ4p>1pNtuI_PM{p}&eZL7o+4q|~yx9q3V=y`|3nx6a-e(7#4qe&|Hi z(chAykN%q(IX70~zeD(6C#=JdRviB)coXDp;YLa=o0;IBQD^V3v-j57yX)+oA%#z= zHV@aC#pChgE<0wVSw!~(F-&6j`ZU}mc6U07-RZOVO_SIcv==?xV-oWv$t3oWX!sIS zM*~`M8hX#?X!u^~6HIE^JOd3+)!8TN>|=HIk-$M8>R>2y06mowkR60i`25`@_E@GH zAMpgFli0JuKObQoezfBF2YUQX!c8!#W%CC3U#qjP)Y+Hn?2C2w`H;dOs?F>=vr4ub zUrV!yUIAk0#+Q5=t{Y!WyYYFS#c%4yA+#6G?$M3DBBFzY!TOjw4$z8oP{x}eqY>gjDxW{X!S{9c+d#k; zltPT}y%lsl_S@*4w!O zgO-6m|EAP;JNzzO6*8}@6m^u#7igR!P-UQhT zAr7RL%_4BHaJ^lyKE6vmkWz@r^wPp<_Ss%q5F+CD(R?rU7s7*ZcL>pnBb5gm+rjmAP`zC;uyd9-J==?O5P5cnWZ3D*uyZb!W|{i@^-f2Io!HT_ zGdRP}pnCU!!riqkS%s1#MA|pIi}ExGc^rwf?R?SQ&M?r3Z|B? zx61{NuJT4#dvOgS&(ZQeJ@+7er3^>cVrhm|jG&cLjc2kb4;NXXR3z(!by;C2_mMX@&PYQ z?N@L2skeItnrgjWFE4r{@-*$|GjvDVK^dC*U}+Alpy{BLrtFax$8ap|lVNGEDlE;X zxCd2dX@2UW0~3}G5KDb2b1b11XK5hb1ZhTyC8=dI0hW%aw};o;!|Ls!fv7>=w9Sjb zh&)k8kW3g)lQKlLV`+}8&tDi%N{NbMM>n1h_iQ_+4$ClgXceZ0DDb4}Of5xSbY#NR zL@~8AWsWJd;!F+0n;^eOh$*RMb0SO~UvH1Ax5osUR`PZ$dodi5r|Ec~p&L)9WN2Ci zOLKAsO{b(ZWyez-!?AQ+hNWYwu(Yb;o>HBqPU@nQ6P8XAOCu#|uC1Wyx|F8uc#2~p&()F;1~+>dqm#U$LNoT9LgRwwCH>Y_&yk{%XGr%~oeLMu+v zC3q9$Y=lUX%G(+Yr&;y(g?js3y?v(MJ{73C)LUQX#Z*L|s#%#4^;(9i%ds@C*5`jH zdo86ZiXGjEdLc8Sp397=XEOUwRT*7ZsFByI6L%$b(W?n@uZXy5lsV$iiW3*%O_1vl zB2H@A{14*Zuea~k+qdiO?7-S>-t=}a?m*;Od*3JQCajM$tWC$#d{n{O$0=*s0T#z_ z1MJ-lQEzAVXICNZPPOrIb<*ylF8U}T?L(1vH)W1AwBn>agEv7QMTj)1e6|Q_U)I~t z16|L0qvyPM9+9W(OVS9_)^{1YUcl0PTc3Yo^<7F=6g#>>_POU=e2orA-ir$GU3D&I zQ5SuiaPf_}c!@H{1zK?~-ocw7uOq~T)Uufi7e56e-t`vmdGS6XPsC4|anq|IeQ5Fl zmZr8LPeiYVZ00)@SLQk0q7r5=6FCW&cnBO6XY|5 zc#v8)3pb3i3pUuk4e>*+@4V6XUi^T_Q?Xz}IwtyOsQ3{}vq(e!`B?vyiYRt;W1=rX z!UM7}CVoq(zH|%(V7yG9TRbk+?c47e`|Ff>Zyxb5+0hxLjz@w2ejfm9E3MP z_CkmUsb#YwJgm@QZG&C5!IlCc2UBM{8vNtI;d(r{LYh@%<8h%kbe4^&(Ia~IQ!AUO zloqn)(=V@>G)zeyLR&5=wBkt}g*QPCS29ScES^nLoeg%C2D@^|XfhQf>oe+9MxAL! z*?wAu;_w>A%IUs;xVxGFA5AC+7_B(qGw>$J@dApJ%J*jgUbn%HY_MxJ*fj#?GpWn> z!n!HuXk?}r)=CLadtr^Vuu3n;F8lS$D5Z7|ExOduil=rZ-UPWwDIukn&8DO_y1{PL zU^fVPO{0R8Gkq~S%`4j%8~F@$%tRZc`@S#62=G;ea)8l_1AYK+g4`~kNU3GB4S>fr z*ex6E*all}u$u+gGpNpY$G8-Bv?cg-0@Xn=Hf264H@K(5IxIFzi>q{qdkMxpC*M|? zJw$6RGqmEFy@)qK9#=+4sb#Y}neEnKcWJOYHP{^*>~hk- zNHg0mEv}MTQV>Y{%ov>Ym0_z$U`7PR8D)Z zFWC_j$8aO&getWBUa?NEPRp{?MW-dSoGMzDqs-BQ zR-BgA@g~Sh2+<<7Y%YeD3mfbO4febSdrlx{4R5-p7i%H%T zbNtD0HC>oomXZ_2j&9IgkTMdT=jUGcTjM!tc`|@TDez_0nOdK^=+cC#OT^R$lsTr* ziZeA9Z-Q)s5K~gi<~o?Vromp-V6O}`{mI*H>BUxvJWbd5Oxzf{F+hD;ZdmA{@XU5ZgDN#}E=*H6>DI?Ks z8K!Pc%aieRfCArFov8ztvFK?@FvKi2r(tKY#xKDM;h!y4fer6 z(?oA~q!*JAd72*anYi)vWQL}ruryCp(DY1gVr zClZz(7fZ)b=2${2#L_f=uo7^G5r}M!RWae5Zdnb!OE@ zKPcALB(p_h^5i1gywQ&)UAq2}{d;s7JH0L1l>M;wz^^TQyJaKIYt<2#3tI79w9W@X zCMpM{)Uw&0T()hrTQ}ORLLyrGgB*P#>nV|K(?qh}wzbbg$wXWEJkk%}cM#y?3FQEz z6$c!z5OQ)1SSkxY0C@LCyIZ5(rP1ybFl!YNvrd-$-BZlbZh#B-4ZC>Ij{KcG=*bg~ z`1MtX|EgTHLW#=-t#~f+N*@=;x%ibn_9K^l8tq<9{?ZQXpe5RM+LyMsK_6x z$EJXzqd^Aq+C)dChS9;eROD1e3~lAHYAm(Vp38PY)SmQd#l=_*XRAsg3s1kU=9A`GfX~G=perrUx!9l)_a?K`WNH9zZLe z!XUf}vZ$aVrIyY0q;PGcy*l7;qco5I+7y4b2d*vz{|3R=iX{#{T5*hs0Y zoDBS18|}@F_NI`*^4@L*FNPxWmx#CeK5(z6O>azp?$Kf-=8nev1U5ZQK8hXP1a@