diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b292dcf4c6..96965dd6ab 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -12,6 +12,7 @@ option(ENABLE_PLUGIN_ADC "Enable ADC plugin" ON) option(ENABLE_PLUGIN_SWIOT "Enable SWIOT plugin" ON) option(ENABLE_PLUGIN_PQM "Enable PQM plugin" ON) option(ENABLE_PLUGIN_DATALOGGER "Enable DATALOGGER plugin" ON) +option(ENABLE_PLUGIN_DAC "Enable DAC plugin" ON) if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${SCOPY_PLUGIN_BUILD_PATH}) @@ -74,6 +75,11 @@ if(ENABLE_PLUGIN_DATALOGGER) list(APPEND PLUGINS ${DATALOGGER_TARGET_NAME}) endif() +if(ENABLE_PLUGIN_DAC) + add_subdirectory(dac) + list(APPEND PLUGINS ${PLUGIN_NAME}) +endif() + if(ENABLE_PLUGIN_SWIOT) add_subdirectory(swiot) list(APPEND PLUGINS ${SWIOT_TARGET_NAME}) @@ -84,6 +90,7 @@ if(ENABLE_PLUGIN_PQM) list(APPEND PLUGINS ${PQM_TARGET_NAME}) endif() + if(ENABLE_PLUGIN_M2K) if(NOT WITH_PYTHON) message(STATUS "Python is disabled or not found, M2K plugin disabled") diff --git a/plugins/dac/.gitignore b/plugins/dac/.gitignore new file mode 100644 index 0000000000..8fbfccd316 --- /dev/null +++ b/plugins/dac/.gitignore @@ -0,0 +1 @@ +include/dac/scopy-dac_export.h \ No newline at end of file diff --git a/plugins/dac/CMakeLists.txt b/plugins/dac/CMakeLists.txt new file mode 100644 index 0000000000..6c38c1fdf5 --- /dev/null +++ b/plugins/dac/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.9) + +set(SCOPY_MODULE dac) + +message(STATUS "building plugin: " ${SCOPY_MODULE}) + +project(scopy-${SCOPY_MODULE} VERSION 0.1 LANGUAGES CXX) + +include(GenerateExportHeader) + +# TODO: split stylesheet/resources and add here TODO: export header files correctly + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/ui) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE) + +file(GLOB SRC_LIST src/*.cpp src/*.cc) +file(GLOB HEADER_LIST include/${SCOPY_MODULE}/*.h include/${SCOPY_MODULE}/*.hpp) +file(GLOB UI_LIST ui/*.ui) + +set(ENABLE_TESTING ON) +if(ENABLE_TESTING) + add_subdirectory(test) +endif() + +set(PROJECT_SOURCES ${SRC_LIST} ${HEADER_LIST} ${UI_LIST}) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS REQUIRED Widgets Core) + +if(NOT "${SCOPY_PLUGIN_BUILD_PATH}" STREQUAL "") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${SCOPY_PLUGIN_BUILD_PATH}) +endif() + +qt_add_resources(PROJECT_RESOURCES res/resources.qrc) +add_library(${PROJECT_NAME} SHARED ${PROJECT_SOURCES} ${PROJECT_RESOURCES}) + +generate_export_header( + ${PROJECT_NAME} EXPORT_FILE_NAME ${CMAKE_CURRENT_SOURCE_DIR}/include/${SCOPY_MODULE}/${PROJECT_NAME}_export.h +) + +target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/${SCOPY_MODULE}) + +target_include_directories(${PROJECT_NAME} PUBLIC scopy-pluginbase scopy-gui) + +target_link_libraries( + ${PROJECT_NAME} + PUBLIC Qt::Widgets + Qt::Core + scopy-pluginbase + scopy-gui + scopy-iioutil + scopy-iio-widgets +) + +set(PLUGIN_NAME ${PROJECT_NAME} PARENT_SCOPE) + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${SCOPY_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dac/doc/dacplugin.qmodel b/plugins/dac/doc/dacplugin.qmodel new file mode 100644 index 0000000000..0605151259 --- /dev/null +++ b/plugins/dac/doc/dacplugin.qmodel @@ -0,0 +1,1646 @@ + + + + {35948335-b296-4884-832a-b5da5e589d72} + + + + + + + + {728b8def-eda3-496a-b288-258cfec92e92} + + + dacplugin + + + + + + + {450c3a42-faa3-4e4c-8ab5-f583c49bc0cb} + + + + + + + + + + {450c3a42-faa3-4e4c-8ab5-f583c49bc0cb} + + + dacplugin + + + + + + + + + + + + {6b5370b5-495e-4fd3-b245-9506acbaa33a} + + + {69a1d3a2-c792-4843-bbe0-635291eabc37} + DacPlugin + x:-80;y:200 + x:-40;y:-30;w:80;h:60 + 0 + + + scopy + + + + + + + + + + + {d8b2fc83-50d8-42dc-ab57-6e1b2988f29c} + + + {b28ca152-d909-44b1-8979-1b2a65ede465} + DacInstrument + x:-75;y:325 + x:-50;y:-30;w:100;h:60 + 0 + + + scopy + + + + + + + + + + + {001847bd-b45c-4a2a-98f7-44f510d121bd} + + + {ccc0028f-bde5-486b-954b-b473f28aa45c} + DacDataManager + x:110;y:325 + x:-55;y:-30;w:110;h:60 + 0 + + + scopy + + + + + + + + + + + {d751eab4-4a5f-480d-af19-bb961c57eb47} + + + {c5737e32-c9e9-4013-9202-3b987358ea38} + DacDataModel + x:315;y:-55 + x:-135;y:-130;w:270;h:260 + false + 0 + + + scopy + true + + + + + + + + + + + {efd0fed7-2ad2-41c4-a228-8b6c784d5054} + + + {b6236a7e-294c-4939-8111-2fbd06a90e78} + DacAddon + x:360;y:330 + x:-40;y:-30;w:80;h:60 + 0 + + + + + + + + + + + + + {22b5fbd3-a568-4907-9df7-e7c27e25d4c5} + + + {4c26d043-8b30-443d-b742-befb83e6e3e7} + BufferDacAddon + x:355;y:425 + x:-50;y:-30;w:100;h:60 + 0 + + + + + + + + + + + + + {10e0872d-4bd3-45ef-b673-b933fae40fff} + + + {de57220d-f7e0-497e-9b6d-b9f8676101ee} + DdsBufferAddon + x:235;y:425 + x:-55;y:-30;w:110;h:60 + 0 + + + + + + + + + + + + + {00dca2a0-8a3b-4421-aca9-b05ea6695054} + + + {6d75a4b3-2123-4836-a4ed-b18d1281b96a} + VectorDacAddon + x:535;y:425 + x:-55;y:-30;w:110;h:60 + 0 + + + + + + + + + + + + + {a1c9beb5-acf1-4533-a37f-27b0d91826e6} + + + {e267fadc-19b7-4ac3-9a74-92668c294553} + {22b5fbd3-a568-4907-9df7-e7c27e25d4c5} + {efd0fed7-2ad2-41c4-a228-8b6c784d5054} + + + + + + + + + + + + + {514f08df-cb97-4fae-9ad2-463540ceadf4} + + + {8b19bb46-e8fd-46e9-9b08-49465dad7b81} + {00dca2a0-8a3b-4421-aca9-b05ea6695054} + {efd0fed7-2ad2-41c4-a228-8b6c784d5054} + + + + + + + + + + + + + {e61e3dd8-6601-4885-958e-eefcaccd6efe} + + + {51270397-5245-4d84-980a-3059612ffaaf} + {10e0872d-4bd3-45ef-b673-b933fae40fff} + {efd0fed7-2ad2-41c4-a228-8b6c784d5054} + + + + + + + + + + + + + {1bef8e10-4162-4cf7-bd30-0312c05d435f} + + + {d1adfd40-6c33-4196-9a3c-0a56d8bf7224} + {6b5370b5-495e-4fd3-b245-9506acbaa33a} + {d8b2fc83-50d8-42dc-ab57-6e1b2988f29c} + + + + + 1 + true + 2 + + + + + + + + + + + + + {b8b01c1d-8cd2-4d13-bec9-98b414981483} + + + {cf9f3e3e-4c4d-4241-b2af-560b200459fc} + {001847bd-b45c-4a2a-98f7-44f510d121bd} + {d8b2fc83-50d8-42dc-ab57-6e1b2988f29c} + + + + + 1 + true + 2 + + + + + + + + + + + + + {f959c854-3a65-4f50-9c1f-6a806b3a02ff} + + + {067c0553-7804-4965-8f39-93b41cc9c89b} + {efd0fed7-2ad2-41c4-a228-8b6c784d5054} + {001847bd-b45c-4a2a-98f7-44f510d121bd} + + + + + 1 + true + 2 + + + + + + + + + + + + + {3895ae41-2fb5-4523-a910-918d590d66aa} + + + {52172c08-957e-455f-a0aa-77bbf34008d6} + {001847bd-b45c-4a2a-98f7-44f510d121bd} + {d751eab4-4a5f-480d-af19-bb961c57eb47} + + + + + true + + + + + + + + + + + {601689d2-1172-431a-9ee7-11b953c8023d} + + + QObject + only IIO Control + x:580;y:-140 + x:-430;y:-240;w:860;h:480 + + + + + + + + + {d9ea776e-3f47-42bc-8007-5a3a2cd35094} + + + QWIdgets + UI elements + x:580;y:475 + x:-735;y:-360;w:1470;h:720 + + + + + + + + + + + {e34240eb-861a-4096-97ac-a9feb92aafb0} + + + {652fc0a1-b3ec-4e14-842d-5b14f0dfca7e} + TxNode + x:685;y:-260 + x:-150;y:-85;w:300;h:170 + false + 0 + + + scopy + true + + + + + + + + + + + {5fdae907-d771-48c1-97e1-417cd1a2b7e2} + + + {54db4cb5-d550-47d5-ae80-0f789a545013} + {e34240eb-861a-4096-97ac-a9feb92aafb0} + {d751eab4-4a5f-480d-af19-bb961c57eb47} + + + + + 1 + true + 2 + + + + + + + + + + + + + {bcfc5636-2ba2-440f-bd41-3e3c5c354da3} + + + {3694d30b-f4f7-466a-bcc8-cc5532ee1ee9} + TxChannel + x:150;y:635 + x:-40;y:-30;w:80;h:60 + 0 + + + + + + + + + + + + + {29f69aff-7c09-49e6-81a9-bbee14d2c706} + + + {275184be-6a85-49b5-a191-d977fb75cb83} + TxTone + x:120;y:750 + x:-40;y:-30;w:80;h:60 + 0 + + + + + + + + + + + + + {6c314582-1707-4c93-a24b-100ca43c3602} + + + {506a9c84-5733-4953-b5b1-8f7a7e7ac378} + TxMode + x:195;y:525 + x:-40;y:-30;w:80;h:60 + 0 + + + + + + + + + + + + + {94e650b5-41a1-4554-81c7-1304bec84193} + + + {968c7391-2038-404f-89ce-8d04d0fcf6aa} + {bcfc5636-2ba2-440f-bd41-3e3c5c354da3} + {6c314582-1707-4c93-a24b-100ca43c3602} + + + + + 1 + true + 2 + + + + + + + + + + + + + {9ee20803-c720-4075-9be2-21c00855d148} + + + {4e07f66f-f78c-47cb-a103-fd5a6dbacdc1} + {6c314582-1707-4c93-a24b-100ca43c3602} + {10e0872d-4bd3-45ef-b673-b933fae40fff} + + + + + 1 + true + 2 + + + + + + + + + + + + + {37c26777-a4cf-4ec3-be3b-f34ab225a990} + + + {3866a4f9-52f8-4f5d-9d3d-e7f4b6fd9ef8} + {29f69aff-7c09-49e6-81a9-bbee14d2c706} + {bcfc5636-2ba2-440f-bd41-3e3c5c354da3} + + + + + 1 + true + 2 + + + + + + + + + + + {86e91b0a-7ce9-4337-9a25-3c25453ae39d} + + + x:580;y:420 + x:-110;y:-70;w:220;h:140 + + + + + + + + + {87266803-5a6c-44ff-9b31-3722d5c5158a} + + + Future components + x:480;y:355 + x:0;y:0;w:161;h:29 + false + + + + + + 1719920526345 + General + + + + + + + + + + {69a1d3a2-c792-4843-bbe0-635291eabc37} + + + + + + + + {69a1d3a2-c792-4843-bbe0-635291eabc37} + + + DacPlugin + + + + + + + {d1adfd40-6c33-4196-9a3c-0a56d8bf7224} + + + + + + + + {d1adfd40-6c33-4196-9a3c-0a56d8bf7224} + + + {69a1d3a2-c792-4843-bbe0-635291eabc37} + {b28ca152-d909-44b1-8979-1b2a65ede465} + + + + + 1 + true + 2 + + + + + + + + + + + + + + scopy + + + + + + + + {b28ca152-d909-44b1-8979-1b2a65ede465} + + + + + + + + {b28ca152-d909-44b1-8979-1b2a65ede465} + + + DacInstrument + + + + + + + {56078443-14dd-4114-94b7-48487488b15c} + + + + + + + + {56078443-14dd-4114-94b7-48487488b15c} + + + {b28ca152-d909-44b1-8979-1b2a65ede465} + {ccc0028f-bde5-486b-954b-b473f28aa45c} + + + + + 1 + true + 2 + + + + + + + + + + + + + + scopy + + + + + + + + {ccc0028f-bde5-486b-954b-b473f28aa45c} + + + + + + + + {ccc0028f-bde5-486b-954b-b473f28aa45c} + + + DacDataManager + + + + + + + {cf9f3e3e-4c4d-4241-b2af-560b200459fc} + + + + + + + + {cf9f3e3e-4c4d-4241-b2af-560b200459fc} + + + {ccc0028f-bde5-486b-954b-b473f28aa45c} + {b28ca152-d909-44b1-8979-1b2a65ede465} + + + + + 1 + true + 2 + + + + + + + + + + {52172c08-957e-455f-a0aa-77bbf34008d6} + + + + + + + + {52172c08-957e-455f-a0aa-77bbf34008d6} + + + {ccc0028f-bde5-486b-954b-b473f28aa45c} + {c5737e32-c9e9-4013-9202-3b987358ea38} + + + + + true + + + + + + + + + + + + + + scopy + + + + + + + + {c5737e32-c9e9-4013-9202-3b987358ea38} + + + + + + + + {c5737e32-c9e9-4013-9202-3b987358ea38} + + + DacDataModel + + + + + + + {09138ec6-386a-4a4f-bff0-f5b1c5f46225} + + + + + + + + {09138ec6-386a-4a4f-bff0-f5b1c5f46225} + + + {c5737e32-c9e9-4013-9202-3b987358ea38} + {ccc0028f-bde5-486b-954b-b473f28aa45c} + + + + + true + + + + + + + + + + {cc02cb4c-6e34-44c0-b522-d7bdcedb161f} + + + + + + + + {cc02cb4c-6e34-44c0-b522-d7bdcedb161f} + + + {c5737e32-c9e9-4013-9202-3b987358ea38} + {ccc0028f-bde5-486b-954b-b473f28aa45c} + + + + + 1 + true + 2 + + + + + + + + + + {c433ac4b-fe62-4107-9934-bdd34774a79c} + + + + + + + + {c433ac4b-fe62-4107-9934-bdd34774a79c} + + + {c5737e32-c9e9-4013-9202-3b987358ea38} + {652fc0a1-b3ec-4e14-842d-5b14f0dfca7e} + + + + + 1 + true + 2 + + + + + + + + + + + + + + scopy + + + + + {64e989e2-e417-41a7-9285-c0359906c090} + 1 + - struct iio_device *m_dev + + + + + {9b3da6f8-683e-417b-8d1a-b7933a16071c} + 1 + - QString m_name + + + + + {14b4cac4-c987-425c-928a-15a07ec557b8} + 1 + - bool m_isDds + + + + + {8d619998-6a30-4f12-9269-d0d4196fe6d0} + 1 + - bool m_isBufferCapable + + + + + {a6e637b8-44b4-4be8-a69b-5e0970c4ebae} + 1 + - bool m_cyclic + + + + + {69d47c20-5310-4eba-9e80-c8c1d28bdfad} + 1 + - QList<TxTone*> m_ddsTxs + + + + + {841d025c-8284-4790-bc93-c643780c9c10} + 1 + - QList<TxTone*> m_bufferTx + + + + + {6d90c51e-087d-4b3d-b2b6-0e051c48af60} + 2 + + bool push(QVector<> data) + + + + + {cf5b35bb-6b48-40d9-b5fb-c2f12e25cce0} + 2 + 4 + + QString getName() + + + + + {6fdd3592-e923-4694-a6df-f24341f6e7fc} + 2 + 4 + + bool isBufferCapable() + + + + + {314e4455-ee74-49f3-ac65-3154497cfcf4} + 2 + 4 + + bool isDds() + + + + + {b5cbb1ab-2bca-4cdb-88f6-72bb9aacef4f} + 2 + 4 + + QMap<QString, TxNode*> getBufferTxs() + + + + + {296d3f56-e4ed-4d04-be69-b4a529773f93} + 2 + 4 + + QMap<QString, TxNode*> getDdsTxs() + + + + + {5cfca7c2-8a21-4077-bfbb-b89ed3370509} + 2 + + bool enableChannel(int index) + + + + + + + + + + + + {b6236a7e-294c-4939-8111-2fbd06a90e78} + + + + + + + + {b6236a7e-294c-4939-8111-2fbd06a90e78} + + + DacAddon + + + + + + + {067c0553-7804-4965-8f39-93b41cc9c89b} + + + + + + + + {067c0553-7804-4965-8f39-93b41cc9c89b} + + + {b6236a7e-294c-4939-8111-2fbd06a90e78} + {ccc0028f-bde5-486b-954b-b473f28aa45c} + + + + + 1 + true + 2 + + + + + + + + + + + + + + + + + + + + + {4c26d043-8b30-443d-b742-befb83e6e3e7} + + + + + + + + {4c26d043-8b30-443d-b742-befb83e6e3e7} + + + BufferDacAddon + + + + + + + {e267fadc-19b7-4ac3-9a74-92668c294553} + + + + + + + + {e267fadc-19b7-4ac3-9a74-92668c294553} + + + {4c26d043-8b30-443d-b742-befb83e6e3e7} + {b6236a7e-294c-4939-8111-2fbd06a90e78} + + + + + + + + + + + + + + + + + + + + + {de57220d-f7e0-497e-9b6d-b9f8676101ee} + + + + + + + + {de57220d-f7e0-497e-9b6d-b9f8676101ee} + + + DdsBufferAddon + + + + + + + {51270397-5245-4d84-980a-3059612ffaaf} + + + + + + + + {51270397-5245-4d84-980a-3059612ffaaf} + + + {de57220d-f7e0-497e-9b6d-b9f8676101ee} + {b6236a7e-294c-4939-8111-2fbd06a90e78} + + + + + + + + + + + + + + + + + + + + + {6d75a4b3-2123-4836-a4ed-b18d1281b96a} + + + + + + + + {6d75a4b3-2123-4836-a4ed-b18d1281b96a} + + + VectorDacAddon + + + + + + + {8b19bb46-e8fd-46e9-9b08-49465dad7b81} + + + + + + + + {8b19bb46-e8fd-46e9-9b08-49465dad7b81} + + + {6d75a4b3-2123-4836-a4ed-b18d1281b96a} + {b6236a7e-294c-4939-8111-2fbd06a90e78} + + + + + + + + + + + + + + + + + + + + + {652fc0a1-b3ec-4e14-842d-5b14f0dfca7e} + + + + + + + + {652fc0a1-b3ec-4e14-842d-5b14f0dfca7e} + + + TxNode + + + + + + + {54db4cb5-d550-47d5-ae80-0f789a545013} + + + + + + + + {54db4cb5-d550-47d5-ae80-0f789a545013} + + + {652fc0a1-b3ec-4e14-842d-5b14f0dfca7e} + {c5737e32-c9e9-4013-9202-3b987358ea38} + + + + + 1 + true + 2 + + + + + + + + + + + + + + scopy + + + + + {8f63d0ac-4121-40b9-a64d-36561bb6b770} + 1 + - QString m_txUuid + + + + + {7e2dba80-af33-4ef5-bc01-09cdd1fb583d} + 1 + - QMap<QString, TxNode*> m_childNodes + + + + + {30fefaf5-80d9-4575-b5c0-37248b322c52} + 1 + - struct iio_channel *m_channel + + + + + {f9a59238-a1bf-4414-8ea8-20be81b44766} + 2 + + TxNode* addChildNode(QString uuid, iio_channel *chn) + + + + + {5fb6fd88-8b21-45a7-8c88-31f56a3e69a7} + 2 + 4 + + QMap<QString, TxNode*> getTones() + + + + + {cd25c747-4347-482c-b27a-b1e9216f67b5} + 2 + 4 + + QString getUuid() + + + + + {8deb3f25-ee4e-401d-9782-df76fdf6249b} + 2 + + struct iio_channel* getChannel() + + + + + + + + + + + + {edd8430b-75e0-4004-8f23-8646955d0181} + + + + + + + + {edd8430b-75e0-4004-8f23-8646955d0181} + + + DacTone + + + + + + + {f374fec7-46ff-42c3-b391-3ea2997c42be} + + + + + + + + {f374fec7-46ff-42c3-b391-3ea2997c42be} + + + {edd8430b-75e0-4004-8f23-8646955d0181} + {652fc0a1-b3ec-4e14-842d-5b14f0dfca7e} + + + + + 1 + true + 2 + + + + + + + + + + + + + + + + + + {8590b4cd-aef0-4c34-b3cc-745c580cd0b3} + 1 + - struct iio_channel *m_tone + + + + + {22b005f0-3780-4466-8dbe-0778a6106c4b} + 1 + - + + + + + + + + + + + + {3694d30b-f4f7-466a-bcc8-cc5532ee1ee9} + + + + + + + + {3694d30b-f4f7-466a-bcc8-cc5532ee1ee9} + + + TxChannel + + + + + + + {968c7391-2038-404f-89ce-8d04d0fcf6aa} + + + + + + + + {968c7391-2038-404f-89ce-8d04d0fcf6aa} + + + {3694d30b-f4f7-466a-bcc8-cc5532ee1ee9} + {506a9c84-5733-4953-b5b1-8f7a7e7ac378} + + + + + 1 + true + 2 + + + + + + + + + + + + + + + + + + + + + {275184be-6a85-49b5-a191-d977fb75cb83} + + + + + + + + {275184be-6a85-49b5-a191-d977fb75cb83} + + + TxTone + + + + + + + {3866a4f9-52f8-4f5d-9d3d-e7f4b6fd9ef8} + + + + + + + + {3866a4f9-52f8-4f5d-9d3d-e7f4b6fd9ef8} + + + {275184be-6a85-49b5-a191-d977fb75cb83} + {3694d30b-f4f7-466a-bcc8-cc5532ee1ee9} + + + + + 1 + true + 2 + + + + + + + + + + + + + + + + + + + + + {506a9c84-5733-4953-b5b1-8f7a7e7ac378} + + + + + + + + {506a9c84-5733-4953-b5b1-8f7a7e7ac378} + + + TxMode + + + + + + + {a9860929-6ae0-44b4-918b-15b2aeee1a1f} + + + + + + + + {a9860929-6ae0-44b4-918b-15b2aeee1a1f} + + + {506a9c84-5733-4953-b5b1-8f7a7e7ac378} + {de57220d-f7e0-497e-9b6d-b9f8676101ee} + + + + + true + + + + + + + + + + {4e07f66f-f78c-47cb-a103-fd5a6dbacdc1} + + + + + + + + {4e07f66f-f78c-47cb-a103-fd5a6dbacdc1} + + + {506a9c84-5733-4953-b5b1-8f7a7e7ac378} + {de57220d-f7e0-497e-9b6d-b9f8676101ee} + + + + + 1 + true + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/dac/include/dac/dac_logging_categories.h b/plugins/dac/include/dac/dac_logging_categories.h new file mode 100644 index 0000000000..e39fb062e7 --- /dev/null +++ b/plugins/dac/include/dac/dac_logging_categories.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCOPY_SWIOT_LOGGING_CATEGORIES_H +#define SCOPY_SWIOT_LOGGING_CATEGORIES_H + +#include + +#ifndef QT_NO_DEBUG_OUTPUT +Q_DECLARE_LOGGING_CATEGORY(CAT_DAC) +Q_DECLARE_LOGGING_CATEGORY(CAT_DAC_INSTRUMENT) +Q_DECLARE_LOGGING_CATEGORY(CAT_DAC_DDS) +Q_DECLARE_LOGGING_CATEGORY(CAT_DAC_BUFFER) +Q_DECLARE_LOGGING_CATEGORY(CAT_DAC_DATA) +Q_DECLARE_LOGGING_CATEGORY(CAT_DAC_DATABUILDER) +Q_DECLARE_LOGGING_CATEGORY(CAT_DAC_FILEMANAGER) +Q_DECLARE_LOGGING_CATEGORY(CAT_DAC_DATASTRATEGY) +#else +#define CAT_DAC +#define CAT_DAC_INSTRUMENT +#define CAT_DAC_DDS +#define CAT_DAC_BUFFER +#define CAT_DAC_DATA +#define CAT_DAC_DATABUILDER +#define CAT_DAC_FILEMANAGER +#define CAT_DAC_DATASTRATEGY +#endif + +#endif // SCOPY_SWIOT_LOGGING_CATEGORIES_H diff --git a/plugins/dac/include/dac/dacplugin.h b/plugins/dac/include/dac/dacplugin.h new file mode 100644 index 0000000000..b2643e5c41 --- /dev/null +++ b/plugins/dac/include/dac/dacplugin.h @@ -0,0 +1,41 @@ +#ifndef DACPLUGIN_H +#define DACPLUGIN_H + +#define SCOPY_PLUGIN_NAME DACPlugin + +#include "scopy-dac_export.h" +#include +#include +#include + +#include + +namespace scopy { +namespace dac { +class SCOPY_DAC_EXPORT DACPlugin : public QObject, public PluginBase +{ + Q_OBJECT + SCOPY_PLUGIN; + +public: + bool compatible(QString m_param, QString category) override; + bool loadPage() override; + bool loadIcon() override; + void loadToolList() override; + void unload() override; + void initMetadata() override; + QString about() override; + QString version() override; + QString description() override; + +public Q_SLOTS: + bool onConnect() override; + bool onDisconnect() override; + +private: + struct iio_context *m_ctx; + QWidget *dac; +}; +} // namespace dac +} // namespace scopy +#endif // DACPLUGIN_H diff --git a/plugins/dac/plugin_src_config.json b/plugins/dac/plugin_src_config.json new file mode 100644 index 0000000000..742d94637f --- /dev/null +++ b/plugins/dac/plugin_src_config.json @@ -0,0 +1,33 @@ +{ + "plugin": { + "dir_name": "dac", + "plugin_name": "dac", + "class_name": "DACPlugin", + "cmakelists": true, + "namespace": "scopy::dacplugin", + "device_category": "iio", + "tools": [ + { + "id": "Dac", + "tool_name": "Dac", + "file_name": "Dac", + "class_name": "Dac", + "namespace": "scopy::dacplugin" + } + ] + }, + "cmakelists": { + "cmake_min_required": 3.9, + "cxx_standard": 20, + "enable_testing": "ON" + }, + "test": { + "cmakelists": true, + "cmake_min_required": 3.5, + "tst_pluginloader": true + }, + "resources": { + "resources_qrc": true + }, + "doc": true +} \ No newline at end of file diff --git a/plugins/dac/res/resources.qrc b/plugins/dac/res/resources.qrc new file mode 100644 index 0000000000..94bdd4ed68 --- /dev/null +++ b/plugins/dac/res/resources.qrc @@ -0,0 +1,5 @@ + + + tutorial_chapters.json + + diff --git a/plugins/dac/res/tutorial_chapters.json b/plugins/dac/res/tutorial_chapters.json new file mode 100644 index 0000000000..d1d9b57b80 --- /dev/null +++ b/plugins/dac/res/tutorial_chapters.json @@ -0,0 +1,104 @@ +{ + "dacbuffer": [ + { + "index": 1, + "names": ["MODE_SELECTOR"], + "description": "Change DAC mode (Disabled, DDS, Buffer)", + "anchor": "HP_BOTTOM", + "content": "HP_BOTTOM", + "y_offset": 10 + }, + { + "index": 2, + "names": ["FILE_MANAGER"], + "description": "Select data file for the DAC buffer", + "anchor": "HP_BOTTOM", + "content": "HP_BOTTOM", + "y_offset": 10 + }, + { + "index": 3, + "names": ["CHANNEL_SELECTOR"], + "description": "Enable channels to be used in the DAC buffer", + "anchor": "HP_BOTTOM", + "content": "HP_BOTTOM", + "y_offset": 10 + }, + { + "index": 4, + "names": ["CYCLIC_BUTTON"], + "description": "Select whether to use cyclic or non-cyclic buffer", + "anchor": "HP_BOTTOM", + "content": "HP_BOTTOM", + "y_offset": 10 + }, + { + "index": 5, + "names": ["FILESIZE"], + "description": "Displays the number of samples in file, can be used to truncate the file", + "anchor": "HP_BOTTOM", + "content": "HP_BOTTOM", + "y_offset": 10 + }, + { + "index": 6, + "names": ["RUN_BUTTON"], + "description": "Start the DAC buffer output.", + "anchor": "HP_BOTTOM", + "content": "HP_BOTTOM", + "y_offset": 10 + }, + { + "index": 7, + "names": ["CONSOLE_LOG"], + "description": "Displays information on file load status, buffer output status or other errors.", + "anchor": "HP_TOP", + "content": "HP_TOP", + "y_offset": 10 + } + ], + "dacbuffernoncyclic": [ + { + "index": 1, + "names": ["BUFFERSIZE"], + "description": "Select the buffersize or use the amount automatically provided based on file size.", + "anchor": "HP_TOP", + "content": "HP_TOP", + "y_offset": 10 + }, + { + "index": 2, + "names": ["KERNEL_BUFFERS"], + "description": "Select the number of kernel buffers or use the amount automatically provided based on file size.", + "anchor": "HP_TOP", + "content": "HP_TOP", + "y_offset": 10 + } + ], + "dacdds": [ + { + "index": 1, + "names": ["TX_INDICATOR"], + "description": "Label header for each available TX", + "anchor": "HP_BOTTOM", + "content": "HP_BOTTOM", + "y_offset": 10 + }, + { + "index": 2, + "names": ["TX_READ"], + "description": "Refresh all the IIO Attributes of the corresponding TX.", + "anchor": "HP_BOTTOM", + "content": "HP_BOTTOM", + "y_offset": 10 + }, + { + "index": 3, + "names": ["TX_MODE_SELECTOR"], + "description": "Select the TX DDS configuration mode (one tone, two tones, independent I/Q control if compatible).", + "anchor": "HP_BOTTOM", + "content": "HP_BOTTOM", + "y_offset": 10 + } + ] +} diff --git a/plugins/dac/src/bufferdacaddon.cpp b/plugins/dac/src/bufferdacaddon.cpp new file mode 100644 index 0000000000..dec4c902a8 --- /dev/null +++ b/plugins/dac/src/bufferdacaddon.cpp @@ -0,0 +1,437 @@ +#include "bufferdacaddon.h" +#include "dacdatamodel.h" +#include "txnode.h" +#include "databufferbuilder.h" +#include "databuffer.h" +#include "dac_logging_categories.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +using namespace scopy; +using namespace scopy::dac; +BufferDacAddon::BufferDacAddon(DacDataModel *model, QWidget *parent) + : DacAddon(parent) + , m_model(model) + , m_dataBuffer(nullptr) + , m_optionalGuiStrategy(nullptr) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + auto m_layout = new QVBoxLayout(); + m_layout->setMargin(0); + m_layout->setSpacing(0); + setLayout(m_layout); + + QScrollArea *scroll = new QScrollArea(); + QWidget *w = new QWidget(scroll); + auto scrollLayout = new QVBoxLayout(w); + scrollLayout->setMargin(0); + scrollLayout->setSpacing(5); + w->setLayout(scrollLayout); + scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + scroll->setWidgetResizable(true); + scroll->setWidget(w); + + // Start buffer configuration section + MenuSectionWidget *bufferConfigContainer = new MenuSectionWidget(this); + MenuCollapseSection *bufferConfigSection = + new MenuCollapseSection("FILE CONFIGURATION", MenuCollapseSection::MHCW_ARROW, bufferConfigContainer); + bufferConfigSection->contentLayout()->setSpacing(10); + + // Buffersize section + MenuSectionWidget *buffersizeContainer = new MenuSectionWidget(bufferConfigContainer); + buffersizeContainer->setProperty("tutorial_name", "BUFFERSIZE"); + m_bufferSizeSpin = new TitleSpinBox("Buffer size", true, buffersizeContainer); + m_bufferSizeSpin->setMax(16 * 1024 * 1024); + m_bufferSizeSpin->setMin(16); + m_bufferSizeSpin->setStep(4); + StyleHelper::BackgroundWidget(m_bufferSizeSpin); + connect(m_bufferSizeSpin->getLineEdit(), &QLineEdit::textChanged, this, [this](QString text) { + bool ok; + unsigned int buffersize = text.toUInt(&ok); + if(ok) { + m_model->setBuffersize(buffersize); + } + }); + buffersizeContainer->contentLayout()->setSpacing(0); + buffersizeContainer->contentLayout()->setMargin(0); + buffersizeContainer->contentLayout()->addWidget(m_bufferSizeSpin); + + // Kernel buffers section + MenuSectionWidget *kernelContainer = new MenuSectionWidget(this); + kernelContainer->setProperty("tutorial_name", "KERNEL_BUFFERS"); + m_kernelCountSpin = new TitleSpinBox("Kernel buffers", true, buffersizeContainer); + m_kernelCountSpin->setMax(64); + m_kernelCountSpin->setMin(1); + m_kernelCountSpin->setStep(1); + StyleHelper::BackgroundWidget(m_kernelCountSpin); + connect(m_kernelCountSpin->getLineEdit(), &QLineEdit::textChanged, this, [this](QString text) { + bool ok; + unsigned int kernelCount = text.toUInt(&ok); + if(ok) { + m_model->setKernelBuffersCount(kernelCount); + } + }); + kernelContainer->contentLayout()->setSpacing(0); + kernelContainer->contentLayout()->setMargin(0); + kernelContainer->contentLayout()->addWidget(m_kernelCountSpin); + + // Decimation section - hidden for now + TitleSpinBox *decimationSpin = new TitleSpinBox("Decimation", true, bufferConfigSection); + decimationSpin->setMax(1000); + decimationSpin->setMin(1); + decimationSpin->setStep(1.0); + StyleHelper::BackgroundWidget(decimationSpin); + connect(decimationSpin->getLineEdit(), &QLineEdit::textChanged, this, [this](QString text) { + bool ok; + double val = text.toDouble(&ok); + if(ok) { + m_model->setDecimation(val); + } + }); + decimationSpin->setValue(1); + decimationSpin->hide(); + + bufferConfigSection->contentLayout()->addWidget(buffersizeContainer); + bufferConfigSection->contentLayout()->addWidget(kernelContainer); + bufferConfigSection->contentLayout()->addWidget(decimationSpin); + bufferConfigContainer->contentLayout()->addWidget(bufferConfigSection); + + // Run and file section + QWidget *runConfigContainer = new QWidget(this); + auto runConfigLay = new QHBoxLayout(this); + runConfigLay->setSpacing(10); + runConfigLay->setMargin(0); + runConfigContainer->setLayout(runConfigLay); + + // Run button section + m_runBtn = new RunBtn(runConfigContainer); + m_runBtn->setEnabled(false); + m_runBtn->setProperty("tutorial_name", "RUN_BUTTON"); + connect(m_runBtn, &QPushButton::toggled, this, &BufferDacAddon::runBtnToggled); + connect( + m_model, &DacDataModel::updateBuffersize, this, + [this](unsigned int val) { m_bufferSizeSpin->setValue(val); }, Qt::QueuedConnection); + connect( + m_model, &DacDataModel::updateKernelBuffers, this, + [this](unsigned int val) { m_kernelCountSpin->setValue(val); }, Qt::QueuedConnection); + connect( + m_model, &DacDataModel::invalidRunParams, this, [this]() { m_runBtn->setChecked(false); }, + Qt::QueuedConnection); + + // Cyclic buffer section + MenuSectionWidget *cyclicContainer = new MenuSectionWidget(this); + cyclicContainer->setProperty("tutorial_name", "CYCLIC_BUTTON"); + m_cyclicBtn = new MenuOnOffSwitch("Cyclic", cyclicContainer); + connect(m_cyclicBtn->onOffswitch(), &QPushButton::toggled, this, [=, this](bool toggled) { + bufferConfigContainer->setVisible(!toggled); + m_model->setCyclic(toggled); + }); + connect(this, &BufferDacAddon::toggleCyclic, this, + [=, this](bool toggled) { m_cyclicBtn->onOffswitch()->setChecked(toggled); }); + cyclicContainer->contentLayout()->addWidget(m_cyclicBtn); + cyclicContainer->setFixedHeight(48); + m_cyclicBtn->onOffswitch()->setChecked(true); + + // File size and file truncate section + MenuSectionWidget *filesizeContainer = new MenuSectionWidget(this); + filesizeContainer->setProperty("tutorial_name", "FILESIZE"); + m_fileSizeSpin = new TitleSpinBox("File size", true, filesizeContainer); + m_fileSizeSpin->setMax(16 * 1024 * 1024); + m_fileSizeSpin->setMin(16); + m_fileSizeSpin->setStep(4); + StyleHelper::BackgroundWidget(m_fileSizeSpin); + connect(m_fileSizeSpin->getLineEdit(), &QLineEdit::textChanged, this, [this](QString text) { + bool ok; + unsigned int filesize = text.toUInt(&ok); + if(ok) { + m_model->setFilesize(filesize); + } + }); + filesizeContainer->contentLayout()->addWidget(m_fileSizeSpin); + filesizeContainer->setFixedHeight(48); + + runConfigLay->addWidget(cyclicContainer); + runConfigLay->addWidget(filesizeContainer); + runConfigLay->addWidget(m_runBtn); + + // File browser section + fm = new FileBrowser(this); + fm->setProperty("tutorial_name", "FILE_MANAGER"); + connect(fm, &FileBrowser::load, this, &BufferDacAddon::load); + + // Channel list section + MenuSectionWidget *channelsSection = new MenuSectionWidget(this); + channelsSection->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); + QLabel *channelsLbl = new QLabel("Channels"); + StyleHelper::MenuSmallLabel(channelsLbl); + StyleHelper::BackgroundWidget(channelsSection); + + QScrollArea *scrollArea = new QScrollArea(channelsSection); + scrollArea->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + scrollArea->setProperty("tutorial_name", "CHANNEL_SELECTOR"); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + QWidget *channelsContainer = new QWidget(scrollArea); + channelsContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + QVBoxLayout *channelsContainerLayout = new QVBoxLayout(); + channelsContainerLayout->setMargin(0); + channelsContainerLayout->setSpacing(0); + channelsContainer->setLayout(channelsContainerLayout); + + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + scrollArea->setWidgetResizable(true); + scrollArea->setWidget(channelsContainer); + + channelsSection->contentLayout()->addWidget(channelsLbl); + channelsSection->contentLayout()->addWidget(scrollArea); + + m_optionalGuiStrategy = new QWidget(this); + m_optionalGuiStrategy->setProperty("tutorial_name", "DATA_CONFIG"); + + // Console log section + MenuSectionWidget *logSection = new MenuSectionWidget(this); + logSection->setProperty("tutorial_name", "CONSOLE_LOG"); + m_logText = new QTextBrowser(logSection); + m_logText->setTabStopDistance(30); + QFont mono("Monospace"); + mono.setStyleHint(QFont::Monospace); + m_logText->setFont(mono); + m_logText->setReadOnly(true); + logSection->contentLayout()->addWidget(m_logText); + connect( + this, &BufferDacAddon::log, this, + [this](QString log) { + m_logText->append(">>> " + QDateTime::currentDateTime().toString() + " " + log); + }, + Qt::QueuedConnection); + connect(m_model, &DacDataModel::log, this, &BufferDacAddon::log); + + // Setup buttons for buffer channels + int i = 0; + auto lst = m_model->getBufferTxs(); + for(TxNode *node : lst) { + MenuControlButton *chnBtn = new MenuControlButton(this); + chnBtn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + chnBtn->setName(node->getUuid()); + chnBtn->setCheckBoxStyle(MenuControlButton::CS_CIRCLE); + auto color = StyleHelper::getColor("CH" + QString::number(i % 7)); + chnBtn->setColor(color); + node->setColor(color); + chnBtn->setDoubleClickToOpenMenu(true); + chnBtn->setOpenMenuChecksThis(true); + chnBtn->setCheckable(true); + chnBtn->setChecked(false); + chnBtn->button()->setVisible(false); + connect(chnBtn->checkBox(), &QCheckBox::toggled, this, + [=, this](bool toggled) { m_model->enableBufferChannel(node->getUuid(), toggled); }); + channelsContainerLayout->addWidget(chnBtn); + + QWidget *chnMenu = createMenu(node); + m_channelBtns.insert(node->getUuid(), chnBtn); + m_channelMenus.insert(node->getUuid(), chnMenu); + connect(chnBtn, &QPushButton::toggled, this, [=, this](bool b) { + if(b) { + Q_EMIT requestChannelMenu(node->getUuid()); + } + }); + i++; + } + channelsContainerLayout->addSpacerItem( + new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding)); + + // Insert all sections into layouts + QWidget *topSection = new QWidget(this); + auto topLayout = new QHBoxLayout(); + topSection->setLayout(topLayout); + topSection->setFixedHeight(190); + topLayout->setMargin(0); + topLayout->setSpacing(10); + topLayout->addWidget(fm); + topLayout->addWidget(channelsSection); + + scrollLayout->addWidget(topSection); + scrollLayout->addWidget(runConfigContainer); + scrollLayout->addWidget(bufferConfigContainer); + scrollLayout->addWidget(m_optionalGuiStrategy); + scrollLayout->addWidget(logSection); + scrollLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding)); + m_layout->addWidget(scroll); +} + +BufferDacAddon::~BufferDacAddon() {} + +void BufferDacAddon::load(QString path) +{ + if(m_dataBuffer) { + // handle delete and cleanup + delete m_dataBuffer; + m_dataBuffer = nullptr; + } + + m_dataBuffer = DataBufferBuilder() + .dataStrategy(DataBufferBuilder::FileStrategy) + .guiStrategy(DataBufferBuilder::FileGuiStrategy) + .file(path) + .parent(this) + .build(); + + if(!m_dataBuffer) { + onLoadFailed(); + return; + } + + connect(m_dataBuffer, SIGNAL(loadFinished()), this, SLOT(onLoadFinished()), Qt::QueuedConnection); + connect(m_dataBuffer, SIGNAL(loadFailed()), this, SLOT(onLoadFailed()), Qt::QueuedConnection); + connect(m_dataBuffer, SIGNAL(dataUpdated()), this, SLOT(dataReload()), Qt::QueuedConnection); + + QMetaObject::invokeMethod(m_dataBuffer, "loadData", Qt::QueuedConnection); +} + +void BufferDacAddon::onLoadFailed() +{ + Q_EMIT log("Failed to load " + fm->getFilePath()); + m_model->reset(); + m_runBtn->setEnabled(false); +} + +void BufferDacAddon::onLoadFinished() +{ + Q_EMIT log("Successfully loaded " + fm->getFilePath()); + m_runBtn->setEnabled(true); + updateGuiStrategyWidget(); +} + +void BufferDacAddon::updateGuiStrategyWidget() +{ + if(m_optionalGuiStrategy->layout()) { + delete m_optionalGuiStrategy->layout(); + } + auto optGuiStrLay = new QHBoxLayout(m_optionalGuiStrategy); + optGuiStrLay->setMargin(0); + optGuiStrLay->setSpacing(0); + m_optionalGuiStrategy->setLayout(optGuiStrLay); + + if(m_dataBuffer) { + auto guiStrWidget = m_dataBuffer->getDataGuiStrategyInterface()->ui(); + if(guiStrWidget) { + optGuiStrLay->addWidget(guiStrWidget); + } + + auto fileSize = m_dataBuffer->getDataBufferStrategy()->data().size(); + m_bufferSizeSpin->setValue(fileSize); + m_fileSizeSpin->setValue(fileSize); + m_fileSizeSpin->setMax(fileSize); + } +} + +void BufferDacAddon::enable(bool enable) { m_model->enableBuffer(enable); } + +void BufferDacAddon::runBtnToggled(bool toggled) +{ + if(!m_dataBuffer) { + return; + } + if(toggled) { + dataReload(); + m_model->start(); + } else { + m_model->stop(); + } +} + +void BufferDacAddon::dataReload() +{ + auto data = m_dataBuffer->getDataBufferStrategy()->data(); + m_model->setData(data); +} + +void BufferDacAddon::forwardSamplingFrequencyChange(QDateTime timestamp, QString oldData, QString newData, int retCode, + bool readOp) +{ + if(retCode < 0) { + return; + } + bool ok; + unsigned int val = newData.toUInt(&ok); + if(ok) { + m_model->setSamplingFrequency(val); + } +} + +void BufferDacAddon::detectSamplingFrequency(IIOWidget *w) +{ + if(w->getRecipe().data.contains("sampling_frequency")) { + connect(dynamic_cast(w->getDataStrategy()), + &ChannelAttrDataStrategy::emitStatus, this, &BufferDacAddon::forwardSamplingFrequencyChange); + w->readAsync(); + } +} + +QWidget *BufferDacAddon::createAttrMenu(TxNode *node, QWidget *parent) +{ + MenuSectionWidget *attrContainer = new MenuSectionWidget(parent); + MenuCollapseSection *attr = + new MenuCollapseSection("ATTRIBUTES", MenuCollapseSection::MHCW_NONE, attrContainer); + QList attrWidgets = IIOWidgetBuilder().channel(node->getChannel()).buildAll(); + + auto layout = new QVBoxLayout(); + layout->setSpacing(10); + layout->setContentsMargins(0, 0, 0, 10); + layout->setMargin(0); + + for(IIOWidget *w : qAsConst(attrWidgets)) { + layout->addWidget(w); + detectSamplingFrequency(w); + } + + attr->contentLayout()->addLayout(layout); + attrContainer->contentLayout()->addWidget(attr); + attr->header()->setChecked(true); + return attrContainer; +} + +QWidget *BufferDacAddon::createMenu(TxNode *node) +{ + QWidget *w = new QWidget(); + QVBoxLayout *lay = new QVBoxLayout(); + + QScrollArea *scroll = new QScrollArea(); + QWidget *wScroll = new QWidget(scroll); + QVBoxLayout *layScroll = new QVBoxLayout(wScroll); + layScroll->setMargin(0); + layScroll->setSpacing(10); + + wScroll->setLayout(layScroll); + scroll->setWidgetResizable(true); + scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + scroll->setWidget(wScroll); + + lay->setMargin(0); + lay->setSpacing(10); + w->setLayout(lay); + + MenuHeaderWidget *header = new MenuHeaderWidget(node->getUuid(), node->getColor(), w); + QWidget *attrMenu = createAttrMenu(node, w); + + lay->addWidget(header); + lay->addWidget(scroll); + layScroll->addWidget(attrMenu); + + layScroll->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + return w; +} diff --git a/plugins/dac/src/bufferdacaddon.h b/plugins/dac/src/bufferdacaddon.h new file mode 100644 index 0000000000..42ec84f420 --- /dev/null +++ b/plugins/dac/src/bufferdacaddon.h @@ -0,0 +1,62 @@ +#ifndef BUFFERDACADDON_H_ +#define BUFFERDACADDON_H_ + +#include +#include +#include + +#include +#include + +#include "filebrowser.h" +#include "dacaddon.h" + +namespace scopy { +namespace dac { +class DacDataModel; +class DataBuffer; +class TxNode; +class BufferDacAddon : public DacAddon +{ + Q_OBJECT +public: + BufferDacAddon(DacDataModel *model, QWidget *parent = nullptr); + virtual ~BufferDacAddon(); + virtual void enable(bool enable); + +Q_SIGNALS: + void log(QString log); + void toggleCyclic(bool toggled); + +public Q_SLOTS: + void dataReload(); + void onLoadFailed(); + void onLoadFinished(); + +private Q_SLOTS: + void load(QString path); + void updateGuiStrategyWidget(); + void forwardSamplingFrequencyChange(QDateTime timestamp, QString oldData, QString newData, int retCode, + bool readOp); + +private: + DacDataModel *m_model; + QWidget *m_optionalGuiStrategy; + DataBuffer *m_dataBuffer; + RunBtn *m_runBtn; + TitleSpinBox *m_bufferSizeSpin; + TitleSpinBox *m_fileSizeSpin; + TitleSpinBox *m_kernelCountSpin; + MenuOnOffSwitch *m_cyclicBtn; + QTextBrowser *m_logText; + FileBrowser *fm; + + void runBtnToggled(bool toggled); + QWidget *createMenu(TxNode *node); + QWidget *createAttrMenu(TxNode *node, QWidget *parent); + void detectSamplingFrequency(IIOWidget *w); +}; +} // namespace dac +} // namespace scopy + +#endif // BUFFERDACADDON_H_ diff --git a/plugins/dac/src/csvfilestrategy.cpp b/plugins/dac/src/csvfilestrategy.cpp new file mode 100644 index 0000000000..9d2a5a0459 --- /dev/null +++ b/plugins/dac/src/csvfilestrategy.cpp @@ -0,0 +1,109 @@ +#include "csvfilestrategy.h" +#include "dac_logging_categories.h" +#include "dacutils.h" + +#include +#include +#include + +using namespace scopy; +using namespace scopy::dac; +CSVFileStrategy::CSVFileStrategy(QString filename, QWidget *parent) + : QObject(parent) +{ + m_filename = filename; + m_separator = ","; + m_recipe = {.scale = 0.0, .scaled = false}; +} + +QVector> CSVFileStrategy::data() +{ + qDebug(CAT_DAC_DATASTRATEGY) << "Retrieve data."; + return m_dataConverted; +} + +void CSVFileStrategy::recipeUpdated(DataBufferRecipe recipe) +{ + qDebug(CAT_DAC_DATASTRATEGY) << "Recipe update in CSV file strategy."; + m_recipe = recipe; + applyConversion(); +} + +void CSVFileStrategy::loadData() +{ + double max = 0.0; + QVector> raw_data; + bool dataOk = true; + QFile file(m_filename); + if(!file.open(QIODevice::ReadOnly)) { + qDebug(CAT_DAC_DATASTRATEGY) << "Can't open selected file"; + Q_EMIT loadFailed(); + return; + } + + QTextStream in(&file); + + while(!in.atEnd()) { + QVector line_data; + QString line = in.readLine(); + if(line.isEmpty()) { + continue; + } + QStringList list = line.split(m_separator, Qt::SkipEmptyParts); + for(const QString &list_item : qAsConst(list)) { + line_data.push_back(list_item); + } + if(line_data.size() > 0) { + raw_data.push_back(line_data); + } + } + + // suppose no m_data in the file + m_data.resize(raw_data.size()); + m_dataConverted.resize(raw_data.size()); + for(int i = 0; i < m_data.size(); ++i) { + m_data[i].resize(raw_data[i].size()); + m_dataConverted[i].resize(raw_data[i].size()); + } + + for(int i = 0; i < raw_data.size(); ++i) { + for(int j = 0; j < raw_data[i].size(); ++j) { + double tmp = raw_data[i][j].toDouble(&dataOk); + if(!dataOk) { + qDebug(CAT_DAC_DATASTRATEGY) << "File is corrupted"; + Q_EMIT loadFailed(); + return; + } + m_data[i][j] = tmp; + m_max = (tmp > m_max) ? tmp : m_max; + } + } + applyConversion(); + Q_EMIT loadFinished(); +} + +void CSVFileStrategy::applyConversion() +{ + for(int i = 0; i < m_data.size(); i++) { + for(int j = 0; j < m_data[i].size(); j++) { + m_dataConverted[i][j] = convert(m_data[i][j]); + } + } + Q_EMIT dataUpdated(); + qDebug(CAT_DAC_DATASTRATEGY) << "Apply conversion on all samples"; +} + +unsigned short CSVFileStrategy::convert(double value) +{ + double scale = 0.0; + double offset = 0.0; + double full_scale = DacUtils::dbFullScaleConvert(m_recipe.scale, false); + if(!m_recipe.scaled) { + scale = 16.0; + } + if(scale == 0.0) { + scale = 32767.0 * full_scale / m_max; + } + + return (value * scale + offset); +} diff --git a/plugins/dac/src/csvfilestrategy.h b/plugins/dac/src/csvfilestrategy.h new file mode 100644 index 0000000000..e4a1b93906 --- /dev/null +++ b/plugins/dac/src/csvfilestrategy.h @@ -0,0 +1,40 @@ +#ifndef CSVFILESTRATEGY_H +#define CSVFILESTRATEGY_H + +#include +#include "databufferstrategyinterface.h" +#include "scopy-dac_export.h" +#include "dac_logging_categories.h" +namespace scopy { +namespace dac { +class SCOPY_DAC_EXPORT CSVFileStrategy : public QObject, public DataBufferStrategyInterface +{ + Q_OBJECT + Q_INTERFACES(scopy::dac::DataBufferStrategyInterface) +public: + explicit CSVFileStrategy(QString filename, QWidget *parent = nullptr); + ~CSVFileStrategy(){}; + QVector> data() override; + +public Q_SLOTS: + void recipeUpdated(DataBufferRecipe) override; + void loadData() override; + +Q_SIGNALS: + void loadFinished() override; + void loadFailed() override; + void dataUpdated() override; + +private: + double m_max; + QString m_filename; + QString m_separator; + QVector> m_data; + QVector> m_dataConverted; + DataBufferRecipe m_recipe; + unsigned short convert(double value); + void applyConversion(); +}; +} // namespace dac +} // namespace scopy +#endif // CSVFILESTRATEGY_H diff --git a/plugins/dac/src/dac_logging_categories.cpp b/plugins/dac/src/dac_logging_categories.cpp new file mode 100644 index 0000000000..dc47743e3d --- /dev/null +++ b/plugins/dac/src/dac_logging_categories.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "dac_logging_categories.h" + +#ifndef QT_NO_DEBUG_OUTPUT +Q_LOGGING_CATEGORY(CAT_DAC, "DacPlugin"); +Q_LOGGING_CATEGORY(CAT_DAC_INSTRUMENT, "DacInstrument") +Q_LOGGING_CATEGORY(CAT_DAC_DDS, "DacDds") +Q_LOGGING_CATEGORY(CAT_DAC_BUFFER, "DacBuffer") +Q_LOGGING_CATEGORY(CAT_DAC_DATA, "DacData") +Q_LOGGING_CATEGORY(CAT_DAC_DATABUILDER, "DacDataBuilder") +Q_LOGGING_CATEGORY(CAT_DAC_FILEMANAGER, "DacFileManager") +Q_LOGGING_CATEGORY(CAT_DAC_DATASTRATEGY, "DacDataStrategy") +#endif diff --git a/plugins/dac/src/dacaddon.cpp b/plugins/dac/src/dacaddon.cpp new file mode 100644 index 0000000000..6a6e5c245c --- /dev/null +++ b/plugins/dac/src/dacaddon.cpp @@ -0,0 +1,20 @@ +#include "dacaddon.h" + +#include +#include + +using namespace scopy; +using namespace scopy::dac; +DacAddon::DacAddon(QWidget *parent) + : QWidget(parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +DacAddon::~DacAddon() {} + +void DacAddon::enable(bool enable) {} + +QMap DacAddon::getChannelBtns() { return m_channelBtns; } + +QMap DacAddon::getChannelMenus() { return m_channelMenus; } diff --git a/plugins/dac/src/dacaddon.h b/plugins/dac/src/dacaddon.h new file mode 100644 index 0000000000..06f33a915a --- /dev/null +++ b/plugins/dac/src/dacaddon.h @@ -0,0 +1,29 @@ +#ifndef DACADDON_H_ +#define DACADDON_H_ + +#include +#include +#include + +namespace scopy { +namespace dac { +class DacAddon : public QWidget +{ + Q_OBJECT +public: + DacAddon(QWidget *parent = nullptr); + virtual ~DacAddon(); + virtual void enable(bool enable); + virtual QMap getChannelBtns(); + virtual QMap getChannelMenus(); +Q_SIGNALS: + void requestChannelMenu(QString uuid); + +protected: + QMap m_channelBtns; + QMap m_channelMenus; +}; +} // namespace dac +} // namespace scopy + +#endif // DACADDON_H_ diff --git a/plugins/dac/src/dacdatamanager.cpp b/plugins/dac/src/dacdatamanager.cpp new file mode 100644 index 0000000000..2811ed7489 --- /dev/null +++ b/plugins/dac/src/dacdatamanager.cpp @@ -0,0 +1,209 @@ +#include "dacdatamanager.h" +#include "dacdatamodel.h" +#include "ddsdacaddon.h" +#include "bufferdacaddon.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +using namespace scopy; +using namespace scopy::dac; +DacDataManager::DacDataManager(struct iio_device *dev, QWidget *parent) + : QWidget(parent) +{ + m_model = new DacDataModel(dev, this); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_layout = new QVBoxLayout(); + m_layout->setMargin(0); + m_layout->setSpacing(10); + setLayout(m_layout); + + rightMenuStack = new MapStackedWidget(this); + dacAddonStack = new MapStackedWidget(this); + + MenuSectionWidget *modeSection = new MenuSectionWidget(this); + m_mode = new MenuCombo("MODE", this); + m_mode->setProperty("tutorial_name", "MODE_SELECTOR"); + StyleHelper::IIOComboBox(m_mode->combo()); + StyleHelper::BackgroundWidget(m_mode); + auto cb = m_mode->combo(); + connect(cb, qOverload(&QComboBox::currentIndexChanged), this, [=, this](int idx) { + auto current = dynamic_cast(dacAddonStack->currentWidget()); + if(current) { + current->enable(false); + } + + auto mode = cb->itemData(idx).toInt(); + auto next = dynamic_cast(dacAddonStack->get(QString::number(mode))); + if(next) { + next->enable(true); + } + dacAddonStack->show(QString::number(mode)); + }); + + setupDacMode("Disabled", DAC_DISABLED); + setupDacMode("Buffer", DAC_BUFFER); + setupDacMode("DDS", DAC_DDS); + Q_EMIT m_mode->combo()->currentIndexChanged(0); + modeSection->contentLayout()->addWidget(m_mode); + + m_color = StyleHelper::getColor("CH" + QString::number(QRandomGenerator::global()->bounded(0, 7))); + m_layout->addWidget(modeSection); + m_layout->addWidget(dacAddonStack); + m_layout->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding)); + + cb->setCurrentIndex(DAC_BUFFER); + + // Setup menu widget + m_widget = createMenu(); +} + +DacDataManager::~DacDataManager() {} + +QString DacDataManager::getName() const { return m_model->getName(); } + +QWidget *DacDataManager::getWidget() { return m_widget; } + +QColor DacDataManager::getColor() const { return m_color; } + +void DacDataManager::setColor(QColor newColor) { m_color = newColor; } + +void DacDataManager::setupDdsDac() {} + +QWidget *DacDataManager::createAttrMenu(QWidget *parent) +{ + MenuSectionWidget *attrContainer = new MenuSectionWidget(parent); + MenuCollapseSection *attr = + new MenuCollapseSection("ATTRIBUTES", MenuCollapseSection::MHCW_NONE, attrContainer); + QList attrWidgets = IIOWidgetBuilder().device(m_model->getDev()).buildAll(); + + auto layout = new QVBoxLayout(); + layout->setSpacing(10); + layout->setContentsMargins(0, 0, 0, 10); + layout->setMargin(0); + + for(auto w : attrWidgets) { + layout->addWidget(w); + } + + attr->contentLayout()->addLayout(layout); + attrContainer->contentLayout()->addWidget(attr); + attr->header()->setChecked(true); + return attrContainer; +} + +QWidget *DacDataManager::createMenu() +{ + QWidget *w = new QWidget(); + QVBoxLayout *lay = new QVBoxLayout(); + + QScrollArea *scroll = new QScrollArea(); + QWidget *wScroll = new QWidget(scroll); + QVBoxLayout *layScroll = new QVBoxLayout(wScroll); + layScroll->setMargin(0); + layScroll->setSpacing(10); + + wScroll->setLayout(layScroll); + scroll->setWidgetResizable(true); + scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + scroll->setWidget(wScroll); + + lay->setMargin(0); + lay->setSpacing(10); + w->setLayout(lay); + + MenuHeaderWidget *header = new MenuHeaderWidget(getName(), m_color, w); + QWidget *attrMenu = createAttrMenu(w); + + lay->addWidget(header); + lay->addWidget(scroll); + layScroll->addWidget(attrMenu); + + layScroll->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + return w; +} + +void DacDataManager::setupDacMode(QString mode_name, unsigned int mode) +{ + if(!m_model->isDds() && (mode == DAC_DDS)) + return; + if(!m_model->isBufferCapable() && (mode == DAC_BUFFER)) + return; + DacAddon *dac; + switch(mode) { + case DAC_DDS: + dac = new DdsDacAddon(m_model, this); + break; + case DAC_BUFFER: + dac = new BufferDacAddon(m_model, this); + break; + default: + dac = new DacAddon(this); + break; + } + auto channelBtns = dac->getChannelBtns(); + auto channelMenus = dac->getChannelMenus(); + for(auto btn : qAsConst(channelBtns)) { + QString uuid = channelBtns.key(btn); + QWidget *menu = channelMenus.value(uuid); + if(menu) { + rightMenuStack->add(uuid, menu); + } + m_menuControlBtns.push_back(btn); + } + connect(dac, &DacAddon::requestChannelMenu, this, &DacDataManager::handleChannelMenuRequest); + dacAddonStack->add(QString::number(mode), dac); + m_mode->combo()->addItem(mode_name, mode); +} + +void DacDataManager::handleChannelMenuRequest(QString uuid) +{ + rightMenuStack->show(uuid); + Q_EMIT requestMenu(); +} + +MapStackedWidget *DacDataManager::getRightMenuStack() const { return rightMenuStack; } + +QList DacDataManager::getMenuControlBtns() const { return m_menuControlBtns; } + +bool DacDataManager::isBufferCapable() const { return m_model->isBufferCapable(); } + +bool DacDataManager::isDds() const { return m_model->isDds(); } + +void DacDataManager::toggleCyclicBuffer(bool toggled) +{ + if(isBufferCapable()) { + auto dac = dynamic_cast(dacAddonStack->currentWidget()); + if(!dac) { + return; + } + Q_EMIT dac->toggleCyclic(toggled); + } +} + +void DacDataManager::toggleBufferMode() +{ + if(isBufferCapable()) { + m_mode->combo()->setCurrentIndex(DAC_BUFFER); + // Q_EMIT m_mode->combo()->currentIndexChanged(DAC_DDS); + } +} + +void DacDataManager::toggleDdsMode() +{ + if(isDds()) { + m_mode->combo()->setCurrentIndex(DAC_DDS); + // Q_EMIT m_mode->combo()->currentIndexChanged(DAC_DDS); + } +} diff --git a/plugins/dac/src/dacdatamanager.h b/plugins/dac/src/dacdatamanager.h new file mode 100644 index 0000000000..6aa3cda715 --- /dev/null +++ b/plugins/dac/src/dacdatamanager.h @@ -0,0 +1,75 @@ + +#ifndef DACDATAMANAGER_H +#define DACDATAMANAGER_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace scopy { +namespace dac { +class DacDataModel; +class DacAddon; +class DacDataManager : public QWidget +{ + Q_OBJECT +public: + typedef enum + { + DAC_DISABLED, + DAC_BUFFER, + DAC_DDS + } DacMode; + + DacDataManager(struct iio_device *dev, QWidget *parent = nullptr); + virtual ~DacDataManager(); + + QString getName() const; + QWidget *getWidget(); + + QColor getColor() const; + void setColor(QColor newColor); + + MapStackedWidget *getRightMenuStack() const; + QList getMenuControlBtns() const; + + bool isBufferCapable() const; + bool isDds() const; + + void toggleCyclicBuffer(bool toggled); + void toggleBufferMode(); + void toggleDdsMode(); + +private Q_SLOTS: + void handleChannelMenuRequest(QString uuid); +Q_SIGNALS: + void requestMenu(); + +private: + QVBoxLayout *m_layout; + DacDataModel *m_model; + QWidget *m_widget; + QColor m_color; + MapStackedWidget *dacAddonStack; + MapStackedWidget *rightMenuStack; + MenuCombo *m_mode; + QList m_menuControlBtns; + + void setupDdsDac(); + void setupDacMode(QString mode_name, unsigned int mode); + QWidget *createMenu(); + QWidget *createAttrMenu(QWidget *parent); +}; +} // namespace dac +} // namespace scopy + +#endif // DACDATAMANAGER_H diff --git a/plugins/dac/src/dacdatamodel.cpp b/plugins/dac/src/dacdatamodel.cpp new file mode 100644 index 0000000000..e634c6b2f9 --- /dev/null +++ b/plugins/dac/src/dacdatamodel.cpp @@ -0,0 +1,525 @@ +#include "dacdatamodel.h" +#include "txnode.h" +#include "dac_logging_categories.h" + +#include + +#include +#include + +using namespace scopy; +using namespace scopy::dac; +DacDataModel::DacDataModel(struct iio_device *dev, QObject *parent) + : QObject(parent) + , m_ddsTxs({}) + , m_bufferTxs({}) + , m_activeDds(false) + , m_activeBuffer(false) + , m_buffer(nullptr) + , m_cyclicBuffer(true) + , m_interrupted(false) + , m_userBuffersize(0) + , m_userKernelBufferCount(0) +{ + m_dev = dev; + m_name = iio_device_get_name(m_dev); + + m_isBufferCapable = initBufferDac(); + m_isDds = initDdsDac(); + + connect( + this, &DacDataModel::reqInitBuffer, this, + [this]() { + if(m_buffer) { + initBuffer(); + } + }, + Qt::QueuedConnection); +} + +DacDataModel::~DacDataModel() +{ + deinitBufferDac(); + deinitDdsDac(); +} + +void DacDataModel::reset() +{ + m_interrupted = false; + m_userBuffersize = 0; + m_userKernelBufferCount = 0; + m_data.clear(); +} + +struct iio_device *DacDataModel::getDev() const { return m_dev; } + +QString DacDataModel::getName() const { return m_name; } + +bool DacDataModel::isBufferCapable() const { return m_isBufferCapable; } + +bool DacDataModel::isDds() const { return m_isDds; } + +void DacDataModel::enableBuffer(bool enabled) +{ + qDebug(CAT_DAC_DATA) << QString("Enable buffer %1").arg(enabled); + if(m_isDds && enabled) { + enableDds(false); + } + if(m_isBufferCapable) { + m_activeBuffer = enabled; + if(!enabled) { + stop(); + } + } +} + +void DacDataModel::enableDds(bool enable) +{ + qDebug(CAT_DAC_DATA) << QString("Enable DDS %1").arg(enable); + if(m_isBufferCapable && m_activeBuffer && enable) { + for(auto node : qAsConst(m_bufferTxs)) { + enableBufferChannel(node->getUuid(), false); + } + } + if(m_isDds) { + m_activeDds = enable; + for(auto tx : qAsConst(m_ddsTxs)) { + tx->enableDds(enable); + } + } +} + +QMap DacDataModel::getBufferTxs() const { return m_bufferTxs; } + +QMap DacDataModel::getDdsTxs() const { return m_ddsTxs; } + +void DacDataModel::requestInterruption() +{ + m_interrupted = true; + if(m_pushThd.isRunning()) { + m_pushThd.waitForFinished(); + } + qDebug(CAT_DAC_DATA) << "Thread stopped."; +} + +void DacDataModel::setCyclic(bool cyclic) +{ + requestInterruption(); + m_cyclicBuffer = cyclic; + autoBuffersizeAndKernelBuffers(); + Q_EMIT reqInitBuffer(); +} + +void DacDataModel::setKernelBuffersCount(unsigned int kernelCount) +{ + if(kernelCount != m_userKernelBufferCount) { + requestInterruption(); + m_userKernelBufferCount = kernelCount; + Q_EMIT reqInitBuffer(); + } +} + +void DacDataModel::setDecimation(double decimation) +{ + requestInterruption(); + m_decimation = decimation; + Q_EMIT reqInitBuffer(); +} + +void DacDataModel::setBuffersize(unsigned int buffersize) +{ + if(m_userBuffersize != buffersize) { + requestInterruption(); + m_userBuffersize = buffersize; + Q_EMIT reqInitBuffer(); + } +} + +void DacDataModel::setFilesize(unsigned int filesize) +{ + requestInterruption(); + m_filesize = filesize; + autoBuffersizeAndKernelBuffers(); + Q_EMIT reqInitBuffer(); +} + +void DacDataModel::enableBufferChannel(QString uuid, bool enable) +{ + requestInterruption(); + auto chn = m_bufferTxs.value(uuid)->getChannel(); + if(!chn) { + qDebug(CAT_DAC_DATA) << QString("No channel for uuid %1").arg(uuid); + } + if(enable) { + iio_channel_enable(chn); + } else { + iio_channel_disable(chn); + } + + Q_EMIT reqInitBuffer(); +} + +unsigned int DacDataModel::getEnabledChannelsCount() +{ + unsigned int enChannelsCount = 0; + for(auto node : qAsConst(m_bufferTxs)) { + enChannelsCount += iio_channel_is_enabled(node->getChannel()) ? 1 : 0; + } + return enChannelsCount; +} + +void DacDataModel::setData(QVector> data) +{ + m_data = data; + Q_EMIT reqInitBuffer(); +} + +void DacDataModel::setSamplingFrequency(unsigned int sr) +{ + if(m_samplingFrequency != sr) { + m_samplingFrequency = sr; + requestInterruption(); + autoBuffersizeAndKernelBuffers(); + Q_EMIT reqInitBuffer(); + } +} + +void DacDataModel::autoBuffersizeAndKernelBuffers() +{ + auto bf = 0; + auto kb = 0; + if(!m_cyclicBuffer) { + const unsigned int maxTransferSize = 16 * 1024 * 1024; + const unsigned int minKernelBuffers = 4; + const double maxTransferTime = 0.1; + kb = minKernelBuffers; + while(kb <= 64) { + bf = m_filesize / kb; + kb += (m_filesize % kb != 0) ? 1 : 0; + if((bf / m_samplingFrequency) > maxTransferTime) { + kb++; + } else { + break; + } + } + m_userKernelBufferCount = kb; + m_userBuffersize = bf; + } else { + m_userKernelBufferCount = 0; + m_userBuffersize = 0; + } + Q_EMIT updateBuffersize(m_userBuffersize); + Q_EMIT updateKernelBuffers(m_userKernelBufferCount); +} + +bool DacDataModel::validateBufferParams() +{ + if(!m_isBufferCapable) { + return false; + } + if(!m_activeBuffer) { + return false; + } + + auto enabledChannelsCount = getEnabledChannelsCount(); + ssize_t s_size = iio_device_get_sample_size(m_dev); + if(!s_size || enabledChannelsCount == 0) { + auto msg = "Unable to create buffer, no channel enabled."; + qDebug(CAT_DAC_DATA) << msg; + Q_EMIT log(msg); + return false; + } + + if((m_cyclicBuffer && m_data.size() == 0) || (!m_cyclicBuffer && m_userBuffersize == 0)) { + auto msg = "Unable to create buffer due to data size."; + qDebug(CAT_DAC_DATA) << msg; + Q_EMIT log(msg); + return false; + } + + if(m_data[0].size() < enabledChannelsCount) { + auto msg = "Not enough data columns for all enabled channels."; + qDebug(CAT_DAC_DATA) << msg; + Q_EMIT log(msg); + return false; + } + + if(!txChannelsCheckValidSetup()) { + auto msg = "Unable to create buffer due to incompatible channels enabled."; + qDebug(CAT_DAC_DATA) << msg; + Q_EMIT log(msg); + return false; + } + + if(m_cyclicBuffer) { + m_kernelBufferCount = 4; + m_buffersize = m_filesize; + } else { + m_buffersize = m_userBuffersize; + m_kernelBufferCount = m_userKernelBufferCount; + } + + if(m_decimation != 1) { + if(m_userBuffersize * m_decimation > m_filesize) { + auto msg = "Unable to create buffer due to high decimation."; + qDebug(CAT_DAC_DATA) << msg; + Q_EMIT log(msg); + return false; + } + } + + return true; +} + +void DacDataModel::initBuffer() +{ + if(m_pushThd.isRunning()) { + m_pushThd.cancel(); + qDebug(CAT_DAC_DATA) << "Cancel thread and wait in initBuffer"; + m_pushThd.waitForFinished(); + } + m_pushThd = QtConcurrent::run(this, &DacDataModel::push); +} + +void DacDataModel::push() +{ + qDebug(CAT_DAC_DATA) << "Start push thread"; + m_interrupted = false; + QVector allData = {}; + unsigned int enChannelsCount = getEnabledChannelsCount(); + ssize_t s_size = iio_device_get_sample_size(m_dev); + bool valid = validateBufferParams(); + if(!valid) { + Q_EMIT invalidRunParams(); + return; + } + + if(m_buffer) { + iio_buffer_destroy(m_buffer); + m_buffer = nullptr; + } + + iio_device_set_kernel_buffers_count(m_dev, m_kernelBufferCount); + + m_buffer = iio_device_create_buffer(m_dev, m_buffersize, m_cyclicBuffer); + if(!m_buffer) { + qDebug(CAT_DAC_DATA) << "Unable to create buffer: %s\n" << strerror(errno); + return; + } + + unsigned int additionalSamples = 0; + unsigned int sampleIdx = 0; + if(!m_cyclicBuffer) { + additionalSamples = m_filesize % m_buffersize; + } + for(unsigned int i = 0; i < m_filesize + additionalSamples; i += m_decimation) { + for(int ch = 0; ch < enChannelsCount; ch++) { + sampleIdx = std::min(i, m_filesize - 1); + allData.append(m_data[sampleIdx][ch]); + } + } + + int dataIdx = 0; + int bufferIdx = 1; + int totalNbBuffers = allData.size() / (m_buffersize * enChannelsCount); + while(!m_interrupted && bufferIdx <= totalNbBuffers) { + memcpy(iio_buffer_start(m_buffer), allData.data() + dataIdx, m_buffersize * s_size); + ssize_t bytes = iio_buffer_push(m_buffer); + dataIdx += m_buffersize * enChannelsCount; + QString logMsg = QString("Pushed %1 samples, %2 bytes (%3/%4 buffers)") + .arg(m_buffersize) + .arg(bytes) + .arg(bufferIdx) + .arg(totalNbBuffers); + qDebug(CAT_DAC_DATA) << logMsg; + Q_EMIT log(logMsg); + bufferIdx++; + } + if(m_interrupted) { + Q_EMIT log(QString("Aborting thread...")); + } +} + +void DacDataModel::start() { initBuffer(); } + +void DacDataModel::stop() +{ + requestInterruption(); + if(!m_isBufferCapable) { + return; + } + if(m_buffer) { + iio_buffer_cancel(m_buffer); + iio_buffer_destroy(m_buffer); + m_buffer = nullptr; + qDebug(CAT_DAC_DATA) << "Buffer destroyed."; + } +} + +bool DacDataModel::initBufferDac() +{ + unsigned int txCount = 0; + unsigned int channelCount = iio_device_get_channels_count(m_dev); + for(unsigned int i = 0; i < channelCount; i++) { + struct iio_channel *chn = iio_device_get_channel(m_dev, i); + if(!iio_channel_is_output(chn)) + continue; + if(iio_channel_is_scan_element(chn)) { + txCount++; + QString id = iio_channel_get_id(chn); + QString name = iio_channel_get_name(chn); + if(name != "") { + id += ":" + name; + } + m_bufferTxs.insert(id, new TxNode(id, chn, this)); + } + } + return (txCount != 0); +} + +/** + * TX1_I_F1, TX1_I_F2, TX1_Q_F1, TX1_Q_F2 -> one channel, dual tone, complex + * 1A, 1B -> one channel, dual tone, not complex + */ +bool DacDataModel::initDdsDac() +{ + unsigned int ddsTonesCount = 0; + unsigned int channelCount = iio_device_get_channels_count(m_dev); + for(unsigned int i = 0; i < channelCount; i++) { + struct iio_channel *chn = iio_device_get_channel(m_dev, i); + if(!iio_channel_is_output(chn)) + continue; + iio_chan_type chnType = iio_channel_get_type(chn); + if(chnType != IIO_ALTVOLTAGE) + continue; + ddsTonesCount++; + + // Name should contain TX*_I/Q_F or *A/*B + QString name = iio_channel_get_name(chn); + QString id = iio_channel_get_id(chn); + if(name == "") { + name = generateToneName(id); + } + QStringList txNodesNames = generateTxNodesForChannel(name); + QString txNameCurrent = txNodesNames.at(0); + TxNode *parentTxNode = m_ddsTxs.value(txNameCurrent, nullptr); + if(!parentTxNode) { + parentTxNode = new TxNode(txNameCurrent, nullptr, this); + m_ddsTxs.insert(txNameCurrent, parentTxNode); + } + if(txNodesNames.size() == 2) { + QString toneName = txNodesNames.at(1); + parentTxNode->addChildNode(toneName, chn); + } else if(txNodesNames.size() == 3) { + QString complexChnName = txNodesNames.at(1); + QString toneName = txNodesNames.at(2); + TxNode *complexChnNode = parentTxNode->addChildNode(complexChnName, nullptr); + complexChnNode->addChildNode(toneName, chn); + } + } + return (ddsTonesCount != 0); +} + +int DacDataModel::getTxChannelEnabledCount(unsigned *enabled_mask) +{ + bool enabled; + int num_enabled = 0; + int ch_pos = 0; + + if(enabled_mask) + *enabled_mask = 0; + + for(auto ch : qAsConst(m_bufferTxs)) { + bool enabled = iio_channel_is_enabled(ch->getChannel()); + if(enabled) { + num_enabled++; + if(enabled_mask) + *enabled_mask |= 1 << ch_pos; + } + ch_pos++; + } + + qDebug(CAT_DAC_DATA) << QString("Enabled channels %1").arg(num_enabled); + return num_enabled; +} + +bool DacDataModel::txChannelsCheckValidSetup() +{ + int enabled_channels; + unsigned int mask; + + enabled_channels = getTxChannelEnabledCount(&mask); + + return (dmaValidSelection(mask, m_bufferTxs.count()) && enabled_channels > 0); +} + +bool DacDataModel::dmaValidSelection(unsigned mask, unsigned channel_count) +{ + static const unsigned long eight_channel_masks[] = {0x01, 0x02, 0x04, 0x08, 0x03, 0x0C, /* 1 & 2 chan */ + 0x10, 0x20, 0x40, 0x80, 0x30, 0xC0, /* 1 & 2 chan */ + 0x33, 0xCC, 0xC3, 0x3C, 0x0F, 0xF0, /* 4 chan */ + 0xFF, /* 8chan */ + 0x00}; + static const unsigned long four_channel_masks[] = {0x01, 0x02, 0x04, 0x08, 0x03, 0x0C, 0x0F, 0x00}; + bool ret = true; + unsigned int i; + + if(channel_count == 8) { + ret = false; + for(i = 0; i < sizeof(eight_channel_masks) / sizeof(eight_channel_masks[0]); i++) + if(mask == eight_channel_masks[i]) + return true; + } else if(channel_count == 4) { + ret = false; + for(i = 0; i < sizeof(four_channel_masks) / sizeof(four_channel_masks[0]); i++) + if(mask == four_channel_masks[i]) + return true; + } + + return ret; +} + +// returns a list of nodes - this is specifically ordered +// for channels of 1A/B type it returns: {"1", "1A/B"} +// for channels of TX*_I/Q_F* type it returns: {"TX*", "TX_I/Q", "TX*_I/Q_F*"} +QStringList DacDataModel::generateTxNodesForChannel(QString name) +{ + QStringList txNodes; + QStringList id_list = name.split("_"); + QString tone_uuid = name; + QString complex_chn_uuid = ""; + QString tx_uuid = ""; + if(id_list.size() == 3) { // TX*_Q/I_F* complex format + complex_chn_uuid = name.mid(0, name.size() - id_list[2].size() - 1); // TX*_Q/I + tx_uuid = id_list[0]; + txNodes << tx_uuid; + txNodes << complex_chn_uuid; + } else if(id_list.size() == 1) { // *A/*B non-complex format + tx_uuid = name[0]; + txNodes << tx_uuid; + } + txNodes << tone_uuid; + return txNodes; +} + +QString DacDataModel::generateToneName(QString chnId) +{ + bool ok; + QString name = ""; + int idx = chnId.indexOf(toneId); + if(idx != -1) { + int chnIndex = chnId.midRef(idx + toneId.size()).toInt(&ok); + if(!ok) + return name; + int txIndex = (chnIndex / MAX_NB_TONES) + 1; // TX indexing from 1 + int toneIndex = (chnIndex % 2) + 1; // assuming dual tones, indexing from 1 + QString typeIQ = (chnIndex & 0x02) ? Q_CHANNEL + : I_CHANNEL; /* First two indexes are I, next two are Q and so on*/ + name = "TX" + QString::number(txIndex) + "_" + typeIQ + "_F" + QString::number(toneIndex); + } + return name; +} + +void DacDataModel::deinitBufferDac() { enableBuffer(false); } + +void DacDataModel::deinitDdsDac() { enableDds(false); } diff --git a/plugins/dac/src/dacdatamodel.h b/plugins/dac/src/dacdatamodel.h new file mode 100644 index 0000000000..9ce28bfb55 --- /dev/null +++ b/plugins/dac/src/dacdatamodel.h @@ -0,0 +1,107 @@ +#ifndef DACDATAMODEL_H +#define DACDATAMODEL_H + +#include +#include +#include +#include +#include + +#include + +namespace scopy { +namespace dac { +class TxNode; +class DacDataModel : public QObject +{ + Q_OBJECT +public: + DacDataModel(struct iio_device *dev, QObject *parent = nullptr); + virtual ~DacDataModel(); + + QString getName() const; + bool isBufferCapable() const; + bool isDds() const; + + void enableDds(bool enable); + void enableBuffer(bool enabled); + + QMap getBufferTxs() const; + QMap getDdsTxs() const; + + void setCyclic(bool cyclic); + void setKernelBuffersCount(unsigned int kernelCount); + void setDecimation(double decimation); + void setBuffersize(unsigned int buffersize); + void setFilesize(unsigned int filesize); + void setData(QVector> data); + void setSamplingFrequency(unsigned int sr); + + void enableBufferChannel(QString uuid, bool enable); + void start(); + void stop(); + struct iio_device *getDev() const; +Q_SIGNALS: + void reqInitBuffer(); + void log(QString log); + void invalidRunParams(); + void updateBuffersize(unsigned int bf); + void updateKernelBuffers(unsigned int kb); + +public Q_SLOTS: + void reset(); + +private Q_SLOTS: + void initBuffer(); + +private: + struct iio_device *m_dev; + QString m_name; + QList m_channels; + struct iio_buffer *m_buffer; + + unsigned int m_buffersize; + unsigned int m_userBuffersize; + unsigned int m_filesize; + unsigned int m_kernelBufferCount; + unsigned int m_userKernelBufferCount; + unsigned int m_samplingFrequency; + bool m_isBufferCapable; + bool m_isDds; + bool m_cyclicBuffer; + int m_decimation; + + bool m_activeDds; + bool m_activeBuffer; + + const QString toneId = "altvoltage"; + const int MAX_NB_TONES = 4; + const QString Q_CHANNEL = "Q"; + const QString I_CHANNEL = "I"; + + QMap m_ddsTxs; + QMap m_bufferTxs; + + QVector> m_data; + QFuture m_pushThd; + bool m_interrupted; + + bool initBufferDac(); + bool initDdsDac(); + void deinitBufferDac(); + void deinitDdsDac(); + QString generateToneName(QString chnId); + QStringList generateTxNodesForChannel(QString name); + void push(); + unsigned int getEnabledChannelsCount(); + bool validateBufferParams(); + void requestInterruption(); + bool dmaValidSelection(unsigned mask, unsigned channel_count); + bool txChannelsCheckValidSetup(); + int getTxChannelEnabledCount(unsigned *enabled_mask); + void autoBuffersizeAndKernelBuffers(); +}; +} // namespace dac +} // namespace scopy + +#endif // DACDATAMODEL_H diff --git a/plugins/dac/src/dacinstrument.cpp b/plugins/dac/src/dacinstrument.cpp new file mode 100644 index 0000000000..9844c2b41e --- /dev/null +++ b/plugins/dac/src/dacinstrument.cpp @@ -0,0 +1,244 @@ +#include "dacinstrument.h" +#include "dacdatamanager.h" +#include "dac_logging_categories.h" + +#include +#include + +using namespace scopy; +using namespace scopy::dac; +using namespace scopy::gui; + +DacInstrument::DacInstrument(const Connection *conn, QWidget *parent) + : QWidget(parent) + , m_conn(conn) +{ + StyleHelper::GetInstance()->initColorMap(); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + QHBoxLayout *lay = new QHBoxLayout(this); + lay->setMargin(0); + setLayout(lay); + tool = new ToolTemplate(this); + tool->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tool->bottomContainer()->setVisible(true); + tool->rightContainer()->setVisible(true); + tool->topContainer()->setVisible(true); + tool->setLeftContainerWidth(210); + tool->setRightContainerWidth(300); + tool->setTopContainerHeight(100); + tool->setBottomContainerHeight(90); + lay->addWidget(tool); + + openLastMenuBtn = new OpenLastMenuBtn(dynamic_cast(tool->rightContainer()), true, this); + rightMenuBtnGrp = dynamic_cast(openLastMenuBtn)->getButtonGroup(); + + infoBtn = new InfoBtn(this); + settingsBtn = new GearBtn(this); + + devicesGroup = new QButtonGroup(this); + devicesBtn = new MenuControlButton(this); + devicesBtn->setName("Devices"); + devicesBtn->setOpenMenuChecksThis(true); + devicesBtn->setDoubleClickToOpenMenu(true); + devicesBtn->checkBox()->setVisible(false); + devicesBtn->setChecked(true); + + rightMenuBtnGrp->addButton(settingsBtn); + rightMenuBtnGrp->addButton(devicesBtn->button()); + + connect(infoBtn, &QPushButton::clicked, this, &DacInstrument::startTutorial); + connect(devicesBtn, &QPushButton::toggled, dynamic_cast(tool->leftContainer()), + &MenuHAnim::toggleMenu); + + connect(settingsBtn, &QPushButton::toggled, this, [this](bool b) { + if(b) + tool->requestMenu(settingsMenuId); + }); + + deviceStack = new MapStackedWidget(this); + tool->rightStack()->add(devicesMenuId, deviceStack); + connect(devicesBtn->button(), &QAbstractButton::toggled, this, [this](bool b) { + if(b) + tool->requestMenu(devicesMenuId); + }); + + dacManagerStack = new MapStackedWidget(this); + tool->addWidgetToCentralContainerHelper(dacManagerStack); + + tool->addWidgetToTopContainerMenuControlHelper(openLastMenuBtn, TTA_RIGHT); + tool->addWidgetToTopContainerMenuControlHelper(settingsBtn, TTA_LEFT); + tool->addWidgetToTopContainerHelper(infoBtn, TTA_LEFT); + tool->addWidgetToBottomContainerHelper(devicesBtn, TTA_LEFT); + + setupDacDataManagers(); + setupDacDataDeviceButtons(); +} + +void DacInstrument::startTutorial() +{ + QWidget *parent = Util::findContainingWindow(this); + DacDataManager *currentDac = dynamic_cast(dacManagerStack->currentWidget()); + if(currentDac) { + parent = Util::findContainingWindow(currentDac); + } + m_dacBufferTutorial = new TutorialBuilder(this, ":/dacinstrument/tutorial_chapters.json", "dacbuffer", parent); + m_dacBufferNonCyclicTutorial = + new TutorialBuilder(this, ":/dacinstrument/tutorial_chapters.json", "dacbuffernoncyclic", parent); + m_dacDdsTutorial = new TutorialBuilder(this, ":/dacinstrument/tutorial_chapters.json", "dacdds", parent); + + connect(m_dacBufferTutorial, &TutorialBuilder::finished, this, &DacInstrument::startBufferNonCyclicTutorial); + connect(m_dacBufferNonCyclicTutorial, &TutorialBuilder::finished, this, &DacInstrument::startDdsTutorial); + connect(m_dacBufferTutorial, &gui::TutorialBuilder::aborted, this, &DacInstrument::abortTutorial); + + startBufferTutorial(); +} + +void DacInstrument::startBufferTutorial() +{ + DacDataManager *currentDac = dynamic_cast(dacManagerStack->currentWidget()); + if(currentDac) { + if(currentDac->isBufferCapable()) { + qInfo(CAT_DAC) << "Start Dac buffer tutorial"; + currentDac->toggleBufferMode(); + m_dacBufferTutorial->setTitle("Dac Buffer"); + m_dacBufferTutorial->start(); + } else { + startDdsTutorial(); + } + } +} + +void DacInstrument::startBufferNonCyclicTutorial() +{ + DacDataManager *currentDac = dynamic_cast(dacManagerStack->currentWidget()); + if(currentDac) { + if(currentDac->isBufferCapable()) { + qInfo(CAT_DAC) << "Start Dac Buffer non cyclic tutorial"; + currentDac->toggleCyclicBuffer(false); + m_dacBufferNonCyclicTutorial->setTitle("Dac Buffer"); + m_dacBufferNonCyclicTutorial->start(); + } + } +} + +void DacInstrument::startDdsTutorial() +{ + DacDataManager *currentDac = dynamic_cast(dacManagerStack->currentWidget()); + if(currentDac) { + if(currentDac->isBufferCapable()) { + currentDac->toggleCyclicBuffer(true); + } + if(currentDac->isDds()) { + qInfo(CAT_DAC) << "Start Dac DDS tutorial"; + currentDac->toggleDdsMode(); + m_dacDdsTutorial->setTitle("Dac DDS"); + m_dacDdsTutorial->start(); + } + } +} + +void DacInstrument::abortTutorial() +{ + disconnect(m_dacBufferTutorial, &gui::TutorialBuilder::finished, this, + &DacInstrument::startBufferNonCyclicTutorial); + disconnect(m_dacBufferNonCyclicTutorial, &gui::TutorialBuilder::finished, this, + &DacInstrument::startDdsTutorial); +} + +void DacInstrument::setupDacDataManagers() +{ + QStringList deviceList; + int devCount = iio_context_get_devices_count(m_conn->context()); + qDebug(CAT_DAC_INSTRUMENT) << " Found " << devCount << "devices"; + for(int i = 0; i < devCount; i++) { + iio_device *dev = iio_context_get_device(m_conn->context(), i); + QString dev_name = QString::fromLocal8Bit(iio_device_get_name(dev)); + + qDebug(CAT_DAC_INSTRUMENT) << "Looking for scan elements in " << dev_name; + QStringList channelList; + for(int j = 0; j < iio_device_get_channels_count(dev); j++) { + + struct iio_channel *chn = iio_device_get_channel(dev, j); + QString chn_name = QString::fromLocal8Bit(iio_channel_get_id(chn)); + qDebug(CAT_DAC_INSTRUMENT) << "Verify if " << chn_name << "is scan element"; + if(chn_name == "timestamp" /*|| chn_name == "accel_z" || chn_name =="accel_y"*/) + continue; + if(iio_channel_is_output(chn) && iio_channel_is_scan_element(chn)) { + channelList.append(chn_name); + deviceList.append(dev_name); + + // Create a DataManager for each detected dac iio_device + auto dm = new DacDataManager(dev, dacManagerStack); + m_dacDataManagers.append(dm); + + auto name = dm->getName(); + if(tool->rightStack()->contains(name)) { + name = name + QString::number(m_dacDataManagers.size() - 1); + } + + // Connect the DacManager menu request for its stack + connect(dm, &DacDataManager::requestMenu, [=]() { tool->requestMenu(name); }); + + // Map the DacManager menu stack to its name + tool->rightStack()->add(name, dm->getRightMenuStack()); + + // Add all the DacManager control btns to the group + auto menuBtns = dm->getMenuControlBtns(); + for(MenuControlButton *btn : qAsConst(menuBtns)) { + devicesGroup->addButton(btn); + } + break; + } + } + } +} + +void DacInstrument::setupDacDataDeviceButtons() +{ + // If only one dac device available, no need to have a leftside manager + if(m_dacDataManagers.size() == 1) { + DacDataManager *dev = m_dacDataManagers.at(0); + devicesBtn->setName(dev->getName()); + devicesBtn->setCheckable(false); + addDeviceToStack(dev, devicesBtn); + return; + } + + vcm = new VerticalChannelManager(this); + tool->leftContainer()->setVisible(true); + tool->leftStack()->add(verticalChannelManagerId, vcm); + for(DacDataManager *dac : qAsConst(m_dacDataManagers)) { + MenuControlButton *devBtn = addDevice(dac, vcm); + vcm->add(devBtn); + } +} + +MenuControlButton *DacInstrument::addDevice(DacDataManager *dev, QWidget *parent) +{ + auto devBtn = new MenuControlButton(parent); + devBtn->setName(dev->getName()); + devBtn->checkBox()->setVisible(false); + devBtn->setCheckable(true); + devBtn->button()->setVisible(false); + devBtn->setOpenMenuChecksThis(true); + devBtn->setDoubleClickToOpenMenu(true); + devicesGroup->addButton(devBtn); + addDeviceToStack(dev, devBtn); + return devBtn; +} + +void DacInstrument::addDeviceToStack(DacDataManager *dev, MenuControlButton *btn) +{ + QString id = dev->getName() + QString::number(uuid++); + dacManagerStack->add(id, dev); + deviceStack->add(id, dev->getWidget()); + connect(btn, &QPushButton::toggled, this, [=, this](bool b) { + if(b) { + tool->requestMenu(devicesMenuId); + deviceStack->show(id); + dacManagerStack->show(id); + } + }); +} + +DacInstrument::~DacInstrument() {} diff --git a/plugins/dac/src/dacinstrument.h b/plugins/dac/src/dacinstrument.h new file mode 100644 index 0000000000..6c3ef4431e --- /dev/null +++ b/plugins/dac/src/dacinstrument.h @@ -0,0 +1,64 @@ +#ifndef DACINSTRUMENT_H +#define DACINSTRUMENT_H +#include "scopy-dac_export.h" + +#include +#include +#include + +#include "verticalchannelmanager.h" +#include +#include +#include +#include +#include +#include + +namespace scopy { +namespace dac { +class DacDataManager; +class SCOPY_DAC_EXPORT DacInstrument : public QWidget +{ + Q_OBJECT +public: + DacInstrument(const Connection *conn, QWidget *parent = nullptr); + virtual ~DacInstrument(); + +public Q_SLOTS: + void startTutorial(); + +private: + void setupDacDataManagers(); + MenuControlButton *addDevice(DacDataManager *dev, QWidget *parent); + void addDeviceToStack(DacDataManager *dev, MenuControlButton *btn); + void setupDacDataDeviceButtons(); + void startBufferTutorial(); + void startBufferNonCyclicTutorial(); + void startDdsTutorial(); + void abortTutorial(); + + const Connection *m_conn; + ToolTemplate *tool; + InfoBtn *infoBtn; + GearBtn *settingsBtn; + QPushButton *openLastMenuBtn; + QButtonGroup *devicesGroup; + QButtonGroup *rightMenuBtnGrp; + MenuControlButton *devicesBtn; + MapStackedWidget *deviceStack; + MapStackedWidget *dacManagerStack; + QList m_dacDataManagers; + VerticalChannelManager *vcm; + gui::TutorialBuilder *m_dacBufferTutorial; + gui::TutorialBuilder *m_dacBufferNonCyclicTutorial; + gui::TutorialBuilder *m_dacDdsTutorial; + int uuid = 0; + + const QString settingsMenuId = "settings"; + const QString devicesMenuId = "devices"; + const QString verticalChannelManagerId = "vcm"; +}; +} // namespace dac +} // namespace scopy + +#endif // DACINSTRUMENT_H_ diff --git a/plugins/dac/src/dacplugin.cpp b/plugins/dac/src/dacplugin.cpp new file mode 100644 index 0000000000..0d573ca5fe --- /dev/null +++ b/plugins/dac/src/dacplugin.cpp @@ -0,0 +1,114 @@ +#include "dacplugin.h" +#include "dac_logging_categories.h" +#include "dacinstrument.h" + +#include + +#include + +using namespace scopy; +using namespace scopy::dac; + +bool DACPlugin::compatible(QString m_param, QString category) +{ + qDebug(CAT_DAC) << "compatible"; + bool ret = false; + Connection *conn = ConnectionProvider::GetInstance()->open(m_param); + if(conn == nullptr) + return ret; + + for(int i = 0; i < iio_context_get_devices_count(conn->context()); i++) { + iio_device *dev = iio_context_get_device(conn->context(), i); + if(dev) { + ret = true; + goto finish; + } + } +finish: + + ConnectionProvider::GetInstance()->close(m_param); + return ret; +} + +bool DACPlugin::loadPage() +{ + m_page = new QWidget(); + QVBoxLayout *lay = new QVBoxLayout(m_page); + m_page->setLayout(lay); + return true; +} + +bool DACPlugin::loadIcon() +{ + SCOPY_PLUGIN_ICON(":/gui/icons/scopy-default/icons/tool_oscilloscope.svg"); + return true; +} + +void DACPlugin::loadToolList() +{ + m_toolList.append( + SCOPY_NEW_TOOLMENUENTRY("dac", "Dac", ":/gui/icons/scopy-default/icons/tool_signal_generator.svg")); +} + +void DACPlugin::unload() +{ + if(m_page) { + delete m_page; + } +} + +QString DACPlugin::description() { return "Tool for generic IIO DAC control."; } + +QString DACPlugin::about() +{ + QString content = "DAC plugin"; + return content; +} + +bool DACPlugin::onConnect() +{ + Connection *conn = ConnectionProvider::GetInstance()->open(m_param); + if(conn == nullptr) + return false; + m_ctx = conn->context(); + dac = new DacInstrument(conn); + m_toolList[0]->setTool(dac); + m_toolList[0]->setEnabled(true); + m_toolList[0]->setRunBtnVisible(true); + return true; +} + +bool DACPlugin::onDisconnect() +{ + qDebug(CAT_DAC) << "disconnect"; + for(auto &tool : m_toolList) { + tool->setEnabled(false); + tool->setRunning(false); + tool->setRunBtnVisible(false); + QWidget *w = tool->tool(); + if(w) { + w->deleteLater(); + tool->setTool(nullptr); + } + } + if(m_ctx) { + ConnectionProvider::GetInstance()->close(m_param); + } + return true; +} + +void DACPlugin::initMetadata() +{ + loadMetadata( + R"plugin( + { + "priority":10, + "category":[ + "iio", + "dac" + ] + } +)plugin"); +} + +QString DACPlugin::version() { return "0.1"; } diff --git a/plugins/dac/src/dacutils.cpp b/plugins/dac/src/dacutils.cpp new file mode 100644 index 0000000000..d8f86d712e --- /dev/null +++ b/plugins/dac/src/dacutils.cpp @@ -0,0 +1,22 @@ +#include "dacutils.h" + +#include +#include + +using namespace scopy; +using namespace scopy::dac; + +#define SCALE_MINUS_INFINITE -91 + +double DacUtils::dbFullScaleConvert(double scale, bool inverse) +{ + if(inverse) { + if(scale == 0) + return -DBL_MAX; + return (int)((20 * log10(scale)) - 0.5); + } else { + if(scale == SCALE_MINUS_INFINITE) + return 0; + return pow(10, scale / 20.0); + } +} diff --git a/plugins/dac/src/dacutils.h b/plugins/dac/src/dacutils.h new file mode 100644 index 0000000000..ba7831be02 --- /dev/null +++ b/plugins/dac/src/dacutils.h @@ -0,0 +1,18 @@ +#ifndef DACUTILS_H +#define DACUTILS_H + +#include +#include "scopy-dac_export.h" + +namespace scopy { +namespace dac { +class SCOPY_DAC_EXPORT DacUtils : public QObject +{ + Q_OBJECT +public: + static double dbFullScaleConvert(double scale, bool inverse); +}; +} // namespace dac +} // namespace scopy + +#endif // DACUTILS_H diff --git a/plugins/dac/src/databuffer.cpp b/plugins/dac/src/databuffer.cpp new file mode 100644 index 0000000000..26dad786bf --- /dev/null +++ b/plugins/dac/src/databuffer.cpp @@ -0,0 +1,31 @@ +#include "databuffer.h" + +#include + +using namespace scopy; +using namespace scopy::dac; + +DataBuffer::DataBuffer(DataGuiStrategyInterface *guids, DataBufferStrategyInterface *ds, QWidget *parent) + : QObject(parent) + , m_dataStrategy(ds) + , m_guiStrategy(guids) +{ + m_parent = parent; + if(m_guiStrategy) { + connect(dynamic_cast(m_guiStrategy), SIGNAL(recipeUpdated(DataBufferRecipe)), + dynamic_cast(m_dataStrategy), SLOT(recipeUpdated(DataBufferRecipe))); + } + connect(dynamic_cast(m_dataStrategy), SIGNAL(loadFinished()), this, SIGNAL(loadFinished())); + connect(dynamic_cast(m_dataStrategy), SIGNAL(loadFailed()), this, SIGNAL(loadFailed())); + connect(dynamic_cast(m_dataStrategy), SIGNAL(dataUpdated()), this, SIGNAL(dataUpdated())); +} + +DataBuffer::~DataBuffer() {} + +DataBufferStrategyInterface *DataBuffer::getDataBufferStrategy() { return m_dataStrategy; } + +DataGuiStrategyInterface *DataBuffer::getDataGuiStrategyInterface() { return m_guiStrategy; } + +QWidget *DataBuffer::getParent() { return m_parent; } + +void DataBuffer::loadData() { m_dataStrategy->loadData(); } diff --git a/plugins/dac/src/databuffer.h b/plugins/dac/src/databuffer.h new file mode 100644 index 0000000000..022b4119d2 --- /dev/null +++ b/plugins/dac/src/databuffer.h @@ -0,0 +1,37 @@ +#ifndef DATABUFFER_H +#define DATABUFFER_H +#include "scopy-dac_export.h" +#include "databufferstrategyinterface.h" +#include "dataguistrategyinterface.h" + +#include +namespace scopy { +namespace dac { +class SCOPY_DAC_EXPORT DataBuffer : public QObject +{ + Q_OBJECT +public: + explicit DataBuffer(DataGuiStrategyInterface *guids, DataBufferStrategyInterface *ds, + QWidget *parent = nullptr); + ~DataBuffer(); + DataBufferStrategyInterface *getDataBufferStrategy(); + DataGuiStrategyInterface *getDataGuiStrategyInterface(); + + QWidget *getParent(); + +public Q_SLOTS: + void loadData(); + +Q_SIGNALS: + void loadFinished(); + void loadFailed(); + void dataUpdated(); + +private: + QWidget *m_parent; + DataBufferStrategyInterface *m_dataStrategy; + DataGuiStrategyInterface *m_guiStrategy; +}; +} // namespace dac +} // namespace scopy +#endif // DATABUFFER_H diff --git a/plugins/dac/src/databufferbuilder.cpp b/plugins/dac/src/databufferbuilder.cpp new file mode 100644 index 0000000000..8e75f861ed --- /dev/null +++ b/plugins/dac/src/databufferbuilder.cpp @@ -0,0 +1,138 @@ +#include "databufferbuilder.h" +#include "filemanager.h" +#include "databuffer.h" +#include "dac_logging_categories.h" +#include "csvfilestrategy.h" +#include "databufferstrategyinterface.h" +#include "filedataguistrategy.h" +#include "dataguistrategyinterface.h" +#include +#include + +using namespace scopy; +using namespace scopy::dac; +DataBufferBuilder::DataBufferBuilder(QObject *parent) + : QObject(parent) + , m_dataStrategy(DS::NoDataStrategy) + , m_guiStrategy(GuiDS::NoGuiStrategy) + , m_widgetParent(nullptr) + , m_filename("") +{} + +DataBufferBuilder::~DataBufferBuilder() {} + +DataBuffer *DataBufferBuilder::build() +{ + DataBufferStrategyInterface *ds = nullptr; + DataGuiStrategyInterface *guids = nullptr; + DataBuffer *widget = nullptr; + if(m_dataStrategy == DS::NoDataStrategy) { + qDebug(CAT_DAC_DATABUILDER) << "Not enough information provided"; + return nullptr; + } + ds = createDS(); + if(ds == nullptr) { + return widget; + } + + if(m_guiStrategy == GuiDS::NoGuiStrategy) { + qDebug(CAT_DAC_DATABUILDER) << "Not building UI Strategy"; + } else { + guids = createGuiGS(); + } + + widget = new DataBuffer(guids, ds, m_widgetParent); + if(guids) { + guids->init(); + } + return widget; +} + +DataBufferBuilder &DataBufferBuilder::dataStrategy(DataBufferBuilder::DS dataStrategy) +{ + m_dataStrategy = dataStrategy; + return *this; +} + +DataBufferBuilder &DataBufferBuilder::guiStrategy(GuiDS guiStrategy) +{ + m_guiStrategy = guiStrategy; + return *this; +} + +DataBufferBuilder &DataBufferBuilder::file(QString fullFilename) +{ + m_filename = fullFilename; + return *this; +} + +DataBufferBuilder &DataBufferBuilder::parent(QWidget *parent) +{ + m_widgetParent = parent; + return *this; +} + +DataBufferStrategyInterface *DataBufferBuilder::createDS() +{ + DataBufferStrategyInterface *ds = nullptr; + DS tempStrategy = m_dataStrategy; + bool fileOk = true; + + switch(tempStrategy) { + case DS::NoDataStrategy: + qDebug(CAT_DAC_DATABUILDER) << "Could not determine an appropriate strategy"; + break; + case DS::FileStrategy: + if(m_filename.endsWith(".csv")) { + ds = new CSVFileStrategy(m_filename, m_widgetParent); + } else { + qDebug(CAT_DAC_DATABUILDER) << "No compatible strategy found"; + } + break; + case DS::CsvFileStrategy: + if(m_filename == "") { + qDebug(CAT_DAC_DATABUILDER) << "Provide a valid file path for CSV Strategy"; + } + fileOk = checkFileValidity(m_filename, m_dataStrategy); + if(fileOk) { + ds = new CSVFileStrategy(m_filename, m_widgetParent); + } else { + qDebug(CAT_DAC_DATABUILDER) << "Provide a valid file path for CSV Strategy"; + } + break; + case DS::SinewaveData: + default: + qDebug(CAT_DAC_DATABUILDER) << "No valid arguments provided"; + break; + } + return ds; +} + +DataGuiStrategyInterface *DataBufferBuilder::createGuiGS() +{ + DataGuiStrategyInterface *guids = nullptr; + + if(m_filename != "") { + guids = new FileDataGuiStrategy(m_widgetParent); + } + return guids; +} + +bool DataBufferBuilder::checkFileValidity(QString filepath, DataBufferBuilder::DS ds) +{ + bool valid = true; + QFile file(filepath); + file.open(QFile::ReadOnly); + if(!file.isOpen()) { + qDebug(CAT_DAC_DATABUILDER) << "Invalid file path"; + valid = false; + } else { + file.close(); + if(ds == DS::CsvFileStrategy) { + valid = filepath.endsWith(".csv"); + } else if(ds == DS::MatlabFileStrategy) { + valid = filepath.endsWith(".mat"); + } + } + return valid; +} diff --git a/plugins/dac/src/databufferbuilder.h b/plugins/dac/src/databufferbuilder.h new file mode 100644 index 0000000000..73c05e72f1 --- /dev/null +++ b/plugins/dac/src/databufferbuilder.h @@ -0,0 +1,81 @@ +#ifndef SCOPY_DATABUFFERBUILDER_H +#define SCOPY_DATABUFFERBUILDER_H + +#include "scopy-dac_export.h" +#include + +namespace scopy { +namespace dac { +class DataBufferStrategyInterface; +class DataGuiStrategyInterface; +class DataBuffer; +class SCOPY_DAC_EXPORT DataBufferBuilder : public QObject +{ + Q_OBJECT +public: + enum DS + { // Data Strategy + NoDataStrategy, + FileStrategy, + CsvFileStrategy, + MatlabFileStrategy, + BinaryFileStrategy, + SinewaveData, + }; + + enum GuiDS + { // GUI Strategy + NoGuiStrategy, + AutoGuiStrategy, + FileGuiStrategy, + SinewaveGuiStrategy, + }; + + explicit DataBufferBuilder(QObject *parent = nullptr); + ~DataBufferBuilder(); + + /** + * @brief Build a DataBuffer based on the given parameters + * @return DataBuffer* + */ + DataBuffer *build(); + + /** + * @brief Sets the data strategy (DS) that will be used. + * @param dataStrategy + */ + DataBufferBuilder &dataStrategy(DataBufferBuilder::DS dataStrategy); + + /** + * @brief Sets the data strategy (GuiDS) that will be used. + * @param dataStrategy + */ + DataBufferBuilder &guiStrategy(DataBufferBuilder::GuiDS guiStrategy); + + /** + * @brief Set the filename to be used by the DS. + * @param fullFilename + * @return + */ + DataBufferBuilder &file(QString fullFilename); + + /** + * @brief Sets the parent of the DataBuffer that will be built. + * @param parent + */ + DataBufferBuilder &parent(QWidget *parent); + +private: + DataBufferStrategyInterface *createDS(); + DataGuiStrategyInterface *createGuiGS(); + + DataBufferBuilder::DS m_dataStrategy; + DataBufferBuilder::GuiDS m_guiStrategy; + QWidget *m_widgetParent; + QString m_filename; + + bool checkFileValidity(QString filepath, DataBufferBuilder::DS ds); +}; +} // namespace dac +} // namespace scopy +#endif // SCOPY_DATABUFFERBUILDER_H diff --git a/plugins/dac/src/databufferrecipe.h b/plugins/dac/src/databufferrecipe.h new file mode 100644 index 0000000000..a5305a5162 --- /dev/null +++ b/plugins/dac/src/databufferrecipe.h @@ -0,0 +1,19 @@ +#ifndef DATABUFFERRECIPE_H +#define DATABUFFERRECIPE_H + +#include "scopy-dac_export.h" + +namespace scopy { +namespace dac { +struct SCOPY_DAC_EXPORT DataBufferRecipe +{ + double scale = 0.0; + int offset = 0; + int decimation = 0; + bool scaled = false; + double phase = 0.0; + int frequency = 0; +}; +} // namespace dac +} // namespace scopy +#endif // DATABUFFERRECIPE_H diff --git a/plugins/dac/src/databufferstrategyinterface.h b/plugins/dac/src/databufferstrategyinterface.h new file mode 100644 index 0000000000..ca343882fc --- /dev/null +++ b/plugins/dac/src/databufferstrategyinterface.h @@ -0,0 +1,28 @@ +#ifndef DATABUFFERSTRATEGYINTERFACE_H +#define DATABUFFERSTRATEGYINTERFACE_H + +#include "scopy-dac_export.h" +#include "databufferrecipe.h" +#include +#include + +namespace scopy { +namespace dac { +class SCOPY_DAC_EXPORT DataBufferStrategyInterface +{ +public: + virtual ~DataBufferStrategyInterface() = default; + + virtual QVector> data() = 0; +public Q_SLOTS: + virtual void recipeUpdated(DataBufferRecipe) = 0; + virtual void loadData() = 0; +Q_SIGNALS: + virtual void loadFinished() = 0; + virtual void loadFailed() = 0; + virtual void dataUpdated() = 0; +}; +} // namespace dac +} // namespace scopy +Q_DECLARE_INTERFACE(scopy::dac::DataBufferStrategyInterface, "scopy::DataBufferStrategyInterface") +#endif // DATABUFFERSTRATEGYINTERFACE_H diff --git a/plugins/dac/src/dataguistrategyinterface.h b/plugins/dac/src/dataguistrategyinterface.h new file mode 100644 index 0000000000..e0aaeac0a3 --- /dev/null +++ b/plugins/dac/src/dataguistrategyinterface.h @@ -0,0 +1,26 @@ +#ifndef DATAGUISTRATEGYINTERFACE_H +#define DATAGUISTRATEGYINTERFACE_H + +#include "scopy-dac_export.h" +#include "databufferrecipe.h" +#include + +namespace scopy { +namespace dac { +class SCOPY_DAC_EXPORT DataGuiStrategyInterface +{ +public: + virtual ~DataGuiStrategyInterface() = default; + virtual QWidget *ui() = 0; + virtual void init() = 0; + +Q_SIGNALS: + virtual void recipeUpdated(DataBufferRecipe) = 0; + +protected: + DataBufferRecipe m_recipe; +}; +} // namespace dac +} // namespace scopy +Q_DECLARE_INTERFACE(scopy::dac::DataGuiStrategyInterface, "scopy::DataGuiStrategyInterface") +#endif // DATAGUISTRATEGYINTERFACE_H diff --git a/plugins/dac/src/ddsdacaddon.cpp b/plugins/dac/src/ddsdacaddon.cpp new file mode 100644 index 0000000000..f851ec73bf --- /dev/null +++ b/plugins/dac/src/ddsdacaddon.cpp @@ -0,0 +1,202 @@ +#include "ddsdacaddon.h" +#include "dacdatamodel.h" +#include "txnode.h" +#include "txchannel.h" +#include "txtone.h" +#include "dac_logging_categories.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace scopy; +using namespace scopy::dac; +DdsDacAddon::DdsDacAddon(DacDataModel *model, QWidget *parent) + : DacAddon(parent) + , m_model(model) +{ + auto m_layout = new QHBoxLayout(); + m_layout->setMargin(0); + m_layout->setAlignment(Qt::AlignTop); + setLayout(m_layout); + + QScrollArea *scrollArea = new QScrollArea(this); + QWidget *txsContainer = new QWidget(scrollArea); + QVBoxLayout *txsContainerLayout = new QVBoxLayout(txsContainer); + txsContainerLayout->setMargin(0); + txsContainerLayout->setSpacing(10); + txsContainerLayout->setAlignment(Qt::AlignTop); + txsContainer->setLayout(txsContainerLayout); + + scrollArea->setWidgetResizable(true); + scrollArea->setWidget(txsContainer); + + for(int i = 0; i < model->getDdsTxs().size(); i++) { + TxNode *txNode = model->getDdsTxs().value(model->getDdsTxs().keys().at(i)); + QWidget *tx = setupDdsTx(txNode); + txsContainerLayout->addWidget(tx); + } + txsContainerLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding)); + m_layout->addWidget(scrollArea); +} + +QWidget *DdsDacAddon::setupDdsTx(TxNode *txNode) +{ + QWidget *tx = new QWidget(this); + QVBoxLayout *txLay = new QVBoxLayout(); + txLay->setMargin(0); + txLay->setSpacing(5); + tx->setLayout(txLay); + tx->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + + MapStackedWidget *ddsModeStack = new MapStackedWidget(this); + ddsModeStack->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + ddsModeStack->add(QString::number(TxMode::DISABLED), setupTxMode(txNode, TxMode::DISABLED)); + ddsModeStack->add(QString::number(TxMode::ONE_TONE), setupTxMode(txNode, TxMode::ONE_TONE)); + ddsModeStack->add(QString::number(TxMode::TWO_TONES), setupTxMode(txNode, TxMode::TWO_TONES)); + ddsModeStack->add(QString::number(TxMode::INDEPENDENT_IQ_CTRL), + setupTxMode(txNode, TxMode::INDEPENDENT_IQ_CTRL)); + + QWidget *txHeader = new QWidget(this); + QHBoxLayout *txHeaderLay = new QHBoxLayout(); + txHeaderLay->setMargin(0); + txHeaderLay->setSpacing(5); + txHeader->setLayout(txHeaderLay); + + MenuSectionWidget *txLabelSection = new MenuSectionWidget(this); + txLabelSection->setProperty("tutorial_name", "TX_INDICATOR"); + QLabel *txLabel = new QLabel(txNode->getUuid(), txLabelSection); + StyleHelper::MenuMediumLabel(txLabel); + txLabelSection->contentLayout()->addWidget(txLabel); + txLabelSection->setFixedHeight(60); + + MenuSectionWidget *txReadSection = new MenuSectionWidget(this); + txReadSection->setProperty("tutorial_name", "TX_READ"); + txReadSection->setFixedHeight(60); + txReadSection->setFixedWidth(60); + QPushButton *m_readBtn = new QPushButton(txReadSection); + StyleHelper::RefreshButton(m_readBtn, "ReadAttributesButton"); + connect(m_readBtn, &QPushButton::clicked, this, [=, this]() { + qDebug(CAT_DAC_DDS) << "Read button pressed"; + dynamic_cast(ddsModeStack->currentWidget())->read(); + }); + txReadSection->contentLayout()->addWidget(m_readBtn); + txReadSection->contentLayout()->setMargin(15); + txReadSection->contentLayout()->setSpacing(0); + + MenuSectionWidget *ddsModeSection = new MenuSectionWidget(this); + ddsModeSection->setProperty("tutorial_name", "TX_MODE_SELECTOR"); + m_ddsModeCombo = new MenuCombo("DDS MODE", this); + StyleHelper::IIOComboBox(m_ddsModeCombo->combo()); + StyleHelper::BackgroundWidget(m_ddsModeCombo); + auto cb = m_ddsModeCombo->combo(); + m_ddsModeCombo->combo()->addItem("Disabled", TxMode::DISABLED); + m_ddsModeCombo->combo()->addItem("One CW Tone", TxMode::ONE_TONE); + m_ddsModeCombo->combo()->addItem("Two CW Tones", TxMode::TWO_TONES); + m_ddsModeCombo->combo()->addItem("Independent I/Q Control", TxMode::INDEPENDENT_IQ_CTRL); + connect(cb, qOverload(&QComboBox::currentIndexChanged), this, [=](int idx) { + auto mode = cb->itemData(idx).toInt(); + TxMode *current = dynamic_cast(ddsModeStack->get(QString::number(mode))); + if(current) { + current->enable(mode != TxMode::DISABLED); + current->read(); + } + ddsModeStack->show(QString::number(mode)); + }); + + ddsModeSection->contentLayout()->addWidget(m_ddsModeCombo); + txHeaderLay->addWidget(txLabelSection, 1); + txHeaderLay->addWidget(txReadSection, 1); + txHeaderLay->addWidget(ddsModeSection, 4); + + txLay->addWidget(txHeader); + txLay->addWidget(ddsModeStack); + + return tx; +} + +QWidget *DdsDacAddon::setupTxMode(TxNode *txNode, unsigned int mode) +{ + TxMode *txModeWidget = new TxMode(txNode, mode, this); + return txModeWidget; +} + +DdsDacAddon::~DdsDacAddon() {} + +void DdsDacAddon::enable(bool enable) { m_model->enableDds(enable); } + +TxMode::TxMode(TxNode *node, unsigned int mode, QWidget *parent) + : QWidget(parent) + , m_mode(mode) + , m_node(node) +{ + QVBoxLayout *txChnsLay = new QVBoxLayout(); + this->setLayout(txChnsLay); + txChnsLay->setSpacing(10); + txChnsLay->setMargin(0); + + auto txNodeChildren = m_node->getTones(); + for(TxNode *child : txNodeChildren) { + TxChannel *txChn = new TxChannel(child, mode, this); + m_txChannels.append(txChn); + if(mode != DISABLED) { + txChnsLay->addWidget(txChn); + } else { + txChn->setVisible(false); + } + } + + auto emitterChannel = m_txChannels.at(0); + auto toneCount = emitterChannel->toneCount(); + switch(mode) { + case ONE_TONE: + case TWO_TONES: + for(int chIdx = 1; chIdx < m_txChannels.size(); chIdx++) { + m_txChannels.at(chIdx)->setVisible(false); + // Pair all the first channel tones to the + // corresponding tones in each channel + for(int toneIdx = 0; toneIdx < toneCount; toneIdx++) { + TxTone *emitterTone = emitterChannel->tone(toneIdx); + if(!emitterTone) { + continue; + } + TxTone *pairTone = m_txChannels.at(chIdx)->tone(toneIdx); + if(!pairTone) { + continue; + } + emitterTone->setPairedTone(pairTone); + } + } + break; + default: + break; + } + this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); +} + +TxMode::~TxMode() {} + +void TxMode::read() +{ + for(TxChannel *chn : qAsConst(m_txChannels)) { + chn->read(); + } +} + +void TxMode::enable(bool enabled) +{ + // Write all the scale attrs to IIO + for(int chIdx = 0; chIdx < m_txChannels.size(); chIdx++) { + Q_EMIT m_txChannels[chIdx]->resetChannelScales(); + } + + // Set the "raw" attr to 0 or 1 + m_node->enableDds(enabled); +} diff --git a/plugins/dac/src/ddsdacaddon.h b/plugins/dac/src/ddsdacaddon.h new file mode 100644 index 0000000000..3282068478 --- /dev/null +++ b/plugins/dac/src/ddsdacaddon.h @@ -0,0 +1,55 @@ +#ifndef DDSDACADDON_H_ +#define DDSDACADDON_H_ + +#include +#include +#include +#include "dacaddon.h" + +namespace scopy { +namespace dac { +class DacDataModel; +class TxNode; +class TxChannel; +class DdsDacAddon : public DacAddon +{ + Q_OBJECT +public: + DdsDacAddon(DacDataModel *model, QWidget *parent = nullptr); + virtual ~DdsDacAddon(); + virtual void enable(bool enable); + +private: + DacDataModel *m_model; + MenuCombo *m_ddsModeCombo; + QWidget *setupDdsTx(TxNode *txNode); + QWidget *setupTxMode(TxNode *txNode, unsigned int mode); +}; + +class TxMode : public QWidget +{ + Q_OBJECT +public: + TxMode(TxNode *node, unsigned int mode, QWidget *parent = nullptr); + virtual ~TxMode(); + + typedef enum + { + DISABLED = 0, + ONE_TONE = 1, + TWO_TONES = 2, + INDEPENDENT_IQ_CTRL + } DdsMode; + + void read(); + void enable(bool enable); + +private: + TxNode *m_node; + unsigned int m_mode; + QList m_txChannels; +}; +} // namespace dac +} // namespace scopy + +#endif // DDSDACADDON_H_ diff --git a/plugins/dac/src/filebrowser.cpp b/plugins/dac/src/filebrowser.cpp new file mode 100644 index 0000000000..90237ea28a --- /dev/null +++ b/plugins/dac/src/filebrowser.cpp @@ -0,0 +1,60 @@ +#include "filebrowser.h" +#include "dac_logging_categories.h" + +#include +#include +#include + +#include + +using namespace scopy; +using namespace scopy::dac; +FileBrowser::FileBrowser(QWidget *parent) + : QWidget(parent) + , m_filename("") +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + auto m_layout = new QVBoxLayout(); + m_layout->setMargin(0); + m_layout->setSpacing(10); + setLayout(m_layout); + + MenuSectionWidget *fileBufferContainer = new MenuSectionWidget(this); + MenuCollapseSection *fileBufferSection = + new MenuCollapseSection("DAC Buffer Settings", MenuCollapseSection::MHCW_NONE, fileBufferContainer); + fileBufferSection->contentLayout()->setSpacing(10); + + m_fileBufferPath = new ProgressLineEdit(this); + m_fileBufferPath->getLineEdit()->setReadOnly(true); + + m_fileBufferBrowseBtn = new QPushButton("Browse", fileBufferSection); + m_fileBufferLoadBtn = new QPushButton("Load", fileBufferSection); + connect(m_fileBufferBrowseBtn, &QPushButton::clicked, this, &FileBrowser::chooseFile); + connect(m_fileBufferLoadBtn, &QPushButton::clicked, this, &FileBrowser::loadFile); + fileBufferSection->contentLayout()->addWidget(new QLabel("Choose file")); + fileBufferSection->contentLayout()->addWidget(m_fileBufferPath); + fileBufferSection->contentLayout()->addWidget(m_fileBufferBrowseBtn); + fileBufferSection->contentLayout()->addWidget(m_fileBufferLoadBtn); + fileBufferContainer->contentLayout()->addWidget(fileBufferSection); + StyleHelper::BlueButton(m_fileBufferBrowseBtn); + StyleHelper::BlueButton(m_fileBufferLoadBtn); + + m_layout->addWidget(fileBufferContainer); +} + +FileBrowser::~FileBrowser() {} + +QString FileBrowser::getFilePath() const { return m_filename; } + +void FileBrowser::loadFile() { Q_EMIT load(m_filename); } + +void FileBrowser::chooseFile() +{ + QString selectedFilter; + QString tmpFilename = QFileDialog::getOpenFileName(this, tr("Export"), "", tr("All Files(*)"), &selectedFilter, + QFileDialog::Options(QFileDialog::DontUseNativeDialog)); + if(!tmpFilename.isEmpty()) { + m_filename = tmpFilename; + m_fileBufferPath->getLineEdit()->setText(m_filename); + } +} diff --git a/plugins/dac/src/filebrowser.h b/plugins/dac/src/filebrowser.h new file mode 100644 index 0000000000..2b6ffc35c9 --- /dev/null +++ b/plugins/dac/src/filebrowser.h @@ -0,0 +1,34 @@ +#ifndef FILEBROWSER_H +#define FILEBROWSER_H + +#include "scopy-dac_export.h" +#include +#include + +#include + +namespace scopy { +namespace dac { +class SCOPY_DAC_EXPORT FileBrowser : public QWidget +{ + Q_OBJECT +public: + explicit FileBrowser(QWidget *parent = nullptr); + ~FileBrowser(); + QString getFilePath() const; +Q_SIGNALS: + void load(QString path); +private Q_SLOTS: + void chooseFile(); + void loadFile(); + +private: + ProgressLineEdit *m_fileBufferPath; + QPushButton *m_fileBufferBrowseBtn; + QPushButton *m_fileBufferLoadBtn; + QString m_filename; +}; +} // namespace dac +} // namespace scopy + +#endif // FILEBROWSER_H diff --git a/plugins/dac/src/filedataguistrategy.cpp b/plugins/dac/src/filedataguistrategy.cpp new file mode 100644 index 0000000000..4b927ef60b --- /dev/null +++ b/plugins/dac/src/filedataguistrategy.cpp @@ -0,0 +1,61 @@ +#include "filedataguistrategy.h" + +#include +#include +#include +#include +#include + +#include "dac_logging_categories.h" + +using namespace scopy; +using namespace scopy::dac; +FileDataGuiStrategy::FileDataGuiStrategy(QWidget *parent) + : QObject(parent) +{ + m_ui = new QWidget(parent); + + m_ui->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + auto lay = new QHBoxLayout(m_ui); + lay->setMargin(0); + lay->setSpacing(0); + m_ui->setLayout(lay); + + MenuSectionWidget *guiContainer = new MenuSectionWidget(m_ui); + MenuCollapseSection *guiSection = + new MenuCollapseSection("DATA CONFIGURATION", MenuCollapseSection::MHCW_ARROW, guiContainer); + guiSection->contentLayout()->setSpacing(10); + + m_scaled = new MenuOnOffSwitch("Scaled", guiSection); + m_scaleSpin = new TitleSpinBox("Scale (DBFS)", true, guiSection); + m_scaleSpin->setMax(0.0); + m_scaleSpin->setMin(-91.0); + m_scaleSpin->setStep(1.0); // when value invalid display -Inf db + StyleHelper::BackgroundWidget(m_scaleSpin); + + connect(m_scaled->onOffswitch(), &QAbstractButton::toggled, this, [this](bool b) { + m_recipe.scaled = b; + Q_EMIT recipeUpdated(m_recipe); + }); + connect(m_scaleSpin->getLineEdit(), &QLineEdit::textChanged, this, [this](QString text) { + bool ok = true; + double val = text.toDouble(&ok); + if(ok) { + m_recipe.scale = val; + Q_EMIT recipeUpdated(m_recipe); + } + }); + + guiSection->contentLayout()->addWidget(m_scaleSpin); + guiSection->contentLayout()->addWidget(m_scaled); + guiContainer->contentLayout()->addWidget(guiSection); + lay->addWidget(guiContainer); +} + +QWidget *FileDataGuiStrategy::ui() { return m_ui; } + +void FileDataGuiStrategy::init() +{ + m_scaleSpin->setValue(0.0); + m_scaled->onOffswitch()->setChecked(true); +} diff --git a/plugins/dac/src/filedataguistrategy.h b/plugins/dac/src/filedataguistrategy.h new file mode 100644 index 0000000000..5fba9393be --- /dev/null +++ b/plugins/dac/src/filedataguistrategy.h @@ -0,0 +1,35 @@ +#ifndef FILEDATAGUISTRATEGY_H +#define FILEDATAGUISTRATEGY_H + +#include +#include "dataguistrategyinterface.h" +#include "databufferrecipe.h" +#include "scopy-dac_export.h" +#include "dac_logging_categories.h" + +namespace scopy { +class MenuOnOffSwitch; +class TitleSpinBox; +namespace dac { +class SCOPY_DAC_EXPORT FileDataGuiStrategy : public QObject, public DataGuiStrategyInterface +{ + Q_OBJECT + Q_INTERFACES(scopy::dac::DataGuiStrategyInterface) +public: + explicit FileDataGuiStrategy(QWidget *parent = nullptr); + ~FileDataGuiStrategy() {} + + QWidget *ui() override; + void init() override; + +Q_SIGNALS: + void recipeUpdated(DataBufferRecipe) override; + +private: + QWidget *m_ui; + MenuOnOffSwitch *m_scaled; + TitleSpinBox *m_scaleSpin; +}; +} // namespace dac +} // namespace scopy +#endif // FILEDATAGUISTRATEGY_H diff --git a/plugins/dac/src/txchannel.cpp b/plugins/dac/src/txchannel.cpp new file mode 100644 index 0000000000..329ff33909 --- /dev/null +++ b/plugins/dac/src/txchannel.cpp @@ -0,0 +1,127 @@ +#include "txchannel.h" +#include "txtone.h" +#include "txnode.h" +#include "dac_logging_categories.h" + +#include +#include + +using namespace scopy; +using namespace scopy::dac; +TxChannel::TxChannel(TxNode *node, unsigned int nbTonesMode, QWidget *parent) + : QWidget(parent) + , m_node(node) + , m_nbTonesMode(nbTonesMode) +{ + QVBoxLayout *chnIqLay = new QVBoxLayout(); + chnIqLay->setMargin(0); + chnIqLay->setSpacing(10); + this->setLayout(chnIqLay); + + connect(this, &TxChannel::resetChannelScales, this, &TxChannel::resetToneScales); + + auto children = m_node->getTones(); + bool isTone = (children.size() == 0); + if(!isTone) { + unsigned int i = 1; + QWidget *toneList = new QWidget(this); + toneList->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + QHBoxLayout *toneListLay = new QHBoxLayout(); + toneList->setLayout(toneListLay); + toneListLay->setMargin(0); + toneListLay->setSpacing(10); + + chnIqLay->addWidget(toneList); + + for(TxNode *nodeTone : children) { + MenuSectionWidget *toneSection = new MenuSectionWidget(this); + + TxTone *tone = setupTxTone(nodeTone, i); + connect(tone, &TxTone::scaleUpdated, this, &TxChannel::scaleUpdated); + // if hidden don't add to layout and set visible to false + if(i <= m_nbTonesMode) { + toneListLay->addWidget(toneSection); + toneSection->contentLayout()->addWidget(tone); + } else { + toneSection->setVisible(false); + } + i++; + } + } else { + MenuSectionWidget *toneSection = new MenuSectionWidget(this); + this->layout()->addWidget(toneSection); + auto tone = setupTxTone(node, 1); + toneSection->contentLayout()->addWidget(tone); + } + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); +} + +TxChannel::~TxChannel() {} + +void TxChannel::resetToneScales() +{ + auto keys = m_tones.keys(); + for(unsigned int idx : keys) { + if(idx > m_nbTonesMode) { + m_tones[idx]->updateScale("0"); + } + } +} + +void TxChannel::scaleUpdated(int toneIdx, QString oldScale, QString newScale) +{ + auto keys = m_tones.keys(); + for(unsigned int idx : keys) { + if(idx != toneIdx) { + // compute paired scale + auto scale2 = m_tones[idx]->scale(); + auto scale1 = newScale; + + bool ok; + double d_scale1 = scale1.toDouble(&ok); + if(!ok) { + return; + } + double d_scale2 = scale2.toDouble(&ok); + if(!ok) { + return; + } + + if((d_scale1 + d_scale2) > 1) { + m_tones[toneIdx]->updateScale(oldScale); + } + } + } +} + +TxTone *TxChannel::setupTxTone(TxNode *nodeTone, unsigned int index) +{ + TxTone *tone = new TxTone(nodeTone, index, this); + m_tones.insert(index, tone); + return tone; +} + +void TxChannel::read() +{ + for(auto tone : qAsConst(m_tones)) { + tone->read(); + } +} + +void TxChannel::enable(bool enable) { m_node->enableDds(enable); } + +QString TxChannel::channelUuid() const { return m_node->getUuid(); } + +QString TxChannel::frequency(unsigned int toneIdx) { return m_tones.value(toneIdx)->frequency(); } + +QString TxChannel::phase(unsigned int toneIdx) { return m_tones.value(toneIdx)->phase(); } + +unsigned int TxChannel::toneCount() { return m_tones.count(); } + +TxTone *TxChannel::tone(unsigned int toneIdx) +{ + if(m_tones.contains(toneIdx)) { + return m_tones[toneIdx]; + } + return nullptr; +} diff --git a/plugins/dac/src/txchannel.h b/plugins/dac/src/txchannel.h new file mode 100644 index 0000000000..0e20909ffd --- /dev/null +++ b/plugins/dac/src/txchannel.h @@ -0,0 +1,44 @@ +#ifndef TXCHANNEL_H +#define TXCHANNEL_H + +#include +#include + +namespace scopy { +namespace dac { +class TxNode; +class TxTone; +class TxChannel : public QWidget +{ + Q_OBJECT +public: + TxChannel(TxNode *node, unsigned int nbTonesMode, QWidget *parent = nullptr); + virtual ~TxChannel(); + + void read(); + void enable(bool enable); + QString channelUuid() const; + QString frequency(unsigned int toneIdx); + QString phase(unsigned int toneIdx); + TxTone *tone(unsigned int toneIdx); + unsigned int toneCount(); + QMap getToneMenus() const; +Q_SIGNALS: + void resetChannelScales(); +public Q_SLOTS: + void scaleUpdated(int toneIdx, QString oldScale, QString newScale); + +private Q_SLOTS: + void resetToneScales(); + +private: + TxNode *m_node; + QMap m_tones; + unsigned int m_nbTonesMode; + + TxTone *setupTxTone(TxNode *nodeTone, unsigned int index); +}; +} // namespace dac +} // namespace scopy + +#endif // TXCHANNEL_H diff --git a/plugins/dac/src/txnode.cpp b/plugins/dac/src/txnode.cpp new file mode 100644 index 0000000000..0806366388 --- /dev/null +++ b/plugins/dac/src/txnode.cpp @@ -0,0 +1,71 @@ +#include "txnode.h" +#include "dac_logging_categories.h" + +using namespace scopy; +using namespace scopy::dac; +TxNode::TxNode(QString uuid, iio_channel *chn, QObject *parent) + : QObject(parent) + , m_channel(chn) + , m_txUuid(uuid) +{} + +TxNode::~TxNode() +{ + for(auto node : qAsConst(m_childNodes)) { + delete node; + } + m_childNodes.clear(); + qDebug(CAT_DAC_DATA) << QString("Delete TX Node %1").arg(m_txUuid); +} + +TxNode *TxNode::addChildNode(QString uuid, iio_channel *chn) +{ + TxNode *child = m_childNodes.value(uuid, nullptr); + if(!child) { + child = new TxNode(uuid, chn, this); + m_childNodes.insert(uuid, child); + } + return child; +} + +QMap TxNode::getTones() const { return m_childNodes; } + +QString TxNode::getUuid() const { return m_txUuid; } + +iio_channel *TxNode::getChannel() { return m_channel; } + +bool TxNode::enableDds(bool enable) +{ + qDebug(CAT_DAC_DATA) << QString("Try enable DDS TXNode %1").arg(m_txUuid); + if(m_channel) { + if(iio_channel_get_type(m_channel) == IIO_ALTVOLTAGE) { + int ret = iio_channel_attr_write_bool(m_channel, "raw", enable); + if(ret < 0) { + qDebug(CAT_DAC_DATA) << QString("Can't enable DDS channel, error: %1").arg(ret); + return false; + } + qDebug(CAT_DAC_DATA) << QString("DDS channel %1 enabled: %2, ret code %3") + .arg(m_txUuid) + .arg(enable) + .arg(ret); + } else { + qDebug(CAT_DAC_DATA) << QString("%1 not a DDS channel").arg(m_txUuid); + return false; + } + } else if(m_childNodes.size() != 0) { + for(auto node : qAsConst(m_childNodes)) { + bool ret = node->enableDds(enable); + if(!ret) { + return ret; + } + } + } else { + qDebug(CAT_DAC_DATA) << "can't enable DDS channel, invalid selection"; + return false; + } + return true; +} + +const QColor &TxNode::getColor() const { return m_color; } + +void TxNode::setColor(const QColor &newColor) { m_color = newColor; } diff --git a/plugins/dac/src/txnode.h b/plugins/dac/src/txnode.h new file mode 100644 index 0000000000..f52aa1cbfd --- /dev/null +++ b/plugins/dac/src/txnode.h @@ -0,0 +1,40 @@ +#ifndef TXNODE_H +#define TXNODE_H + +#include +#include +#include +#include + +#include + +namespace scopy { +namespace dac { +class TxNode : public QObject +{ + Q_OBJECT +public: + TxNode(QString uuid, struct iio_channel *chn = nullptr, QObject *parent = nullptr); + virtual ~TxNode(); + + TxNode *addChildNode(QString uuid, struct iio_channel *chn = nullptr); + + QMap getTones() const; + QString getUuid() const; + struct iio_channel *getChannel(); + + bool enableDds(bool enable); + + const QColor &getColor() const; + void setColor(const QColor &newColor); + +private: + QString m_txUuid; + QMap m_childNodes = {}; + struct iio_channel *m_channel; + QColor m_color; +}; +} // namespace dac +} // namespace scopy + +#endif // TXNODE_H diff --git a/plugins/dac/src/txtone.cpp b/plugins/dac/src/txtone.cpp new file mode 100644 index 0000000000..0fab118de6 --- /dev/null +++ b/plugins/dac/src/txtone.cpp @@ -0,0 +1,287 @@ +#include "txtone.h" +#include "txnode.h" +#include "dac_logging_categories.h" +#include "dacutils.h" + +#include +#include + +#include +#include + +using namespace scopy; +using namespace scopy::dac; + +TxTone::TxTone(TxNode *node, unsigned int idx, QWidget *parent) + : QWidget(parent) + , m_node(node) + , m_pairedTone(nullptr) + , m_idx(idx) +{ + QVBoxLayout *toneLay = new QVBoxLayout(this); + toneLay->setMargin(0); + toneLay->setSpacing(10); + this->setLayout(toneLay); + + QWidget *headerWidget = new QWidget(this); + QHBoxLayout *headerLay = new QHBoxLayout(headerWidget); + headerLay->setMargin(0); + headerLay->setSpacing(0); + headerWidget->setLayout(headerLay); + + QLabel *name = new QLabel(this); + name->setText("Tone " + QString::number(idx) + " : " + m_node->getUuid()); + StyleHelper::MenuComboLabel(name); + StyleHelper::BackgroundWidget(this); + + m_frequency = IIOWidgetBuilder() + .channel(m_node->getChannel()) + .attribute("frequency") + .uiStrategy(IIOWidgetBuilder::UIS::EditableUi) + .parent(this) + .buildSingle(); + m_frequency->setUItoDataConversion(std::bind(&TxTone::frequencyUItoDS, this, std::placeholders::_1)); + m_frequency->setDataToUIConversion(std::bind(&TxTone::frequencyDStoUI, this, std::placeholders::_1)); + + m_scale = IIOWidgetBuilder() + .channel(m_node->getChannel()) + .attribute("scale") + .uiStrategy(IIOWidgetBuilder::UIS::EditableUi) + .parent(this) + .buildSingle(); + m_scale->setUItoDataConversion(std::bind(&TxTone::scaleUItoDS, std::placeholders::_1)); + m_scale->setDataToUIConversion(std::bind(&TxTone::scaleDStoUI, std::placeholders::_1)); + + m_phase = IIOWidgetBuilder() + .channel(m_node->getChannel()) + .attribute("phase") + .uiStrategy(IIOWidgetBuilder::UIS::EditableUi) + .parent(this) + .buildSingle(); + + m_phase->setUItoDataConversion(std::bind(&TxTone::phaseUItoDS, this, std::placeholders::_1)); + m_phase->setDataToUIConversion(std::bind(&TxTone::phaseDStoUI, this, std::placeholders::_1)); + + connect(dynamic_cast(m_frequency->getDataStrategy()), + &ChannelAttrDataStrategy::emitStatus, this, &TxTone::forwardFreqChange); + + connect(dynamic_cast(m_scale->getDataStrategy()), + &ChannelAttrDataStrategy::emitStatus, this, &TxTone::forwardScaleChange); + + connect(dynamic_cast(m_phase->getDataStrategy()), + &ChannelAttrDataStrategy::emitStatus, this, &TxTone::forwardPhaseChange); + + headerLay->addWidget(name); + headerLay->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred)); + + toneLay->addWidget(headerWidget); + toneLay->addWidget(m_frequency); + toneLay->addWidget(m_scale); + toneLay->addWidget(m_phase); +} + +TxTone::~TxTone() {} + +void TxTone::updateFrequency(QString frequency) +{ + if(m_frequency->read().first == frequency) { + return; + } + qDebug(CAT_DAC_DDS) << "Tone " << m_node->getUuid() << "frequency update."; + m_frequency->getDataStrategy()->write(frequency); + m_frequency->getUiStrategy()->requestData(); +} + +void TxTone::updateScale(QString scale) +{ + if(m_scale->read().first == scale) { + return; + } + m_scale->getDataStrategy()->write(scale); + m_scale->getUiStrategy()->requestData(); +} + +void TxTone::updatePhase(QString phase) +{ + if(m_phase->read().first == phase) { + return; + } + m_phase->getDataStrategy()->write(phase); + m_phase->getUiStrategy()->requestData(); +} + +void TxTone::forwardScaleChange(QDateTime timestamp, QString oldData, QString newData, int retCode, bool readOp) +{ + if(retCode < 0 || readOp) { + return; + } + qDebug(CAT_DAC_DDS) << "Tone " << m_node->getUuid() << "emit scale updated."; + Q_EMIT scaleUpdated(m_idx, oldData, newData); +} + +void TxTone::forwardPhaseChange(QDateTime timestamp, QString oldData, QString newData, int retCode, bool readOp) +{ + if(retCode < 0 || readOp) { + return; + } + qDebug(CAT_DAC_DDS) << "Tone " << m_node->getUuid() << "emit phase updated."; + Q_EMIT phaseUpdated(m_idx, newData); +} + +void TxTone::forwardFreqChange(QDateTime timestamp, QString oldData, QString newData, int retCode, bool readOp) +{ + if(retCode < 0 || readOp) { + return; + } + qDebug(CAT_DAC_DDS) << "Tone " << m_node->getUuid() << "emit frequency updated."; + Q_EMIT frequencyUpdated(m_idx, newData); +} + +void TxTone::read() +{ + m_frequency->getUiStrategy()->requestData(); + m_scale->getUiStrategy()->requestData(); + m_phase->getUiStrategy()->requestData(); +} + +QString TxTone::toneUuid() const { return m_node->getUuid(); } + +QString TxTone::frequency() { return m_frequency->read().first; } + +QString TxTone::phase() { return m_phase->read().first; } + +QString TxTone::scale() { return m_scale->read().first; } + +void TxTone::setPairedTone(TxTone *tone) +{ + m_pairedTone = tone; + if(m_pairedTone) { + connect(this, &TxTone::phaseUpdated, [=, this](int toneIdx, QString phase) { + QString emitterFreq = frequency(); + QString ph2 = computePairedPhase(emitterFreq, phase); + m_pairedTone->updatePhase(ph2); + }); + connect(this, &TxTone::frequencyUpdated, [=, this](int toneIdx, QString frequency) { + m_pairedTone->updateFrequency(frequency); + QString emitterPhase = phase(); + QString ph2 = computePairedPhase(frequency, emitterPhase); + m_pairedTone->updatePhase(ph2); + }); + connect(this, &TxTone::scaleUpdated, + [=, this](int toneIdx, QString scale) { m_pairedTone->updateScale(scale); }); + } +} + +QString TxTone::computePairedPhase(QString frequency, QString phase) +{ + bool ok; + int freq1 = frequency.toInt(&ok); + int ph1 = phase.toInt(&ok); + double inc1 = 0; + + if(freq1 >= 0) { + inc1 = 90; + } else { + inc1 = 270; + } + if((ph1 - inc1) < 0) { + ph1 += 360; + } + return QString::number(ph1 - inc1); +} + +QString TxTone::phaseUItoDS(QString data) +{ + bool ok = true; + QString tmp_data = data; + tmp_data = tmp_data.toLower(); + tmp_data = tmp_data.simplified(); + tmp_data.replace(" ", ""); + double d_data = tmp_data.toDouble(&ok); + if(!ok) { + qDebug(CAT_DAC_DDS) << QString("Invalid phase value in UI %1").arg(data); + return ""; + } + d_data = d_data * 1000; + return QString::number(d_data); +} + +QString TxTone::phaseDStoUI(QString data) +{ + bool ok = true; + double d_data = data.toInt(&ok); + if(!ok) { + qDebug(CAT_DAC_DDS) << QString("Invalid phase value on device %1").arg(data); + return data; + } + d_data = d_data / 1000; + QString s_data = QString::number(d_data); + return s_data; +} + +QString TxTone::scaleUItoDS(QString data) +{ + bool ok; + QString tmp_data = data; + tmp_data = tmp_data.toLower(); + tmp_data = tmp_data.simplified(); + tmp_data.replace("db", ""); + tmp_data.replace(" ", ""); + double d_data = tmp_data.toDouble(&ok); + if(!ok) { + qDebug(CAT_DAC_DDS) << QString("Invalid scale value in UI %1").arg(data); + d_data = 0; + } + d_data = DacUtils::dbFullScaleConvert(d_data, false); + return QString::number(d_data); +} + +QString TxTone::scaleDStoUI(QString data) +{ + bool ok; + double d_data = data.toDouble(&ok); + if(!ok) { + qDebug(CAT_DAC_DDS) << QString("Invalid scale value on device %1").arg(data); + return data; + } + d_data = DacUtils::dbFullScaleConvert(d_data, true); + QString s_data; + if(d_data == -DBL_MAX) { + s_data = "-Inf"; + } else { + s_data = QString::number(d_data); + } + s_data += " dB"; + return s_data; +} + +QString TxTone::frequencyUItoDS(QString data) +{ + bool ok; + QString tmp_data = data; + tmp_data = tmp_data.toLower(); + tmp_data = tmp_data.simplified(); + tmp_data.replace("mhz", ""); + tmp_data.replace(" ", ""); + double d_data = tmp_data.toDouble(&ok); + if(!ok) { + qDebug(CAT_DAC_DDS) << QString("Invalid frequency value in UI %1").arg(data); + return ""; + } + d_data = d_data * 1e6; + return QString::number(d_data, 'g', 10); +} + +QString TxTone::frequencyDStoUI(QString data) +{ + bool ok; + double d_data = data.toDouble(&ok); + if(!ok) { + qDebug(CAT_DAC_DDS) << QString("Invalid frequency value on device %1").arg(data); + return data; + } + d_data = d_data / 1e6; + QString s_data = QString::number(d_data, 'g', 10); + s_data += " MHz"; + return s_data; +} diff --git a/plugins/dac/src/txtone.h b/plugins/dac/src/txtone.h new file mode 100644 index 0000000000..05753e5259 --- /dev/null +++ b/plugins/dac/src/txtone.h @@ -0,0 +1,56 @@ +#ifndef TXTONE_H +#define TXTONE_H + +#include +#include + +namespace scopy { +namespace dac { +class TxNode; +class TxTone : public QWidget +{ + Q_OBJECT +public: + TxTone(TxNode *node, unsigned int idx, QWidget *parent = nullptr); + virtual ~TxTone(); + + void read(); + QString toneUuid() const; + QString frequency(); + QString phase(); + QString scale(); + void setPairedTone(TxTone *tone); + static QString scaleDStoUI(QString data); + static QString scaleUItoDS(QString data); +Q_SIGNALS: + void frequencyUpdated(unsigned int toneIdx, QString frequency); + void scaleUpdated(unsigned int toneIdx, QString oldScale, QString scale); + void phaseUpdated(unsigned int toneIdx, QString phase); + +public Q_SLOTS: + void updateFrequency(QString frequency); + void updateScale(QString scale); + void updatePhase(QString phase); + +private Q_SLOTS: + void forwardFreqChange(QDateTime timestamp, QString oldData, QString newData, int retCode, bool readOp); + void forwardScaleChange(QDateTime timestamp, QString oldData, QString newData, int retCode, bool readOp); + void forwardPhaseChange(QDateTime timestamp, QString oldData, QString newData, int retCode, bool readOp); + +private: + QString phaseDStoUI(QString data); + QString phaseUItoDS(QString data); + QString frequencyDStoUI(QString data); + QString frequencyUItoDS(QString data); + QString computePairedPhase(QString frequency, QString phase); + + TxNode *m_node; + TxTone *m_pairedTone; + unsigned int m_idx; + IIOWidget *m_frequency; + IIOWidget *m_scale; + IIOWidget *m_phase; +}; +} // namespace dac +} // namespace scopy +#endif // TXTONE_H diff --git a/plugins/dac/test/CMakeLists.txt b/plugins/dac/test/CMakeLists.txt new file mode 100644 index 0000000000..b26ba3bfe4 --- /dev/null +++ b/plugins/dac/test/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.5) + +include(ScopyTest) + +setup_scopy_tests(pluginloader) \ No newline at end of file diff --git a/plugins/dac/test/tst_pluginloader.cpp b/plugins/dac/test/tst_pluginloader.cpp new file mode 100644 index 0000000000..b258baf60c --- /dev/null +++ b/plugins/dac/test/tst_pluginloader.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2023 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "qpluginloader.h" + +#include +#include + +#include + +using namespace scopy; + +class TST_DACPlugin : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void fileExists(); + void isLibrary(); + void loaded(); + void className(); + void instanceNotNull(); + void multipleInstances(); + void qobjectcast_to_plugin(); + void clone(); + void name(); + void metadata(); +}; + +#define PLUGIN_LOCATION "../../plugins" +#define FILENAME PLUGIN_LOCATION "/libscopy-dac.so" + +void TST_DACPlugin::fileExists() +{ + QFile f(FILENAME); + bool ret; + ret = f.open(QIODevice::ReadOnly); + if(ret) + f.close(); + QVERIFY(ret); +} + +void TST_DACPlugin::isLibrary() { QVERIFY(QLibrary::isLibrary(FILENAME)); } + +void TST_DACPlugin::className() +{ + QPluginLoader qp(FILENAME, this); + QVERIFY(qp.metaData().value("className") == "DACPlugin"); +} + +void TST_DACPlugin::loaded() +{ + QPluginLoader qp(FILENAME, this); + qp.load(); + QVERIFY(qp.isLoaded()); +} + +void TST_DACPlugin::instanceNotNull() +{ + QPluginLoader qp(FILENAME, this); + QVERIFY(qp.instance() != nullptr); +} + +void TST_DACPlugin::multipleInstances() +{ + QPluginLoader qp1(FILENAME, this); + QPluginLoader qp2(FILENAME, this); + + QVERIFY(qp1.instance() == qp2.instance()); +} + +void TST_DACPlugin::qobjectcast_to_plugin() +{ + QPluginLoader qp(FILENAME, this); + auto instance = qobject_cast(qp.instance()); + QVERIFY(instance != nullptr); +} + +void TST_DACPlugin::clone() +{ + QPluginLoader qp(FILENAME, this); + + Plugin *p1 = nullptr, *p2 = nullptr; + auto original = qobject_cast(qp.instance()); + p1 = original->clone(); + QVERIFY(p1 != nullptr); + p2 = original->clone(); + QVERIFY(p2 != nullptr); + QVERIFY(p1 != p2); +} + +void TST_DACPlugin::name() +{ + QPluginLoader qp(FILENAME, this); + + Plugin *p1 = nullptr, *p2 = nullptr; + auto original = qobject_cast(qp.instance()); + p1 = original->clone(); + qDebug() << p1->name(); +} + +void TST_DACPlugin::metadata() +{ + QPluginLoader qp(FILENAME, this); + + Plugin *p1 = nullptr, *p2 = nullptr; + auto original = qobject_cast(qp.instance()); + original->initMetadata(); + p1 = original->clone(); + qDebug() << p1->metadata(); + QVERIFY(!p1->metadata().isEmpty()); +} + +QTEST_MAIN(TST_DACPlugin) + +#include "tst_pluginloader.moc" \ No newline at end of file