├── .gitignore ├── .gitmodules ├── .travis.yml ├── CI └── travis │ └── lib.sh ├── CMakeLists.txt ├── LICENSE ├── Plot ├── FloatBuffer.cpp ├── FloatBuffer.h ├── PhosphorRender.cpp └── PhosphorRender.h ├── README.md ├── SMU.cpp ├── SMU.h ├── appveyor.yml ├── azure-pipelines.yml ├── config.h.cmakein ├── debian ├── .gitignore ├── README.source ├── changelog ├── compat ├── control ├── copyright ├── docs ├── pixelpulse2.desktop ├── pixelpulse2.install ├── pixelpulse2.udev ├── pp2.png ├── rules └── source │ ├── format │ └── include-binaries ├── docs ├── Doxyfile └── mainpage.dox ├── icons ├── constant.png ├── constant.svg ├── gear.png ├── mv.png ├── mv.svg ├── pause.png ├── play.png ├── pp2.icns ├── pp2.ico ├── sawtooth.png ├── sawtooth.svg ├── simv.png ├── simv.svg ├── sine.png ├── sine.svg ├── square.png ├── square.svg ├── stairstep.png ├── stairstep.svg ├── svmi.png ├── svmi.svg ├── triangle.png └── triangle.svg ├── main.cpp ├── pixelpulse2.iss.cmakein ├── properties.rc.cmakein ├── qml.qrc ├── qml ├── AcquisitionSettingsDialog.qml ├── Axes.qml ├── ChannelRow.qml ├── ColorControlDialog.qml ├── ContentPane.qml ├── Controller.qml ├── DeviceManagerPane.qml ├── DeviceRow.qml ├── DragDot.qml ├── FileDialog.qml ├── OverlayConstant.qml ├── OverlayPeriodic.qml ├── PlotPane.qml ├── SignalRow.qml ├── StyleSlider.qml ├── TimelineFlickable.qml ├── TimelineHeader.qml ├── Toolbar.qml ├── ToolbarStyle.qml ├── XYPlot.qml ├── dataexport.js ├── jsutils.js ├── main.qml └── sesssave.js └── utils ├── filedownloader.cpp ├── filedownloader.h └── fileio.h /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile* 2 | pixelpulse2 3 | release 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/.gitmodules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: required 3 | 4 | cache: 5 | ccache: true 6 | directories: 7 | - .ccache 8 | 9 | python: 10 | - "3.5" 11 | 12 | matrix: 13 | fast_finish: true 14 | include: 15 | - compiler: "gcc" 16 | os: linux 17 | dist: xenial 18 | - os: linux 19 | dist: bionic 20 | - compiler: "clang" 21 | os: osx 22 | osx_image: xcode10.1 23 | - compiler: "clang" 24 | os: osx 25 | osx_image: xcode11.3 26 | - compiler: "clang" 27 | os: osx 28 | osx_image: xcode12 29 | 30 | before_install: 31 | - if [[ ${TRAVIS_OS_NAME} == linux ]]; then sudo apt-get update ; fi 32 | - if [[ ${TRAVIS_OS_NAME} == linux ]]; then sudo apt-get install libudev-dev libusb-1.0-0-dev libboost-all-dev ; fi 33 | 34 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then brew update 2>&1 > /dev/null ; fi 35 | #- if [[ ${TRAVIS_OS_NAME} == osx ]]; then brew unlink libusb ; fi 36 | #- if [[ ${TRAVIS_OS_NAME} == osx ]]; then brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/cb21e9ac30e47e3ea161c4247a6397967f83c83f/Formula/libusb.rb --universal; fi 37 | #- if [[ ${TRAVIS_OS_NAME} == osx ]]; then brew unlink qt ; fi 38 | #- if [[ ${TRAVIS_OS_NAME} == osx ]]; then brew install --force https://raw.githubusercontent.com/Homebrew/homebrew-core/8846805afc0cb8e5d114d5e222af1de3b35289df/Formula/qt.rb ; fi 39 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then brew install libusb qt; fi 40 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then brew upgrade qt; fi 41 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then brew link libusb ; fi 42 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then brew link qt ; fi 43 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then export PATH=/usr/local/opt/qt/bin:$PATH ; fi 44 | 45 | install: 46 | - if [[ ${TRAVIS_OS_NAME} == linux ]]; then sudo apt-get install -y qt5-default qttools5-dev qtdeclarative5-dev libqt5svg5-dev libqt5opengl5-dev cmake ; fi 47 | #- if [[ ${TRAVIS_OS_NAME} == linux ]]; then sudo apt-get -y install qt55d qt55base qt55connectivity qt55declarative qt55graphicaleffects qt55imageformats qt55quick1 qt55quickcontrols qt55script qt55tools ;fi 48 | 49 | 50 | before_script: 51 | - git clone https://github.com/analogdevicesinc/libsmu.git 52 | - cd libsmu 53 | - mkdir build && cd build && cmake -DBUILD_PYTHON=OFF -DCMAKE_INSTALL_PREFIX=/usr/local/ .. 54 | - make 55 | - sudo make install 56 | - cd ../.. 57 | - mkdir build && cd build && cmake .. && make 58 | - cd .. 59 | - sudo chmod +x CI/travis/lib.sh 60 | - . CI/travis/lib.sh 61 | - export LDIST="-$(get_ldist)" 62 | - echo $LDIST 63 | 64 | script: 65 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then cd build; fi 66 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then /usr/local/opt/qt5/bin/macdeployqt pixelpulse2.app -always-overwrite -verbose=2 -qmldir=../qml ;fi 67 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then cd ..; fi 68 | 69 | - | 70 | if [[ ${TRAVIS_OS_NAME} == osx ]]; then 71 | libsmupath="$(otool -L ./build/pixelpulse2.app/Contents/MacOS/pixelpulse2 | grep libsmu | cut -d " " -f 1)" 72 | libsmuid="$(echo ${libsmupath} | rev | cut -d "/" -f 1 | rev)" 73 | fi 74 | 75 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then cp libsmu/build/src/${libsmuid} build/pixelpulse2.app/Contents/Frameworks/ ;fi 76 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then otool -L libsmu/build/src/${libsmuid} ;fi 77 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then sudo install_name_tool -id @executable_path/../Frameworks/${libsmuid} build/pixelpulse2.app/Contents/Frameworks/${libsmuid} ;fi 78 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then sudo install_name_tool -change ${libsmupath} @executable_path/../Frameworks/${libsmuid} build/pixelpulse2.app/Contents/MacOS/Pixelpulse2 ;fi 79 | 80 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then otool -L build/pixelpulse2.app/Contents/MacOS/Pixelpulse2 ;fi 81 | 82 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then cd build; fi 83 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then /usr/local/opt/qt5/bin/macdeployqt pixelpulse2.app -dmg -no-plugins ;fi 84 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then for x in *.dmg ; do mv $x ${x%.dmg}${LDIST}.dmg ; done ; fi 85 | - if [[ ${TRAVIS_OS_NAME} == osx ]]; then cd ..; fi 86 | 87 | deploy: 88 | provider: releases 89 | api_key: 90 | secure: gjhjymTOZHb+J6h4wPkZBjgP2wYGhhRBiLGadKfiq8mherF+XRTZyVHMeRmBaJI963OkrIMP9lt2H131Iv5dsHXmxokosxQ2xO/gJA2ft4P23vpjC4KNhdOI65G0agr+cixSfANOQPswCETu/L5k5Q79RN+VnilUaYka2UiGqK8= 91 | file_glob: true 92 | file: "build/*.dmg" 93 | skip_cleanup: true 94 | on: 95 | condition: "${TRAVIS_OS_NAME} == osx" 96 | repo: analogdevicesinc/Pixelpulse2 97 | tags: true 98 | 99 | -------------------------------------------------------------------------------- /CI/travis/lib.sh: -------------------------------------------------------------------------------- 1 | get_ldist() { 2 | case "$(uname)" in 3 | Linux*) 4 | if [ ! -f /etc/os-release ] ; then 5 | if [ -f /etc/centos-release ] ; then 6 | echo "centos-$(sed -e 's/CentOS release //' -e 's/(.*)$//' \ 7 | -e 's/ //g' /etc/centos-release)-$(uname -m)" 8 | return 0 9 | fi 10 | ls /etc/*elease 11 | [ -z "${OSTYPE}" ] || { 12 | echo "${OSTYPE}-unknown" 13 | return 0 14 | } 15 | echo "linux-unknown" 16 | return 0 17 | fi 18 | . /etc/os-release 19 | if ! command_exists dpkg ; then 20 | echo $ID-$VERSION_ID-$(uname -m) 21 | else 22 | echo $ID-$VERSION_ID-$(dpkg --print-architecture) 23 | fi 24 | ;; 25 | Darwin*) 26 | echo "darwin-$(sw_vers -productVersion)" 27 | ;; 28 | *) 29 | echo "$(uname)-unknown" 30 | ;; 31 | esac 32 | return 0 33 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(MSVC) 2 | # needed for CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS support 3 | cmake_minimum_required(VERSION 3.4) 4 | else() 5 | cmake_minimum_required(VERSION 2.8.7) 6 | endif() 7 | project(pixelpulse2 CXX C) 8 | 9 | # libsmu versioning 10 | set(PP_VERSION_MAJOR 1) 11 | set(PP_VERSION_MINOR 0) 12 | set(PP_VERSION_PATCH 5) 13 | set(PP_VERSION ${PP_VERSION_MAJOR}.${PP_VERSION_MINOR}.${PP_VERSION_PATCH}) 14 | 15 | # determine a more descriptive project version using git info if available 16 | set(PP_VERSION_STR ${PP_VERSION}) 17 | if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/.git) 18 | # determine if the current revision is a tag 19 | execute_process(COMMAND git describe --exact-match --tags HEAD 20 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 21 | OUTPUT_VARIABLE PP_TAG_VERSION 22 | OUTPUT_STRIP_TRAILING_WHITESPACE 23 | ERROR_QUIET 24 | ) 25 | # if the current revision isn't a tag, add git revision info 26 | if(PP_TAG_VERSION STREQUAL "") 27 | execute_process(COMMAND git rev-parse --short HEAD 28 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 29 | OUTPUT_VARIABLE PP_GIT_REV 30 | OUTPUT_STRIP_TRAILING_WHITESPACE 31 | ERROR_QUIET 32 | ) 33 | set(PP_VERSION_STR ${PP_VERSION_STR}-g${PP_GIT_REV}) 34 | endif() 35 | endif() 36 | 37 | # Set .exe properties 38 | if (WIN32) 39 | string(REPLACE "." "," PP_FILEVERSION ${PP_VERSION}) 40 | set(PP_PRODUCTVERSION_STR ${PP_VERSION}) 41 | string(TIMESTAMP BUILD_YEAR "%Y") 42 | endif() 43 | 44 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 45 | set(CMAKE_AUTOMOC ON) 46 | 47 | if (NOT CMAKE_BUILD_TYPE) 48 | set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING 49 | "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." 50 | FORCE) 51 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS None Debug Release RelWithDebInfo MinSizeRel) 52 | endif() 53 | 54 | # write version info to file -- used for CI artifact versioning 55 | file(WRITE ${CMAKE_BINARY_DIR}/.version ${PP_VERSION_STR}) 56 | 57 | find_package(Qt5Core REQUIRED) 58 | find_package(Qt5Sql REQUIRED) 59 | find_package(Qt5Widgets REQUIRED) 60 | find_package(Qt5Quick REQUIRED) 61 | find_package(Qt5Network REQUIRED) 62 | find_package(Qt5Qml REQUIRED) 63 | 64 | string(TIMESTAMP BUILD_DATE "%Y/%m/%d") 65 | set(GIT_VERSION ${PP_GIT_REV}) 66 | configure_file(config.h.cmakein ${CMAKE_BINARY_DIR}/config.h @ONLY) 67 | configure_file(pixelpulse2.iss.cmakein ${CMAKE_CURRENT_BINARY_DIR}/pixelpulse2.iss @ONLY) 68 | 69 | # use pkg-config for everything that's not Windows 70 | if(NOT WIN32) 71 | include(FindPkgConfig REQUIRED) 72 | pkg_check_modules(LIBUSB REQUIRED libusb-1.0) 73 | link_directories(${LINK_DIRECTORIES} ${LIBUSB_LIBRARY_DIRS}) 74 | else() 75 | find_library(LIBUSB_LIBRARIES usb-1.0) 76 | find_path(LIBUSB_INCLUDE_DIRS libusb.h) 77 | endif() 78 | 79 | find_library(LIBSMU_LIBRARIES NAMES smu libsmu) 80 | find_path(LIBSMU_INCLUDE_DIRS libsmu/libsmu.hpp) 81 | 82 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 83 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -std=c++11") 84 | endif() 85 | 86 | # don't complain about extra format args for g++ 87 | if(CMAKE_COMPILER_IS_GNUCXX) 88 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format-extra-args") 89 | endif() 90 | 91 | include(GNUInstallDirs) 92 | 93 | # set default install path to /usr 94 | if (NOT WIN32 AND CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 95 | set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "default install path" FORCE) 96 | endif() 97 | 98 | # handle RPATH issues on OS X 99 | if(APPLE) 100 | set(CMAKE_MACOSX_RPATH ON) 101 | set(CMAKE_SKIP_BUILD_RPATH FALSE) 102 | set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) 103 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_LIBDIR}") 104 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 105 | list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_LIBDIR}" isSystemDir) 106 | if("${isSystemDir}" STREQUAL "-1") 107 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_LIBDIR}") 108 | endif() 109 | endif() 110 | 111 | FILE(GLOB PP_RESOURCES *.qrc) 112 | qt5_add_resources(pixelpulse_RESOURCES ${PP_RESOURCES}) 113 | 114 | FILE(GLOB QML_SRC "qml/*.qml") 115 | 116 | if (WIN32) 117 | set(RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/properties.rc) 118 | configure_file(properties.rc.cmakein ${RESOURCES} @ONLY) 119 | endif() 120 | 121 | FILE(GLOB SRC_LIST *.cpp 122 | Plot/*.cpp 123 | utils/*.cpp 124 | utils/*.h 125 | ) 126 | 127 | 128 | include_directories( 129 | ${LIBUSB_INCLUDE_DIRS} 130 | ${LIBSMU_INCLUDE_DIRS} 131 | ${Qt5Widgets_INCLUDE_DIRS} 132 | ${Qt5Quick_INCLUDE_DIRS} 133 | ${Qt5Qml_INCLUDE_DIRS} 134 | ${Qt5Network_INCLUDE_DIRS} 135 | ${Qt5Core_INCLUDE_DIRS} 136 | ${Qt5Gui_INCLUDE_DIRS} 137 | ${CMAKE_SOURCE_DIR}/Plot 138 | ${CMAKE_SOURCE_DIR}/utils 139 | ${CMAKE_SOURCE_DIR} 140 | ) 141 | 142 | if (APPLE) 143 | option(ENABLE_APPLICATION_BUNDLE "Enable application bundle for OSX" ON) 144 | endif(APPLE) 145 | 146 | if (ENABLE_APPLICATION_BUNDLE) 147 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") 148 | 149 | set(PKGINFO ${CMAKE_BINARY_DIR}/PkgInfo) 150 | file(WRITE ${PKGINFO} "APPLPixelpulse") 151 | set_source_files_properties(${PKGINFO} PROPERTIES MACOSX_PACKAGE_LOCATION .) 152 | 153 | set(QT_CONF ${CMAKE_BINARY_DIR}/qt.conf) 154 | file(WRITE ${QT_CONF} "") 155 | set_source_files_properties(${QT_CONF} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) 156 | 157 | set(ICON_FILE ${CMAKE_SOURCE_DIR}/icons/pp2.icns) 158 | set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) 159 | 160 | set(CMAKE_EXE_LINKER_FLAGS "-Wl,-headerpad_max_install_names -Wl,-search_paths_first ${CMAKE_EXE_LINKER_FLAGS}") 161 | 162 | foreach(plugin ${Qt5Gui_PLUGINS} ${Qt5Svg_PLUGINS}) 163 | get_target_property(_loc ${plugin} LOCATION) 164 | get_filename_component(_name ${_loc} NAME) 165 | get_filename_component(_dir ${_loc} DIRECTORY) 166 | get_filename_component(_dir ${_dir} NAME) 167 | 168 | set_source_files_properties(${_loc} PROPERTIES MACOSX_PACKAGE_LOCATION plugins/${_dir}) 169 | set(QT_PLUGINS ${QT_PLUGINS} ${_loc}) 170 | set(BUNDLED_QT_PLUGINS ${BUNDLED_QT_PLUGINS} ${CMAKE_BINARY_DIR}/Pixelpulse2.app/Contents/plugins/${_dir}/${_name}) 171 | endforeach() 172 | 173 | install(CODE " 174 | set(BU_CHMOD_BUNDLE_ITEMS ON) 175 | include(BundleUtilities) 176 | fixup_bundle(\"${CMAKE_BINARY_DIR}/Pixelpulse2.app\" \"${BUNDLED_QT_PLUGINS}\" \"${CMAKE_SOURCE_DIR}\")" 177 | ) 178 | 179 | set(OSX_BUNDLE MACOSX_BUNDLE) 180 | set(EXTRA_BUNDLE_FILES ${QT_PLUGINS} ${ICON_FILE} ${PKGINFO} ${QT_CONF}) 181 | endif() 182 | 183 | add_executable(${PROJECT_NAME} WIN32 ${OSX_BUNDLE} 184 | ${SRC_LIST} 185 | ${pixelpulse_RESOURCES} 186 | ${RESOURCES} 187 | ) 188 | 189 | if (NOT ENABLE_APPLICATION_BUNDLE) 190 | install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) 191 | install(FILES ${CMAKE_SOURCE_DIR}/icons/pp2.ico DESTINATION share/icons/hicolor/apps/scalable RENAME pp2.ico) 192 | endif() 193 | 194 | # Compiler options 195 | target_compile_options(${PROJECT_NAME} PUBLIC -Wall) 196 | 197 | #List of warnings to be treated as errors 198 | target_compile_options(${PROJECT_NAME} PUBLIC 199 | -Werror=return-type 200 | -Werror=uninitialized 201 | -Werror=init-self 202 | -Werror=switch 203 | ) 204 | 205 | if (WIN32 OR ENABLE_APPLICATION_BUNDLE) 206 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME pixelpulse2) 207 | endif() 208 | 209 | target_link_libraries(${PROJECT_NAME} LINK_PRIVATE 210 | ${LIBUSB_LIBRARIES} 211 | ${LIBSMU_LIBRARIES} 212 | ${Qt5Widgets_LIBRARIES} 213 | ${Qt5Quick_LIBRARIES} 214 | ${Qt5Qml_LIBRARIES} 215 | ${Qt5Network_LIBRARIES} 216 | ${Qt5Core_LIBRARIES} 217 | ${Qt5Gui_LIBRARIES} 218 | ) 219 | 220 | 221 | if (NOT WIN32) 222 | find_library(PTHREAD_LIBRARIES pthread) 223 | if (PTHREAD_LIBRARIES) 224 | target_link_libraries(${PROJECT_NAME} LINK_PRIVATE ${PTHREAD_LIBRARIES}) 225 | endif() 226 | endif() 227 | -------------------------------------------------------------------------------- /Plot/FloatBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "FloatBuffer.h" 2 | 3 | FloatBuffer::FloatBuffer(QObject *parent): 4 | QObject(parent), 5 | m_secondsPerSample(0.00001), 6 | m_start(0), 7 | m_length(0), 8 | m_first_samples_ignored(0), 9 | m_start_test(0) 10 | {} 11 | -------------------------------------------------------------------------------- /Plot/FloatBuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | class FloatBuffer : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | FloatBuffer(QObject *parent = 0); 17 | // this access mechanism is slow 18 | //Q_PROPERTY( QList qData READ getData NOTIFY dataChanged ); 19 | unsigned countPointsBetween(double start, double end) { 20 | return timeToIndex(end) - timeToIndex(start); 21 | } 22 | 23 | unsigned size() { 24 | return m_length; 25 | } 26 | 27 | Q_INVOKABLE unsigned long ignoredFirstSamplesCount() 28 | { 29 | return m_first_samples_ignored; 30 | } 31 | 32 | Q_INVOKABLE void setIgnoredFirstSamplesCount(unsigned long count) 33 | { 34 | m_first_samples_ignored = count; 35 | } 36 | 37 | Q_INVOKABLE float get(unsigned i) { 38 | return m_data[wrapIndex(i)]; 39 | } 40 | 41 | // Not needed anymore 42 | // void shift(float d) { 43 | // m_data[(m_start + m_length) % m_data.size()] = d; 44 | 45 | // if (m_length < m_data.size()) { 46 | // m_length += 1; 47 | // } else { 48 | // m_start = (m_start + 1) % m_data.size(); 49 | // } 50 | // } 51 | 52 | void append_samples(const std::vector>& samples, int signal_index) 53 | { 54 | size_t free_buffer_space = m_data.size() - m_start_test; 55 | int num_samples_to_add = std::min(samples.size(), free_buffer_space); 56 | 57 | for (int i = 0; i < num_samples_to_add; i++) { 58 | m_data[(m_start_test + i) % m_data.size()] = samples[i][signal_index]; 59 | } 60 | m_start_test += num_samples_to_add; 61 | if (m_length < m_start_test) 62 | m_length = m_start_test; 63 | 64 | emit dataChanged(); 65 | } 66 | 67 | void append_samples_circular(const std::vector>& samples, int signal_index) 68 | { 69 | int num_samples_to_add = samples.size(); 70 | if(m_length > maxSize){ 71 | m_data.erase(m_data.begin(),std::min(m_data.end(), m_data.begin()+num_samples_to_add)); 72 | for(int i=0;i length) { 124 | m_length = length; 125 | dataChanged(); 126 | } 127 | maxSize = length; 128 | } 129 | 130 | //not needed anymore 131 | // float* data() { 132 | // return m_data.data(); 133 | // } 134 | 135 | Q_INVOKABLE QList getData() { 136 | QList qData; 137 | qData.reserve(m_length); 138 | for (unsigned i=0; i < m_length; i++) { 139 | qreal d = get(i); 140 | qData.append(d); 141 | } 142 | return qData; 143 | } 144 | 145 | void startSweep() { 146 | // When switching from continuous to repeated-sweep mode, stop acting like a ring buffer 147 | if (m_start != 0) { 148 | m_start = 0; 149 | m_length = 0; 150 | dataChanged(); 151 | } 152 | 153 | m_start_test = 0; 154 | } 155 | 156 | // Not needed anymore 157 | // void sweepProgress(unsigned sample) { 158 | // if (m_length < sample) { 159 | // m_length = sample; 160 | // } 161 | // dataChanged(); 162 | // } 163 | 164 | // Not needed anymore 165 | // void continuousProgress(unsigned sample) { 166 | // Q_UNUSED(sample); 167 | // // m_start and m_length are adjusted in shift() 168 | // dataChanged(); 169 | // } 170 | 171 | 172 | template 173 | struct square 174 | { 175 | T operator()(const T& Left, const T& Right) const 176 | { 177 | return (Left + Right*Right); 178 | } 179 | }; 180 | 181 | double rms() { 182 | std::vector tmp = dif_mean(mean()); 183 | return sqrt(accumulate(tmp.begin(), tmp.end(), 0.0, square()) / tmp.size()); 184 | } 185 | 186 | double peak_to_peak(){ 187 | if(m_data.size() != 0) { 188 | return *std::max_element(data_begin(), m_data.end()) - *std::min_element(data_begin(), m_data.end()); 189 | } 190 | return 0; 191 | } 192 | 193 | std::vector dif_mean(double avg){ 194 | std::vector tmp; 195 | for (std::deque::iterator it = data_begin(); it != m_data.end(); ++it){ 196 | tmp.push_back(*it - avg); 197 | } 198 | return tmp; 199 | } 200 | 201 | double mean() { 202 | return accumulate(data_begin(), m_data.end(), 0.0) / data_size(); 203 | } 204 | 205 | signals: 206 | void dataChanged(); 207 | 208 | public slots: 209 | QObject * getObject() { 210 | return new FloatBuffer(); 211 | } 212 | 213 | private: 214 | float m_secondsPerSample; 215 | std::deque m_data; 216 | size_t m_start; 217 | size_t m_length; 218 | size_t m_first_samples_ignored; 219 | size_t m_start_test; 220 | size_t maxSize; 221 | 222 | unsigned unwrapIndex(unsigned index) { 223 | if (index >= m_start) { 224 | return (index - m_start); 225 | } else { 226 | return (index + (m_data.size() - m_start)); 227 | } 228 | } 229 | 230 | unsigned wrapIndex(unsigned index) { 231 | return (index + m_start + m_first_samples_ignored) % m_data.size(); 232 | } 233 | 234 | unsigned timeToIndex(double time) { 235 | if (time < 0) time = 0; 236 | unsigned t = time / m_secondsPerSample; 237 | if (t > m_length) return m_length; 238 | return t; 239 | } 240 | 241 | double indexToTime(unsigned index) { 242 | return index * m_secondsPerSample; 243 | } 244 | 245 | /* A number of samples at the beginning of the buffer can be ignored by 246 | * the application. Use data_begin() and data_size() to access the rest of data */ 247 | std::deque::iterator data_begin() 248 | { 249 | return (m_data.begin() + m_first_samples_ignored); 250 | } 251 | 252 | float data_size() 253 | { 254 | return (m_data.size() - m_first_samples_ignored); 255 | } 256 | }; 257 | -------------------------------------------------------------------------------- /Plot/PhosphorRender.cpp: -------------------------------------------------------------------------------- 1 | #include "PhosphorRender.h" 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | class Shader : public QSGMaterialShader 11 | { 12 | public: 13 | const char *vertexShader() const { 14 | return 15 | "attribute highp vec4 vertex; \n" 16 | "uniform highp mat4 matrix; \n" 17 | "void main() { \n" 18 | " gl_Position = matrix * vertex; \n" 19 | "}"; 20 | } 21 | 22 | const char *fragmentShader() const { 23 | return 24 | "#version 120 \n" 25 | "uniform lowp float opacity;" 26 | "uniform lowp vec4 color;" 27 | "void main() {" 28 | " float dist = length(gl_PointCoord - vec2(0.5))*2;" 29 | " gl_FragColor = color * opacity * (1-dist);" 30 | //" if(dist > 1)" 31 | //" discard;" 32 | "}"; 33 | } 34 | 35 | char const *const *attributeNames() const 36 | { 37 | static char const *const names[] = { "vertex", 0 }; 38 | return names; 39 | } 40 | 41 | void initialize() 42 | { 43 | QSGMaterialShader::initialize(); 44 | m_id_matrix = program()->uniformLocation("matrix"); 45 | m_id_opacity = program()->uniformLocation("opacity"); 46 | m_id_color = program()->uniformLocation("color"); 47 | m_glFuncs = QOpenGLContext::currentContext()->functions(); 48 | } 49 | 50 | void updateState(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial); 51 | 52 | void deactivate() { 53 | m_glFuncs->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 54 | m_glFuncs->glDisable(GL_POINT_SPRITE); 55 | } 56 | 57 | private: 58 | int m_id_matrix; 59 | int m_id_opacity; 60 | int m_id_color; 61 | QOpenGLFunctions *m_glFuncs; 62 | }; 63 | 64 | class Material : public QSGMaterial 65 | { 66 | public: 67 | QSGMaterialType *type() const { static QSGMaterialType type; return &type; } 68 | QSGMaterialShader *createShader() const { return new Shader; } 69 | QSGMaterial::Flags flags() const { return QSGMaterial::Blending; } 70 | 71 | QMatrix4x4 transformation; 72 | float pointSize; 73 | QColor color; 74 | }; 75 | 76 | void Shader::updateState(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) 77 | { 78 | Q_UNUSED(oldMaterial); 79 | Q_ASSERT(program()->isLinked()); 80 | 81 | Material* m = static_cast(newMaterial); 82 | program()->setUniformValue(m_id_matrix, state.combinedMatrix()*m->transformation); 83 | 84 | if (state.isOpacityDirty()) { 85 | program()->setUniformValue(m_id_opacity, state.opacity()); 86 | } 87 | 88 | program()->setUniformValue(m_id_color, m->color); 89 | 90 | m_glFuncs->glBlendFunc(GL_ONE, GL_ONE); 91 | m_glFuncs->glEnable(GL_POINT_SPRITE); 92 | // glPointSize(m->pointSize); // Commenting this out since it is not available in QOpenGLFunctions (OpenGL ES 2.0) and apparently does not affect the point size 93 | } 94 | 95 | PhosphorRender::PhosphorRender(QQuickItem *parent) 96 | : QQuickItem(parent), m_ybuffer(NULL), m_xbuffer(NULL), 97 | m_xmin(0), m_xmax(1), m_ymin(0), m_ymax(1), m_pointSize(0), 98 | m_color(0.03*255, 0.3*255, 0.03*255, 1*255) 99 | { 100 | setFlag(ItemHasContents, true); 101 | } 102 | 103 | PhosphorRender::~PhosphorRender() 104 | { 105 | } 106 | 107 | QSGNode *PhosphorRender::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) 108 | { 109 | if (!m_ybuffer) { 110 | return 0; 111 | } 112 | 113 | QSGGeometryNode *node = 0; 114 | QSGGeometry *geometry = 0; 115 | Material *material = 0; 116 | 117 | unsigned n_points; 118 | 119 | if (m_xbuffer) { 120 | n_points = std::min(m_xbuffer->size(), m_ybuffer->size()); 121 | } else { 122 | n_points = m_ybuffer->countPointsBetween(m_xmin, m_xmax); 123 | } 124 | 125 | n_points = std::min(n_points,(unsigned) 65767); 126 | 127 | if (!oldNode) { 128 | node = new QSGGeometryNode; 129 | geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), n_points); 130 | geometry->setDrawingMode(GL_POINTS); 131 | node->setGeometry(geometry); 132 | node->setFlag(QSGNode::OwnsGeometry); 133 | material = new Material; 134 | material->setFlag(QSGMaterial::Blending); 135 | node->setMaterial(material); 136 | node->setFlag(QSGNode::OwnsMaterial); 137 | } else { 138 | node = static_cast(oldNode); 139 | geometry = node->geometry(); 140 | geometry->allocate(n_points); 141 | geometry->setLineWidth(m_pointSize); 142 | material = static_cast(node->material()); 143 | } 144 | 145 | QRectF bounds = boundingRect(); 146 | 147 | material->transformation.setToIdentity(); 148 | material->transformation.scale(bounds.width()/(m_xmax - m_xmin), bounds.height()/(m_ymin - m_ymax)); 149 | material->transformation.translate(-m_xmin, -m_ymax); 150 | 151 | material->pointSize = m_pointSize; 152 | material->color = m_color; 153 | 154 | auto verticies = geometry->vertexDataAsPoint2D(); 155 | if (m_xbuffer) { 156 | for (unsigned i=0; iget(i), m_ybuffer->get(i)); 158 | } 159 | } else { 160 | m_ybuffer->toVertexData(m_xmin, m_xmax, verticies, n_points); 161 | } 162 | node->markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); 163 | 164 | return node; 165 | } 166 | -------------------------------------------------------------------------------- /Plot/PhosphorRender.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "FloatBuffer.h" 5 | 6 | class PhosphorRender : public QQuickItem 7 | { 8 | Q_OBJECT 9 | 10 | Q_PROPERTY(FloatBuffer* buffer READ buffer WRITE setBuffer NOTIFY yBufferChanged) 11 | Q_PROPERTY(FloatBuffer* xBuffer READ xBuffer WRITE setXBuffer NOTIFY xBufferChanged) 12 | 13 | Q_PROPERTY(double xmin READ xmin WRITE setXmin NOTIFY xminChanged) 14 | Q_PROPERTY(double xmax READ xmax WRITE setXmax NOTIFY xmaxChanged) 15 | Q_PROPERTY(double ymin READ ymin WRITE setYmin NOTIFY yminChanged) 16 | Q_PROPERTY(double ymax READ ymax WRITE setYmax NOTIFY ymaxChanged) 17 | 18 | Q_PROPERTY(double pointSize READ pointSize WRITE setPointSize NOTIFY pointSizeChanged) 19 | Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) 20 | 21 | 22 | public: 23 | PhosphorRender(QQuickItem *parent = 0); 24 | ~PhosphorRender(); 25 | 26 | QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); 27 | 28 | #define SETTER(PROP) \ 29 | if (m_ ## PROP == PROP) return; \ 30 | m_ ## PROP = PROP; \ 31 | emit PROP ## Changed(PROP); \ 32 | update(); 33 | 34 | FloatBuffer* buffer() const { return m_ybuffer; } 35 | void setBuffer(FloatBuffer* ybuffer) { 36 | if (m_ybuffer != ybuffer) { 37 | if (m_ybuffer) { 38 | QObject::disconnect(m_ybuffer, &FloatBuffer::dataChanged, this, 0); 39 | } 40 | m_ybuffer = ybuffer; 41 | if (m_ybuffer) { 42 | QObject::connect(m_ybuffer, &FloatBuffer::dataChanged, this, &PhosphorRender::update); 43 | } 44 | emit yBufferChanged(m_ybuffer); 45 | update(); 46 | } 47 | } 48 | 49 | FloatBuffer* xBuffer() const { return m_xbuffer; } 50 | void setXBuffer(FloatBuffer* xbuffer) { 51 | if (m_xbuffer) { 52 | QObject::disconnect(m_xbuffer, &FloatBuffer::dataChanged, this, 0); 53 | } 54 | m_xbuffer = xbuffer; 55 | if (m_xbuffer) { 56 | QObject::connect(m_xbuffer, &FloatBuffer::dataChanged, this, &PhosphorRender::update); 57 | } 58 | emit xBufferChanged(m_xbuffer); 59 | update(); 60 | } 61 | 62 | 63 | double xmin() const { return m_xmin; } 64 | void setXmin(double xmin) { SETTER(xmin) } 65 | 66 | double xmax() const { return m_xmax; } 67 | void setXmax(double xmax) { SETTER(xmax) } 68 | 69 | double ymin() const { return m_ymin; } 70 | void setYmin(double ymin) { SETTER(ymin) } 71 | 72 | double ymax() const { return m_ymax; } 73 | void setYmax(double ymax) { SETTER(ymax) } 74 | 75 | double pointSize() const { return m_pointSize; } 76 | void setPointSize(double pointSize) { SETTER(pointSize) } 77 | 78 | QColor color() const { return m_color; } 79 | void setColor(QColor color) { SETTER(color) } 80 | 81 | signals: 82 | void xBufferChanged(FloatBuffer* b); 83 | void yBufferChanged(FloatBuffer* b); 84 | void xminChanged(double v); 85 | void xmaxChanged(double v); 86 | void yminChanged(double v); 87 | void ymaxChanged(double v); 88 | void pointSizeChanged(double v); 89 | void colorChanged(QColor v); 90 | 91 | private: 92 | FloatBuffer* m_ybuffer; 93 | FloatBuffer* m_xbuffer; 94 | 95 | double m_xmin; 96 | double m_xmax; 97 | double m_ymin; 98 | double m_ymax; 99 | double m_pointSize; 100 | QColor m_color; 101 | }; 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Pixelpulse2 2 | 3 | [![Windows Status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true)](https://ci.appveyor.com/project/analogdevicesinc/pixelpulse2/branch/master) 4 | [![OSX Status](https://api.travis-ci.org/analogdevicesinc/Pixelpulse2.svg?branch=master&label=OSX)](https://travis-ci.org/analogdevicesinc/Pixelpulse2) 5 | [![License](https://img.shields.io/badge/license-MPL-blue.svg)](https://github.com/analogdevicesinc/Pixelpulse2/blob/master/LICENSE) 6 | 7 | Pixelpulse is a powerful user interface for visualizing and manipulating signals while exploring systems attached to affordable analog interface devices, such as Analog Devices' ADALM1000. 8 | 9 | Fully cross-platform using the Qt5 graphics toolkit and OpenGL accelerated density-gradiated rendering, it provides a powerful and accessible tool for initial interactive explorations. 10 | 11 | Intuitive click-and-drag interfaces make exploring system behaviors across a wide range of signal amplitudes, frequencies, or phases a trivial exercise. Just click once to source a constant voltage or current and see what happens. Choose a function (sawtooth, triangle, sinusoidal, square) - adjust parameters, and make waves. 12 | 13 | Zoom in and out with your scroll wheel or multitouch gestures (on supported platforms). Hold "Shift" to for Y-axis zooming. 14 | 15 | Click and drag the X axis to pan in time. 16 | 17 | ### Screenshot 18 | 19 | ![Screenshot of PP2 on Windows 7](https://analogdevicesinc.github.io/Pixelpulse2/pp2screenshot.png "Pixelpulse on Windows 7") 20 | 21 | ### Getting Pixelpulse2 22 | 23 | #### Easy 24 | 25 | * OSX - Navigate to the [releases](https://github.com/analogdevicesinc/pixelpulse2/releases) and collect the latest `pixelpulse2-.dmg` package, specific for you OS version. 26 | * Windows - For a testing build, download the dependency package and the latest binary build from [appveyor](https://ci.appveyor.com/project/analogdevicesinc/pixelpulse2/build/artifacts). For an official release build, navigate to releases and collect the latest pixelpulse2-setup.exe. 27 | * Linux - Build from source (below) 28 | #### Advanced 29 | 30 | To build from source on any platform, you need to install a C++ compiler toolchain, collect the build dependencies, setup your build environment, and compile the project. 31 | 32 | If you have not built packages from source before, this is ill-advised. 33 | * **Build and install libsmu (https://github.com/analogdevicesinc/libsmu)**. 34 | Libsmu is a library wich contains abstractions for streaming data to and from USB-connected analog interface devices, currently supporting the Analog Devices' ADALM1000. 35 | * Install Qt5. We recommend using a version greater than or equal to 5.14. 36 | * On most Linux Distributions, Qt5 is available in repositories. The complete list of packages required varies, but includes qt's support for declarative (qml) UI programming, qtquick, qtquick-window, qtquick-controls, and qtquick-layouts. 37 | 38 | To build / run on a generic POSIX platform 39 | 40 | git clone https://github.com/analogdevicesinc/Pixelpulse2 41 | cd Pixelpulse2 42 | mkdir build 43 | cd build 44 | cmake .. 45 | make 46 | 47 | On Windows the process is similar. Write the following commands in a cmd console 48 | 49 | git clone https://github.com/analogdevicesinc/Pixelpulse2 50 | cd Pixelpulse2 51 | mkdir build 52 | cd build 53 | cmake -DLIBSMU_LIBRARY="path_to_libsmu_dll" -DLIBSMU_INCLUDE_PATH="path_to_libsmu_include_folder" -DLIBUSB_INCLUDE_DIRS="path_to_libusb_include_folder" .. 54 | make 55 | 56 | After it is finished building, you have to copy the libsmu shared library into the build folder and Pixelpulse2 should be ready to use with your M1K 57 | 58 | To build / run on Ubuntu 59 | 60 | * Please note that you make encounter issues if you are running a version of Ubuntu lower than 15.04, because the version of QT in the repositories will likely be less than 5.4 (this also applies if you are running a Linux distribution that uses an older version of Ubuntu, for example Linux Mint 17.1, which uses Ubuntu 14.04.) 61 | * The build process is tested and supported on Ubuntu 16, 18 and 20. 62 | 63 | * Get ready 64 | 65 | ```bash 66 | sudo apt-get update 67 | ``` 68 | 69 | * Build and install libsmu (https://github.com/analogdevicesinc/libsmu) 70 | 71 | * Install Qt5 and some Qt modules 72 | 73 | ```bash 74 | sudo apt-get install -y qt5-default qtdeclarative5-dev qml-module-qtquick-dialogs qml-module-qt-labs-settings qml-module-qt-labs-folderlistmodel qml-module-qtqml-models2 qml-module-qtquick-controls 75 | ``` 76 | 77 | * Make a new folder, clone the pixelpulse library into it from git, and build it! 78 | 79 | ```bash 80 | mkdir development 81 | cd development 82 | git clone https://github.com/analogdevicesinc/Pixelpulse2 83 | cd pixelpulse2 84 | mkdir build 85 | cd build 86 | cmake .. 87 | make 88 | ``` 89 | 90 | * Make sure your M1K is plugged into your computer. The onboard LED should light up when it is connected. You can double-check by typing ```lsusb```. You should see something along the lines of ```ID 064b:784c Analog Devices, Inc. (White Mountain DSP)``` 91 | * You should be ready to launch Pixelpulse2. First, go to the directory it was built in: 92 | 93 | ```bash 94 | cd ~/development/pixelpulse2/build 95 | ``` 96 | 97 | * Run Pixelpulse2 98 | 99 | ```bash 100 | ./pixelpulse2 101 | ``` 102 | -------------------------------------------------------------------------------- /SMU.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "utils/filedownloader.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | class SessionItem; 16 | class DeviceItem; 17 | class ChannelItem; 18 | class SignalItem; 19 | class ModeItem; 20 | class SrcItem; 21 | class TimerItem; 22 | class BufferChanger; 23 | class FloatBuffer; 24 | class DataLogger; 25 | 26 | /// SessionItem is the primary object in Pixelpulse2 27 | /// It abstracts over a libsmu session, exposing relevant parameters to QML. 28 | class SessionItem : public QObject { 29 | Q_OBJECT 30 | Q_PROPERTY(QQmlListProperty devices READ getDevices NOTIFY devicesChanged) 31 | Q_PROPERTY(bool active READ getActive NOTIFY activeChanged); 32 | Q_PROPERTY(unsigned sampleRate MEMBER m_sample_rate NOTIFY sampleRateChanged); 33 | Q_PROPERTY(unsigned sampleCount MEMBER m_sample_count NOTIFY sampleCountChanged); 34 | Q_PROPERTY(double sampleTime MEMBER m_sample_time NOTIFY sampleTimeChanged); 35 | Q_PROPERTY(unsigned logging MEMBER m_logging NOTIFY loggingChanged); 36 | Q_PROPERTY(int activeDevices READ getActiveDevices NOTIFY activeChanged); 37 | Q_PROPERTY(int availableDevices READ getAvailableDevices NOTIFY devicesChanged); 38 | Q_PROPERTY(int queueSize MEMBER m_queue_size CONSTANT) 39 | 40 | public: 41 | SessionItem(); 42 | ~SessionItem(); 43 | Q_INVOKABLE void openAllDevices(); 44 | Q_INVOKABLE void closeAllDevices(); 45 | 46 | Q_INVOKABLE void start(bool continuous); 47 | Q_INVOKABLE void cancel(); 48 | Q_INVOKABLE void restart(); 49 | int getAvailableDevices() { return m_session->m_available_devices.size(); } 50 | int getActiveDevices() { return m_session->m_devices.size(); } 51 | 52 | Q_INVOKABLE void updateMeasurements(); 53 | Q_INVOKABLE void updateAllMeasurements(); 54 | 55 | Q_INVOKABLE void downloadFromUrl(QString url); 56 | Q_INVOKABLE QString flash_firmware(QString url); 57 | Q_INVOKABLE QString getTmpPathForFirmware(); 58 | Q_INVOKABLE int programmingModeDeviceExists(); 59 | 60 | bool isContinuous(){return m_continuous;} 61 | bool getActive() { return m_active; } 62 | QQmlListProperty getDevices() { return QQmlListProperty(this, m_devices); } 63 | static void usb_handle_thread_method(SessionItem *session_item); 64 | 65 | signals: 66 | void devicesChanged(); 67 | void activeChanged(); 68 | void sampleRateChanged(); 69 | void sampleCountChanged(); 70 | void sampleTimeChanged(); 71 | void loggingChanged(); 72 | void finished(unsigned status); 73 | void attached(smu::Device* device); 74 | void detached(smu::Device* device); 75 | void firmwareDownloaded(); 76 | 77 | protected slots: 78 | void onFinished(); 79 | void onAttached(smu::Device* device); 80 | void onDetached(smu::Device* device); 81 | void handleDownloadedFirmware(); 82 | void onSampleCountChanged(); 83 | void onSampleTimeChanged(); 84 | void onLoggingChanged(); 85 | void getSamples(); 86 | void beginNewSweep(); 87 | 88 | protected: 89 | smu::Session* m_session; 90 | bool m_active; 91 | bool m_continuous; 92 | unsigned m_sample_rate; 93 | unsigned m_sample_count; 94 | double m_sample_time; 95 | unsigned m_queue_size; 96 | DataLogger *m_data_logger; 97 | unsigned m_logging; 98 | FileDownloader *m_firmware_fd; 99 | QList m_devices; 100 | QTimer timer; 101 | QTimer *sweepTimer; 102 | }; 103 | 104 | 105 | /// DeviceItem abstracts over a LibSMU Device exposing relevant parameters to QML 106 | class DeviceItem : public QObject { 107 | Q_OBJECT 108 | Q_PROPERTY(QQmlListProperty channels READ getChannels CONSTANT); 109 | Q_PROPERTY(QString label READ getLabel CONSTANT); 110 | Q_PROPERTY(QString FWVer READ getFWVer CONSTANT); 111 | Q_PROPERTY(QString HWVer READ getHWVer CONSTANT); 112 | Q_PROPERTY(int DefaultRate READ getDefaultRate CONSTANT); 113 | Q_PROPERTY(QString UUID READ getDevSN CONSTANT); 114 | 115 | public: 116 | DeviceItem(SessionItem*, smu::Device*); 117 | QQmlListProperty getChannels() { return QQmlListProperty(this, m_channels); } 118 | QString getLabel() { return QString(m_device->info()->label); } 119 | QString getFWVer() { return QString::fromStdString(m_device->m_fwver); } 120 | QString getHWVer() { return QString::fromStdString(m_device->m_hwver); } 121 | QString getDevSN() { return QString::fromStdString(m_device->m_serial); } 122 | int getDefaultRate() { return m_device->get_default_rate(); } 123 | friend class DataLogger; 124 | Q_INVOKABLE int ctrl_transfer( int x, int y, int z) { return m_device->ctrl_transfer(0x40, x, y, z, 0, 0, 100);} 125 | Q_INVOKABLE void blinkLeds(); 126 | 127 | size_t samplesAdded() { return m_samples_added; } 128 | void setSamplesAdded(size_t count) { m_samples_added = count; } 129 | void write(ChannelItem* chn = nullptr); 130 | 131 | protected: 132 | smu::Device* const m_device; 133 | QList m_channels; 134 | friend class SessionItem; 135 | size_t m_samples_added; 136 | }; 137 | 138 | class ChannelItem : public QObject { 139 | Q_OBJECT 140 | Q_PROPERTY(QQmlListProperty signals READ getSignals CONSTANT); 141 | Q_PROPERTY(QString label READ getLabel CONSTANT); 142 | Q_PROPERTY(unsigned mode MEMBER m_mode NOTIFY modeChanged); 143 | 144 | public: 145 | ChannelItem(DeviceItem*, smu::Device*, unsigned index); 146 | QQmlListProperty getSignals() { return QQmlListProperty(this, m_signals); } 147 | QString getLabel() const { return QString(m_device->channel_info(m_index)->label); } 148 | 149 | void buildTxBuffer(); 150 | 151 | signals: 152 | void modeChanged(unsigned mode); 153 | 154 | protected: 155 | smu::Device* const m_device; 156 | const unsigned m_index; 157 | unsigned m_mode; 158 | 159 | QList m_modes; 160 | QList m_signals; 161 | 162 | std::vector m_tx_data; 163 | TimerItem *timer; 164 | 165 | friend class SessionItem; 166 | friend class DeviceItem; 167 | friend class SignalItem; 168 | friend class TimerItem; 169 | }; 170 | 171 | /// Abstracts over a LibSMU Signal and the BufferItem used for rendering data 172 | class SignalItem : public QObject { 173 | Q_OBJECT 174 | Q_PROPERTY(FloatBuffer* buffer READ getBuffer CONSTANT); 175 | Q_PROPERTY(QString label READ getLabel CONSTANT); 176 | Q_PROPERTY(double min READ getMin CONSTANT); 177 | Q_PROPERTY(double max READ getMax CONSTANT); 178 | Q_PROPERTY(double resolution READ getResolution CONSTANT); 179 | Q_PROPERTY(SrcItem* src READ getSrc CONSTANT); 180 | Q_PROPERTY(bool isOutput READ getIsOutput NOTIFY isOutputChanged); 181 | Q_PROPERTY(bool isInput READ getIsInput NOTIFY isInputChanged); 182 | Q_PROPERTY(double measurement READ getMeasurement NOTIFY measurementChanged); 183 | Q_PROPERTY(double peak_to_peak READ getPeak NOTIFY peakChanged); 184 | Q_PROPERTY(double rms READ getRms NOTIFY rmsChanged); 185 | Q_PROPERTY(double mean READ getMean NOTIFY meanChanged); 186 | 187 | public: 188 | SignalItem(ChannelItem*, int index, smu::Signal*); 189 | FloatBuffer* getBuffer() const { return m_buffer; } 190 | QString getLabel() const { return QString(m_signal->info()->label); } 191 | double getMin() const { return m_signal->info()->min; } 192 | double getMax() const { return m_signal->info()->max; } 193 | double getResolution() const { return m_signal->info()->resolution; } 194 | SrcItem* getSrc() const { return m_src; } 195 | bool getIsOutput() const { 196 | return m_signal->info()->outputModes & (1<m_mode); 197 | } 198 | bool getIsInput() const { 199 | return m_signal->info()->inputModes & (1<m_mode); 200 | } 201 | double getMeasurement() { 202 | return m_measurement; 203 | } 204 | double getPeak(){ 205 | return m_peak_to_peak; 206 | } 207 | double getRms() { 208 | return m_rms; 209 | } 210 | double getMean() { 211 | return m_mean; 212 | } 213 | 214 | signals: 215 | void isOutputChanged(bool); 216 | void isInputChanged(bool); 217 | void measurementChanged(double); 218 | void peakChanged(double); 219 | void rmsChanged(double); 220 | void meanChanged(double); 221 | 222 | protected slots: 223 | void onParentModeChanged(int); 224 | 225 | protected: 226 | int const m_index; 227 | ChannelItem* const m_channel; 228 | smu::Signal* const m_signal; 229 | FloatBuffer* m_buffer; 230 | SrcItem* m_src; 231 | double m_measurement; 232 | double m_peak_to_peak; 233 | double m_rms; 234 | double m_mean; 235 | friend class SessionItem; 236 | friend class ChannelItem; 237 | friend class SrcItem; 238 | friend class TimerItem; 239 | 240 | void updateMeasurementMean(); 241 | void updateMeasurementLatest(); 242 | void updatePeakToPeak(); 243 | void updateRms(); 244 | }; 245 | 246 | class SrcItem : public QObject { 247 | Q_OBJECT 248 | Q_PROPERTY(QString src MEMBER m_src NOTIFY srcChanged); 249 | Q_PROPERTY(double v1 MEMBER m_v1 NOTIFY v1Changed); 250 | Q_PROPERTY(double v2 MEMBER m_v2 NOTIFY v2Changed); 251 | Q_PROPERTY(double period MEMBER m_period NOTIFY periodChanged); 252 | Q_PROPERTY(double phase MEMBER m_phase WRITE setPhase NOTIFY phaseChanged); 253 | Q_PROPERTY(double duty MEMBER m_duty NOTIFY dutyChanged); 254 | 255 | public: 256 | SrcItem(SignalItem*); 257 | Q_INVOKABLE void update(); 258 | void setPhase(double phase) { 259 | if (m_src.compare("constant") != 0){ 260 | phase = fmod(fmod(phase, m_period)+m_period, m_period); 261 | if (phase != m_phase) { 262 | m_phase = phase; 263 | phaseChanged(m_phase); 264 | } 265 | } 266 | } 267 | 268 | signals: 269 | void srcChanged(QString); 270 | void v1Changed(double); 271 | void v2Changed(double); 272 | void periodChanged(double); 273 | void phaseChanged(double); 274 | void dutyChanged(double); 275 | void changed(); 276 | 277 | protected: 278 | QString m_src; 279 | double m_v1; 280 | double m_v2; 281 | double m_period; 282 | double m_phase; 283 | double m_duty; 284 | 285 | SignalItem* m_parent; 286 | friend class TimerItem; 287 | }; 288 | 289 | class TimerItem : public QObject{ 290 | Q_OBJECT 291 | private: 292 | ChannelItem *channel; 293 | DeviceItem *device; 294 | SessionItem *session; 295 | QTimer *changeBufferTimer; 296 | BufferChanger *bc; 297 | QThread *thread; 298 | bool modified; 299 | 300 | public: 301 | TimerItem(ChannelItem *channel,DeviceItem *dev); 302 | protected: 303 | friend class DeviceItem; 304 | friend class SrcItem; 305 | friend class SignalItem; 306 | friend class ChannelItem; 307 | public slots: 308 | void parameterChanged(); 309 | private slots: 310 | void needChangeBuffer(); 311 | void clean(); 312 | }; 313 | 314 | class BufferChanger :public QObject{ 315 | Q_OBJECT 316 | private: 317 | ChannelItem *channel; 318 | DeviceItem *device; 319 | public: 320 | BufferChanger(ChannelItem *chan,DeviceItem *dev); 321 | ~BufferChanger(){} 322 | protected slots: 323 | void changeBuffer(); 324 | }; 325 | 326 | void registerTypes(); 327 | 328 | class DataLogger: public QObject { 329 | Q_OBJECT 330 | public: 331 | DataLogger(float sampleTime, QObject* parent = nullptr); 332 | void addData(DeviceItem*, std::array < float, 4 >); 333 | void addBulkData(DeviceItem*, std::vector < std::array < float, 4 > >); 334 | double computeAverage(DeviceItem*, int channel); 335 | double computeMinimum(DeviceItem*, int channel); 336 | double computeMaximum(DeviceItem*, int channel); 337 | void printData(DeviceItem* deviceItem); 338 | void setSampleTime(float sampleTime); 339 | private: 340 | float sampleTime; 341 | std::ofstream fileStream; 342 | std::map < DeviceItem*, std::vector < std::array < float, 4 > > > data; 343 | std::map < DeviceItem*, int > dataCounter; 344 | std::map < DeviceItem*, std::array < float, 4 > > minimum; 345 | std::map < DeviceItem*, std::array < float, 4 > > maximum; 346 | std::map < DeviceItem*, std::array < float, 4 > > sum; 347 | void updateMinimum(DeviceItem*, std::array < float, 4 >); 348 | void updateMaximum(DeviceItem*, std::array < float, 4 >); 349 | void updateSum(DeviceItem*, std::array < float, 4 >); 350 | std::array < float, 4 > computeAverage(DeviceItem*); 351 | void resetData(DeviceItem*); 352 | std::string modifyDateTime(std::string); 353 | std::chrono::time_point startTime; 354 | std::chrono::time_point lastLog; 355 | void createLoggingFolder(); 356 | std::mutex m_logMutex; 357 | int m_threadsNumber = 5; 358 | QThreadPool m_threadPool; 359 | void doAddBulkData(DeviceItem*, std::vector < std::array < float, 4 > >); 360 | }; 361 | 362 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Visual Studio 2019 2 | clone_depth: 1 3 | 4 | install: 5 | - cmd: C:\msys64\usr\bin\bash -lc "pacman -Syu --noconfirm" 6 | 7 | # Install dependencies 8 | - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -Sy mingw-w64-i686-boost mingw-w64-i686-python3 mingw-w64-i686-libzip mingw-w64-i686-icu" 9 | - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -Sy mingw-w64-x86_64-boost mingw-w64-x86_64-python3 mingw-w64-x86_64-libzip mingw-w64-x86_64-icu" 10 | 11 | # Hack: Qt5Qml CMake script throws errors when loading its plugins. So let's just drop those plugins. 12 | - C:\msys64\usr\bin\bash -lc "rm -f /mingw32/lib/cmake/Qt5Qml/*Factory.cmake" 13 | - C:\msys64\usr\bin\bash -lc "rm -f /mingw64/lib/cmake/Qt5Qml/*Factory.cmake" 14 | 15 | # Install Inno Setup 16 | - choco install InnoSetup 17 | 18 | # Download dpinst for Driver installing from swdownloads 19 | - appveyor DownloadFile http://swdownloads.analog.com/cse/m1k/drivers/dpinst.zip -FileName C:\dpinst.zip 20 | - 7z x -y "c:\dpinst.zip" -o"c:\dpinst" > nul 21 | 22 | # Download a 32-bit version of windres.exe 23 | - appveyor DownloadFile http://swdownloads.analog.com/cse/build/windres.exe.gz -FileName C:\windres.exe.gz 24 | - C:\msys64\usr\bin\bash -lc "cd /c ; gunzip windres.exe.gz" 25 | 26 | - git clone https://github.com/analogdevicesinc/libsmu.git C:\projects\libsmu 27 | - if not exist "c:\projects\pixelpulse2\distrib\drivers" mkdir c:\projects\pixelpulse2\distrib\drivers 28 | - copy C:\projects\libsmu\dist\m1k-winusb.inf c:\projects\pixelpulse2\distrib\drivers 29 | - copy C:\projects\libsmu\dist\m1k-winusb.cat c:\projects\pixelpulse2\distrib\drivers 30 | - if not exist "c:\projects\pixelpulse2\distrib\drivers\x86" mkdir c:\projects\pixelpulse2\distrib\drivers\x86 31 | - copy C:\projects\libsmu\dist\x86\* c:\projects\pixelpulse2\distrib\drivers\x86 32 | - copy C:\dpinst\dpinst.exe c:\projects\pixelpulse2\distrib 33 | 34 | - if not exist "c:\projects\pixelpulse2\distrib\drivers\amd64" mkdir c:\projects\pixelpulse2\distrib\drivers\amd64 35 | - copy C:\projects\libsmu\dist\amd64\* c:\projects\pixelpulse2\distrib\drivers\amd64 36 | - copy C:\dpinst\dpinst_amd64.exe c:\projects\pixelpulse2\distrib 37 | 38 | - 'appveyor DownloadFile "https://ci.appveyor.com/api/projects/analogdevicesinc/pixelpulse2-trayer/artifacts/distrib.zip?branch=master" -FileName c:\distrib.zip' 39 | - 7z x -y "c:\distrib.zip" -o"c:\" > nul 40 | 41 | build_script: 42 | # build 32-bit MinGW 43 | #clone and build libsmu with mingw 44 | - set PATH=C:\msys64\mingw32\bin;%PATH% 45 | - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -U https://repo.msys2.org/mingw/i686/mingw-w64-i686-libusb-1.0.23-1-any.pkg.tar.xz" 46 | - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -U https://repo.msys2.org/mingw/i686/mingw-w64-i686-qt5-5.15.2-5-any.pkg.tar.zst" 47 | 48 | # Hack: Qt5Qml CMake script throws errors when loading its plugins. So let's just drop those plugins. 49 | - rm -f /mingw32/lib/cmake/Qt5Qml/*Factory.cmake 50 | - mkdir c:\projects\libsmu\mingw-32 51 | - C:\msys64\usr\bin\bash -lc "cd C:/projects/libsmu/mingw-32 && cmake -G 'Unix Makefiles' -DCMAKE_INSTALL_PREFIX=/mingw32 -DCMAKE_C_COMPILER:FILEPATH=/mingw32/bin/i686-w64-mingw32-gcc.exe -DCMAKE_CXX_COMPILER:FILEPATH=/mingw32/bin/i686-w64-mingw32-g++.exe -DLIBUSB_LIBRARIES=C:/msys64/mingw32/lib/libusb-1.0.dll.a -DLIBUSB_INCLUDE_DIRS=C:/msys64/mingw32/include/libusb-1.0 -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON -DBUILD_PYTHON=OFF .. && cmake --build . && cmake --build . --target install" 52 | 53 | - set RC_COMPILER="-DCMAKE_RC_COMPILER=/c/windres.exe" 54 | - C:\msys64\usr\bin\bash -lc "/mingw32/bin/python3.exe --version" 55 | - C:\msys64\usr\bin\bash -lc "/c/msys64/mingw32/bin/python3.exe --version" 56 | - C:\msys64\usr\bin\bash -lc "mkdir /c/projects/build_32 ; cd /c/projects/build_32 ; cmake -G 'Unix Makefiles' %RC_COMPILER% -DCMAKE_BUILD_TYPE=RelWithDebInfo -DGIT_EXECUTABLE=/c/Program\\ Files/Git/cmd/git.exe -DPKG_CONFIG_EXECUTABLE=/mingw32/bin/pkg-config.exe -DCMAKE_C_COMPILER=i686-w64-mingw32-gcc.exe -DCMAKE_CXX_COMPILER=i686-w64-mingw32-g++.exe -DPYTHON_EXECUTABLE=/mingw32/bin/python3.exe -DLIBUSB_LIBRARIES=C:/msys64/mingw32/lib/libusb-1.0.dll.a -DLIBUSB_INCLUDE_DIRS=C:/msys64/mingw32/include/libusb-1.0 -DLIBSMU_LIBRARIES=C:/msys64/mingw32/lib/libsmu.dll.a -DLIBSMU_INCLUDE_DIRS=C:/msys64/mingw32/include /c/projects/pixelpulse2" 57 | - C:\msys64\usr\bin\bash -lc "cd /c/projects/build_32/ && sed -i 's/^\(FILEVERSION .*\)$/\1,0,"{build}"/' properties.rc 58 | - C:\msys64\usr\bin\bash -lc "cd /c/projects/build_32 && make -j3" 59 | 60 | 61 | # Copy the dependencies 62 | - mkdir c:\pixelpulse_32 63 | - mkdir c:\pixelpulse_32\pp2trayer 64 | - copy c:\projects\build_32\pixelpulse2.exe c:\pixelpulse_32\ 65 | - if not exist "c:\pixelpulse_32\drivers" mkdir c:\pixelpulse_32\drivers 66 | - copy C:\projects\libsmu\dist\m1k-winusb.inf c:\pixelpulse_32\drivers 67 | - copy C:\projects\libsmu\dist\m1k-winusb.cat c:\pixelpulse_32\drivers 68 | - if not exist "c:\pixelpulse_32\drivers\x86" mkdir c:\pixelpulse_32\drivers\x86 69 | - copy C:\projects\libsmu\dist\x86\* c:\pixelpulse_32\drivers\x86 70 | - copy C:\dpinst\dpinst.exe C:\pixelpulse_32\drivers 71 | 72 | - C:\msys64\usr\bin\bash -lc "cp -r /c/distrib/* /c/pixelpulse_32/pp2trayer" 73 | - 'appveyor DownloadFile "https://ci.appveyor.com/api/projects/analogdevicesinc/pixelpulse2-trayer/artifacts/pp2trayer.exe?branch=master" -FileName c:\pixelpulse_32\pp2trayer\pp2trayer.exe' 74 | - c:\msys64\mingw32\bin\windeployqt.exe --dir c:\pixelpulse_32 --no-system-d3d-compiler --no-compiler-runtime --opengl --qmldir c:\projects\pixelpulse2\qml --qmlimport c:\projects\pixelpulse2\qml c:\projects\build_32\pixelpulse2.exe 75 | - C:\msys64\usr\bin\bash -lc "cd /mingw32/bin ; cp -r libffi*.dll libbrotli*.dll libbz2*.dll libgobject-2.*.dll libfreetype-*.dll libgraphite*.dll libpng16-*.dll libharfbuzz*.dll libicu*.dll libiconv*.dll zlib*.dll libsmu*.dll libwinpthread-*.dll libgcc_*.dll libstdc++-*.dll libboost_{system,filesystem,atomic,program_options,regex,thread}-*.dll libglib-*.dll libintl-*.dll libusb-*.dll libzstd*.dll libgomp*.dll libdouble-conversion*.dll libpcre*.dll Qt5Core.dll Qt5Gui.dll Qt5Svg.dll Qt5Widgets.dll /c/pixelpulse_32/" 76 | 77 | - 7z a "c:\pixelpulse-32bit.zip" c:\pixelpulse_32 78 | - appveyor PushArtifact c:\pixelpulse-32bit.zip 79 | 80 | # build 64-bit MinGW 81 | #clone and build libsmu with mingw 82 | - set PATH=C:\msys64\mingw64\bin;%PATH% 83 | - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -U https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-libusb-1.0.23-1-any.pkg.tar.xz" 84 | - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -U https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-qt5-5.15.2-5-any.pkg.tar.zst" 85 | 86 | # Hack: Qt5Qml CMake script throws errors when loading its plugins. So let's just drop those plugins. 87 | - rm -f /mingw64/lib/cmake/Qt5Qml/*Factory.cmake 88 | - mkdir c:\projects\libsmu\mingw-64 89 | - C:\msys64\usr\bin\bash -lc "cd C:/projects/libsmu/mingw-64 && cmake -G 'Unix Makefiles' -DCMAKE_INSTALL_PREFIX=/mingw64 -DCMAKE_C_COMPILER:FILEPATH=/mingw64/bin/x86_64-w64-mingw32-gcc.exe -DCMAKE_CXX_COMPILER:FILEPATH=/mingw64/bin/x86_64-w64-mingw32-g++.exe -DLIBUSB_LIBRARIES=C:/msys64/mingw64/lib/libusb-1.0.dll.a -DLIBUSB_INCLUDE_DIRS=C:/msys64/mingw64/include/libusb-1.0 -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON -DBUILD_PYTHON=OFF .. && cmake --build . && cmake --build . --target install" 90 | 91 | - copy C:\msys64\mingw64\bin\windres.exe C:\msys64\mingw64\bin\x86_64-w64-mingw32-windres.exe 92 | - C:\msys64\usr\bin\bash -lc "/mingw64/bin/python3.exe --version" 93 | - C:\msys64\usr\bin\bash -lc "/c/msys64/mingw64/bin/python3.exe --version" 94 | - C:\msys64\usr\bin\bash -lc "mkdir /c/projects/build_64 ; cd /c/projects/build_64 ; cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo -DGIT_EXECUTABLE=/c/Program\\ Files/Git/cmd/git.exe -DPKG_CONFIG_EXECUTABLE=/mingw64/bin/pkg-config.exe -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc.exe -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++.exe -DPYTHON_EXECUTABLE=/mingw64/bin/python3.exe -DLIBUSB_LIBRARIES=C:/msys64/mingw64/lib/libusb-1.0.dll.a -DLIBUSB_INCLUDE_DIRS=C:/msys64/mingw64/include/libusb-1.0 -DLIBSMU_LIBRARIES=C:/msys64/mingw64/lib/libsmu.dll.a -DLIBSMU_INCLUDE_DIRS=C:/msys64/mingw64/include /c/projects/pixelpulse2" 95 | - C:\msys64\usr\bin\bash -lc "cd /c/projects/build_64/ && sed -i 's/^\(FILEVERSION .*\)$/\1,0,"{build}"/' properties.rc 96 | - C:\msys64\usr\bin\bash -lc "cd /c/projects/build_64 && make -j3" 97 | 98 | 99 | # Copy the dependencies 100 | - mkdir c:\pixelpulse_64 101 | - mkdir c:\pixelpulse_64\pp2trayer 102 | - copy c:\projects\build_64\pixelpulse2.exe c:\pixelpulse_64\ 103 | - if not exist "c:\pixelpulse_64\drivers" mkdir c:\pixelpulse_64\drivers 104 | - copy C:\projects\libsmu\dist\m1k-winusb.inf c:\pixelpulse_64\drivers 105 | - copy C:\projects\libsmu\dist\m1k-winusb.cat c:\pixelpulse_64\drivers 106 | - if not exist "c:\pixelpulse_64\drivers\amd64" mkdir c:\pixelpulse_64\drivers\amd64 107 | - copy C:\projects\libsmu\dist\amd64\* c:\pixelpulse_64\drivers\amd64 108 | - copy C:\dpinst\dpinst_amd64.exe C:\pixelpulse_64\drivers 109 | 110 | - C:\msys64\usr\bin\bash -lc "cp -r /c/distrib/* /c/pixelpulse_64/pp2trayer" 111 | - 'appveyor DownloadFile "https://ci.appveyor.com/api/projects/analogdevicesinc/pixelpulse2-trayer/artifacts/pp2trayer.exe?branch=master" -FileName c:\pixelpulse_64\pp2trayer\pp2trayer.exe' 112 | - c:\msys64\mingw64\bin\windeployqt.exe --dir c:\pixelpulse_64 --no-system-d3d-compiler --no-compiler-runtime --opengl --qmldir c:\projects\pixelpulse2\qml --qmlimport c:\projects\pixelpulse2\qml c:\projects\build_64\pixelpulse2.exe 113 | 114 | - C:\msys64\usr\bin\bash -lc "cd /mingw64/bin ; cp -r libffi*.dll libbrotli*.dll libbz2*.dll libgobject-2.*.dll libfreetype-*.dll libgraphite*.dll libpng16-*.dll libharfbuzz*.dll libicu*.dll libiconv*.dll zlib*.dll libsmu*.dll libwinpthread-*.dll libgcc_*.dll libstdc++-*.dll libboost_{system,filesystem,atomic,program_options,regex,thread}-*.dll libglib-*.dll libintl-*.dll libusb-*.dll libzstd*.dll libgomp*.dll libdouble-conversion*.dll libpcre*.dll /c/pixelpulse_64/" 115 | - C:\msys64\usr\bin\bash -lc "cd /mingw64/bin ; cp -r Qt5Core.dll Qt5Gui.dll Qt5Svg.dll Qt5Widgets.dll /c/pixelpulse_64/" 116 | 117 | - 7z a "c:\pixelpulse-64bit.zip" c:\pixelpulse_64 118 | - appveyor PushArtifact c:\pixelpulse-64bit.zip 119 | 120 | # Install Inno Setup 121 | - choco install InnoSetup 122 | 123 | 124 | # Build the installer 125 | - cd "c:\projects\build_64\" 126 | - set PATH=%PATH%;"C:\Program Files (x86)\Inno Setup 5" 127 | - ISCC /Q pixelpulse2.iss 128 | 129 | - ps: appveyor PushArtifact c:\Pixelpulse2_win_setup.exe 130 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: ubuntu-latest 11 | 12 | steps: 13 | - script: echo Hello, world! 14 | displayName: 'Run a one-line script' 15 | 16 | - script: | 17 | echo Add other tasks to build, test, and deploy your project. 18 | echo See https://aka.ms/yaml 19 | displayName: 'Run a multi-line script' 20 | -------------------------------------------------------------------------------- /config.h.cmakein: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_CMAKEIN 2 | #define CONFIG_H_CMAKEIN 3 | 4 | #define BUILD_DATE "@BUILD_DATE@" 5 | #define GIT_VERSION "@PP_VERSION_STR@" 6 | 7 | #endif // CONFIG_H_CMAKEIN 8 | -------------------------------------------------------------------------------- /debian/.gitignore: -------------------------------------------------------------------------------- 1 | pixelpulse2.debhelper.log 2 | pixelpulse2.postinst.debhelper 3 | pixelpulse2.preinst.debhelper 4 | pixelpulse2.substvars 5 | pixelpulse2 6 | files 7 | stamp-makefile-build 8 | stamp-makefile-install 9 | -------------------------------------------------------------------------------- /debian/README.source: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | pixelpulse2 (0.8-1~precise) precise; urgency=low 2 | 3 | * Version 0.8 initial public release. 4 | 5 | -- Ian Daniher Fri, 13 Mar 2015 21:08:02 -0400 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: pixelpulse2 2 | Section: electronics 3 | Priority: extra 4 | Maintainer: Ian Daniher 5 | Build-Depends: libudev-dev, libusb-1.0-0-dev (>=1.0.10), debhelper (>= 8.0.0), libqt5quick5, qt5-qmake, pkg-config, cdbs, qtdeclarative5-dev, qtbase5-dev 6 | Standards-Version: 3.9.4 7 | Homepage: https://analog.com 8 | Vcs-Git: https://github.com/analogdevicesinc/pixelpulse2.git 9 | Vcs-Browser: https://github.com/analogdevicesinc/pixelpulse2 10 | 11 | Package: pixelpulse2 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends}, qml-module-qtquick2, qml-module-qtquick-window2, qml-module-qtquick-controls, qml-module-qtquick-layouts 14 | Description: User interface for analog systems exploration. 15 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This work was packaged for Debian by: 2 | 3 | Ian Daniher and Ezra Varady 4 | 5 | It was downloaded from: 6 | 7 | https://github.com/analogdevicesinc/pixelpulse2 8 | 9 | Copyright: 10 | 11 | Copyright (C) 2012 Nonolith Labs, LLC 12 | 13 | License: 14 | 15 | Licensed under the Mozilla Public License, Version 2.0 16 | 17 | files in the project carrying such 18 | notice may not be copied, modified, or distributed except 19 | according to those terms. 20 | 21 | This package is distributed in the hope that it will be useful, 22 | but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | GNU General Public License for more details. 25 | 26 | The Debian packaging is: 27 | 28 | Copyright (C) 2015 Ezra Varady 29 | Copyright (C) 2015 Ian Daniher 30 | 31 | This program is free software: you can redistribute it and/or modify 32 | it under the terms of the GNU General Public License as published by 33 | the Free Software Foundation, either version 3 of the License, or 34 | (at your option) any later version. 35 | 36 | This package is distributed in the hope that it will be useful, 37 | but WITHOUT ANY WARRANTY; without even the implied warranty of 38 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 39 | GNU General Public License for more details. 40 | 41 | You should have received a copy of the GNU General Public License 42 | along with this program. If not, see . 43 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /debian/pixelpulse2.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=0.1 3 | Name=Pixelpulse2 4 | Comment=Digital frontend for the ADALM1000 5 | Exec=/usr/bin/pixelpulse2 6 | Icon=/usr/share/icons/pp2.png 7 | Terminal=false 8 | Type=Application 9 | Categories=Utility;Application;Development; 10 | -------------------------------------------------------------------------------- /debian/pixelpulse2.install: -------------------------------------------------------------------------------- 1 | debian/pixelpulse2.desktop usr/share/applications 2 | debian/pp2.png usr/share/icons/ 3 | -------------------------------------------------------------------------------- /debian/pixelpulse2.udev: -------------------------------------------------------------------------------- 1 | SUBSYSTEM=="usb", ATTR{idVendor}=="064b", ATTR{idProduct}=="784c", MODE="0666" 2 | SUBSYSTEM=="usb", ATTR{idVendor}=="03eb", ATTR{idProduct}=="6124", MODE="0666" 3 | -------------------------------------------------------------------------------- /debian/pp2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/debian/pp2.png -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | include /usr/share/cdbs/1/rules/debhelper.mk 4 | include /usr/share/cdbs/1/class/qmake.mk 5 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/source/include-binaries: -------------------------------------------------------------------------------- 1 | debian/pp2.png 2 | -------------------------------------------------------------------------------- /docs/mainpage.dox: -------------------------------------------------------------------------------- 1 | /** 2 | @mainpage Pixelpulse2 3 | 4 | @section introduction Introduction 5 | 6 | Pixelpulse2 Doxygen Documentation. 7 | */ 8 | -------------------------------------------------------------------------------- /icons/constant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/constant.png -------------------------------------------------------------------------------- /icons/constant.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 47 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 76 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /icons/gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/gear.png -------------------------------------------------------------------------------- /icons/mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/mv.png -------------------------------------------------------------------------------- /icons/mv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 64 | 74 | Z 85 | 86 | 87 | -------------------------------------------------------------------------------- /icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/pause.png -------------------------------------------------------------------------------- /icons/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/play.png -------------------------------------------------------------------------------- /icons/pp2.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/pp2.icns -------------------------------------------------------------------------------- /icons/pp2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/pp2.ico -------------------------------------------------------------------------------- /icons/sawtooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/sawtooth.png -------------------------------------------------------------------------------- /icons/sawtooth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 47 | 51 | 55 | 59 | 60 | 62 | 63 | 65 | image/svg+xml 66 | 68 | 69 | 70 | 71 | 72 | 77 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /icons/simv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/simv.png -------------------------------------------------------------------------------- /icons/simv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 64 | 74 | A 85 | 86 | 87 | -------------------------------------------------------------------------------- /icons/sine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/sine.png -------------------------------------------------------------------------------- /icons/sine.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 47 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 78 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /icons/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/square.png -------------------------------------------------------------------------------- /icons/square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 47 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /icons/stairstep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/stairstep.png -------------------------------------------------------------------------------- /icons/stairstep.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 47 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 78 | 87 | 96 | 105 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /icons/svmi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/svmi.png -------------------------------------------------------------------------------- /icons/svmi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 64 | 74 | V 85 | 86 | 87 | -------------------------------------------------------------------------------- /icons/triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogdevicesinc/Pixelpulse2/d066f2bce880544a6d1de02f91d7c391c5f380d5/icons/triangle.png -------------------------------------------------------------------------------- /icons/triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 47 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 76 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "SMU.h" 10 | 11 | #include "utils/fileio.h" 12 | #include "config.h" 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | // Prevent config being written to ~/.config/Unknown Organization/pixelpulse2.conf 17 | QCoreApplication::setOrganizationName("ADI"); 18 | QCoreApplication::setApplicationName("Pixelpulse2"); 19 | 20 | QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); 21 | 22 | QGuiApplication app(argc, argv); 23 | QQmlApplicationEngine engine; 24 | 25 | registerTypes(); 26 | 27 | FileIO fileIO; 28 | SessionItem smu_session; 29 | engine.rootContext()->setContextProperty("session", &smu_session); 30 | 31 | QVariantMap versions; 32 | versions.insert("build_date", BUILD_DATE); 33 | versions.insert("git_version", GIT_VERSION); 34 | engine.rootContext()->setContextProperty("versions", versions); 35 | engine.rootContext()->setContextProperty("fileio", &fileIO); 36 | if (argc > 1) { 37 | if (strcmp(argv[1], "-v") || strcmp(argv[1], "--version")) { 38 | std::cout << GIT_VERSION << ": Built on " << BUILD_DATE << std::endl; 39 | return 0; 40 | } 41 | engine.load(argv[1]); 42 | } else { 43 | engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); 44 | } 45 | 46 | app.setWindowIcon(QIcon(":/icons/pp2.ico")); 47 | QApplication::setWindowIcon(QIcon(":/icons/pp2.ico")); 48 | 49 | int r = app.exec(); 50 | smu_session.closeAllDevices(); 51 | 52 | return r; 53 | } 54 | -------------------------------------------------------------------------------- /pixelpulse2.iss.cmakein: -------------------------------------------------------------------------------- 1 | #define AppName "Pixelpulse2" 2 | #define AppVersion "@GIT_VERSION@" 3 | #define AppPublisher "Analog Devices, Inc." 4 | #define AppURL "http://www.analog.com" 5 | #define AppExeName "@PROJECT_NAME@.exe" 6 | 7 | [Setup] 8 | AppId={{258C031E-98C7-4609-9122-65A4D36274AF} 9 | AppName={#AppName} 10 | AppVersion={#AppVersion} 11 | AppPublisher={#AppPublisher} 12 | AppPublisherURL={#AppURL} 13 | AppSupportURL={#AppURL} 14 | AppUpdatesURL={#AppURL} 15 | DefaultDirName={pf}\Analog Devices\{#AppName} 16 | DefaultGroupName=Analog Devices\{#AppName} 17 | OutputDir="C:\" 18 | OutputBaseFilename=Pixelpulse2_win_setup 19 | UninstallDisplayIcon={app}\{#AppExeName} 20 | Compression=lzma 21 | SolidCompression=yes 22 | ArchitecturesInstallIn64BitMode=x64 23 | 24 | [Languages] 25 | Name: "english"; MessagesFile: "compiler:Default.isl" 26 | Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" 27 | Name: "catalan"; MessagesFile: "compiler:Languages\Catalan.isl" 28 | Name: "corsican"; MessagesFile: "compiler:Languages\Corsican.isl" 29 | Name: "czech"; MessagesFile: "compiler:Languages\Czech.isl" 30 | Name: "danish"; MessagesFile: "compiler:Languages\Danish.isl" 31 | Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl" 32 | Name: "finnish"; MessagesFile: "compiler:Languages\Finnish.isl" 33 | Name: "french"; MessagesFile: "compiler:Languages\French.isl" 34 | Name: "german"; MessagesFile: "compiler:Languages\German.isl" 35 | Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl" 36 | Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" 37 | Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" 38 | Name: "norwegian"; MessagesFile: "compiler:Languages\Norwegian.isl" 39 | Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl" 40 | Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl" 41 | Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" 42 | Name: "slovenian"; MessagesFile: "compiler:Languages\Slovenian.isl" 43 | Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" 44 | Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl" 45 | Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl" 46 | 47 | [Tasks] 48 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" 49 | Name: drivers; Description: Install WinUSB drivers for the M1K; 50 | 51 | [Files] 52 | Source: "c:\pixelpulse_64\*"; DestDir: "{app}"; Check: IsWin64; Flags: replacesameversion recursesubdirs createallsubdirs 53 | Source: "c:\pixelpulse_32\*"; DestDir: "{app}"; Flags: replacesameversion recursesubdirs createallsubdirs; Check: "not IsWin64" 54 | Source: "c:\projects\pixelpulse2\distrib\drivers\*"; DestDir: "{app}\drivers"; Flags: recursesubdirs createallsubdirs 55 | Source: "c:\projects\pixelpulse2\distrib\dpinst_amd64.exe"; DestDir: "{app}\drivers"; DestName: "dpinst.exe"; Flags: ignoreversion; Check: IsWin64 56 | Source: "c:\projects\pixelpulse2\distrib\dpinst.exe"; DestDir: "{app}\drivers"; Flags: ignoreversion; Check: "not IsWin64" 57 | Source: "c:\projects\pixelpulse2\distrib\drivers\m1k-winusb.inf"; DestDir: "{app}\drivers"; Tasks: drivers 58 | Source: "c:\projects\pixelpulse2\distrib\drivers\m1k-winusb.cat"; DestDir: "{app}\drivers"; Tasks: drivers 59 | 60 | [Icons] 61 | Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}" 62 | Name: "{group}\{cm:UninstallProgram,{#AppName}}"; Filename: "{uninstallexe}" 63 | Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon 64 | 65 | [Run] 66 | Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent unchecked 67 | Filename: {app}\pp2trayer\pp2trayer.exe; Description: "Launch Pixelpulse2 Trayer"; Flags: nowait postinstall skipifsilent 68 | Filename: "{app}\drivers\dpinst.exe"; Parameters: "/PATH ""{app}\drivers"" {param:dpinstflags|/F}"; Flags: waituntilterminated; Tasks: drivers 69 | 70 | [UninstallRun] 71 | Filename: "{cmd}"; Parameters: "/C""taskkill /im pp2trayer\pp2trayer.exe /f /t"; Flags: runhidden 72 | 73 | [Registry] 74 | ; Make Pixelpulse2 Trayer run every time Windows boots 75 | Root: HKLM; Subkey: "SOFTWARE\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "Pixelpulse2Trayer"; ValueData: """{app}\pp2trayer\pp2trayer.exe"""; Flags: uninsdeletevalue 76 | 77 | ; Functions GetNumber(), CompareInner(), CompareVersion() and InitializeSetup() can be used to detect 78 | ; differences between the version being installed and the version that might be already installed. 79 | [Code] 80 | function GetNumber(var temp: String): Integer; 81 | var 82 | part: String; 83 | pos1: Integer; 84 | begin 85 | if Length(temp) = 0 then 86 | begin 87 | Result := -1; 88 | Exit; 89 | end; 90 | pos1 := Pos('.', temp); 91 | if (pos1 = 0) then 92 | begin 93 | Result := StrToInt(temp); 94 | temp := ''; 95 | end 96 | else 97 | begin 98 | part := Copy(temp, 1, pos1 - 1); 99 | temp := Copy(temp, pos1 + 1, Length(temp)); 100 | Result := StrToInt(part); 101 | end; 102 | end; 103 | 104 | function CompareInner(var temp1, temp2: String): Integer; 105 | var 106 | num1, num2: Integer; 107 | begin 108 | num1 := GetNumber(temp1); 109 | num2 := GetNumber(temp2); 110 | if (num1 = -1) or (num2 = -1) then 111 | begin 112 | Result := 0; 113 | Exit; 114 | end; 115 | if (num1 > num2) then 116 | begin 117 | Result := 1; 118 | end 119 | else if (num1 < num2) then 120 | begin 121 | Result := -1; 122 | end 123 | else 124 | begin 125 | Result := CompareInner(temp1, temp2); 126 | end; 127 | end; 128 | 129 | function CompareVersion(str1, str2: String): Integer; 130 | var 131 | temp1, temp2: String; 132 | begin 133 | temp1 := str1; 134 | temp2 := str2; 135 | Result := CompareInner(temp1, temp2); 136 | end; 137 | 138 | // function InitializeSetup(): Boolean; 139 | // var 140 | // oldVersion: String; 141 | // uninstaller: String; 142 | // ErrorCode: Integer; 143 | // vCurID :String; 144 | // vCurAppName :String; 145 | // begin 146 | // vCurID:= '{#SetupSetting("AppName")}'; 147 | // vCurAppName:= '{#SetupSetting("AppName")}'; 148 | 149 | // if RegKeyExists(HKLM, 150 | // 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1') then 151 | // begin 152 | // RegQueryStringValue(HKLM, 153 | // 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1', 154 | // 'DisplayVersion', oldVersion); 155 | // if (not (CompareVersion(oldVersion, '{#SetupSetting("AppVersion")}') = 0)) then 156 | // begin 157 | // if MsgBox('An older version ' + oldVersion + ' of ' + vCurAppName + ' is already installed. Continue to use this version?', 158 | // mbConfirmation, MB_YESNO) = IDYES then 159 | // begin 160 | // Result := False; 161 | // end 162 | // else 163 | // begin 164 | // RegQueryStringValue(HKLM, 165 | // 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1', 166 | // 'UninstallString', uninstaller); 167 | // ShellExec('runas', uninstaller, '/SILENT', '', SW_HIDE, ewWaitUntilTerminated, ErrorCode); 168 | // Result := True; 169 | // end; 170 | // end 171 | // else 172 | // begin 173 | // if MsgBox('Version ' + oldVersion + ' of ' + vCurAppName + ' is already installed. Continue with the install?', 174 | // mbConfirmation, MB_YESNO) = IDNO then 175 | // begin 176 | // Result := False; 177 | // end 178 | // else 179 | // begin 180 | // RegQueryStringValue(HKLM, 181 | // 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1', 182 | // 'UninstallString', uninstaller); 183 | // ShellExec('runas', uninstaller, '/SILENT', '', SW_HIDE, ewWaitUntilTerminated, ErrorCode); 184 | // Result := True; 185 | // end; 186 | // end 187 | // end 188 | // else 189 | // begin 190 | // Result := True; 191 | // end; 192 | // end; 193 | -------------------------------------------------------------------------------- /properties.rc.cmakein: -------------------------------------------------------------------------------- 1 | #include 2 | AppIcon ICON DISCARDABLE "@CMAKE_SOURCE_DIR@/icons/pp2.ico" 3 | 4 | LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT 5 | 6 | VS_VERSION_INFO VERSIONINFO 7 | FILEVERSION @PP_FILEVERSION@ 8 | BEGIN 9 | BLOCK "StringFileInfo" 10 | BEGIN 11 | BLOCK "040904E4" 12 | BEGIN 13 | VALUE "FileVersion", "@PP_PRODUCTVERSION_STR@" 14 | VALUE "CompanyName", "Analog Devices" 15 | VALUE "InternalName", "@PROJECT_NAME@" 16 | VALUE "OriginalFilename", "@PROJECT_NAME@.exe" 17 | VALUE "ProductName", "Pixelpulse2" 18 | VALUE "ProductVersion", "@PP_PRODUCTVERSION_STR@-@GIT_VERSION@" 19 | END 20 | END 21 | 22 | BLOCK "VarFileInfo" 23 | BEGIN 24 | VALUE "Translation", 0x409, 1252 25 | END 26 | END 27 | -------------------------------------------------------------------------------- /qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qml/main.qml 4 | qml/Toolbar.qml 5 | qml/PlotPane.qml 6 | qml/ToolbarStyle.qml 7 | qml/ContentPane.qml 8 | qml/XYPlot.qml 9 | qml/Controller.qml 10 | qml/SignalRow.qml 11 | qml/ChannelRow.qml 12 | qml/OverlayConstant.qml 13 | qml/TimelineFlickable.qml 14 | qml/TimelineHeader.qml 15 | qml/Axes.qml 16 | qml/OverlayPeriodic.qml 17 | qml/DragDot.qml 18 | qml/DeviceRow.qml 19 | qml/jsutils.js 20 | qml/dataexport.js 21 | qml/sesssave.js 22 | icons/simv.png 23 | icons/svmi.png 24 | icons/mv.png 25 | icons/sine.png 26 | icons/square.png 27 | icons/triangle.png 28 | icons/sawtooth.png 29 | icons/stairstep.png 30 | icons/constant.png 31 | icons/play.png 32 | icons/pause.png 33 | icons/gear.png 34 | qml/DeviceManagerPane.qml 35 | qml/ColorControlDialog.qml 36 | qml/StyleSlider.qml 37 | qml/AcquisitionSettingsDialog.qml 38 | icons/pp2.ico 39 | 40 | 41 | -------------------------------------------------------------------------------- /qml/AcquisitionSettingsDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Window 2.0 3 | import QtQuick.Layouts 1.0 4 | import QtQuick.Controls 1.0 5 | import QtQuick.Controls.Styles 1.1 6 | 7 | Window { 8 | title: "Acquisition Settings" 9 | minimumWidth: 400 10 | minimumHeight: 180 11 | maximumWidth: minimumWidth 12 | maximumHeight: minimumHeight 13 | modality: Qt.NonModal 14 | flags: Qt.Window | Qt.WindowSystemMenuHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint 15 | 16 | property real timeDelay: delay.value 17 | property bool showStatusBar: toggleStatusBar.checked 18 | 19 | property real timeDelayOld: 0; 20 | 21 | function delayToSamples(delayVal) 22 | { 23 | var timeInSeconds = delayVal / 1000.0; 24 | var sampleCount = Math.floor(controller.sampleRate * timeInSeconds + 0.5); 25 | 26 | return sampleCount; 27 | } 28 | 29 | function samplesToSecondsDelay(samplesCount) 30 | { 31 | return (samplesCount / controller.sampleRate); 32 | } 33 | 34 | function onContinuousModeChanged(continuous) 35 | { 36 | if (continuous) { 37 | timeDelayOld = timeDelay; 38 | delay.value = 0; 39 | } else { 40 | delay.value = timeDelayOld; 41 | } 42 | 43 | delay.enabled = !continuous; 44 | } 45 | 46 | Rectangle { 47 | id: rectangle 48 | anchors.fill: parent 49 | color: '#222' 50 | 51 | ColumnLayout { 52 | anchors.fill: parent 53 | anchors.leftMargin: 25 54 | anchors.rightMargin: 25 55 | anchors.topMargin: 35 56 | anchors.bottomMargin: 35 57 | spacing: 0 58 | 59 | Text { 60 | text: "Amount of time the received data should be delayed with:" 61 | color: 'white' 62 | font.pixelSize: 14 63 | } 64 | 65 | RowLayout { 66 | spacing: 15 67 | 68 | Text { 69 | id: delayLabel 70 | text: "Delay (ms)" 71 | color: 'white' 72 | font.pixelSize: 14 73 | } 74 | 75 | SpinBox { 76 | id: delay 77 | maximumValue: 1000 78 | minimumValue: 0 79 | decimals: 2 80 | stepSize: 0.01 81 | 82 | onValueChanged: { 83 | var sampleCount = delayToSamples(delay.value); 84 | 85 | if (sampleCount !== controller.delaySampleCount) { 86 | controller.delaySampleCount = sampleCount; 87 | } 88 | } 89 | Keys.onPressed: { 90 | switch (event.key) { 91 | case Qt.Key_PageDown: 92 | delay.value -= 0.5; 93 | break; 94 | case Qt.Key_PageUp: 95 | delay.value += 0.5; 96 | break; 97 | } 98 | } 99 | } // SpinBox 100 | } // RowLayout 101 | 102 | CheckBox { 103 | id: toggleStatusBar 104 | style: CheckBoxStyle { 105 | label: Label { 106 | color: 'white'; 107 | text: 'Show delay value on main window' 108 | font.pixelSize: 14 109 | } 110 | } 111 | } // Checkbox 112 | } // ColumnLayout 113 | } // Rectangle 114 | } // Window 115 | -------------------------------------------------------------------------------- /qml/Axes.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Window 2.0 3 | import QtQuick.Layouts 1.0 4 | 5 | Item { 6 | id: axes 7 | 8 | property bool xbottom: true 9 | property bool yleft: true 10 | property bool yright: true 11 | 12 | property real xmin: 0 13 | property real xmax: 1 14 | 15 | property real ymin: 0 16 | property real ymax: 1 17 | 18 | property real textSpacing: 12 19 | 20 | property int xgridticks: width / 12 21 | property int ygridticks: height / 12 22 | 23 | property var gridColor: '#fff' 24 | property var textColor: '#fff' 25 | property var textSize: 14 26 | 27 | function step(min, max, count) { 28 | // Inspired by d3.js 29 | var span = max - min; 30 | var step = Math.pow(10, Math.floor(Math.log(span / count) / Math.LN10)); 31 | var err = count / span * step; 32 | 33 | // Filter ticks to get closer to the desired count. 34 | if (err <= .35) step *= 10 35 | else if (err <= .75) step *= 5 36 | else if (err <= 1.0) step *= 2 37 | 38 | return step 39 | } 40 | 41 | property real xstep: step(xmin, xmax, xgridticks) 42 | property real xstart: Math.ceil(xmin / xstep) 43 | 44 | property real ystep: step(ymin, ymax, ygridticks) 45 | property real ystart: Math.ceil(ymin / ystep) 46 | 47 | property real yscale: height / (ymax - ymin) 48 | function yToPx(y) { return height - (y - ymin) * yscale } 49 | function yToPxClamped(y) { return Math.min(Math.max(yToPx(y), 0), height) } 50 | function pxToY(px) { return (height - px) / yscale + ymin } 51 | function pxToX(px) { return px / xscale + xmin } 52 | function snapx(x) { return Math.round(x / (timeline_header.step/(1/controller.sampleRate))) * (timeline_header.step/(1/controller.sampleRate)) } 53 | function snapy(y) { return Math.round(y / ystep)*ystep } 54 | property real xscale: width / (xmax - xmin) 55 | function xToPx(x) { return (x - xmin) * xscale } 56 | 57 | 58 | function decimals(x) 59 | { 60 | var tmp = x 61 | if (tmp.indexOf(".")>-1) 62 | return tmp.length-tmp.indexOf(".")-1; 63 | else 64 | return 0; 65 | } 66 | 67 | Repeater { 68 | model: ygridticks 69 | 70 | Rectangle { 71 | property real yval: ((ystart + index) * ystep) 72 | property string syval: yval.toFixed(decimals(Math.abs(ystep).toString())) 73 | visible: yval <= ymax 74 | x: 0 75 | y: yToPx(yval) 76 | width: axes.width 77 | height: 1 78 | color: gridColor 79 | 80 | Text { 81 | visible: yleft 82 | anchors.right: parent.left 83 | anchors.rightMargin: textSpacing*2 84 | anchors.leftMargin: textSpacing 85 | anchors.verticalCenter: parent.verticalCenter 86 | font.pixelSize: textSize 87 | color: textColor 88 | text: syval 89 | } 90 | 91 | Text { 92 | visible: yright 93 | anchors.left: parent.right 94 | anchors.rightMargin: textSpacing*2 95 | anchors.leftMargin: textSpacing 96 | anchors.verticalCenter: parent.verticalCenter 97 | font.pixelSize: textSize 98 | color: textColor 99 | text: syval 100 | } 101 | } 102 | } 103 | 104 | Repeater { 105 | model: xgridticks 106 | 107 | Rectangle { 108 | property real xval: (xstart + index) * xstep 109 | property string sxval: xval.toFixed(decimals(Math.abs(xstep).toString())) 110 | visible: xval <= xmax 111 | x: xToPx(xval) 112 | y: 0 113 | width: 1 114 | height: axes.height 115 | color: gridColor 116 | 117 | Text { 118 | visible: xbottom 119 | anchors.horizontalCenter: parent.horizontalCenter 120 | anchors.top: parent.bottom 121 | anchors.bottomMargin: textSpacing*2 122 | anchors.topMargin: textSpacing 123 | font.pixelSize: textSize 124 | color: textColor 125 | text: sxval 126 | rotation: -90 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /qml/ChannelRow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Window 2.0 3 | import QtQuick.Layouts 1.0 4 | import QtQuick.Controls 1.0 5 | import QtQuick.Controls.Styles 1.1 6 | 7 | Rectangle { 8 | id: channelBlock 9 | property var channel 10 | property alias signalRepeater:signalRepeater 11 | color: '#333' 12 | 13 | Button { 14 | anchors.top: parent.top 15 | anchors.left: parent.left 16 | width: timelinePane.spacing 17 | height: timelinePane.spacing 18 | 19 | property var icons: [ 20 | 'mv', 21 | 'svmi', 22 | 'simv', 23 | ] 24 | iconSource: 'qrc:/icons/' + icons[channel.mode] + '.png' 25 | 26 | style: ButtonStyle { 27 | background: Rectangle { 28 | opacity: control.pressed ? 0.3 : control.checked ? 0.2 : 0.1 29 | color: 'black' 30 | } 31 | } 32 | 33 | function updateMode() { 34 | var chIdx = {A: 0, B: 1}[channel.label]; 35 | var devIdx = parent.parent.parent.currentIndex * 2; 36 | var xyPlot = xyPane.devRep.itemAt(parent.parent.parent.currentIndex).itemAt(chIdx); 37 | 38 | xyPlot.ysignal = (channel.mode == 1) ? xyPlot.isignal : xyPlot.vsignal; 39 | xyPlot.xsignal = (channel.mode == 1) ? xyPlot.vsignal : xyPlot.isignal; 40 | } 41 | 42 | menu: Menu { 43 | MenuItem { text: "Measure Voltage" 44 | onTriggered: channel.mode = 0 45 | } 46 | MenuItem { text: "Source Voltage, Measure Current" 47 | onTriggered: channel.mode = 1 48 | } 49 | MenuItem { text: "Source Current, Measure Voltage" 50 | onTriggered: channel.mode = 2 51 | } 52 | } 53 | } 54 | 55 | 56 | Text { 57 | text: "Channel " + channel.label 58 | color: 'white' 59 | rotation: -90 60 | transformOrigin: Item.TopLeft 61 | font.pixelSize: 18 / session.devices.length 62 | y: width + timelinePane.spacing + 8 63 | x: (timelinePane.spacing - height) / 2 64 | } 65 | 66 | ColumnLayout { 67 | anchors.fill: parent 68 | anchors.leftMargin: timelinePane.spacing 69 | spacing: 0 70 | 71 | Repeater { 72 | id: signalRepeater 73 | model: modelData.signals 74 | 75 | SignalRow { 76 | Layout.fillHeight: true 77 | Layout.fillWidth: true 78 | Layout.minimumHeight: channelBlock.height / 2 79 | 80 | signal: model 81 | xaxis: timeline_xaxis 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /qml/ColorControlDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.0 3 | import QtQuick.Controls 1.0 4 | import QtQuick.Controls.Styles 1.1 5 | import QtQuick.Dialogs 1.0 6 | import QtQuick.Dialogs 1.2 7 | import QtGraphicalEffects 1.0 8 | 9 | Dialog { 10 | title: "Display settings" 11 | width: 300 12 | height: 300 13 | modality: Qt.NonModal 14 | property alias plotCheckBox: plotsCheckBox 15 | property alias sigCheckBox: signalCheckBox 16 | property alias sliderB: sliderBrightness 17 | property alias sliderC: sliderContrast 18 | property alias sliderDot: sliderDotSize 19 | property alias sliderPh: sliderPhosphorRender 20 | 21 | contentItem: 22 | RowLayout { 23 | id: layout 24 | 25 | Rectangle{ 26 | id: rectangle 27 | color: '#333' 28 | anchors.fill: parent 29 | Layout.preferredWidth: 300 30 | Layout.preferredHeight: 300 31 | Layout.maximumHeight: 300 32 | Layout.maximumWidth: Layout.preferredWidth 33 | Layout.minimumHeight: Layout.maximumHeight 34 | Layout.minimumWidth: Layout.maximumWidth 35 | property var lastModified; 36 | property color intermPlotColor: '#0c0c0c'; 37 | property color intermPlotAxes: '#222'; 38 | property color intermSignalColor: '#0c0c0c'; 39 | property color intermSignalAxes: '#222'; 40 | 41 | 42 | CheckBox { 43 | id: signalCheckBox 44 | focus: true 45 | checked: true 46 | anchors.top: parent.top 47 | anchors.left: parent.left 48 | anchors.topMargin: 5/100 * parent.height 49 | anchors.leftMargin:15/100 * parent.width 50 | style: CheckBoxStyle { 51 | label: Text { 52 | color: "white" 53 | text: 'Time Plots' 54 | font.pixelSize: 14 55 | } 56 | } 57 | onClicked: { 58 | sliderContrast.valueHasChanged(signalCheckBox); 59 | sliderPhosphorRender.valueHasChanged(signalCheckBox); 60 | sliderDotSize.valueHasChanged(signalCheckBox); 61 | 62 | } 63 | } 64 | CheckBox { 65 | id: plotsCheckBox 66 | focus: true 67 | checked: xyPane.visible ? true : false 68 | anchors.top: parent.top 69 | anchors.left: signalCheckBox.right 70 | anchors.topMargin: 5/100 * parent.height 71 | anchors.leftMargin: 30 72 | style: CheckBoxStyle { 73 | label: Text { 74 | color: "white" 75 | text: 'XY Plots' 76 | font.pixelSize: 14 77 | } 78 | } 79 | onClicked: { 80 | sliderContrast.valueHasChanged(plotsCheckBox); 81 | sliderPhosphorRender.valueHasChanged(plotsCheckBox); 82 | sliderDotSize.valueHasChanged(plotsCheckBox); 83 | 84 | } 85 | } 86 | 87 | Text{ 88 | id: brightLabel 89 | visible: true 90 | text: 'Brightness' 91 | font.pixelSize: 14 92 | color: 'white' 93 | anchors.top: plotsCheckBox.bottom 94 | anchors.left: signalCheckBox.left 95 | anchors.topMargin: 5/100 * parent.height 96 | } 97 | Slider { 98 | id: sliderBrightness 99 | focus: true 100 | anchors.top: brightLabel.bottom 101 | anchors.topMargin: 1/100 * parent.height 102 | anchors.left: signalCheckBox.left 103 | value: 0.0 104 | minimumValue: 0.0 105 | maximumValue: 1.0 106 | stepSize: 0.01 107 | width: 70/100 * parent.width 108 | activeFocusOnPress: true 109 | activeFocusOnTab: true 110 | updateValueWhileDragging: true 111 | property real factor; 112 | property real oldValue: 0.0 113 | 114 | function valueHasChanged(obj){ 115 | factor = (sliderBrightness.value) 116 | if (plotsCheckBox.checked && (obj !== signalCheckBox)) { 117 | var rPlot = parent.intermPlotColor.r + (100 * factor) / 255 118 | var rAxes = parent.intermPlotAxes.r + (100 * factor) / 255 119 | window.xyplotColor = Qt.rgba(rPlot, rPlot, rPlot, 1.0) 120 | window.gridAxesColor = Qt.rgba(rAxes, rAxes, rAxes, 1.0) 121 | } 122 | 123 | if (signalCheckBox.checked && (obj !== plotsCheckBox)){ 124 | rPlot = parent.intermSignalColor.r + (100 * factor) / 255 125 | rAxes = parent.intermSignalAxes .r + (100 * factor) / 255 126 | window.signalRowColor = Qt.rgba(rPlot, rPlot, rPlot, 1.0) 127 | window.signalAxesColor = Qt.rgba(rAxes, rAxes, rAxes, 1.0) 128 | } 129 | if(signalCheckBox.checked && plotsCheckBox.checked){ 130 | oldValue = value 131 | } 132 | } 133 | style: StyleSlider { } 134 | onValueChanged: valueHasChanged(sliderBrightness) 135 | 136 | } 137 | 138 | Text{ 139 | id: contrastLabel 140 | visible: true 141 | text: 'Contrast' 142 | font.pixelSize: 14 143 | color: 'white' 144 | anchors.top: sliderBrightness.bottom 145 | anchors.left: signalCheckBox.left 146 | anchors.topMargin: 5/100 * parent.height 147 | } 148 | Slider { 149 | id: sliderContrast 150 | anchors.top: contrastLabel.bottom 151 | anchors.topMargin: 1/100 * parent.height 152 | anchors.left: signalCheckBox.left 153 | value: 0.0 154 | minimumValue: 0.0 155 | focus: true 156 | maximumValue: 1.0 157 | stepSize: 0.01 158 | width: 70/100 * parent.width 159 | activeFocusOnTab: true 160 | activeFocusOnPress: true 161 | updateValueWhileDragging: true 162 | property real factor; 163 | property real oldValue: 0.0; 164 | property color plotC: '#0c0c0c' 165 | property color gridC: '#222' 166 | 167 | function valueHasChanged(obj){ 168 | factor = (sliderContrast.value) 169 | var rPlot = plotC.r - (100 * factor) / 255 170 | var rAxes = gridC.r + (100 * factor) / 255 171 | 172 | if (plotsCheckBox.checked && (obj !== signalCheckBox)) { 173 | parent.intermPlotAxes = Qt.rgba(rAxes, rAxes, rAxes, 1.0) 174 | parent.intermPlotColor = Qt.rgba(rPlot, rPlot, rPlot, 1.0) 175 | if (factor === 1.0) {parent.intermPlotAxes = '#fdfdfd'} 176 | } 177 | if (signalCheckBox.checked && (obj !== plotsCheckBox)){ 178 | parent.intermSignalAxes = Qt.rgba(rAxes, rAxes, rAxes, 1.0) 179 | parent.intermSignalColor = Qt.rgba(rPlot, rPlot, rPlot, 1.0) 180 | if (factor === 1.0) {parent.intermSignalAxes = '#fdfdfd' } 181 | } 182 | if(signalCheckBox.checked && plotsCheckBox.checked){ 183 | oldValue = value 184 | } 185 | /* Check for value updates in brightness slider */ 186 | sliderBrightness.valueHasChanged(sliderContrast) 187 | } 188 | style: StyleSlider { } 189 | onValueChanged: sliderContrast.valueHasChanged(sliderContrast) 190 | } 191 | Text{ 192 | id: phosphorLabel 193 | visible: true 194 | text: 'Dot Brightness' 195 | font.pixelSize: 14 196 | color: 'white' 197 | anchors.top: sliderContrast.bottom 198 | anchors.left: signalCheckBox.left 199 | anchors.topMargin: 5/100 * parent.height 200 | } 201 | Slider { 202 | id: sliderPhosphorRender 203 | anchors.top: phosphorLabel.bottom 204 | anchors.topMargin: 1/100 * parent.height 205 | anchors.left: signalCheckBox.left 206 | value: 0.0 207 | minimumValue: 0.0 208 | focus: true 209 | maximumValue: 1.0 210 | stepSize: 0.01 211 | width: 70/100 * parent.width 212 | activeFocusOnTab: true 213 | activeFocusOnPress: true 214 | updateValueWhileDragging: true 215 | property real factor; 216 | property color dotCurrent: Qt.rgba(0.2, 0.2, 0.03, 1); 217 | property color dotVoltage: Qt.rgba(0.03, 0.3, 0.03, 1); 218 | 219 | function valueHasChanged(obj){ 220 | factor = (sliderPhosphorRender.value) 221 | var rCurrent = dotCurrent.r + (300 * factor) / 255 222 | var gCurrent = dotCurrent.g + (500 * factor) / 255 223 | var bCurrent = dotCurrent.b + (100 * factor) / 255 224 | 225 | var rVoltage = dotVoltage.r + (100 * factor) / 255 226 | var gVoltage = dotVoltage.g + (500 * factor) / 255 227 | var bVoltage = dotVoltage.b + (100 * factor) / 255 228 | if (plotsCheckBox.checked && (obj !== signalCheckBox)) { 229 | window.dotPlotsCurrent = Qt.rgba(rCurrent, gCurrent, bCurrent, 1.0) 230 | window.dotPlotsVoltage = Qt.rgba(rVoltage, gVoltage, bVoltage, 1.0) 231 | } 232 | if(signalCheckBox.checked && (obj !== plotsCheckBox)){ 233 | window.dotSignalCurrent = Qt.rgba(rCurrent, gCurrent, bCurrent, 1.0) 234 | window.dotSignalVoltage = Qt.rgba(rVoltage, gVoltage, bVoltage, 1.0) 235 | } 236 | } 237 | style: StyleSlider { } 238 | onValueChanged: sliderPhosphorRender.valueHasChanged(sliderPhosphorRender) 239 | } 240 | Text{ 241 | id: dotSizeLabel 242 | visible: true 243 | text: 'Dot Size' 244 | font.pixelSize: 14 245 | color: 'white' 246 | anchors.top: sliderPhosphorRender.bottom 247 | anchors.left: signalCheckBox.left 248 | anchors.topMargin: 5/100 * parent.height 249 | } 250 | Slider { 251 | id: sliderDotSize 252 | anchors.top: dotSizeLabel.bottom 253 | anchors.topMargin: 1/100 * parent.height 254 | anchors.left: signalCheckBox.left 255 | value: 0.1 256 | minimumValue: 0.1 257 | focus: true 258 | maximumValue: 1.0 259 | stepSize: 0.1 260 | width: 70/100 * parent.width 261 | activeFocusOnTab: true 262 | activeFocusOnPress: true 263 | updateValueWhileDragging: true 264 | property real factor; 265 | 266 | function valueHasChanged(obj){ 267 | factor = sliderDotSize.value 268 | if (plotsCheckBox.checked && (obj !== signalCheckBox)){ 269 | window.dotSizePlots = factor 270 | } 271 | if (signalCheckBox.checked && (obj !== plotsCheckBox)){ 272 | window.dotSizeSignal = factor 273 | } 274 | } 275 | style: StyleSlider { } 276 | onValueChanged: sliderDotSize.valueHasChanged(sliderDotSize) 277 | } 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /qml/ContentPane.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Layouts 1.0 3 | import QtQuick.Controls 1.1 4 | import QtQuick.Controls.Styles 1.1 5 | import "jsutils.js" as JSUtils 6 | import "sesssave.js" as StateSave 7 | 8 | ColumnLayout { 9 | id: cLayout 10 | spacing: 12 11 | 12 | ToolbarStyle { 13 | Layout.fillWidth: true 14 | Layout.minimumWidth: parent.Layout.minimumWidth 15 | Layout.maximumWidth: parent.Layout.maximumWidth 16 | height: toolbarHeight 17 | } 18 | 19 | TextArea { 20 | id: outField 21 | readOnly: true 22 | Layout.fillWidth: true 23 | Layout.minimumWidth: parent.Layout.minimumWidth 24 | Layout.maximumWidth: parent.Layout.maximumWidth 25 | Layout.fillHeight: true 26 | selectByKeyboard: true 27 | selectByMouse: true 28 | backgroundVisible: false 29 | text: "Built: " + versions.build_date + " " + "Version: " + versions.git_version + JSUtils.checkLatest(outField); 30 | style: TextAreaStyle { 31 | textColor: "#fff" 32 | selectionColor: "steelblue" 33 | selectedTextColor: "#eee" 34 | backgroundColor: "#eee" 35 | } 36 | } 37 | 38 | TextInput { 39 | id: inField 40 | Layout.fillWidth: true 41 | Layout.minimumWidth: parent.Layout.minimumWidth 42 | Layout.maximumWidth: parent.Layout.maximumWidth 43 | cursorVisible: true 44 | text: "type here." 45 | color: "#FFF" 46 | onAccepted: { 47 | // wow, javascript. 48 | var out; 49 | try { 50 | out = JSUtils.toJSON(eval(text), 5, 10, " "); 51 | outField.textColor = "#fff"; 52 | } catch (e) { 53 | out = e.message; 54 | outField.textColor = "#f77"; 55 | }; 56 | outField.text = out; 57 | } 58 | selectByMouse: true 59 | } 60 | MouseArea { 61 | anchors.fill: inField 62 | onPressed: { mouse.accepted = false; if (inField.text == "type here.") {inField.text = "" }} 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /qml/Controller.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import "sesssave.js" as StateSave 3 | 4 | Item { 5 | property bool enabled: session.active 6 | property bool continuous: false 7 | property bool repeat: true 8 | property real sampleRate: session.devices.length ? session.devices[0].DefaultRate : 0 9 | property real maxOutSignalFreq: sampleRate / 5 // A period of a signal should contain at least 5 samples 10 | property real sampleTime: 0.1 11 | readonly property int sampleCount: sampleTime * sampleRate + delaySampleCount 12 | property bool restartAfterStop: false 13 | property int delaySampleCount: 0 14 | 15 | property bool dlySmplCntChanged: false 16 | property int queueSize: session.queueSize 17 | property real minOutSignalFreq: 1 / (queueSize/sampleRate) 18 | 19 | // function trigger() { 20 | // session.sampleRate = sampleRate 21 | // session.sampleCount = sampleCount 22 | 23 | // if (dlySmplCntChanged) { 24 | // for (var i = 0; i < session.devices.length; i++) { 25 | // for (var j = 0; j < session.devices[i].channels.length; j++) { 26 | // session.devices[i].channels[j].signals[0].buffer.setIgnoredFirstSamplesCount(delaySampleCount); 27 | // session.devices[i].channels[j].signals[1].buffer.setIgnoredFirstSamplesCount(delaySampleCount); 28 | // } 29 | // } 30 | // dlySmplCntChanged = false; 31 | // } 32 | 33 | // session.start(continuous); 34 | // if ( session.devices.length > 0 ) { 35 | // lastConfig = StateSave.saveState(); 36 | // } 37 | // } 38 | 39 | // onSampleTimeChanged: { 40 | // if (continuous && enabled) { 41 | // enabled = false; 42 | // restartAfterStop = true; 43 | // session.cancel(); 44 | // enabled = true; 45 | // } 46 | // } 47 | 48 | // Timer { 49 | // id: timer 50 | // interval: 100 51 | // onTriggered: { trigger() } 52 | // } 53 | 54 | // function toggle() { 55 | // if (!enabled) { 56 | // trigger(); 57 | // enabled = true; 58 | // } else { 59 | // enabled = false; 60 | // if (continuous || sampleTime > 0.1) { 61 | // session.cancel(); 62 | // } 63 | // } 64 | // } 65 | 66 | function toggle() { 67 | //console.log("queue size"+queueSize) 68 | //console.log("min freq"+minOutSignalFreq) 69 | if (!session.active) { 70 | session.sampleRate = sampleRate 71 | session.sampleCount = sampleCount 72 | session.sampleTime = sampleTime 73 | session.start(continuous); 74 | } else { 75 | session.cancel(); 76 | } 77 | } 78 | onSampleCountChanged: { 79 | //console.log("onSampleCountChanged"); 80 | //console.log(sampleCount); 81 | session.sampleCount = sampleCount; 82 | } 83 | 84 | onSampleTimeChanged: { 85 | session.sampleTime = sampleTime; 86 | } 87 | 88 | // Timer { 89 | // id: updateMeasurementsTimer 90 | // interval: 50 91 | // repeat: true 92 | // running: enabled && continuous 93 | // onTriggered: session.updateMeasurements() 94 | // } 95 | 96 | // Timer { 97 | // id: updateLabelsTimer 98 | // interval: 500 99 | // repeat: true 100 | // running: enabled 101 | // onTriggered: session.updateAllMeasurements() 102 | // } 103 | 104 | onContinuousChanged: { 105 | // Restart the session so the new sampling mode takes effect 106 | //restartAfterStop = true; 107 | //session.cancel(); 108 | //console.log("onContinuousChanged",continuous); 109 | ////console.log("session cont",session. 110 | //session.restart(); 111 | if(session.active){ 112 | session.cancel(); 113 | session.start(continuous); 114 | toolbar.acqusitionDialog.onContinuousModeChanged(continuous); 115 | } 116 | } 117 | 118 | // onDelaySampleCountChanged: { 119 | // dlySmplCntChanged = true; 120 | // } 121 | 122 | // Connections { 123 | // target: session 124 | 125 | // onFinished: { 126 | // if (enabled && restartAfterStop) { 127 | // restartAfterStop = false; 128 | // timer.start() 129 | // return; 130 | // } 131 | 132 | // if (!continuous && repeat && enabled) { 133 | // timer.start(); 134 | // } 135 | // } 136 | 137 | // onDetached: { 138 | // enabled = false; 139 | // continuous = false; 140 | // } 141 | // } 142 | 143 | Repeater { 144 | model: session.devices 145 | Item { 146 | Repeater { 147 | model: modelData.channels 148 | Item { 149 | Connections { 150 | target: modelData 151 | onModeChanged: { 152 | if(continuous) 153 | session.restart(); 154 | } 155 | } 156 | } 157 | } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /qml/DeviceManagerPane.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Layouts 1.0 3 | import QtQml.Models 2.1 4 | import QtQuick.Controls 1.1 5 | import QtQuick.Controls.Styles 1.1 6 | import "jsutils.js" as JSUtils 7 | 8 | ColumnLayout { 9 | id: smpLayout 10 | spacing: 12 11 | 12 | property bool updateNeeded: false 13 | property bool justUpdated: false 14 | property bool firmwareDownloaded: false 15 | function addProgModeDeviceToList() 16 | { 17 | devicesModel.insert(devicesModel.count, 18 | {"name": "[Device In Programming Mode]", 19 | "uid":"N/A", 20 | "firmware_version": "N/A", 21 | "hardware_version": "N/A", 22 | "fw_updt_needed": true && devListView.latestVersion != 'v0.0', 23 | "updt_in_progress": false, 24 | "status": "prog" 25 | }); 26 | } 27 | 28 | function clearProgModeDeviceFromList() 29 | { 30 | for (var i = 0; i < devicesModel.count; i++) { 31 | if (devicesModel.get(i).name === "[Device In Programming Mode]") { 32 | devicesModel.remove(i, 1); 33 | break; 34 | } 35 | } 36 | } 37 | 38 | function programmingModeDeviceExists() 39 | { 40 | var deviceExists = false; 41 | if(session.programmingModeDeviceExists() > 0) 42 | deviceExists = true; 43 | 44 | return deviceExists; 45 | } 46 | 47 | function programmingModeDeviceDetect() 48 | { 49 | var ret; 50 | clearProgModeDeviceFromList(); 51 | ret = programmingModeDeviceExists(); 52 | if (ret) 53 | addProgModeDeviceToList(); 54 | 55 | return ret; 56 | } 57 | 58 | function deviceManagerListFill() { 59 | var showPane = false; 60 | 61 | if (devicesModel.count > 0) { 62 | var n; 63 | var updatingDevice = false; 64 | for (n = 0; n < devicesModel.count; n++) { 65 | if (devicesModel.get(n).updt_in_progress == true) 66 | break; 67 | } 68 | updatingDevice = n < devicesModel.count; 69 | 70 | if (updatingDevice) { 71 | var model = devicesModel.get(n); 72 | var modelCopy = {"name": model.name, 73 | "uid": model.uid, 74 | "firmware_version": model.firmware_version, 75 | "hardware_version": model.hardware_version, 76 | "fw_updt_needed": model.fw_updt_needed, 77 | "updt_in_progress": model.updt_in_progress, 78 | "status": model.status}; 79 | } 80 | devicesModel.clear(); 81 | } 82 | //console.log(programmingModeDeviceDetect()+" detected devices"); 83 | if(programmingModeDeviceDetect()){ 84 | if(!justUpdated){ 85 | updateNeeded = true; 86 | } 87 | justUpdated = false; 88 | //console.log("update needded: " + updateNeeded); 89 | } 90 | for (var i = 0; i < session.devices.length; i++) { 91 | var device = session.devices[i]; 92 | var updt_needed = false; 93 | 94 | if (device.FWVer.indexOf('.') === -1) // The firmware might be so old that won't provide the version in the 'major.minor' format 95 | { 96 | updt_needed = true; 97 | updateNeeded = true; 98 | } 99 | 100 | else if (parseFloat(device.FWVer) < parseFloat(devListView.latestVersion.substring(1))){ 101 | updt_needed = true; 102 | updateNeeded = true; 103 | } 104 | 105 | //console.log("Right before dev insert \n"); 106 | devicesModel.insert(devicesModel.count, 107 | {"name": device.label, 108 | "uid":device.UUID, 109 | "firmware_version": device.FWVer, 110 | "hardware_version": device.HWVer, 111 | "fw_updt_needed": updt_needed && devListView.latestVersion != 'v0.0', 112 | "updt_in_progress": false, 113 | "status": (!updt_needed && (devListView.latestVersion != 'v0.0')) ? 'fw_ok' : "n/a" 114 | }); 115 | 116 | if (updt_needed === true) 117 | showPane = true; 118 | } 119 | 120 | if (updatingDevice) { 121 | modelCopy.updt_in_progress = false; 122 | devicesModel.insert(n, modelCopy); 123 | } 124 | 125 | if (!updatingDevice) 126 | if(programmingModeDeviceDetect()){ 127 | showPane = true; 128 | } 129 | 130 | if (showPane) 131 | deviceMngrVisible = true; 132 | } 133 | 134 | function checkFWversion() 135 | { 136 | JSUtils.checkLatestFw( 137 | function(ver) { 138 | devListView.latestVersion = ver; 139 | }, 140 | function(err) { 141 | if (err === JSUtils.GIT_RATE_LIMIT_EXCEEDED) { 142 | logOutput.appendMessage('Failed to get the latest firmware version. ' + 143 | 'Number of requests to GIT was exceeded. A new request will be possible in an hour.'); 144 | } else { 145 | logOutput.appendMessage('Failed to get the latest firmware version. ' + 146 | 'Check your internet connection and then click the "Refresh" button.'); 147 | } 148 | } 149 | ); // end JSUtils.checkLatestFw() 150 | } 151 | 152 | ToolbarStyle { 153 | Layout.fillWidth: true 154 | Layout.minimumWidth: parent.Layout.minimumWidth 155 | Layout.maximumWidth: parent.Layout.maximumWidth 156 | height: toolbarHeight 157 | } 158 | 159 | Rectangle { 160 | id: devListRefreshBtn 161 | anchors { left: parent.left; 162 | right: parent.right; 163 | leftMargin: 5; 164 | rightMargin: 5 } 165 | height: 25 166 | radius: 4 167 | color: 'grey' 168 | 169 | Rectangle { 170 | anchors.horizontalCenter: parent.horizontalCenter 171 | anchors.verticalCenter: parent.verticalCenter 172 | width: parent.width - 2 173 | height: parent.height - 2 174 | radius: 4 175 | gradient: Gradient { 176 | GradientStop { position: 0.0; color: '#565666' } 177 | GradientStop { position: 0.15; color: '#6a6a7d' } 178 | GradientStop { position: 0.5; color: '#5a5a6a' } 179 | GradientStop { position: 1.0; color: '#585868' } 180 | } 181 | 182 | Text { 183 | x: 5 184 | text: "Refresh Device List" 185 | font.pointSize: 10 186 | color: 'white' 187 | anchors.verticalCenter: parent.verticalCenter 188 | anchors.horizontalCenter: parent.horizontalCenter 189 | } 190 | 191 | MouseArea { 192 | hoverEnabled: true 193 | anchors.fill: parent 194 | onClicked: { 195 | if (devListView.latestVersion == 'v0.0') 196 | checkFWversion(); 197 | deviceManagerListFill(); 198 | logOutput.clearLog(); 199 | } 200 | 201 | onPressed: devListRefreshBtn.color = 'black' 202 | onReleased: devListRefreshBtn.color = 'grey' 203 | } 204 | } 205 | } 206 | 207 | Rectangle { 208 | id: devListView 209 | Layout.fillHeight: true 210 | Layout.fillWidth: true 211 | Layout.minimumWidth: parent.Layout.minimumWidth 212 | Layout.maximumWidth: parent.Layout.maximumWidth 213 | color: 'black' 214 | 215 | property string latestVersion: 'v0.0' 216 | 217 | Component.onCompleted: { 218 | checkFWversion(); 219 | } 220 | 221 | onLatestVersionChanged: { 222 | //console.log("latestVersion changed to: ", latestVersion); 223 | if (latestVersion === 'v0.0') 224 | return; 225 | 226 | deviceManagerListFill(); 227 | JSUtils.getFirmwareURL(function(url) { 228 | //console.log("LOG URL: ", url); 229 | session.downloadFromUrl(url); 230 | }); 231 | } 232 | 233 | ListModel { 234 | id: devicesModel 235 | property variant states: { 'ok': 'Succesfully updated. Disconnect device.', 236 | 'error': 'Failed to load firmware.', 237 | 'prog': 'In programming mode.', 238 | 'fw_ok': 'Firmware is up to date.', 239 | 'n/a': '' } 240 | property variant statesColor: { 'ok': 'green', 241 | 'error': 'red', 242 | 'prog': 'blue', 243 | 'fw_ok': 'white', 244 | 'n/a': 'white' } 245 | } 246 | 247 | Component { 248 | id: devDelegate 249 | 250 | Rectangle { 251 | anchors { left: parent.left; right: parent.right; 252 | leftMargin: 15; rightMargin: 15} 253 | height: 80 254 | color: 'black' 255 | Column { 256 | height: parent.height 257 | width: parent.width 258 | 259 | Item { 260 | width: parent.width 261 | height: 20 262 | Text { text: "Device: " + name; 263 | font.pointSize: 10; 264 | color: 'white'; 265 | anchors.verticalCenter: parent.verticalCenter} 266 | } 267 | Item { 268 | width: parent.width 269 | height: 20 270 | Text { text: "Serial Number: " + uid; 271 | font.pointSize: 10; 272 | color: 'white'; 273 | anchors.verticalCenter: parent.verticalCenter} 274 | } 275 | Item { 276 | width: parent.width 277 | height: 20 278 | Row { 279 | anchors.fill: parent 280 | spacing: 10 281 | Text { text: "Firmware Version: " + firmware_version; 282 | font.pointSize: 10; 283 | color: 'white'; 284 | anchors.verticalCenter: parent.verticalCenter} 285 | Rectangle { 286 | id: devUpdateBtn 287 | height: parent.height 288 | width: 115 289 | radius: 4 290 | color: 'black' 291 | visible: fw_updt_needed === true 292 | 293 | Text { 294 | x: 5 295 | text: "Update Firmware" 296 | font.pointSize: 10 297 | color: 'steelblue' 298 | anchors.verticalCenter: parent.verticalCenter 299 | } 300 | } 301 | } 302 | } 303 | Item { 304 | width: parent.width 305 | height: 20 306 | visible: false 307 | Text { text: "Hardware Version: " + hardware_version; 308 | font.pointSize: 10; 309 | color: 'white'; 310 | anchors.verticalCenter: parent.verticalCenter} 311 | } 312 | Item { 313 | width: parent.width 314 | height: 20 315 | visible: status !== 'n/a' 316 | Row { 317 | Text { text: "Status: "; 318 | font.pointSize: 10; 319 | color: 'white'; 320 | anchors.verticalCenter: parent.verticalCenter} 321 | Text { text: devicesModel.states[status] 322 | font.pointSize: 10; 323 | color: devicesModel.statesColor[status]; 324 | anchors.verticalCenter: parent.verticalCenter} 325 | } 326 | } 327 | } 328 | } 329 | } 330 | 331 | ListView { 332 | id: view 333 | anchors.fill: parent 334 | 335 | orientation: ListView.Vertical 336 | interactive: false 337 | spacing: 10 338 | 339 | model: devicesModel 340 | delegate: devDelegate 341 | 342 | Component.onCompleted: { 343 | deviceManagerListFill(); 344 | } 345 | } 346 | 347 | Connections { 348 | target: session 349 | onDevicesChanged: { 350 | deviceManagerListFill(); 351 | } 352 | onFirmwareDownloaded:{ 353 | firmwareDownloaded = true; 354 | } 355 | } 356 | } 357 | Rectangle { 358 | id: updateBtn 359 | anchors { right: parent.right; 360 | bottom: logCleanBtn.top; 361 | bottomMargin: 5; 362 | rightMargin: 5 } 363 | height: 25 364 | width: 145 365 | radius: 4 366 | color: 'grey' 367 | visible: updateNeeded === true && firmwareDownloaded === true 368 | 369 | Rectangle { 370 | anchors.horizontalCenter: parent.horizontalCenter 371 | anchors.verticalCenter: parent.verticalCenter 372 | width: parent.width - 2 373 | height: parent.height - 2 374 | radius: 4 375 | 376 | gradient: Gradient { 377 | GradientStop { position: 0.0; color: '#565666' } 378 | GradientStop { position: 0.15; color: '#6a6a7d' } 379 | GradientStop { position: 0.5; color: '#5a5a6a' } 380 | GradientStop { position: 1.0; color: '#585868' } 381 | } 382 | 383 | Text { 384 | x: 5 385 | text: "Update Firmware" 386 | font.pointSize: 10 387 | color: 'white' 388 | anchors.verticalCenter: parent.verticalCenter 389 | anchors.horizontalCenter: parent.horizontalCenter 390 | } 391 | 392 | MouseArea { 393 | hoverEnabled: true 394 | anchors.fill: parent 395 | onClicked: { 396 | var firmwareFilePath = session.getTmpPathForFirmware() + "/firmware.bin"; 397 | var ret; 398 | 399 | session.closeAllDevices(); 400 | devicesModel.clear(); 401 | justUpdated = true; 402 | updateNeeded = false; 403 | ret = session.flash_firmware(firmwareFilePath); 404 | 405 | if (ret.length === 0) { 406 | logOutput.appendMessage("All devices were succesfully updated. Disconnect devices"); 407 | } else { 408 | logOutput.appendMessage(ret); 409 | } 410 | } 411 | 412 | onPressed: updateBtn.color = 'black' 413 | onReleased: updateBtn.color = 'grey' 414 | } 415 | } 416 | } 417 | 418 | 419 | Rectangle { 420 | id: logCleanBtn 421 | anchors { right: parent.right; 422 | bottom: logOutput.top; 423 | bottomMargin: 5; 424 | rightMargin: 5 } 425 | height: 25 426 | width: 85 427 | radius: 4 428 | color: 'grey' 429 | 430 | Rectangle { 431 | anchors.horizontalCenter: parent.horizontalCenter 432 | anchors.verticalCenter: parent.verticalCenter 433 | width: parent.width - 2 434 | height: parent.height - 2 435 | radius: 4 436 | gradient: Gradient { 437 | GradientStop { position: 0.0; color: '#565666' } 438 | GradientStop { position: 0.15; color: '#6a6a7d' } 439 | GradientStop { position: 0.5; color: '#5a5a6a' } 440 | GradientStop { position: 1.0; color: '#585868' } 441 | } 442 | 443 | Text { 444 | x: 5 445 | text: "Clean Log" 446 | font.pointSize: 10 447 | color: 'white' 448 | anchors.verticalCenter: parent.verticalCenter 449 | anchors.horizontalCenter: parent.horizontalCenter 450 | } 451 | 452 | MouseArea { 453 | hoverEnabled: true 454 | anchors.fill: parent 455 | onClicked: { 456 | logOutput.clearLog(); 457 | } 458 | 459 | onPressed: logCleanBtn.color = 'black' 460 | onReleased: logCleanBtn.color = 'grey' 461 | } 462 | } 463 | } 464 | 465 | TextArea { 466 | property int logId: 0 467 | 468 | id: logOutput 469 | readOnly: true; 470 | Layout.fillWidth: true 471 | Layout.minimumWidth: parent.Layout.minimumWidth 472 | Layout.maximumWidth: parent.Layout.maximumWidth 473 | backgroundVisible: false 474 | selectByKeyboard: true 475 | selectByMouse: true 476 | implicitHeight: 70 477 | 478 | style: TextAreaStyle { 479 | textColor: "#fff" 480 | selectionColor: "steelblue" 481 | selectedTextColor: "#eee" 482 | backgroundColor: "#eee" 483 | } 484 | 485 | TextEdit { 486 | id: textEdit 487 | } 488 | 489 | function appendMessage(message) 490 | { 491 | logOutput.append(logId.toString() + ': ' + message + '\n'); 492 | logId ++; 493 | } 494 | function clearLog() 495 | { 496 | logOutput.cursorPosition = 0; 497 | logOutput.text = ""; 498 | logId = 0; 499 | } 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /qml/DeviceRow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Window 2.0 3 | import QtQuick.Layouts 1.0 4 | 5 | Rectangle { 6 | property var device 7 | property alias channelRepeater: channelRepeater 8 | property var currentIndex 9 | color: '#222' 10 | 11 | Text { 12 | text: device.label 13 | color: 'white' 14 | rotation: -90 15 | transformOrigin: Item.TopLeft 16 | font.pixelSize: 18/session.devices.length 17 | y: width + timelinePane.spacing + 8 18 | x: (timelinePane.spacing - height) / 2 19 | } 20 | 21 | MouseArea { 22 | anchors.fill: parent 23 | onClicked: { 24 | session.devices[currentIndex].blinkLeds() 25 | } 26 | } 27 | 28 | ColumnLayout { 29 | anchors.fill: parent 30 | anchors.leftMargin: timelinePane.spacing 31 | spacing: 0 32 | 33 | Repeater { 34 | id: channelRepeater 35 | model: device.channels 36 | 37 | ChannelRow { 38 | Layout.fillWidth: true 39 | Layout.fillHeight: true 40 | 41 | channel: model 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /qml/DragDot.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | 3 | Item { 4 | id: dot 5 | width: 0 6 | height: 0 7 | default property alias content: rect.children 8 | 9 | property real value 10 | property var color 11 | property bool filled 12 | 13 | property var dragOn 14 | signal drag(variant pos) 15 | signal pressed(variant mouse) 16 | signal released() 17 | 18 | property bool label: true 19 | 20 | Text { 21 | anchors.verticalCenter: parent.verticalCenter 22 | anchors.right: parent.left 23 | anchors.rightMargin: 12 24 | text: label ? value.toFixed(4):"" 25 | color: 'white' 26 | visible: parent.x <= 50 ? false : true; 27 | } 28 | 29 | Text { 30 | anchors.verticalCenter: parent.verticalCenter 31 | anchors.left: parent.right 32 | anchors.leftMargin: 12 33 | text: label ? value.toFixed(4):"" 34 | color: 'white' 35 | visible: parent.x <= 50 ? true : false; 36 | } 37 | 38 | Rectangle { 39 | id: rect 40 | width: 12 41 | height: width 42 | radius: width/2 43 | color: filled ? parent.color : "black" 44 | border.width: filled ? 0 : 3 45 | border.color: parent.color 46 | x: -height/2 47 | y: -width/2 48 | 49 | MouseArea { 50 | id: mouse_area 51 | visible: !!dragOn 52 | anchors.fill: parent 53 | anchors.margins: -4 54 | onPressed: { 55 | dot.pressed(mouse) 56 | } 57 | onPositionChanged: { 58 | var pos = this.mapToItem(dragOn, mouse.x, mouse.y) 59 | pos.modifiers = mouse.modifiers 60 | dot.drag(pos, x, y) 61 | } 62 | onReleased: { dot.released() } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /qml/FileDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.2 2 | import QtQuick.Dialogs 1.0 3 | 4 | Item { 5 | id: dialogItem 6 | 7 | FileDialog { 8 | id: fileDialog 9 | property alias visible: toolbar.dialogVisible 10 | 11 | title: "Please choose a file" 12 | onAccepted: { 13 | fileio.write(fileDialog.fileUrls, "Ask Ubuntu"); 14 | } 15 | onRejected: { 16 | console.log("Canceled") 17 | } 18 | Component.onCompleted: visible = true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /qml/OverlayConstant.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | 3 | MouseArea { 4 | anchors.top: parent.top 5 | anchors.horizontalCenter: parent.right 6 | anchors.bottom: parent.bottom 7 | width: 16 8 | 9 | cursorShape: Qt.SizeVerCursor 10 | 11 | function set(mouse) { 12 | parent.parent.updateMode() 13 | var out = Math.min(Math.max(axes.pxToY(mouse.y), signal.min), signal.max); 14 | if (mouse.modifiers & Qt.AltModifier) { 15 | signal.src.v1 = axes.snapy(out) 16 | } 17 | else { 18 | signal.src.v1 = out 19 | } 20 | } 21 | 22 | onPositionChanged: set(mouse) 23 | onClicked: set(mouse) 24 | 25 | DragDot { 26 | id: dragDot 27 | anchors.horizontalCenter: parent.horizontalCenter 28 | y: axes.yToPxClamped(value) 29 | label: false 30 | value: signal.isOutput ? signal.src.v1 : signal.measurement 31 | filled: signal.isOutput 32 | color: "blue" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /qml/OverlayPeriodic.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | 3 | Item { 4 | id: overlay 5 | anchors.fill: parent 6 | 7 | property real sampleTick: 1/controller.sampleRate 8 | property real period: signal.src.period * sampleTick 9 | property real phase: ((signal.src.phase + controller.delaySampleCount) % signal.src.period) * sampleTick 10 | 11 | function phaseZeroNearCenter() { 12 | if (dragging && relX != null) return relX 13 | var center = (xaxis.visibleMin + xaxis.visibleMax) / 2; 14 | var offset = Math.round((center + phase) / period); 15 | return offset * period - phase; 16 | } 17 | function periodDivisor() { 18 | return ( (signal.src.src == 'square' || signal.src.src == 'sawtooth' || signal.src.src == 'stairstep') ? 1: 2) 19 | } 20 | 21 | property var dragging: null 22 | property var relX: 0 23 | 24 | function dragStart(id) { 25 | dragging = id 26 | relX = null 27 | relX = phaseZeroNearCenter() 28 | } 29 | 30 | function dragEnd() { 31 | dragging = null; 32 | } 33 | 34 | function constrainInterval(value, center, radius){ 35 | if (Math.abs(value) < radius){ 36 | if (value < center){ value = -radius; } 37 | if (value >= center) { value = radius; } 38 | } 39 | return value; 40 | } 41 | 42 | function mapY(pos) { 43 | var y = Math.min(Math.max(axes.pxToY(pos.y), signal.min), signal.max); 44 | if (pos.modifiers & Qt.AltModifier) { 45 | y = axes.snapy(y); 46 | } 47 | return y; 48 | } 49 | 50 | DragDot { 51 | id: d1 52 | value: signal.src.v1 53 | filled: signal.isOutput 54 | color: "blue" 55 | 56 | x: constrainValue(xaxis.xToPx(phaseZeroNearCenter() + period/periodDivisor()), xaxis.xToPx(xaxis.visibleMin), xaxis.xToPx(xaxis.visibleMax)) 57 | y: axes.yToPxClamped(value) 58 | 59 | dragOn: overlay 60 | onPressed: overlay.dragStart('d1') 61 | onReleased: { 62 | signal.src.period = constrainInterval(signal.src.period, controller.sampleRate / controller.minOutSignalFreq, controller.sampleRate / controller.maxOutSignalFreq); 63 | overlay.dragEnd(); 64 | } 65 | onDrag: { 66 | var lx = xaxis.pxToX(Math.max(0, Math.min(pos.x, xaxis.width))); 67 | var oldPeriod = signal.src.period; 68 | var newPeriod = (lx - relX) / sampleTick * periodDivisor(); 69 | if (pos.modifiers & Qt.ControlModifier) { 70 | newPeriod = axes.snapx(newPeriod) 71 | if ( newPeriod == 0 ) { 72 | newPeriod = 1 73 | } 74 | } 75 | signal.src.period = constrainInterval(newPeriod, controller.sampleRate / controller.minOutSignalFreq, controller.sampleRate / controller.maxOutSignalFreq); 76 | signal.src.v1 = overlay.mapY(pos); 77 | // Adjust phase so the signal stays in the same position relative to the other dot 78 | signal.src.phase = -relX/sampleTick - controller.delaySampleCount; 79 | } 80 | } 81 | 82 | DragDot { 83 | id: d2 84 | value: signal.src.v2 85 | filled: signal.isOutput 86 | color: "blue" 87 | 88 | x: constrainValue(xaxis.xToPx(phaseZeroNearCenter()), xaxis.xToPx(xaxis.visibleMin), xaxis.xToPx(xaxis.visibleMax)) 89 | y: axes.yToPxClamped(value) 90 | 91 | dragOn: overlay 92 | onPressed: overlay.dragStart('d2') 93 | onReleased: overlay.dragEnd() 94 | onDrag: { 95 | var lx = xaxis.pxToX(Math.max(0, Math.min(pos.x, xaxis.width))); 96 | relX = lx; 97 | if (pos.modifiers & Qt.ControlModifier) { 98 | signal.src.phase = axes.snapx(-relX/sampleTick) 99 | } else { 100 | signal.src.phase = -relX/sampleTick - controller.delaySampleCount; 101 | } 102 | signal.src.v2 = overlay.mapY(pos); 103 | } 104 | } 105 | 106 | DragDot { 107 | id: d3 108 | value: Math.round(100*signal.src.duty) 109 | filled: signal.isOutput 110 | color: "blue" 111 | visible: signal.src.src == 'square' 112 | x: constrainValue(xaxis.xToPx(phaseZeroNearCenter() + signal.src.duty*period), xaxis.xToPx(xaxis.visibleMin), xaxis.xToPx(xaxis.visibleMax)) 113 | y: axes.yToPxClamped((signal.src.v2 + signal.src.v1)/2) 114 | z: -1 115 | 116 | dragOn: overlay 117 | onPressed: overlay.dragStart('d3') 118 | onReleased: overlay.dragEnd() 119 | onDrag: { 120 | var lx = xaxis.pxToX(Math.max(0, Math.min(pos.x, xaxis.width))); 121 | var duty = (lx - relX) / period 122 | if (pos.modifiers & Qt.ControlModifier) { 123 | duty = Math.round(duty*20)/20 124 | } 125 | signal.src.duty = Math.min(Math.max(duty, 0), 1); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /qml/PlotPane.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Layouts 1.0 3 | import QtQuick.Controls 1.1 4 | 5 | ColumnLayout { 6 | spacing: 32 7 | id: xyplot 8 | Layout.minimumWidth: 0.3*parent.width 9 | Layout.maximumWidth: 0.6*parent.width 10 | 11 | property alias devRep: dev_rep 12 | 13 | ToolbarStyle { 14 | Layout.fillWidth: true 15 | Layout.minimumWidth: parent.Layout.minimumWidth 16 | Layout.maximumWidth: parent.Layout.maximumWidth 17 | height: toolbarHeight 18 | } 19 | 20 | Repeater { 21 | id: dev_rep 22 | model: session.devices 23 | 24 | Repeater { 25 | model: modelData.channels 26 | 27 | XYPlot { 28 | // if mode == SIMV, current is independent variable 29 | // if mode == SVMI (or Hi-Z), voltage is independent variable 30 | Layout.minimumWidth: parent.width 31 | Layout.maximumWidth: parent.width 32 | isignal: modelData.signals[1] 33 | vsignal: modelData.signals[0] 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /qml/StyleSlider.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls.Styles 1.1 3 | 4 | SliderStyle { 5 | groove: Rectangle { 6 | implicitWidth: 200 7 | implicitHeight: 8 8 | gradient: Gradient { 9 | GradientStop { position: 1.0; color: Qt.rgba(1,1,1,0.08)} 10 | GradientStop { position: 0.0; color: Qt.rgba(0,0,0,0.0)} 11 | } 12 | radius: 8 13 | } 14 | handle: Rectangle { 15 | 16 | color: control.pressed ? "#858484" : "#4E4E4E" 17 | border.color: "#4E4E4E" 18 | border.width: 2 19 | width: 20 20 | height: 20 21 | radius: 8 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /qml/TimelineFlickable.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | 3 | MouseArea { 4 | z: -1 5 | property real boundMin: 0 6 | property real boundMax: 1 7 | 8 | property real maxScale: 100000000 9 | property real xscale: 100 // pixels per unit 10 | 11 | readonly property real visibleMin: boundMin + timeline_flickable.contentX / xscale 12 | readonly property real visibleMax: boundMin + (timeline_flickable.contentX + timeline_flickable.width) / xscale 13 | 14 | property alias timelineflickable: timeline_flickable 15 | 16 | function xToPx(x) { 17 | return xscale * (x - boundMin) - timeline_flickable.contentX 18 | } 19 | 20 | function pxToX(px) { 21 | return boundMin + (timeline_flickable.contentX + px) / xscale 22 | } 23 | 24 | function setVisible(min, max) { 25 | xscale = timeline_flickable.width / (max - min) 26 | timeline_flickable.contentX = xscale*(min - boundMin) 27 | } 28 | 29 | function setBounds(min, max) { 30 | boundMin = min 31 | boundMax = max 32 | } 33 | 34 | onBoundMaxChanged: { 35 | if (boundMax < visibleMax || timeline_flickable.atXEnd) { 36 | setVisible(0, boundMax) 37 | } 38 | } 39 | 40 | onWheel: { 41 | var s = Math.pow(1.15, wheel.angleDelta.y/120) 42 | var oldScale = xscale 43 | var minScale = timeline_flickable.width/(boundMax - boundMin) 44 | xscale = Math.min(Math.max(xscale*s, minScale), maxScale) 45 | 46 | timeline_flickable.contentX = (xscale / oldScale) * (timeline_flickable.contentX + wheel.x) - wheel.x 47 | timeline_flickable.returnToBounds() 48 | } 49 | 50 | onWidthChanged: { 51 | var minScale = timeline_flickable.width/(boundMax - boundMin) 52 | xscale = Math.min(Math.max(xscale, minScale), maxScale) 53 | timeline_flickable.returnToBounds() 54 | } 55 | 56 | Flickable { 57 | id: timeline_flickable 58 | 59 | flickableDirection: Flickable.HorizontalFlick 60 | contentWidth: parent.xscale * (parent.boundMax - parent.boundMin) 61 | 62 | anchors.fill: parent 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /qml/TimelineHeader.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | 3 | Rectangle { 4 | id: timeline 5 | anchors.top: parent.top 6 | color: "#424242" 7 | clip: true 8 | 9 | gradient: Gradient { 10 | GradientStop { position: 0.0; color: '#404040' } 11 | GradientStop { position: 0.15; color: '#5a5a5a' } 12 | GradientStop { position: 0.5; color: '#444444' } 13 | GradientStop { position: 1.0; color: '#424242' } 14 | } 15 | 16 | property var xaxis 17 | property real min_spacing: 70 18 | property real pow: Math.floor(Math.log(min_spacing * 100 / xaxis.xscale) / Math.LN10) 19 | property real majorStep: Math.pow(10, pow) 20 | property real step: majorStep / 10 21 | property real start: Math.floor(xaxis.visibleMin / step) 22 | 23 | property real unitPow: (pow - 2) % 3 + 1 24 | property real unitScale: Math.pow(10, unitPow) 25 | property string unit: unitFor(pow) 26 | 27 | function unitFor(pow) { 28 | switch (Math.floor(pow / 3) * 3) { 29 | case 0: return ' s' 30 | case -3: return ' ms' 31 | case -6: return ' us' 32 | case -9: return ' ns' 33 | case -12: return ' ps' 34 | default: return '' 35 | } 36 | } 37 | 38 | Repeater { 39 | model: timeline.width / min_spacing + 1 40 | Rectangle { 41 | property real n: start + index 42 | property real xval: n * step 43 | property real rel: ((n % 10 + 10) % 10) 44 | property bool isMajor: rel == 0 45 | property real relVal: rel * unitScale 46 | 47 | visible: xval >= xaxis.boundMin && xval <= xaxis.boundMax 48 | 49 | x: xaxis.xToPx(xval) 50 | y: timeline.height-height 51 | width: 1 52 | height: isMajor ? 40 : 12 53 | 54 | Text { 55 | text: isMajor ? (xval.toFixed(Math.max(-pow, 0)) + ' s') : ('+' + relVal.toFixed((unitPow==-1)?1:0) + unit) 56 | color: 'white' 57 | anchors.left: parent.left 58 | anchors.leftMargin: isMajor ? 6 : -4 59 | anchors.bottomMargin: isMajor ? -12 : -1 60 | anchors.bottom: parent.top 61 | } 62 | } 63 | } 64 | 65 | MouseArea { 66 | id: timelineheader_mouse_area 67 | anchors.fill: parent 68 | 69 | property var zoomParams 70 | acceptedButtons: Qt.RightButton 71 | onPressed: { 72 | if (mouse.button == Qt.RightButton) { 73 | zoomParams = { 74 | firstX : mouse.x, 75 | prevX : mouse.x, 76 | } 77 | } else { 78 | mouse.accepted = false; 79 | } 80 | } 81 | onReleased: { 82 | zoomParams = null 83 | } 84 | onPositionChanged: { 85 | if (zoomParams) { 86 | var delta = (mouse.x - zoomParams.prevX) 87 | zoomParams.prevX = mouse.x 88 | var s = Math.pow(1.005, delta) 89 | var oldScale = xaxis.xscale 90 | var minScale = xaxis.timelineflickable.width/(xaxis.boundMax - xaxis.boundMin) 91 | xaxis.xscale = Math.min(Math.max(xaxis.xscale*s, minScale), xaxis.maxScale) 92 | 93 | xaxis.timelineflickable.contentX = (xaxis.xscale / oldScale) * (xaxis.timelineflickable.contentX + zoomParams.firstX) - zoomParams.firstX 94 | xaxis.timelineflickable.returnToBounds() 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /qml/Toolbar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Layouts 1.0 3 | import QtQuick.Controls 1.0 4 | import QtQuick.Controls.Styles 1.1 5 | import QtQuick.Dialogs 1.0 6 | import QtQuick.Dialogs 1.2 7 | import QtGraphicalEffects 1.0 8 | import "dataexport.js" as CSVExport 9 | import "sesssave.js" as StateSave 10 | 11 | ToolbarStyle { 12 | ExclusiveGroup { 13 | id: timeGroup 14 | } 15 | 16 | property alias repeatedSweep: repeatedSweepItem.checked 17 | property alias plotsVisible: plotsVisibleItem.checked 18 | property alias contentVisible: contentVisibleItem.checked 19 | property alias deviceMngrVisible: deviceMngrVisibleItem.checked 20 | property alias colorDialog: sessColorDialog 21 | property alias acqusitionDialog: sessAcqSettDialog 22 | 23 | AcquisitionSettingsDialog { 24 | id: sessAcqSettDialog 25 | } 26 | 27 | FileDialog { 28 | id: dataDialog 29 | selectExisting: false 30 | title: "Please enter a location to save your data." 31 | nameFilters: [ "CSV files (*.csv)", "All files (*)" ] 32 | onAccepted: { CSVExport.saveData(dataDialog.fileUrls[0]);} 33 | } 34 | FileDialog { 35 | id: sessSaveDialog 36 | selectExisting: false 37 | title: "Please enter a location to save your session." 38 | nameFilters: [ "JSON files (*.json)", "All files (*)" ] 39 | onAccepted: { fileio.writeByURI(sessSaveDialog.fileUrls[0], JSON.stringify(StateSave.saveState(), 0, 2));} 40 | } 41 | FileDialog { 42 | id: sessRestoreDialog 43 | selectExisting: true 44 | title: "Please select a session to restore." 45 | nameFilters: [ "JSON files (*.json)", "All files (*)" ] 46 | onAccepted: { StateSave.restoreState(JSON.parse(fileio.readByURI(sessRestoreDialog.fileUrls[0])));} 47 | } 48 | 49 | ColorControlDialog { 50 | id: sessColorDialog 51 | } 52 | 53 | Button { 54 | tooltip: "Menu" 55 | Layout.fillHeight: true 56 | style: btnStyle 57 | 58 | menu: Menu { 59 | 60 | MenuItem { 61 | id: repeatedSweepItem 62 | text: "Repeated sweep" 63 | checkable: true 64 | checked: true 65 | } 66 | 67 | Menu { 68 | title: "Sample Time" 69 | MenuItem { exclusiveGroup: timeGroup; checkable: true; checked: controller.sampleTime == 0.01 ? true : false 70 | onTriggered: controller.sampleTime = 0.01; text: '10 ms' } 71 | MenuItem { exclusiveGroup: timeGroup; checkable: true; checked: controller.sampleTime == 0.1 ? true : false 72 | onTriggered: controller.sampleTime = 0.1; text: '100 ms' } 73 | MenuItem { exclusiveGroup: timeGroup; checkable: true; checked: controller.sampleTime == 1 ? true : false 74 | onTriggered: controller.sampleTime = 1; text: '1 s' } 75 | MenuItem { exclusiveGroup: timeGroup; checkable: true; checked: controller.sampleTime == 10 ? true : false 76 | onTriggered: controller.sampleTime = 10; text: '10 s' } 77 | } 78 | 79 | MenuItem { 80 | id: dataLoggingItem 81 | text: "Data logging" 82 | checkable: true 83 | checked: false 84 | enabled: controller.sampleTime == 0.1 || controller.sampleTime == 0.01 ? false : true 85 | onTriggered: session.onLoggingChanged() 86 | } 87 | 88 | MenuItem { 89 | id: plotsVisibleItem 90 | text: "X-Y Plots" 91 | checkable: true 92 | } 93 | 94 | MenuItem { 95 | id: contentVisibleItem 96 | text: "About" 97 | checkable: true 98 | } 99 | 100 | MenuItem { 101 | id: deviceMngrVisibleItem 102 | text: "Device Manager" 103 | checkable: true 104 | } 105 | 106 | MenuSeparator{} 107 | MenuItem { 108 | id: acquisVisibleItem 109 | text: "Acqusition Settings" 110 | onTriggered: sessAcqSettDialog.visible = true 111 | } 112 | MenuItem { 113 | id: dataSaveVisibleItem 114 | text: "Export Data" 115 | onTriggered: dataDialog.visible = true 116 | } 117 | MenuItem { 118 | id: sessionSaveVisibleItem 119 | text: "Save Session" 120 | onTriggered: sessSaveDialog.visible = true 121 | } 122 | MenuItem { 123 | id: sessionRestoreVisibleItem 124 | text: "Restore Session" 125 | onTriggered: sessRestoreDialog.visible = true 126 | } 127 | MenuItem { 128 | id: colorControlVisibleItem 129 | text: "Display Settings" 130 | onTriggered: sessColorDialog.visible = true 131 | } 132 | 133 | MenuSeparator{} 134 | MenuItem { text: "Exit"; onTriggered: Qt.quit() } 135 | } 136 | iconSource: 'qrc:/icons/gear.png' 137 | } 138 | 139 | Button { 140 | tooltip: "Start" 141 | Layout.fillHeight: true 142 | Layout.alignment: Qt.AlignRight 143 | style: btnStyle 144 | iconSource: (controller.enabled & (session.availableDevices > 0)) ? 'qrc:/icons/pause.png' : 'qrc:/icons/play.png' 145 | 146 | onClicked: { 147 | if (session.availableDevices > 0) { 148 | controller.toggle() 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /qml/ToolbarStyle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | import QtQuick.Controls.Styles 1.1 5 | 6 | Rectangle { 7 | gradient: Gradient { 8 | GradientStop { position: 0.0; color: '#565666' } 9 | GradientStop { position: 0.15; color: '#6a6a7d' } 10 | GradientStop { position: 0.5; color: '#5a5a6a' } 11 | GradientStop { position: 1.0; color: '#585868' } 12 | } 13 | 14 | property alias btnStyle: btnStyle 15 | Component { 16 | id: btnStyle 17 | ButtonStyle { 18 | background: Rectangle { 19 | implicitWidth: 56 20 | opacity: control.pressed ? 0.3 : control.checked ? 0.2 : 0.01 21 | color: 'white' 22 | } 23 | } 24 | } 25 | 26 | default property alias data: inner.data 27 | RowLayout { 28 | anchors.fill: parent 29 | id: inner 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /qml/XYPlot.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Layouts 1.0 3 | import QtQuick.Controls 1.0 4 | import Plot 1.0 5 | 6 | 7 | Item { 8 | // keep plots slightly smaller than half the height 9 | Layout.preferredHeight: (parent.parent.height/2)-80 10 | property var vsignal 11 | property var isignal 12 | property var xsignal: vsignal; 13 | property var ysignal: isignal; 14 | property int ygridticks: axes.ygridticks; 15 | property int xgridticks: axes.xgridticks; 16 | 17 | Axes { 18 | id: axes 19 | 20 | anchors.fill: parent 21 | anchors.leftMargin: 94 22 | anchors.rightMargin: 52 23 | anchors.topMargin: 42 24 | anchors.bottomMargin: 32 25 | 26 | xmin: xsignal.min 27 | xmax: xsignal.max 28 | ymin: ysignal.min 29 | ymax: ysignal.max 30 | yleft: true 31 | yright: false 32 | xbottom: true 33 | 34 | // Shift + scroll for Y-axis zoom 35 | MouseArea { 36 | anchors.fill: parent 37 | onPressed: { 38 | mouse.accepted = false 39 | } 40 | 41 | onWheel: { 42 | if (wheel.modifiers & Qt.ShiftModifier) { 43 | var s = Math.pow(1.15, -wheel.angleDelta.y/120); 44 | var y = axes.pxToY(wheel.y); 45 | 46 | if (axes.ymax - axes.ymin < ysignal.resolution * ygridticks * 8 && s < 1) return; 47 | 48 | axes.ymin = Math.max(y - s * (y - axes.ymin), ysignal.min); 49 | axes.ymax = Math.min(y - s * (y - axes.ymax), ysignal.max); 50 | } 51 | else { 52 | var s = Math.pow(1.15, -wheel.angleDelta.y/120); 53 | var x = axes.pxToX(wheel.x); 54 | 55 | if (axes.xmax - axes.xmin < xsignal.resolution * xgridticks && s < 1) return; 56 | axes.xmin = Math.max(x - s * (x - axes.xmin), xsignal.min); 57 | axes.xmax = Math.min(x - s * (x - axes.xmax), xsignal.max); 58 | } 59 | } 60 | } 61 | gridColor: window.gridAxesColor 62 | textColor: '#fff' 63 | 64 | Rectangle { 65 | id: axesBackground 66 | anchors.fill: parent 67 | color: window.xyplotColor 68 | z: -1 69 | } 70 | 71 | PhosphorRender { 72 | id: line 73 | anchors.fill: parent 74 | clip: true 75 | 76 | xBuffer: xsignal.buffer 77 | buffer: ysignal.buffer 78 | pointSize: Math.min(25, Math.max(2, axes.xscale/session.sampleRate*3) * window.dotSizePlots * 10) 79 | color: ysignal.label == 'Current' ? window.dotPlotsCurrent : window.dotPlotsVoltage //Qt.rgba(0.2, 0.2, 0.03, 1) : Qt.rgba(0.03, 0.3, 0.03, 1) 80 | xmin: axes.xmin 81 | xmax: axes.xmax 82 | ymin: axes.ymin 83 | ymax: axes.ymax 84 | } 85 | } 86 | 87 | Item { 88 | id: vertAxisScale 89 | 90 | anchors.top: parent.top 91 | anchors.bottom: parent.bottom 92 | anchors.left: parent.left 93 | anchors.bottomMargin: axes.anchors.bottomMargin 94 | width: axes.anchors.leftMargin 95 | 96 | MouseArea { 97 | anchors.fill: parent 98 | property var zoomParams 99 | acceptedButtons: Qt.RightButton 100 | onPressed: { 101 | if (mouse.button == Qt.RightButton) { 102 | zoomParams = { 103 | firstY : mouse.y, 104 | prevY : mouse.y, 105 | } 106 | } else { 107 | mouse.accepted = false 108 | } 109 | } 110 | onReleased: { 111 | zoomParams = null; 112 | } 113 | onPositionChanged: { 114 | if (zoomParams) { 115 | var delta = (mouse.y - zoomParams.prevY) 116 | zoomParams.prevY = mouse.y 117 | var s = Math.pow(1.01, delta) 118 | var y = axes.pxToY((zoomParams.firstY)) 119 | 120 | if (axes.ymax - axes.ymin < ysignal.resolution * ygridticks * 8 && s < 1) return; 121 | 122 | axes.ymin = Math.max(y - s * (y - axes.ymin), ysignal.min); 123 | axes.ymax = Math.min(y - s * (y - axes.ymax), ysignal.max); 124 | } 125 | } 126 | } 127 | } 128 | 129 | Item { 130 | id: labelVert 131 | anchors.left: parent.left 132 | anchors.leftMargin: 43 133 | anchors.top: parent.top 134 | anchors.topMargin: 0 135 | Text { 136 | font.pixelSize: 20 137 | color: '#fff' 138 | text: ysignal.label == 'Current' ? '[A]' : '[V]' 139 | } 140 | } 141 | 142 | Item { 143 | id: horizAxisScale 144 | 145 | anchors.bottom: parent.bottom 146 | anchors.left: parent.left 147 | anchors.right: parent.right 148 | anchors.leftMargin: axes.anchors.leftMargin 149 | anchors.rightMargin: axes.anchors.rightMargin 150 | height: axes.anchors.bottomMargin 151 | 152 | MouseArea { 153 | anchors.fill: parent 154 | property var zoomParams 155 | acceptedButtons: Qt.RightButton 156 | onPressed: { 157 | if (mouse.button == Qt.RightButton) { 158 | zoomParams = { 159 | firstX : mouse.x, 160 | prevX : mouse.x, 161 | } 162 | } else { 163 | mouse.accepted = false 164 | } 165 | } 166 | onReleased: { 167 | zoomParams = null; 168 | } 169 | onPositionChanged: { 170 | if (zoomParams) { 171 | var delta = -(mouse.x - zoomParams.prevX) 172 | zoomParams.prevX = mouse.x 173 | var s = Math.pow(1.01, delta) 174 | var x = axes.pxToX((zoomParams.firstX)) 175 | 176 | if (axes.xmax - axes.xmin < xsignal.resolution * xgridticks && s < 1) return; 177 | 178 | axes.xmin = Math.max(x - s * (x - axes.xmin), xsignal.min); 179 | axes.xmax = Math.min(x - s * (x - axes.xmax), xsignal.max); 180 | } 181 | } 182 | } 183 | } 184 | 185 | Item { 186 | id: labelHoriz 187 | anchors.right: parent.right 188 | anchors.rightMargin: 32 189 | anchors.bottom: parent.bottom 190 | anchors.bottomMargin: 30 191 | Text { 192 | font.pixelSize: 20 193 | color: '#fff' 194 | text: xsignal.label == 'Current' ? '[A]' : '[V]' 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /qml/dataexport.js: -------------------------------------------------------------------------------- 1 | // basic csv serialisation function 2 | // accepts a list of lists representing the columns of data, and a list of text labels 3 | var dumpSamples = function (columns, labels) { 4 | if (columns.length != labels.length) { 5 | throw("label length mismatches number of columns"); 6 | } 7 | var lengths = columns.map(function(x) {return x.length;}) 8 | var csvContent = ''; 9 | for (var i = 0; i < labels.length; i++) { 10 | csvContent += labels[i] + ((i != (labels.length-1)) ? "," : ""); 11 | } 12 | csvContent += "\n"; 13 | var minimumLength = Math.min.apply(null, lengths); 14 | for (var i = 0; i < minimumLength; i++) { 15 | for (var j = 0; j < columns.length; j++) { 16 | var x = columns[j][i].toFixed(4); 17 | x = (x == 0) ? (0.00001).toFixed(4) : x; 18 | csvContent += (x >= 0 ? "+" : "") + x + ((j != (columns.length-1)) ? "," : ""); 19 | } 20 | csvContent += (i != (minimumLength-1) ? "\n" : ""); 21 | } 22 | return csvContent 23 | } 24 | 25 | var saveData = function (target) { 26 | var labels = []; 27 | var columns = []; 28 | if (session.devices.length) { 29 | for (var i = 0; i < session.devices.length; i++) { 30 | for (var j = 0; j < session.devices[i].channels.length; j++) { 31 | for (var k = 0; k < session.devices[i].channels[i].signals.length; k++) { 32 | var label = '' + i + session.devices[i].channels[j].label +"_"+ session.devices[i].channels[j].signals[k].label; 33 | labels.push(label); 34 | columns.push(session.devices[i].channels[j].signals[k].buffer.getData()); 35 | }; 36 | }; 37 | }; 38 | fileio.writeByURI(target, dumpSamples(columns, labels)); 39 | }; 40 | }; 41 | 42 | -------------------------------------------------------------------------------- /qml/jsutils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * From http://stackoverflow.com/questions/13861254/json-stringify-deep-objects, post by Gili. 3 | * Modified for integration with QML by Ian Daniher, 03/26/15. 4 | * Returns the JSON representation of an object. 5 | * 6 | * @param {value} object the object 7 | * @param {number} objectMaxDepth for objects, the maximum number of times to recurse into descendants 8 | * @param {number} arrayMaxLength for arrays, the maximum number of elements to enumerate 9 | * @param {string} indent the string to use for indentation 10 | * @return {string} the JSON representation 11 | */ 12 | 13 | var HTTP_REQUEST_NO_RESPONSE = 0 14 | var HTTP_REQUEST_NOT_FOUND = 404 15 | var HTTP_REQUEST_OK = 200 16 | 17 | var GIT_RATE_LIMIT_EXCEEDED = 777 18 | 19 | var request = function (url, callback) { 20 | var xhr = new XMLHttpRequest(); 21 | xhr.onreadystatechange = (function(myxhr) { 22 | return function() { 23 | if(myxhr.readyState === 4) callback(myxhr) 24 | } 25 | })(xhr); 26 | xhr.open('GET', url, true); 27 | xhr.send(''); 28 | } 29 | 30 | var checkRateLimitExceeded = function (callback, fail_callback) { 31 | request("https://api.github.com/rate_limit", function(t) { 32 | if (t.status === HTTP_REQUEST_OK) { 33 | var d = JSON.parse(t.responseText); 34 | 35 | if (d.resources.core.remaining > 0) { 36 | callback(); 37 | } else { 38 | if (fail_callback) 39 | fail_callback(GIT_RATE_LIMIT_EXCEEDED); 40 | } 41 | } else { 42 | if (fail_callback) 43 | fail_callback(t.status); 44 | } 45 | }); 46 | } 47 | 48 | var checkLatest = function (target) { 49 | var text; 50 | checkRateLimitExceeded( function() { 51 | request("https://api.github.com/repos/analogdevicesinc/pixelpulse2/releases", function(t) { 52 | var d = JSON.parse(t.responseText)[0]; 53 | text = "The most recent release is " + d.tag_name + ", published at " + (new Date(d.published_at)).toString() + "." + '\n\n' + "It is available for download at " + d.html_url + "."; 54 | target.text += text; 55 | }); 56 | }); 57 | 58 | return '\n\n\n'; 59 | } 60 | 61 | var checkLatestFw = function (callback, fail_callback) { 62 | checkRateLimitExceeded( function() { 63 | var text; 64 | request("https://api.github.com/repos/analogdevicesinc/m1k-fw/releases", function(t) { 65 | var d = JSON.parse(t.responseText)[0]; 66 | callback(d.tag_name); 67 | }); 68 | }, 69 | fail_callback 70 | ); 71 | 72 | return '\n\n\n'; 73 | } 74 | 75 | var requestFile = function(url, callback) { 76 | var xhr = new XMLHttpRequest(); 77 | 78 | xhr.onloadstart = (function(myxhr) { 79 | return function() { 80 | //console.log('LOG: onloadstart: ', myxhr.status); 81 | } 82 | })(xhr); 83 | xhr.onprogress = (function(myxhr) { 84 | return function() { 85 | //console.log('LOG: progress: ', myxhr.status); 86 | } 87 | })(xhr); 88 | xhr.onerror = (function(myxhr) { 89 | return function() { 90 | //console.log('LOG: error: ', myxhr.status); 91 | } 92 | })(xhr); 93 | xhr.ontimeout = (function(myxhr) { 94 | return function() { 95 | //console.log('LOG: timeout: ', myxhr.status); 96 | } 97 | })(xhr); 98 | xhr.onloadend = (function(myxhr) { 99 | return function() { 100 | //console.log('LOG: onloadend: ', myxhr.status); 101 | } 102 | })(xhr); 103 | xhr.onreadystatechange = (function(myxhr) { 104 | return function() { 105 | if(myxhr) //console.log('LOG: status ready: ', myxhr.readyState);//if(myxhr.readyState === 4) 106 | if(myxhr.readyState === 4 && myxhr.status === 200) callback(myxhr) 107 | } 108 | })(xhr); 109 | 110 | xhr.onload = (function(myxhr) { 111 | return function() { 112 | //console.log('LOG: status load: ', myxhr.status); 113 | if(myxhr.status === 200) callback(myxhr) 114 | } 115 | })(xhr); 116 | xhr.open('GET', url, true); 117 | //xhr.responseType = "arraybuffer"; 118 | xhr.send(''); 119 | } 120 | 121 | var getFirmwareURL = function(callback) { 122 | var releaseURL = 'https://api.github.com/repos/analogdevicesinc/m1k-fw/releases'; 123 | 124 | request(releaseURL, function(t) { 125 | var d = JSON.parse(t.responseText)[0]; 126 | var id = d.id; 127 | var releaseAssetURL = releaseURL + '/' + id + '/assets'; 128 | 129 | request(releaseAssetURL, function(t) { 130 | var d = JSON.parse(t.responseText)[0]; 131 | var fileDownloadURL = d.browser_download_url; 132 | 133 | request(fileDownloadURL, function(t) { 134 | var header = t.getResponseHeader('Location'); 135 | callback(header); 136 | }); 137 | }); 138 | }); 139 | }; 140 | 141 | var toJSON = function(object, objectMaxDepth, arrayMaxLength, indent) 142 | { 143 | "use strict"; 144 | 145 | /** 146 | * Escapes control characters, quote characters, backslash characters and quotes the string. 147 | * 148 | * @param {string} string the string to quote 149 | * @returns {String} the quoted string 150 | */ 151 | function quote(string) 152 | { 153 | escapable.lastIndex = 0; 154 | var escaped; 155 | if (escapable.test(string)) 156 | { 157 | escaped = string.replace(escapable, function(a) 158 | { 159 | var replacement = replacements[a]; 160 | if (typeof (replacement) === "string") 161 | return replacement; 162 | // Pad the unicode representation with leading zeros, up to 4 characters. 163 | return "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); 164 | }); 165 | } 166 | else 167 | escaped = string; 168 | return "\"" + escaped + "\""; 169 | } 170 | 171 | /** 172 | * Returns the String representation of an object. 173 | * 174 | * Based on https://github.com/Canop/JSON.prune/blob/master/JSON.prune.js 175 | * 176 | * @param {string} path the fully-qualified path of value in the JSON object 177 | * @param {type} value the value of the property 178 | * @param {string} cumulativeIndent the indentation to apply at this level 179 | * @param {number} depth the current recursion depth 180 | * @return {String} the JSON representation of the object, or "null" for values that aren't valid 181 | * in JSON (e.g. infinite numbers). 182 | */ 183 | function toString(path, value, cumulativeIndent, depth) 184 | { 185 | switch (typeof (value)) 186 | { 187 | case "string": 188 | return quote(value); 189 | case "number": 190 | { 191 | // JSON numbers must be finite 192 | if (isFinite(value)) 193 | return String(value); 194 | return "null"; 195 | } 196 | case "boolean": 197 | return String(value); 198 | case typeof(function(){}): 199 | case "object": 200 | { 201 | /*if (!value) 202 | return "null";*/ 203 | var valueIndex = values.indexOf(value); 204 | if (valueIndex !== -1) 205 | return "Reference => " + paths[valueIndex]; 206 | values.push(value); 207 | paths.push(path); 208 | if (depth > objectMaxDepth) 209 | return "..."; 210 | 211 | // Make an array to hold the partial results of stringifying this object value. 212 | var partial = []; 213 | 214 | // Is the value an array? 215 | var i; 216 | if (value.length) 217 | { 218 | // The value is an array. Stringify every element 219 | var length = Math.min(value.length, arrayMaxLength); 220 | 221 | // Whether a property has one or multiple values, they should be treated as the same 222 | // object depth. As such, we do not increment the object depth when recursing into an 223 | // array. 224 | for (i = 0; i < length; ++i) 225 | { 226 | partial[i] = toString(path + "." + i, value[i], cumulativeIndent + indent, depth, 227 | arrayMaxLength); 228 | } 229 | if (i < value.length) 230 | { 231 | // arrayMaxLength reached 232 | partial[i] = "..."; 233 | } 234 | return "\n" + cumulativeIndent + "[" + partial.join(", ") + "\n" + cumulativeIndent + 235 | "]"; 236 | } 237 | 238 | // Otherwise, iterate through all of the keys in the object. 239 | for (var subKey in value) 240 | { 241 | if (Object.prototype.hasOwnProperty.call(value, subKey) & (subKey != "parent") & (typeof(value[subKey] != "function"))) 242 | { 243 | var subValue; 244 | try 245 | { 246 | subValue = toString(path + "." + subKey, value[subKey], cumulativeIndent + indent, 247 | depth + 1); 248 | partial.push(quote(subKey) + ": " + subValue); 249 | } 250 | catch (e) 251 | { 252 | // this try/catch due to forbidden accessors on some objects 253 | if (e.message) 254 | subKey = e.message; 255 | else 256 | subKey = "access denied"; 257 | } 258 | } 259 | if (typeof(value[subKey] != "function")) 260 | { 261 | partial.push(quote(subKey) +": "+ value[subKey].toString()); 262 | } 263 | } 264 | var result = "\n" + cumulativeIndent + "{\n"; 265 | for (i = 0; i < partial.length; ++i) 266 | result += cumulativeIndent + indent + partial[i] + ",\n"; 267 | if (partial.length > 0) 268 | { 269 | // Remove trailing comma 270 | result = result.slice(0, result.length - 2) + "\n"; 271 | } 272 | result += cumulativeIndent + "}"; 273 | return result; 274 | } 275 | default: 276 | return "null"; 277 | } 278 | } 279 | 280 | if (indent === undefined) 281 | indent = " "; 282 | if (objectMaxDepth === undefined) 283 | objectMaxDepth = 0; 284 | if (arrayMaxLength === undefined) 285 | arrayMaxLength = 50; 286 | // Matches characters that must be escaped 287 | var escapable = 288 | /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; 289 | // The replacement characters 290 | var replacements = 291 | { 292 | "\b": "\\b", 293 | "\t": "\\t", 294 | "\n": "\\n", 295 | "\f": "\\f", 296 | "\r": "\\r", 297 | "\"": "\\\"", 298 | "\\": "\\\\" 299 | }; 300 | // A list of all the objects that were seen (used to avoid recursion) 301 | var values = []; 302 | // The path of an object in the JSON object, with indexes corresponding to entries in the 303 | // "values" variable. 304 | var paths = []; 305 | return toString("root", object, "", 0); 306 | }; 307 | -------------------------------------------------------------------------------- /qml/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Window 2.0 3 | import QtQuick.Layouts 1.0 4 | import QtQuick.Controls 1.0 5 | import QtGraphicalEffects 1.0 6 | import "sesssave.js" as StateSave 7 | 8 | ApplicationWindow { 9 | width: 1024 10 | height: 768 11 | minimumHeight: 600 12 | minimumWidth: 800 13 | title: "Pixelpulse2" 14 | visible: true 15 | property var toolbarHeight: 56 16 | id: window 17 | property real brightness: 3.0 18 | 19 | property alias repeatedSweep: toolbar.repeatedSweep 20 | property alias plotsVisible: toolbar.plotsVisible 21 | property alias contentVisible: toolbar.contentVisible 22 | property alias deviceMngrVisible: toolbar.deviceMngrVisible 23 | property var lastConfig: {} 24 | /*Color control properties*/ 25 | property color xyplotColor: Qt.rgba(0.12, 0.12, 0.12, 0.0 ) 26 | property color gridAxesColor: '#222' 27 | //signal row 28 | property color signalRowColor: '#0c0c0c' 29 | property color signalAxesColor: '#222' 30 | //Phosphor render 31 | property color dotSignalCurrent: Qt.rgba(0.2, 0.2, 0.03, 1); 32 | property color dotSignalVoltage: Qt.rgba(0.03, 0.3, 0.03, 1); 33 | property color dotPlotsCurrent: Qt.rgba(0.2, 0.2, 0.03, 1); 34 | property color dotPlotsVoltage: Qt.rgba(0.03, 0.3, 0.03, 1); 35 | //Dot size 36 | property real dotSizeSignal: 0.1; 37 | property real dotSizePlots: 0.1; 38 | 39 | Controller { 40 | id: controller 41 | continuous: !repeatedSweep 42 | } 43 | 44 | 45 | Rectangle { 46 | id: background 47 | anchors.fill: parent 48 | color: '#000' 49 | } 50 | 51 | SplitView { 52 | anchors.fill: parent 53 | 54 | Item { 55 | // The entire signal + timeline pane 56 | id: timelinePane 57 | Layout.fillHeight: true 58 | Layout.fillWidth: true 59 | Layout.minimumWidth: 0.4*window.width 60 | 61 | // column width 62 | property real spacing: 40 63 | 64 | ColumnLayout { 65 | anchors.fill: parent 66 | id: signals_column 67 | 68 | spacing: 0 69 | 70 | RowLayout { 71 | Layout.fillWidth: true 72 | Layout.minimumHeight: toolbarHeight 73 | Layout.maximumHeight: toolbarHeight 74 | 75 | spacing: 0 76 | 77 | Toolbar { 78 | id: toolbar 79 | width: timelinePane.spacing * 3 80 | Layout.fillHeight: true 81 | } 82 | 83 | TimelineHeader { 84 | id: timeline_header 85 | Layout.fillWidth: true 86 | Layout.fillHeight: true 87 | 88 | xaxis: timeline_xaxis 89 | } 90 | } 91 | 92 | Rectangle { 93 | // The signals column to the left of the timeline plots. The plots are 94 | // contained within this, but positioned to the right of the width 95 | // specified here. 96 | id: signalsPane 97 | Layout.fillHeight: true 98 | width: toolbar.width 99 | color: '#000' 100 | 101 | ColumnLayout { 102 | anchors.fill: parent 103 | anchors.bottomMargin: 10 104 | spacing: 0 105 | 106 | Repeater { 107 | id: deviceRepeater 108 | model: session.devices 109 | DeviceRow { 110 | Layout.fillHeight: true 111 | Layout.fillWidth: true 112 | device: model 113 | currentIndex: index 114 | } 115 | onItemAdded: { 116 | if ( lastConfig ) { 117 | if ((Object.keys(lastConfig).length) > 0) { 118 | StateSave.restoreState(lastConfig); 119 | } 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | Item { 127 | id: statusBar 128 | anchors.left: parent.left 129 | anchors.right: parent.right 130 | anchors.bottom: parent.bottom 131 | height: 30 132 | visible: toolbar.acqusitionDialog.showStatusBar 133 | 134 | ColumnLayout { 135 | anchors.fill: parent 136 | spacing: 0 137 | 138 | Rectangle { 139 | id: hiddenBar 140 | Layout.fillWidth: true 141 | height: 5 142 | color: '#000' 143 | } 144 | Rectangle { 145 | id: statusBarRect 146 | Layout.fillWidth: true 147 | height: 25 148 | gradient: Gradient { 149 | GradientStop { position: 0.0; color: '#404040' } 150 | GradientStop { position: 0.15; color: '#5a5a5a' } 151 | GradientStop { position: 0.5; color: '#444444' } 152 | GradientStop { position: 1.0; color: '#424242' } 153 | } 154 | 155 | // Left vertical separator 156 | Rectangle { 157 | id: leftSeparator 158 | height: parent.height 159 | width: 1 160 | color: '#333333' 161 | x: signalsPane.width 162 | } 163 | 164 | Text { 165 | id: delayText 166 | text: "Delay: " + toolbar.acqusitionDialog.timeDelay.toFixed(2) + " ms" 167 | color: 'white' 168 | anchors.left: leftSeparator.right 169 | anchors.leftMargin: 10 170 | anchors.verticalCenter: parent.verticalCenter 171 | } 172 | 173 | // Right vertical separator 174 | Rectangle { 175 | id: rightSeparator 176 | height: parent.height 177 | width: 1 178 | color: '#333333' 179 | x: signalsPane.width + timeline_xaxis.width 180 | } 181 | } 182 | } 183 | } 184 | } 185 | 186 | TimelineFlickable { 187 | id: timeline_xaxis 188 | anchors.fill: parent 189 | anchors.leftMargin: toolbar.width 190 | anchors.rightMargin: 78 191 | 192 | boundMin: 0 193 | boundMax: controller.sampleTime 194 | } 195 | } 196 | 197 | PlotPane { 198 | id: xyPane 199 | visible: plotsVisible 200 | width: 360 201 | Layout.minimumWidth: 0.2*window.width 202 | Layout.maximumWidth: 0.4*window.width 203 | } 204 | 205 | ContentPane { 206 | id: contentPane 207 | visible: contentVisible 208 | width: 360 209 | Layout.minimumWidth: 0.2*window.width 210 | Layout.maximumWidth: 0.4*window.width 211 | } 212 | 213 | DeviceManagerPane { 214 | id: deviceMngrPane 215 | visible: deviceMngrVisible 216 | width: 360 217 | Layout.minimumWidth: 0.2*window.width 218 | Layout.maximumWidth: 0.4*window.width 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /qml/sesssave.js: -------------------------------------------------------------------------------- 1 | var saveState = function () { 2 | var signalStates = {}; 3 | for (var a = 0; a < deviceRepeater.count; a++){ 4 | var deviceItem = deviceRepeater.itemAt(a); 5 | for (var b = 0; b < deviceItem.channelRepeater.count; b++){ 6 | var channelItem = deviceItem.channelRepeater.itemAt(b); 7 | var channel = channelItem.channel; 8 | for (var c = 0; c < channelItem.signalRepeater.count; c++) { 9 | var label = '' + a + session.devices[a].channels[b].label +"_"+ session.devices[a].channels[b].signals[c].label; 10 | var signalState = {}; 11 | var signalItem = channelItem.signalRepeater.itemAt(c); 12 | var signal = signalItem.signal; 13 | signalState.src = signal.src.src; 14 | signalState.v1 = signal.src.v1; 15 | signalState.v2 = signal.src.v2; 16 | signalState.period = signal.src.period; 17 | signalState.phase = signal.src.phase; 18 | signalState.duty = signal.src.duty; 19 | signalState.xscale = signalItem.xaxis.xscale; 20 | signalState.ymin = signalItem.ymin; 21 | signalState.ymax = signalItem.ymax; 22 | signalState.mode = channel.mode; 23 | signalStates[label] = signalState; 24 | } 25 | } 26 | } 27 | var generalState = {} 28 | generalState.signalCheckBox = toolbar.colorDialog.sigCheckBox.checked; 29 | generalState.plotsCheckBox = toolbar.colorDialog.plotCheckBox.checked; 30 | generalState.sliderBrightness = toolbar.colorDialog.sliderB.value; 31 | generalState.sliderContrast = toolbar.colorDialog.sliderC.value; 32 | generalState.sliderPhosphor = toolbar.colorDialog.sliderPh.value; 33 | generalState.sliderDotSize = toolbar.colorDialog.sliderDot.value; 34 | generalState.xy_checked = plotsVisible; 35 | generalState.repeatedSweep = repeatedSweep; 36 | generalState.sampleTime = controller.sampleTime; 37 | signalStates['generalSettings'] = generalState; 38 | return signalStates; 39 | }; 40 | 41 | 42 | var restoreState = function (signalStates){ 43 | for (var a = 0; a < deviceRepeater.count; a++){ 44 | var deviceItem = deviceRepeater.itemAt(a); 45 | for (var b = 0; b < deviceItem.channelRepeater.count; b++){ 46 | var channelItem = deviceItem.channelRepeater.itemAt(b); 47 | var channel = channelItem.channel; 48 | for (var c = 0; c < channelItem.signalRepeater.count; c++) { 49 | var label = '' + a + session.devices[a].channels[b].label +"_"+ session.devices[a].channels[b].signals[c].label; 50 | var signalItem = channelItem.signalRepeater.itemAt(c); 51 | var signalState = signalStates[label]; 52 | var signal = signalItem.signal; 53 | channel.mode = signalState.mode; 54 | signal.src.src = signalState.src; 55 | signal.src.v1 = signalState.v1; 56 | signal.src.v2 = signalState.v2; 57 | signal.src.period = signalState.period; 58 | signal.src.phase = signalState.phase; 59 | signal.src.duty = signalState.duty; 60 | signalItem.ymin = signalState.ymin; 61 | signalItem.ymax = signalState.ymax; 62 | } 63 | } 64 | } 65 | var generalState = signalStates['generalSettings'] 66 | toolbar.colorDialog.sigCheckBox.checked = generalState.signalCheckBox; 67 | toolbar.colorDialog.plotCheckBox.checked = generalState.plotsCheckBox; 68 | toolbar.colorDialog.sliderB.value = generalState.sliderBrightness; 69 | toolbar.colorDialog.sliderC.value = generalState.sliderContrast; 70 | toolbar.colorDialog.sliderPh.value = generalState.sliderPhosphor; 71 | toolbar.colorDialog.sliderDot.value = generalState.sliderDotSize; 72 | plotsVisible = generalState.xy_checked; 73 | repeatedSweep = generalState.repeatedSweep; 74 | controller.sampleTime = generalState.sampleTime; 75 | } 76 | -------------------------------------------------------------------------------- /utils/filedownloader.cpp: -------------------------------------------------------------------------------- 1 | /* From: 2 | https://wiki.qt.io/Download_Data_from_URL 3 | */ 4 | #include "filedownloader.h" 5 | 6 | FileDownloader::FileDownloader(QUrl imageUrl, QObject *parent) : 7 | QObject(parent) 8 | { 9 | connect( 10 | &m_WebCtrl, SIGNAL (finished(QNetworkReply*)), 11 | this, SLOT (fileDownloaded(QNetworkReply*)) 12 | ); 13 | 14 | QNetworkRequest request(imageUrl); 15 | m_WebCtrl.get(request); 16 | } 17 | 18 | FileDownloader::~FileDownloader() { } 19 | 20 | void FileDownloader::fileDownloaded(QNetworkReply* pReply) { 21 | m_DownloadedData = pReply->readAll(); 22 | //emit a signal 23 | pReply->deleteLater(); 24 | emit downloaded(); 25 | } 26 | 27 | QByteArray FileDownloader::downloadedData() const { 28 | return m_DownloadedData; 29 | } 30 | -------------------------------------------------------------------------------- /utils/filedownloader.h: -------------------------------------------------------------------------------- 1 | /* From: 2 | https://wiki.qt.io/Download_Data_from_URL 3 | */ 4 | #ifndef FILEDOWNLOADER_H 5 | #define FILEDOWNLOADER_H 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class FileDownloader : public QObject 14 | { 15 | Q_OBJECT 16 | public: 17 | explicit FileDownloader(QUrl imageUrl, QObject *parent = 0); 18 | virtual ~FileDownloader(); 19 | QByteArray downloadedData() const; 20 | 21 | signals: 22 | void downloaded(); 23 | 24 | private slots: 25 | void fileDownloaded(QNetworkReply* pReply); 26 | 27 | private: 28 | QNetworkAccessManager m_WebCtrl; 29 | QByteArray m_DownloadedData; 30 | }; 31 | 32 | #endif // FILEDOWNLOADER_H 33 | -------------------------------------------------------------------------------- /utils/fileio.h: -------------------------------------------------------------------------------- 1 | /// simple implementation of writing files for qml 2 | 3 | #ifndef FILEIO_H 4 | #define FILEIO_H 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class FileIO : public QObject 14 | { 15 | Q_OBJECT 16 | 17 | public slots: 18 | /// accept a file handle by URI and source datastring 19 | bool writeByURI(const QUrl& destination, const QString& data) { 20 | auto path = destination.toLocalFile(); 21 | return writeByFilename(path, data); 22 | } 23 | /// accept a file handle by string and source datastring 24 | bool writeByFilename(const QString& source, const QString& data) 25 | { 26 | if (source.isEmpty()) 27 | return false; 28 | QString s = source; 29 | QFile file(s); 30 | file.open(QIODevice::WriteOnly | QIODevice::Text); 31 | QTextStream out(&file); 32 | // end with a newline 33 | out << data << "\n"; 34 | return true; 35 | } 36 | /// accept a file handle by URI and source datastring 37 | bool writeRawByURI(const QUrl& destination, const QByteArray& data) { 38 | auto path = destination.toLocalFile(); 39 | return writeRawByFilename(path, data); 40 | } 41 | /// accept a file handle by string and source datastring 42 | bool writeRawByFilename(const QString& source, const QByteArray& data) 43 | { 44 | if (source.isEmpty()) 45 | return false; 46 | QString s = source; 47 | QFile file(s); 48 | file.open(QIODevice::WriteOnly); 49 | QDataStream out(&file); 50 | out.writeRawData(data, data.length()); 51 | return true; 52 | } 53 | 54 | QString readByURI(const QUrl& source) { 55 | auto path = source.toLocalFile(); 56 | QFile file(path); 57 | file.open(QIODevice::ReadOnly | QIODevice::Text); 58 | QTextStream in(&file); 59 | return in.readAll(); 60 | } 61 | 62 | public: 63 | FileIO() {} 64 | }; 65 | 66 | #endif // FILEIO_H 67 | --------------------------------------------------------------------------------