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}
+
+
+
+ DacPlugin
+ x:-80;y:200
+ x:-40;y:-30;w:80;h:60
+ 0
+
+
+ scopy
+
+
+
+ -
+
+
+
+
+
+
+ {d8b2fc83-50d8-42dc-ab57-6e1b2988f29c}
+
+
+
+ DacInstrument
+ x:-75;y:325
+ x:-50;y:-30;w:100;h:60
+ 0
+
+
+ scopy
+
+
+
+ -
+
+
+
+
+
+
+ {001847bd-b45c-4a2a-98f7-44f510d121bd}
+
+
+
+ DacDataManager
+ x:110;y:325
+ x:-55;y:-30;w:110;h:60
+ 0
+
+
+ scopy
+
+
+
+ -
+
+
+
+
+
+
+ {d751eab4-4a5f-480d-af19-bb961c57eb47}
+
+
+
+ DacDataModel
+ x:315;y:-55
+ x:-135;y:-130;w:270;h:260
+ false
+ 0
+
+
+ scopy
+ true
+
+
+
+ -
+
+
+
+
+
+
+ {efd0fed7-2ad2-41c4-a228-8b6c784d5054}
+
+
+
+ DacAddon
+ x:360;y:330
+ x:-40;y:-30;w:80;h:60
+ 0
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {22b5fbd3-a568-4907-9df7-e7c27e25d4c5}
+
+
+
+ BufferDacAddon
+ x:355;y:425
+ x:-50;y:-30;w:100;h:60
+ 0
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {10e0872d-4bd3-45ef-b673-b933fae40fff}
+
+
+
+ DdsBufferAddon
+ x:235;y:425
+ x:-55;y:-30;w:110;h:60
+ 0
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {00dca2a0-8a3b-4421-aca9-b05ea6695054}
+
+
+
+ VectorDacAddon
+ x:535;y:425
+ x:-55;y:-30;w:110;h:60
+ 0
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {a1c9beb5-acf1-4533-a37f-27b0d91826e6}
+
+
+
+ {22b5fbd3-a568-4907-9df7-e7c27e25d4c5}
+ {efd0fed7-2ad2-41c4-a228-8b6c784d5054}
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {514f08df-cb97-4fae-9ad2-463540ceadf4}
+
+
+
+ {00dca2a0-8a3b-4421-aca9-b05ea6695054}
+ {efd0fed7-2ad2-41c4-a228-8b6c784d5054}
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {e61e3dd8-6601-4885-958e-eefcaccd6efe}
+
+
+
+ {10e0872d-4bd3-45ef-b673-b933fae40fff}
+ {efd0fed7-2ad2-41c4-a228-8b6c784d5054}
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {1bef8e10-4162-4cf7-bd30-0312c05d435f}
+
+
+
+ {6b5370b5-495e-4fd3-b245-9506acbaa33a}
+ {d8b2fc83-50d8-42dc-ab57-6e1b2988f29c}
+
+
+
+
+ 1
+ true
+ 2
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {b8b01c1d-8cd2-4d13-bec9-98b414981483}
+
+
+
+ {001847bd-b45c-4a2a-98f7-44f510d121bd}
+ {d8b2fc83-50d8-42dc-ab57-6e1b2988f29c}
+
+
+
+
+ 1
+ true
+ 2
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {f959c854-3a65-4f50-9c1f-6a806b3a02ff}
+
+
+
+ {efd0fed7-2ad2-41c4-a228-8b6c784d5054}
+ {001847bd-b45c-4a2a-98f7-44f510d121bd}
+
+
+
+
+ 1
+ true
+ 2
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {3895ae41-2fb5-4523-a910-918d590d66aa}
+
+
+
+ {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}
+
+
+
+ TxNode
+ x:685;y:-260
+ x:-150;y:-85;w:300;h:170
+ false
+ 0
+
+
+ scopy
+ true
+
+
+
+ -
+
+
+
+
+
+
+ {5fdae907-d771-48c1-97e1-417cd1a2b7e2}
+
+
+
+ {e34240eb-861a-4096-97ac-a9feb92aafb0}
+ {d751eab4-4a5f-480d-af19-bb961c57eb47}
+
+
+
+
+ 1
+ true
+ 2
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {bcfc5636-2ba2-440f-bd41-3e3c5c354da3}
+
+
+
+ TxChannel
+ x:150;y:635
+ x:-40;y:-30;w:80;h:60
+ 0
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {29f69aff-7c09-49e6-81a9-bbee14d2c706}
+
+
+
+ TxTone
+ x:120;y:750
+ x:-40;y:-30;w:80;h:60
+ 0
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {6c314582-1707-4c93-a24b-100ca43c3602}
+
+
+
+ TxMode
+ x:195;y:525
+ x:-40;y:-30;w:80;h:60
+ 0
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {94e650b5-41a1-4554-81c7-1304bec84193}
+
+
+
+ {bcfc5636-2ba2-440f-bd41-3e3c5c354da3}
+ {6c314582-1707-4c93-a24b-100ca43c3602}
+
+
+
+
+ 1
+ true
+ 2
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {9ee20803-c720-4075-9be2-21c00855d148}
+
+
+
+ {6c314582-1707-4c93-a24b-100ca43c3602}
+ {10e0872d-4bd3-45ef-b673-b933fae40fff}
+
+
+
+
+ 1
+ true
+ 2
+
+
+
+
+
+ -
+
+
+
+
+
+
+ {37c26777-a4cf-4ec3-be3b-f34ab225a990}
+
+
+
+ {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