├── LICENSE ├── README.md ├── cpp ├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── cmake │ ├── .gitignore │ └── CPM.cmake ├── inc │ ├── hantek.hpp │ ├── hantek_parser.hpp │ ├── utils.hpp │ ├── vcd.hpp │ └── version.hpp ├── src │ ├── hantek.cpp │ ├── hantek_parser.cpp │ ├── main.cpp │ ├── utils.cpp │ ├── vcd.cpp │ └── version.cpp └── test │ └── main.cpp ├── generate.sh ├── kaitai └── hantek.ksy ├── python ├── .gitignore ├── hantek_wave_viewer │ ├── __init__.py │ ├── __main__.py │ ├── _version.py │ └── parser │ │ ├── __init__.py │ │ ├── conversions.py │ │ ├── hantek.py │ │ └── utils.py ├── pyproject.toml ├── setup.cfg ├── setup.py └── tests │ ├── __init__.py │ └── parser │ ├── __init__.py │ └── test_utils.py └── wavefiles ├── README.md ├── scope_wave_26_0.lwf ├── scope_wave_26_1.lwf ├── scope_wave_26_10.lwf ├── scope_wave_26_11.lwf ├── scope_wave_26_12.lwf ├── scope_wave_26_13.lwf ├── scope_wave_26_14.lwf ├── scope_wave_26_15.lwf ├── scope_wave_26_16.lwf ├── scope_wave_26_17.lwf ├── scope_wave_26_18.lwf ├── scope_wave_26_19.lwf ├── scope_wave_26_2.lwf ├── scope_wave_26_3.lwf ├── scope_wave_26_4.lwf ├── scope_wave_26_5.lwf ├── scope_wave_26_6.lwf ├── scope_wave_26_7.lwf ├── scope_wave_26_8.lwf ├── scope_wave_26_9.lwf ├── scope_wave_29_0.lwf ├── scope_wave_29_1.lwf ├── scope_wave_29_2.lwf ├── scope_wave_29_3.lwf ├── scope_wave_30_1.lwf ├── scope_wave_30_2.lwf ├── scope_wave_30_3.lwf ├── scope_wave_30_4.lwf ├── scope_wave_30_5.lwf ├── scope_wave_30_6.lwf ├── scope_wave_30_7.lwf ├── scope_wave_30_8.lwf ├── scope_wave_35_4.lwf ├── scope_wave_35_4.png └── scope_wave_35_4_py.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Matt Davis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hantek Wave Viewer 2 | 3 | This project aims to reverse engineer the Hantek `lwf` file format. I couldn't find any documentation for the format or previous projects so created this one by reverse engineering samples. I hope it works for you. 4 | 5 | **Note:** All tests conducted on a Hantek DSO2C10 6 | 7 | ## Kaitai Struct 8 | 9 | In the `kaitai` directory there is a [Kaitai Struct](https://www.kaitai.io) file which describes the structure of the file. As mentioned above this is based on me reverse engineering and may not be 100% accurate; there are also bytes I haven't yet identified. 10 | 11 | This file can be used to build bindings for other languages. To generate the currently supported Python and C++ files you can run `./generate.sh` from the project root directory. 12 | 13 | I captured a number of wavefiles with known parameters, all files used are in `wavefiles`. You can read more about each sample in `wavefiles/README.md` 14 | 15 | ## Python 16 | 17 | The sample Python allows you to view the `lwf` file using `matplotlib` or to simply describe the file and it's content 18 | 19 | ### Installation 20 | 21 | ```bash 22 | git clone https://github.com/mattdavis90/hantek-wave-viewer.git 23 | cd hantek-wave-viewer/python 24 | pip install . 25 | ``` 26 | 27 | ### Usage 28 | 29 | To display information about an `lwf` file 30 | 31 | ```bash 32 | > hantek_wave_viewer info 33 | Hantek Wave Viewer: v0.1.0 34 | Common: 35 | Version: 2001 36 | Acquisition Mode: Normal [0] 37 | Timebase: 500us [16] 38 | Sampling Depth: 4000 39 | Samples per Second: 500000.0 40 | Trigger Type: UART [9] 41 | Trigger Channel: 0 42 | Trigger Level: 0 43 | Horizontal Offset: 0 44 | Channel 1 45 | Sample Count: 4000 46 | Offset: 24 47 | Vots per Division: 2V [11] 48 | Probe Mode: 1x [0] 49 | Channel 2 50 | Disabled 51 | Channel 3 52 | Disabled 53 | Channel 4 54 | Disabled 55 | Data: 56 | Channel 1: 4000 57 | Channel 2: 0 58 | Channel 3: 0 59 | Channel 4: 0 60 | ``` 61 | 62 | To view the file using `matplotlib` 63 | 64 | ``` bash 65 | hantek_wave_viewer view 66 | ``` 67 | 68 | 69 | ### Screenshots 70 | 71 | This is a screenshot of the Python based viewer 72 | 73 | ![Screenshot of Python viewer](wavefiles/scope_wave_35_4_py.png) 74 | 75 | and the same waves on the Oscilloscope for comparison 76 | 77 | ![Screenshot from Oscilloscope](wavefiles/scope_wave_35_4.png) 78 | 79 | 80 | ## C++ 81 | 82 | The C++ example has the same `info` command as the Python application and will output in the same format. This examples also has a converter that will output a [vcd file](https://en.wikipedia.org/wiki/Value_change_dump) which can be used in GTKWave or Sigrok. 83 | 84 | ### Building 85 | 86 | ```bash 87 | git clone https://github.com/mattdavis90/hantek-wave-viewer.git 88 | cd hantek-wave-viewer/cpp 89 | mkdir build 90 | cd build 91 | cmake .. 92 | make 93 | ``` 94 | 95 | ### Usage 96 | 97 | To output information about the `lwf` file 98 | 99 | ```bash 100 | ./hantek info 101 | ``` 102 | 103 | To convert the file into a `vcd` 104 | 105 | ```bash 106 | ./hantek vcd 107 | ``` 108 | 109 | ## Other Programming Languages 110 | 111 | This project is licensed as MIT so feel free to use and modify as you wish. The Kaitai file is included and, as per their docs, could be used to generate a parser in other languages. 112 | -------------------------------------------------------------------------------- /cpp/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Webkit 3 | ColumnLimit: 88 4 | AlignConsecutiveAssignments: true 5 | -------------------------------------------------------------------------------- /cpp/.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build 3 | compile_commands.json 4 | -------------------------------------------------------------------------------- /cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 2 | 3 | # Main executable 4 | project(HantekWavefile VERSION 0.1.2) 5 | 6 | set(BINARY "hantek") 7 | set(SRC "${PROJECT_SOURCE_DIR}/src") 8 | set(INC "${PROJECT_SOURCE_DIR}/inc") 9 | 10 | file(GLOB all_INCS 11 | "${INC}/*.hpp" 12 | "${INC}/*.h" 13 | ) 14 | file(GLOB all_SRCS 15 | "${SRC}/*.cpp" 16 | ) 17 | add_executable(${BINARY} ${all_SRCS} ${all_INCS}) 18 | include_directories(${SRC}) 19 | target_include_directories(${BINARY} PRIVATE ${INC}) 20 | set_property(TARGET ${BINARY} PROPERTY CXX_STANDARD 17) 21 | target_compile_definitions(${BINARY} PUBLIC DOCTEST_CONFIG_DISABLE) 22 | 23 | # Enable optimised builds 24 | if(NOT CMAKE_BUILD_TYPE) 25 | set(CMAKE_BUILD_TYPE Release) 26 | endif() 27 | set(CMAKE_CXX_FLAGS_DEBUG "-g") 28 | set(CMAKE_CXX_FLAGS_RELEASE "-O3") 29 | 30 | # Test Executable 31 | set(TEST_BINARY "hantek_test") 32 | list(REMOVE_ITEM all_SRCS "${SRC}/main.cpp") 33 | add_executable(${TEST_BINARY} ${all_SRCS} ${all_INCS} "${PROJECT_SOURCE_DIR}/test/main.cpp") 34 | target_include_directories(${TEST_BINARY} PRIVATE ${INC}) 35 | set_property(TARGET ${TEST_BINARY} PROPERTY CXX_STANDARD 17) 36 | 37 | # CPM to manage deps 38 | include(cmake/CPM.cmake) 39 | 40 | # Add build dependecies 41 | CPMAddPackage( 42 | NAME kaitai_struct_cpp_stl_runtime 43 | GITHUB_REPOSITORY kaitai-io/kaitai_struct_cpp_stl_runtime 44 | GIT_TAG 0.10.1 45 | VERSION 0.10.0 46 | OPTIONS "BUILD_TESTS OFF" 47 | ) 48 | CPMAddPackage("gh:p-ranav/argparse@3.1") 49 | CPMAddPackage("gh:agauniyal/rang@3.2") 50 | CPMAddPackage("gh:fmtlib/fmt#11.0.2") 51 | CPMAddPackage("gh:doctest/doctest@2.4.11") 52 | CPMAddPackage("gh:TheLartians/Format.cmake@1.8.1") 53 | target_link_libraries(${BINARY} kaitai_struct_cpp_stl_runtime argparse rang fmt doctest) 54 | target_link_libraries(${TEST_BINARY} kaitai_struct_cpp_stl_runtime argparse rang fmt doctest) 55 | 56 | # Store the version number in "src/version.cpp" 57 | set(VERSION "const char* VERSION = \"${CMAKE_PROJECT_VERSION}\";") 58 | set(VERSION_FILE "${SRC}/version.cpp") 59 | if(EXISTS ${VERSION_FILE}) 60 | file(READ ${VERSION_FILE} VERSION_) 61 | else() 62 | set(VERSION_ "") 63 | endif() 64 | if (NOT "${VERSION}" STREQUAL "${VERSION_}") 65 | file(WRITE ${VERSION_FILE} "${VERSION}") 66 | endif() 67 | -------------------------------------------------------------------------------- /cpp/cmake/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !CPM.cmake 4 | -------------------------------------------------------------------------------- /cpp/cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors 4 | 5 | set(CPM_DOWNLOAD_VERSION 0.40.2) 6 | set(CPM_HASH_SUM "c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d") 7 | 8 | if(CPM_SOURCE_CACHE) 9 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 10 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 11 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 12 | else() 13 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 14 | endif() 15 | 16 | # Expand relative path. This is important if the provided path contains a tilde (~) 17 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 18 | 19 | file(DOWNLOAD 20 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 21 | ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} 22 | ) 23 | 24 | include(${CPM_DOWNLOAD_LOCATION}) 25 | -------------------------------------------------------------------------------- /cpp/inc/hantek.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hantek_parser.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class Hantek { 12 | public: 13 | Hantek(const std::string filename); 14 | void print(std::ostream& os = std::cout); 15 | 16 | double timebase(); 17 | std::tuple, std::optional, std::optional, 18 | std::optional> 19 | next(); 20 | 21 | void reset(); 22 | 23 | private: 24 | std::unique_ptr h; 25 | size_t idx = 0; 26 | 27 | void print_header(std::ostream& os); 28 | void print_channel(std::ostream& os, int n, const hantek_t::channel_t* ch); 29 | std::optional get_data( 30 | size_t idx, const hantek_t::channel_t* ch, const std::vector* data); 31 | 32 | public: 33 | const double TIMEBASE[33] = { 34 | 2e-9, 35 | 5e-9, 36 | 10e-9, 37 | 20e-9, 38 | 50e-9, 39 | 100e-9, 40 | 200e-9, 41 | 500e-9, 42 | 1e-6, 43 | 2e-6, 44 | 5e-6, 45 | 10e-6, 46 | 20e-6, 47 | 50e-6, 48 | 100e-6, 49 | 200e-6, 50 | 500e-6, 51 | 1e-3, 52 | 2e-3, 53 | 5e-3, 54 | 10e-3, 55 | 20e-3, 56 | 50e-3, 57 | 100e-3, 58 | 200e-3, 59 | 500e-3, 60 | 1, 61 | 2, 62 | 5, 63 | 10, 64 | 20, 65 | 50, 66 | 100, 67 | }; 68 | const double VOLTS_PER_DIV[14] = { 69 | 500e-6, 70 | 1e-3, 71 | 2e-3, 72 | 5e-3, 73 | 10e-3, 74 | 20e-3, 75 | 50e-3, 76 | 100e-3, 77 | 200e-3, 78 | 500e-3, 79 | 1, 80 | 2, 81 | 5, 82 | 10, 83 | }; 84 | const std::string TRIGGER_NAME[14] = { 85 | "Edge", 86 | "Pulse", 87 | "Video", 88 | "Slope", 89 | "Overtime", 90 | "Window", 91 | "Pattern", 92 | "Interval", 93 | "Under Amp", 94 | "UART", 95 | "LIN", 96 | "CAN", 97 | "SPI", 98 | "IIC", 99 | }; 100 | const std::string ACQUISITION_MODE[4] = { "Normal", "Average", "Peak", "HR" }; 101 | }; 102 | -------------------------------------------------------------------------------- /cpp/inc/hantek_parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler 4 | // to rebuild 5 | 6 | #include "kaitai/kaitaistruct.h" 7 | #include 8 | #include 9 | #include 10 | 11 | #if KAITAI_STRUCT_VERSION < 9000L 12 | #error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required" 13 | #endif 14 | 15 | class hantek_t : public kaitai::kstruct { 16 | 17 | public: 18 | class header_t; 19 | class channel_t; 20 | class footer_t; 21 | 22 | hantek_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = nullptr, 23 | hantek_t* p__root = nullptr); 24 | 25 | private: 26 | void _read(); 27 | void _clean_up(); 28 | 29 | public: 30 | ~hantek_t(); 31 | 32 | class header_t : public kaitai::kstruct { 33 | 34 | public: 35 | header_t(kaitai::kstream* p__io, hantek_t* p__parent = nullptr, 36 | hantek_t* p__root = nullptr); 37 | 38 | private: 39 | void _read(); 40 | void _clean_up(); 41 | 42 | public: 43 | ~header_t(); 44 | 45 | private: 46 | std::string m_magic; 47 | uint32_t m_version; 48 | std::unique_ptr m__unnamed2; 49 | std::unique_ptr m_channel1; 50 | std::unique_ptr m_channel2; 51 | std::unique_ptr m_channel3; 52 | std::unique_ptr m_channel4; 53 | std::unique_ptr> m_thumbnail; 54 | hantek_t* m__root; 55 | hantek_t* m__parent; 56 | 57 | public: 58 | std::string magic() const { return m_magic; } 59 | uint32_t version() const { return m_version; } 60 | channel_t* _unnamed2() const { return m__unnamed2.get(); } 61 | channel_t* channel1() const { return m_channel1.get(); } 62 | channel_t* channel2() const { return m_channel2.get(); } 63 | channel_t* channel3() const { return m_channel3.get(); } 64 | channel_t* channel4() const { return m_channel4.get(); } 65 | std::vector* thumbnail() const { return m_thumbnail.get(); } 66 | hantek_t* _root() const { return m__root; } 67 | hantek_t* _parent() const { return m__parent; } 68 | }; 69 | 70 | class channel_t : public kaitai::kstruct { 71 | 72 | public: 73 | channel_t(kaitai::kstream* p__io, hantek_t::header_t* p__parent = nullptr, 74 | hantek_t* p__root = nullptr); 75 | 76 | private: 77 | void _read(); 78 | void _clean_up(); 79 | 80 | public: 81 | ~channel_t(); 82 | 83 | private: 84 | uint16_t m_acquisition_mode; 85 | uint8_t m_enabled; 86 | uint8_t m_timebase; 87 | uint32_t m_sampling_depth; 88 | uint32_t m_sample_count; 89 | uint32_t m__unnamed5; 90 | double m_samples_per_second; 91 | uint32_t m_trigger_type; 92 | uint32_t m_trigger_channel; 93 | int32_t m_trigger_level; 94 | uint32_t m__unnamed10; 95 | int64_t m_horizontal_offset; 96 | int32_t m_offset; 97 | uint8_t m_volts_per_div; 98 | uint8_t m_mode; 99 | uint16_t m__unnamed15; 100 | uint32_t m_maybe_const; 101 | uint32_t m__unnamed17; 102 | hantek_t* m__root; 103 | hantek_t::header_t* m__parent; 104 | 105 | public: 106 | uint16_t acquisition_mode() const { return m_acquisition_mode; } 107 | uint8_t enabled() const { return m_enabled; } 108 | uint8_t timebase() const { return m_timebase; } 109 | uint32_t sampling_depth() const { return m_sampling_depth; } 110 | uint32_t sample_count() const { return m_sample_count; } 111 | uint32_t _unnamed5() const { return m__unnamed5; } 112 | double samples_per_second() const { return m_samples_per_second; } 113 | uint32_t trigger_type() const { return m_trigger_type; } 114 | uint32_t trigger_channel() const { return m_trigger_channel; } 115 | int32_t trigger_level() const { return m_trigger_level; } 116 | uint32_t _unnamed10() const { return m__unnamed10; } 117 | int64_t horizontal_offset() const { return m_horizontal_offset; } 118 | int32_t offset() const { return m_offset; } 119 | uint8_t volts_per_div() const { return m_volts_per_div; } 120 | uint8_t mode() const { return m_mode; } 121 | uint16_t _unnamed15() const { return m__unnamed15; } 122 | uint32_t maybe_const() const { return m_maybe_const; } 123 | uint32_t _unnamed17() const { return m__unnamed17; } 124 | hantek_t* _root() const { return m__root; } 125 | hantek_t::header_t* _parent() const { return m__parent; } 126 | }; 127 | 128 | class footer_t : public kaitai::kstruct { 129 | 130 | public: 131 | footer_t(kaitai::kstream* p__io, hantek_t* p__parent = nullptr, 132 | hantek_t* p__root = nullptr); 133 | 134 | private: 135 | void _read(); 136 | void _clean_up(); 137 | 138 | public: 139 | ~footer_t(); 140 | 141 | private: 142 | std::string m_magic; 143 | hantek_t* m__root; 144 | hantek_t* m__parent; 145 | 146 | public: 147 | std::string magic() const { return m_magic; } 148 | hantek_t* _root() const { return m__root; } 149 | hantek_t* _parent() const { return m__parent; } 150 | }; 151 | 152 | private: 153 | std::unique_ptr m_header; 154 | std::unique_ptr> m_data1; 155 | std::unique_ptr> m_data2; 156 | std::unique_ptr> m_data3; 157 | std::unique_ptr> m_data4; 158 | std::unique_ptr m_footer; 159 | hantek_t* m__root; 160 | kaitai::kstruct* m__parent; 161 | 162 | public: 163 | header_t* header() const { return m_header.get(); } 164 | std::vector* data1() const { return m_data1.get(); } 165 | std::vector* data2() const { return m_data2.get(); } 166 | std::vector* data3() const { return m_data3.get(); } 167 | std::vector* data4() const { return m_data4.get(); } 168 | footer_t* footer() const { return m_footer.get(); } 169 | hantek_t* _root() const { return m__root; } 170 | kaitai::kstruct* _parent() const { return m__parent; } 171 | }; 172 | -------------------------------------------------------------------------------- /cpp/inc/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern const std::string LARGE_UNITS[6]; 6 | extern const std::string SMALL_UNITS[6]; 7 | 8 | std::string format_large_number(double v, std::string units = ""); 9 | std::string format_small_number(double v, std::string units = ""); 10 | std::string format_number(double v, std::string units = ""); 11 | -------------------------------------------------------------------------------- /cpp/inc/vcd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class VCD { 8 | public: 9 | VCD(std::string filename, double timebase); 10 | ~VCD(); 11 | 12 | void dump(std::optional ch1, std::optional ch2, 13 | std::optional ch3, std::optional ch4); 14 | 15 | private: 16 | std::ofstream out; 17 | int time = 0; 18 | }; 19 | -------------------------------------------------------------------------------- /cpp/inc/version.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern const char* VERSION; 4 | -------------------------------------------------------------------------------- /cpp/src/hantek.cpp: -------------------------------------------------------------------------------- 1 | #include "hantek.hpp" 2 | #include "utils.hpp" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | Hantek::Hantek(const std::string filename) 10 | { 11 | std::ifstream fs(filename, std::ios::binary); 12 | if (fs.is_open()) { 13 | kaitai::kstream ks(&fs); 14 | this->h = std::unique_ptr(new hantek_t(&ks)); 15 | fs.close(); 16 | } else if (fs.fail()) { 17 | throw std::runtime_error( 18 | fmt::format("Could not open file: {}", std::strerror(errno))); 19 | std::cout << "Could not open file: " << std::strerror(errno) << std::endl; 20 | } else { 21 | std::cout << "Unknown error when opening file" << std::endl; 22 | } 23 | } 24 | 25 | void Hantek::print(std::ostream& os) 26 | { 27 | print_header(os); 28 | 29 | print_channel(os, 1, h->header()->channel1()); 30 | print_channel(os, 2, h->header()->channel2()); 31 | print_channel(os, 3, h->header()->channel3()); 32 | print_channel(os, 4, h->header()->channel4()); 33 | 34 | os << rang::fg::magenta << "Data:" << rang::style::reset << "\n"; 35 | os << "\tChannel 1: " << format_number(h->data1()->size()) << "\n"; 36 | os << "\tChannel 2: " << format_number(h->data2()->size()) << "\n"; 37 | os << "\tChannel 3: " << format_number(h->data3()->size()) << "\n"; 38 | os << "\tChannel 4: " << format_number(h->data4()->size()) << "\n"; 39 | os << std::flush; 40 | } 41 | 42 | void Hantek::print_header(std::ostream& os) 43 | { 44 | os << rang::fg::magenta << "Common:" << rang::style::reset << "\n"; 45 | os << "\tVersion: " << h->header()->version() << "\n"; 46 | os << "\tAcquisition Mode: " 47 | << ACQUISITION_MODE[h->header()->channel1()->acquisition_mode()] << " [" 48 | << h->header()->channel1()->acquisition_mode() << "]\n"; 49 | os << "\tTimebase: " 50 | << format_number(TIMEBASE[h->header()->channel1()->timebase()], "s") << " [" 51 | << (int)h->header()->channel1()->timebase() << "]\n"; 52 | os << "\tSampling Depth: " 53 | << format_number(h->header()->channel1()->sampling_depth()) << "\n"; 54 | os << "\tSamples per Second: " 55 | << format_number(h->header()->channel1()->samples_per_second()) << "\n"; 56 | os << "\tTrigger Type: " << TRIGGER_NAME[h->header()->channel1()->trigger_type()] 57 | << " [" << h->header()->channel1()->trigger_type() << "]\n"; 58 | os << "\tTrigger Channel: " << h->header()->channel1()->trigger_channel() << "\n"; 59 | os << "\tTrigger Level: " << h->header()->channel1()->trigger_level() << "\n"; 60 | os << "\tHorizontal Offset: " << h->header()->channel1()->horizontal_offset() 61 | << "\n"; 62 | os << std::flush; 63 | } 64 | 65 | void Hantek::print_channel(std::ostream& os, int n, const hantek_t::channel_t* ch) 66 | { 67 | os << rang::fg::magenta << "Channel " << n << ": " << rang::style::reset << "\n"; 68 | if (ch->enabled()) { 69 | auto volts_per_div = VOLTS_PER_DIV[ch->volts_per_div()]; 70 | auto volts_per_div_str 71 | = format_number(volts_per_div * pow(10, ch->mode()), "V"); 72 | 73 | os << "\tSample Count: " << format_number(ch->sample_count()) << "\n"; 74 | os << "\tOffset: " << ch->offset() << "\n"; 75 | os << "\tVolts per Division: " << volts_per_div_str << " [" 76 | << (int)ch->volts_per_div() << "]\n"; 77 | os << "\tProbe Mode: " << pow(10, ch->mode()) << "x [" << (int)ch->mode() 78 | << "]\n"; 79 | } else { 80 | os << "\tDisabled\n"; 81 | } 82 | os << std::flush; 83 | } 84 | 85 | double Hantek::timebase() 86 | { 87 | return 1.0f / h->header()->channel1()->samples_per_second(); 88 | } 89 | 90 | std::tuple, std::optional, std::optional, 91 | std::optional> 92 | Hantek::next() 93 | { 94 | auto d1 = h->data1(); 95 | auto d2 = h->data2(); 96 | auto d3 = h->data3(); 97 | auto d4 = h->data4(); 98 | 99 | if (idx < d1->size() || idx < d2->size() || idx < d3->size() || idx < d4->size()) { 100 | auto ch1 = get_data(idx, h->header()->channel1(), d1); 101 | auto ch2 = get_data(idx, h->header()->channel2(), d2); 102 | auto ch3 = get_data(idx, h->header()->channel3(), d3); 103 | auto ch4 = get_data(idx, h->header()->channel4(), d4); 104 | 105 | idx++; 106 | 107 | return std::tuple(ch1, ch2, ch3, ch4); 108 | } else { 109 | throw std::runtime_error("Out of data"); 110 | } 111 | } 112 | 113 | void Hantek::reset() { idx = 0; } 114 | 115 | std::optional Hantek::get_data( 116 | size_t idx, const hantek_t::channel_t* ch, const std::vector* data) 117 | { 118 | if (ch->enabled()) { 119 | // Hantek has 25 ticks per division 120 | auto volts_per_div 121 | = (VOLTS_PER_DIV[ch->volts_per_div()] * pow(10, ch->mode())) / 25.0f; 122 | 123 | if (idx < data->size()) { 124 | return std::optional(((*data)[idx] - ch->offset()) * volts_per_div); 125 | } 126 | } 127 | return std::nullopt; 128 | } 129 | -------------------------------------------------------------------------------- /cpp/src/hantek_parser.cpp: -------------------------------------------------------------------------------- 1 | // This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler 2 | // to rebuild 3 | 4 | #include "hantek_parser.hpp" 5 | #include "kaitai/exceptions.h" 6 | 7 | hantek_t::hantek_t( 8 | kaitai::kstream* p__io, kaitai::kstruct* p__parent, hantek_t* p__root) 9 | : kaitai::kstruct(p__io) 10 | { 11 | m__parent = p__parent; 12 | m__root = this; 13 | m_header = nullptr; 14 | m_data1 = nullptr; 15 | m_data2 = nullptr; 16 | m_data3 = nullptr; 17 | m_data4 = nullptr; 18 | m_footer = nullptr; 19 | _read(); 20 | } 21 | 22 | void hantek_t::_read() 23 | { 24 | m_header = std::unique_ptr(new header_t(m__io, this, m__root)); 25 | m_data1 = std::unique_ptr>(new std::vector()); 26 | const int l_data1 = header()->channel1()->sample_count(); 27 | for (int i = 0; i < l_data1; i++) { 28 | m_data1->push_back(std::move(m__io->read_s2le())); 29 | } 30 | m_data2 = std::unique_ptr>(new std::vector()); 31 | const int l_data2 = header()->channel2()->sample_count(); 32 | for (int i = 0; i < l_data2; i++) { 33 | m_data2->push_back(std::move(m__io->read_s2le())); 34 | } 35 | m_data3 = std::unique_ptr>(new std::vector()); 36 | const int l_data3 = header()->channel3()->sample_count(); 37 | for (int i = 0; i < l_data3; i++) { 38 | m_data3->push_back(std::move(m__io->read_s2le())); 39 | } 40 | m_data4 = std::unique_ptr>(new std::vector()); 41 | const int l_data4 = header()->channel4()->sample_count(); 42 | for (int i = 0; i < l_data4; i++) { 43 | m_data4->push_back(std::move(m__io->read_s2le())); 44 | } 45 | m_footer = std::unique_ptr(new footer_t(m__io, this, m__root)); 46 | } 47 | 48 | hantek_t::~hantek_t() { _clean_up(); } 49 | 50 | void hantek_t::_clean_up() { } 51 | 52 | hantek_t::header_t::header_t( 53 | kaitai::kstream* p__io, hantek_t* p__parent, hantek_t* p__root) 54 | : kaitai::kstruct(p__io) 55 | { 56 | m__parent = p__parent; 57 | m__root = p__root; 58 | m__unnamed2 = nullptr; 59 | m_channel1 = nullptr; 60 | m_channel2 = nullptr; 61 | m_channel3 = nullptr; 62 | m_channel4 = nullptr; 63 | m_thumbnail = nullptr; 64 | _read(); 65 | } 66 | 67 | void hantek_t::header_t::_read() 68 | { 69 | m_magic = m__io->read_bytes(4); 70 | if (!(magic() == std::string("\x6C\x77\x66\x00", 4))) { 71 | throw kaitai::validation_not_equal_error( 72 | std::string("\x6C\x77\x66\x00", 4), magic(), _io(), 73 | std::string("/types/header/seq/0")); 74 | } 75 | m_version = m__io->read_u4le(); 76 | m__unnamed2 = std::unique_ptr(new channel_t(m__io, this, m__root)); 77 | m_channel1 = std::unique_ptr(new channel_t(m__io, this, m__root)); 78 | m_channel2 = std::unique_ptr(new channel_t(m__io, this, m__root)); 79 | m_channel3 = std::unique_ptr(new channel_t(m__io, this, m__root)); 80 | m_channel4 = std::unique_ptr(new channel_t(m__io, this, m__root)); 81 | m_thumbnail = std::unique_ptr>(new std::vector()); 82 | const int l_thumbnail = 16; 83 | for (int i = 0; i < l_thumbnail; i++) { 84 | m_thumbnail->push_back(std::move(m__io->read_s1())); 85 | } 86 | } 87 | 88 | hantek_t::header_t::~header_t() { _clean_up(); } 89 | 90 | void hantek_t::header_t::_clean_up() { } 91 | 92 | hantek_t::channel_t::channel_t( 93 | kaitai::kstream* p__io, hantek_t::header_t* p__parent, hantek_t* p__root) 94 | : kaitai::kstruct(p__io) 95 | { 96 | m__parent = p__parent; 97 | m__root = p__root; 98 | _read(); 99 | } 100 | 101 | void hantek_t::channel_t::_read() 102 | { 103 | m_acquisition_mode = m__io->read_u2le(); 104 | m_enabled = m__io->read_u1(); 105 | m_timebase = m__io->read_u1(); 106 | m_sampling_depth = m__io->read_u4le(); 107 | m_sample_count = m__io->read_u4le(); 108 | m__unnamed5 = m__io->read_u4le(); 109 | m_samples_per_second = m__io->read_f8le(); 110 | m_trigger_type = m__io->read_u4le(); 111 | m_trigger_channel = m__io->read_u4le(); 112 | m_trigger_level = m__io->read_s4le(); 113 | m__unnamed10 = m__io->read_u4le(); 114 | m_horizontal_offset = m__io->read_s8le(); 115 | m_offset = m__io->read_s4le(); 116 | m_volts_per_div = m__io->read_u1(); 117 | m_mode = m__io->read_u1(); 118 | m__unnamed15 = m__io->read_u2le(); 119 | m_maybe_const = m__io->read_u4le(); 120 | m__unnamed17 = m__io->read_u4le(); 121 | } 122 | 123 | hantek_t::channel_t::~channel_t() { _clean_up(); } 124 | 125 | void hantek_t::channel_t::_clean_up() { } 126 | 127 | hantek_t::footer_t::footer_t( 128 | kaitai::kstream* p__io, hantek_t* p__parent, hantek_t* p__root) 129 | : kaitai::kstruct(p__io) 130 | { 131 | m__parent = p__parent; 132 | m__root = p__root; 133 | _read(); 134 | } 135 | 136 | void hantek_t::footer_t::_read() 137 | { 138 | m_magic = m__io->read_bytes(4); 139 | if (!(magic() == std::string("\x78\x56\x34\x12", 4))) { 140 | throw kaitai::validation_not_equal_error( 141 | std::string("\x78\x56\x34\x12", 4), magic(), _io(), 142 | std::string("/types/footer/seq/0")); 143 | } 144 | } 145 | 146 | hantek_t::footer_t::~footer_t() { _clean_up(); } 147 | 148 | void hantek_t::footer_t::_clean_up() { } 149 | -------------------------------------------------------------------------------- /cpp/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "hantek.hpp" 2 | #include "vcd.hpp" 3 | #include "version.hpp" 4 | 5 | #include 6 | #include 7 | 8 | int main(int argc, char* argv[]) 9 | { 10 | argparse::ArgumentParser program("hantek", VERSION); 11 | 12 | argparse::ArgumentParser info("info"); 13 | info.add_description("Show information about the lwf"); 14 | info.add_argument("filename").help("LWF file to process"); 15 | program.add_subparser(info); 16 | 17 | argparse::ArgumentParser vcd("vcd"); 18 | vcd.add_description("Generate a VCD file"); 19 | vcd.add_argument("filename").help("LWF file to process"); 20 | vcd.add_argument("output").help("VCD to generate"); 21 | program.add_subparser(vcd); 22 | 23 | try { 24 | program.parse_args(argc, argv); 25 | } catch (const std::exception& err) { 26 | std::cerr << err.what() << std::endl; 27 | std::cerr << program; 28 | return 1; 29 | } 30 | 31 | std::cout << rang::fg::green << "Hantek Wave Viewer v" << VERSION << "\n" 32 | << rang::style::reset << std::endl; 33 | 34 | try { 35 | if (program.is_subcommand_used("info")) { 36 | auto filename = info.get("filename"); 37 | Hantek hantek(filename); 38 | hantek.print(); 39 | } else if (program.is_subcommand_used("vcd")) { 40 | auto filename = vcd.get("filename"); 41 | auto output = vcd.get("output"); 42 | 43 | Hantek hantek(filename); 44 | 45 | VCD vcd(output, hantek.timebase()); 46 | 47 | while (true) { 48 | try { 49 | auto data = hantek.next(); 50 | vcd.dump(std::get<0>(data), std::get<1>(data), std::get<2>(data), 51 | std::get<3>(data)); 52 | } catch (std::runtime_error e) { 53 | break; 54 | } 55 | } 56 | } else { 57 | std::cout << program << std::endl; 58 | } 59 | } catch (std::runtime_error& e) { 60 | std::cerr << e.what() << std::endl; 61 | } 62 | 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /cpp/src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | const std::string LARGE_UNITS[6] = { "", "K", "M", "G", "T", "P" }; 9 | const std::string SMALL_UNITS[6] = { "", "m", "u", "n", "p", "f" }; 10 | 11 | std::string format_large_number(double v, std::string units) 12 | { 13 | if (v == 0) 14 | return fmt::format("0{}", units); 15 | 16 | auto sign = ""; 17 | if (v < 0) { 18 | v *= -1; 19 | sign = "-"; 20 | } 21 | 22 | auto i = 0; 23 | while (v >= 1000 && i < 5) { 24 | v /= 1000; 25 | i++; 26 | } 27 | 28 | return fmt::format("{}{}{}{}", sign, v, LARGE_UNITS[i], units); 29 | } 30 | 31 | std::string format_small_number(double v, std::string units) 32 | { 33 | if (v == 0) 34 | return fmt::format("0{}", units); 35 | 36 | auto sign = ""; 37 | if (v < 0) { 38 | v *= -1; 39 | sign = "-"; 40 | } 41 | 42 | auto i = 0; 43 | while (v < 1 && i < 5) { 44 | v *= 1000; 45 | i++; 46 | } 47 | auto d = int(v); 48 | 49 | return fmt::format("{}{}{}{}", sign, d, SMALL_UNITS[i], units); 50 | } 51 | 52 | std::string format_number(double v, std::string units) 53 | { 54 | if (fabs(v) > 1) 55 | return format_large_number(v, units); 56 | return format_small_number(v, units); 57 | } 58 | 59 | TEST_CASE("testing format_number") 60 | { 61 | CHECK(format_number(10000000000) == "10G"); 62 | CHECK(format_number(1000000000) == "1G"); 63 | CHECK(format_number(100000000) == "100M"); 64 | CHECK(format_number(10000000) == "10M"); 65 | CHECK(format_number(1000000) == "1M"); 66 | CHECK(format_number(100000) == "100K"); 67 | CHECK(format_number(10000) == "10K"); 68 | CHECK(format_number(1000) == "1K"); 69 | CHECK(format_number(100) == "100"); 70 | CHECK(format_number(10) == "10"); 71 | CHECK(format_number(1) == "1"); 72 | CHECK(format_number(0.1) == "100m"); 73 | CHECK(format_number(0.01) == "10m"); 74 | CHECK(format_number(0.001) == "1m"); 75 | CHECK(format_number(0.0001) == "100u"); 76 | CHECK(format_number(0.00001) == "10u"); 77 | CHECK(format_number(0.000001) == "1u"); 78 | } 79 | -------------------------------------------------------------------------------- /cpp/src/vcd.cpp: -------------------------------------------------------------------------------- 1 | #include "vcd.hpp" 2 | #include "utils.hpp" 3 | #include "version.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | VCD::VCD(std::string filename, double timebase) 11 | { 12 | if (timebase < 0) { 13 | throw std::underflow_error("Timebase must be positive"); 14 | } 15 | 16 | out = std::ofstream(filename); 17 | 18 | auto now = std::chrono::system_clock::now(); 19 | auto now_time = std::chrono::system_clock::to_time_t(now); 20 | auto gmt_time = gmtime(&now_time); 21 | auto timestamp = std::put_time(gmt_time, "%Y-%m-%d %H:%M:%S"); 22 | 23 | out << "$version Hantek Wave Viewer v" << VERSION << " $end\n"; 24 | out << "$date " << timestamp << " $end\n"; 25 | out << "$timescale " << format_number(timebase) << "s $end\n"; 26 | out << "$scope module Hantek $end\n"; 27 | out << "$var real 8 # Channel1 $end\n"; 28 | out << "$var real 8 $ Channel2 $end\n"; 29 | out << "$var real 8 % Channel3 $end\n"; 30 | out << "$var real 8 & Channel4 $end\n"; 31 | out << "$upscope $end\n"; 32 | out << "$enddefinitions $end\n"; 33 | } 34 | 35 | VCD::~VCD() { out.close(); } 36 | 37 | void VCD::dump(std::optional ch1, std::optional ch2, 38 | std::optional ch3, std::optional ch4) 39 | { 40 | if (time == 0) { 41 | out << "$dumpvars\n"; 42 | if (ch1) 43 | out << fmt::format("r{:.16g} #\n", *ch1); 44 | if (ch2) 45 | out << fmt::format("r{:.16g} $\n", *ch2); 46 | if (ch3) 47 | out << fmt::format("r{:.16g} %\n", *ch3); 48 | if (ch4) 49 | out << fmt::format("r{:.16g} &\n", *ch4); 50 | out << "$end\n"; 51 | } else { 52 | out << fmt::format("#{}\n", time); 53 | if (ch1) 54 | out << fmt::format("r{:.16g} #\n", *ch1); 55 | if (ch2) 56 | out << fmt::format("r{:.16g} $\n", *ch2); 57 | if (ch3) 58 | out << fmt::format("r{:.16g} %\n", *ch3); 59 | if (ch4) 60 | out << fmt::format("r{:.16g} &\n", *ch4); 61 | } 62 | time++; 63 | } 64 | -------------------------------------------------------------------------------- /cpp/src/version.cpp: -------------------------------------------------------------------------------- 1 | const char* VERSION = "0.1.2"; -------------------------------------------------------------------------------- /cpp/test/main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Generating Python" 6 | kaitai-struct-compiler --target python --outdir python/hantek_wave_viewer/parser kaitai/hantek.ksy 7 | 8 | echo "Generating C++" 9 | kaitai-struct-compiler --target cpp_stl --cpp-standard=11 --outdir cpp kaitai/hantek.ksy 10 | mv cpp/hantek.cpp cpp/src/hantek_parser.cpp 11 | sed -i -e "s/hantek.h/hantek_parser.hpp/" cpp/src/hantek_parser.cpp 12 | mv cpp/hantek.h cpp/inc/hantek_parser.hpp 13 | -------------------------------------------------------------------------------- /kaitai/hantek.ksy: -------------------------------------------------------------------------------- 1 | meta: 2 | id: hantek 3 | file-extension: lwf 4 | endian: le 5 | seq: 6 | - id: header 7 | type: header 8 | - id: data1 9 | type: s2 10 | repeat: expr 11 | repeat-expr: header.channel1.sample_count 12 | - id: data2 13 | type: s2 14 | repeat: expr 15 | repeat-expr: header.channel2.sample_count 16 | - id: data3 17 | type: s2 18 | repeat: expr 19 | repeat-expr: header.channel3.sample_count 20 | - id: data4 21 | type: s2 22 | repeat: expr 23 | repeat-expr: header.channel4.sample_count 24 | - id: footer 25 | type: footer 26 | types: 27 | header: 28 | seq: 29 | - id: magic 30 | contents: 31 | - l 32 | - w 33 | - f 34 | - 0 35 | - id: version 36 | type: u4 37 | - type: channel # Unused channel? 38 | - id: channel1 39 | type: channel 40 | - id: channel2 41 | type: channel 42 | - id: channel3 43 | type: channel 44 | - id: channel4 45 | type: channel 46 | - id: thumbnail 47 | type: s1 48 | repeat: expr 49 | repeat-expr: 16 50 | channel: 51 | seq: 52 | - id: acquisition_mode 53 | type: u2 54 | - id: enabled 55 | type: u1 56 | - id: timebase 57 | type: u1 58 | - id: sampling_depth 59 | type: u4 60 | - id: sample_count 61 | type: u4 62 | - type: u4 # Unknown 4B 63 | - id: samples_per_second 64 | type: f8 65 | - id: trigger_type 66 | type: u4 67 | - id: trigger_channel 68 | type: u4 69 | - id: trigger_level 70 | type: s4 71 | - type: u4 # Unknown 4B 72 | - id: horizontal_offset 73 | type: s8 74 | - id: offset 75 | type: s4 76 | - id: volts_per_div 77 | type: u1 78 | - id: mode 79 | type: u1 80 | - type: u2 # Unknown 2B (sometimes 0x0032) 81 | - id: maybe_const 82 | type: u4 83 | - type: u4 # Unknown 4B 84 | footer: 85 | seq: 86 | - id: magic 87 | contents: 88 | - 0x78 89 | - 0x56 90 | - 0x34 91 | - 0x12 92 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | build 3 | hantek_wave_viewer.egg-info 4 | -------------------------------------------------------------------------------- /python/hantek_wave_viewer/__init__.py: -------------------------------------------------------------------------------- 1 | import click 2 | import matplotlib.pyplot as plt 3 | import matplotlib.transforms as transforms 4 | import numpy as np 5 | from kaitaistruct import KaitaiStructError 6 | from matplotlib.ticker import MultipleLocator, NullFormatter 7 | 8 | from ._version import VERSION 9 | from .parser import conversions, utils 10 | from .parser.hantek import Hantek 11 | 12 | filename_arg = click.argument( 13 | "filename", 14 | type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True), 15 | ) 16 | 17 | 18 | def load_wave(filename: str) -> Hantek: 19 | try: 20 | return Hantek.from_file(filename) 21 | except (KaitaiStructError, EOFError) as exc: 22 | raise click.ClickException(str(exc)) 23 | 24 | 25 | @click.group() 26 | @click.version_option(VERSION) 27 | def main(): 28 | click.secho(f"Hantek Wave Viewer: v{VERSION}", fg="green") 29 | 30 | 31 | @filename_arg 32 | @main.command() 33 | def info(filename: str): 34 | wave = load_wave(filename) 35 | 36 | click.secho("Common:", fg="magenta") 37 | click.echo(f"\tVersion: {wave.header.version}") 38 | utils.print_channel_common(wave.header.channel1) 39 | 40 | utils.print_channel(1, wave.header.channel1) 41 | utils.print_channel(2, wave.header.channel2) 42 | utils.print_channel(3, wave.header.channel3) 43 | utils.print_channel(4, wave.header.channel4) 44 | 45 | click.secho("Data:", fg="magenta") 46 | click.echo(f"\tChannel 1: {len(wave.data1)}") 47 | click.echo(f"\tChannel 2: {len(wave.data2)}") 48 | click.echo(f"\tChannel 3: {len(wave.data3)}") 49 | click.echo(f"\tChannel 4: {len(wave.data4)}") 50 | 51 | 52 | @filename_arg 53 | @main.command() 54 | def view(filename: str): 55 | wave = load_wave(filename) 56 | 57 | # Build the data array for each channel 58 | data1 = np.array(wave.data1) 59 | data2 = np.array(wave.data2) 60 | data3 = np.array(wave.data3) 61 | data4 = np.array(wave.data4) 62 | 63 | # Time axis we just use channel 1 because the info. is the same for every channel 64 | # Note: won't work if Ch1 is off and Ch2 is on. 65 | # TODO: Look at all 4 channels to determine the correct value. 66 | timebase = conversions.TIMEBASE[wave.header.channel1.timebase] 67 | timebase_str = utils.format_number(timebase, "s") 68 | seconds_per_sample = 1 / wave.header.channel1.samples_per_second 69 | time = np.arange(0, float(wave.header.channel1.sample_count)) 70 | time *= seconds_per_sample 71 | 72 | # Darkmode to simulate oscilloscope 73 | plt.style.use("dark_background") 74 | fig, ax = plt.subplots() 75 | 76 | # Setup the graph like an oscilloscope graticule 77 | ax.set_ylim(-100, 100) 78 | ax.xaxis.set_major_locator(MultipleLocator(timebase)) 79 | ax.xaxis.set_major_formatter(NullFormatter()) 80 | ax.xaxis.set_minor_locator(MultipleLocator(timebase / 5)) 81 | ax.yaxis.set_major_formatter(NullFormatter()) 82 | ax.grid(which="major", axis="both", color="0.5") 83 | ax.grid(which="minor", axis="both", color="0.2") 84 | ax.minorticks_on() 85 | 86 | # Plot the channels 87 | ytrans = transforms.blended_transform_factory( 88 | ax.get_yticklabels()[0].get_transform(), ax.transData 89 | ) 90 | if len(data1) > 0: 91 | vpd = conversions.VOLTS_PER_DIV[wave.header.channel1.volts_per_div] 92 | vpd_str = utils.format_number(vpd * (10**wave.header.channel1.mode), "V") 93 | col = conversions.CHANNEL_COLOUR[0] 94 | 95 | ax.plot(time, data1, color=col, label=f"Ch1: {vpd_str}") 96 | ax.axhline(y=wave.header.channel1.offset, color=col, lw=0.8, ls="-") 97 | ax.text( 98 | 0, 99 | wave.header.channel1.offset, 100 | "1>", 101 | color=col, 102 | transform=ytrans, 103 | ha="right", 104 | va="center", 105 | ) 106 | if len(data2) > 0: 107 | vpd = conversions.VOLTS_PER_DIV[wave.header.channel2.volts_per_div] 108 | vpd_str = utils.format_number(vpd * (10**wave.header.channel1.mode), "V") 109 | col = conversions.CHANNEL_COLOUR[1] 110 | 111 | ax.plot(time, data2, color=col, label=f"Ch2: {vpd_str}") 112 | ax.axhline(y=wave.header.channel2.offset, color=col, lw=0.8, ls="-") 113 | ax.text( 114 | 0, 115 | wave.header.channel2.offset, 116 | "2>", 117 | color=col, 118 | transform=ytrans, 119 | ha="right", 120 | va="center", 121 | ) 122 | if len(data3) > 0: 123 | vpd = conversions.VOLTS_PER_DIV[wave.header.channel3.volts_per_div] 124 | vpd_str = utils.format_number(vpd * (10**wave.header.channel1.mode), "V") 125 | col = conversions.CHANNEL_COLOUR[2] 126 | 127 | ax.plot(time, data3, color=col, label=f"Ch3: {vpd_str}") 128 | ax.axhline(y=wave.header.channel3.offset, color=col, lw=0.8, ls="-") 129 | ax.text( 130 | 0, 131 | wave.header.channel3.offset, 132 | "3>", 133 | color=col, 134 | transform=ytrans, 135 | ha="right", 136 | va="center", 137 | ) 138 | if len(data4) > 0: 139 | vpd = conversions.VOLTS_PER_DIV[wave.header.channel4.volts_per_div] 140 | vpd_str = utils.format_number(vpd * (10**wave.header.channel1.mode), "V") 141 | col = conversions.CHANNEL_COLOUR[3] 142 | 143 | ax.plot(time, data4, color=col, label=f"Ch4: {vpd_str}") 144 | ax.axhline(y=wave.header.channel4.offset, color=col, lw=0.8, ls="-") 145 | ax.text( 146 | 0, 147 | wave.header.channel4.offset, 148 | "4>", 149 | color=col, 150 | transform=ytrans, 151 | ha="right", 152 | va="center", 153 | ) 154 | 155 | # Trigger line 156 | trigger_level = wave.header.channel1.trigger_level 157 | trigger_channel = wave.header.channel1.trigger_channel 158 | col = conversions.CHANNEL_COLOUR[trigger_channel] 159 | ax.axhline(y=trigger_level, color=col, lw=0.8, ls="-") 160 | ax.text(0, trigger_level, "T", color=col, transform=ytrans, ha="right", va="center") 161 | 162 | # Timebase Text 163 | samples_per_second = utils.format_number(wave.header.channel1.samples_per_second) 164 | sampling_depth = utils.format_number(wave.header.channel1.sampling_depth) 165 | plt.title(f"Timebase: {timebase_str}, {samples_per_second}Sa/s, {sampling_depth}Pt") 166 | 167 | # Display it 168 | ax.legend() 169 | plt.show() 170 | -------------------------------------------------------------------------------- /python/hantek_wave_viewer/__main__.py: -------------------------------------------------------------------------------- 1 | from . import main 2 | 3 | if __name__ == "__main__": 4 | main() 5 | -------------------------------------------------------------------------------- /python/hantek_wave_viewer/_version.py: -------------------------------------------------------------------------------- 1 | VERSION = "0.1.0" 2 | -------------------------------------------------------------------------------- /python/hantek_wave_viewer/parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/python/hantek_wave_viewer/parser/__init__.py -------------------------------------------------------------------------------- /python/hantek_wave_viewer/parser/conversions.py: -------------------------------------------------------------------------------- 1 | TIMEBASE = { 2 | 32: 100, 3 | 31: 50, 4 | 30: 20, 5 | 29: 10, 6 | 28: 5, 7 | 27: 2, 8 | 26: 1, 9 | 25: 500e-3, 10 | 24: 200e-3, 11 | 23: 100e-3, 12 | 22: 50e-3, 13 | 21: 20e-3, 14 | 20: 10e-3, 15 | 19: 5e-3, 16 | 18: 2e-3, 17 | 17: 1e-3, 18 | 16: 500e-6, 19 | 15: 200e-6, 20 | 14: 100e-6, 21 | 13: 50e-6, 22 | 12: 20e-6, 23 | 11: 10e-6, 24 | 10: 5e-6, 25 | 9: 2e-6, 26 | 8: 1e-6, 27 | 7: 500e-9, 28 | 6: 200e-9, 29 | 5: 100e-9, 30 | 4: 50e-9, 31 | 3: 20e-9, 32 | 2: 10e-9, 33 | 1: 5e-9, 34 | 0: 2e-9, 35 | } 36 | 37 | VOLTS_PER_DIV = { 38 | 13: 10, 39 | 12: 5, 40 | 11: 2, 41 | 10: 1, 42 | 9: 500e-3, 43 | 8: 200e-3, 44 | 7: 100e-3, 45 | 6: 50e-3, 46 | 5: 20e-3, 47 | 4: 10e-3, 48 | 3: 5e-3, 49 | 2: 2e-3, 50 | 1: 1e-3, # Not available on DSO2C10 51 | 0: 500e-6, # Not available on DSO2C10 52 | } 53 | 54 | TRIGGER_NAME = [ 55 | "Edge", 56 | "Pulse", 57 | "Video", 58 | "Slope", 59 | "Overtime", 60 | "Window", 61 | "Pattern", 62 | "Interval", 63 | "Under Amp", 64 | "UART", 65 | "LIN", 66 | "CAN", 67 | "SPI", 68 | "IIC", 69 | ] 70 | 71 | ACQUISITION_MODE = [ 72 | "Normal", 73 | "Average", 74 | "Peak", 75 | "HR", 76 | ] 77 | 78 | CHANNEL_COLOUR = { 79 | 0: "#f8f601", 80 | 1: "#00f401", 81 | 2: "red", 82 | 3: "green", 83 | } 84 | -------------------------------------------------------------------------------- /python/hantek_wave_viewer/parser/hantek.py: -------------------------------------------------------------------------------- 1 | # This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild 2 | 3 | import kaitaistruct 4 | from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO 5 | 6 | 7 | if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9): 8 | raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__)) 9 | 10 | class Hantek(KaitaiStruct): 11 | def __init__(self, _io, _parent=None, _root=None): 12 | self._io = _io 13 | self._parent = _parent 14 | self._root = _root if _root else self 15 | self._read() 16 | 17 | def _read(self): 18 | self.header = Hantek.Header(self._io, self, self._root) 19 | self.data1 = [] 20 | for i in range(self.header.channel1.sample_count): 21 | self.data1.append(self._io.read_s2le()) 22 | 23 | self.data2 = [] 24 | for i in range(self.header.channel2.sample_count): 25 | self.data2.append(self._io.read_s2le()) 26 | 27 | self.data3 = [] 28 | for i in range(self.header.channel3.sample_count): 29 | self.data3.append(self._io.read_s2le()) 30 | 31 | self.data4 = [] 32 | for i in range(self.header.channel4.sample_count): 33 | self.data4.append(self._io.read_s2le()) 34 | 35 | self.footer = Hantek.Footer(self._io, self, self._root) 36 | 37 | class Header(KaitaiStruct): 38 | def __init__(self, _io, _parent=None, _root=None): 39 | self._io = _io 40 | self._parent = _parent 41 | self._root = _root if _root else self 42 | self._read() 43 | 44 | def _read(self): 45 | self.magic = self._io.read_bytes(4) 46 | if not self.magic == b"\x6C\x77\x66\x00": 47 | raise kaitaistruct.ValidationNotEqualError(b"\x6C\x77\x66\x00", self.magic, self._io, u"/types/header/seq/0") 48 | self.version = self._io.read_u4le() 49 | self._unnamed2 = Hantek.Channel(self._io, self, self._root) 50 | self.channel1 = Hantek.Channel(self._io, self, self._root) 51 | self.channel2 = Hantek.Channel(self._io, self, self._root) 52 | self.channel3 = Hantek.Channel(self._io, self, self._root) 53 | self.channel4 = Hantek.Channel(self._io, self, self._root) 54 | self.thumbnail = [] 55 | for i in range(16): 56 | self.thumbnail.append(self._io.read_s1()) 57 | 58 | 59 | 60 | class Channel(KaitaiStruct): 61 | def __init__(self, _io, _parent=None, _root=None): 62 | self._io = _io 63 | self._parent = _parent 64 | self._root = _root if _root else self 65 | self._read() 66 | 67 | def _read(self): 68 | self.acquisition_mode = self._io.read_u2le() 69 | self.enabled = self._io.read_u1() 70 | self.timebase = self._io.read_u1() 71 | self.sampling_depth = self._io.read_u4le() 72 | self.sample_count = self._io.read_u4le() 73 | self._unnamed5 = self._io.read_u4le() 74 | self.samples_per_second = self._io.read_f8le() 75 | self.trigger_type = self._io.read_u4le() 76 | self.trigger_channel = self._io.read_u4le() 77 | self.trigger_level = self._io.read_s4le() 78 | self._unnamed10 = self._io.read_u4le() 79 | self.horizontal_offset = self._io.read_s8le() 80 | self.offset = self._io.read_s4le() 81 | self.volts_per_div = self._io.read_u1() 82 | self.mode = self._io.read_u1() 83 | self._unnamed15 = self._io.read_u2le() 84 | self.maybe_const = self._io.read_u4le() 85 | self._unnamed17 = self._io.read_u4le() 86 | 87 | 88 | class Footer(KaitaiStruct): 89 | def __init__(self, _io, _parent=None, _root=None): 90 | self._io = _io 91 | self._parent = _parent 92 | self._root = _root if _root else self 93 | self._read() 94 | 95 | def _read(self): 96 | self.magic = self._io.read_bytes(4) 97 | if not self.magic == b"\x78\x56\x34\x12": 98 | raise kaitaistruct.ValidationNotEqualError(b"\x78\x56\x34\x12", self.magic, self._io, u"/types/footer/seq/0") 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /python/hantek_wave_viewer/parser/utils.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | import click 4 | 5 | from . import conversions 6 | from .hantek import Hantek 7 | 8 | _large_units = ["", "K", "M", "G", "T", "P"] 9 | _small_units = ["", "m", "u", "n", "p", "f"] 10 | 11 | 12 | def format_large(v, units=""): 13 | d = Decimal(v) 14 | 15 | if d == 0: 16 | return f"0{units}" 17 | 18 | sign = "" 19 | if d < 0: 20 | d *= -1 21 | sign = "-" 22 | 23 | i = 0 24 | while d >= 1000 and i < len(_large_units) - 1: 25 | d /= 1000 26 | i += 1 27 | return f"{sign}{d}{_large_units[i]}{units}" 28 | 29 | 30 | def format_small(v, units=""): 31 | d = Decimal(v) 32 | 33 | if d == 0: 34 | return f"0{units}" 35 | 36 | sign = "" 37 | if d < 0: 38 | d *= -1 39 | sign = "-" 40 | 41 | i = 0 42 | while d < 1 and i < len(_small_units) - 1: 43 | d *= 1000 44 | i += 1 45 | d = d.to_integral_value() 46 | return f"{sign}{d}{_small_units[i]}{units}" 47 | 48 | 49 | def format_number(v, units=""): 50 | if abs(Decimal(v)) > 1: 51 | return format_large(v, units) 52 | return format_small(v, units) 53 | 54 | 55 | def print_channel_common(channel: Hantek.Channel): 56 | timebase = conversions.TIMEBASE[channel.timebase] 57 | 58 | click.echo( 59 | "\tAcquisition Mode:" 60 | f" {conversions.ACQUISITION_MODE[channel.acquisition_mode]}" 61 | f" [{channel.acquisition_mode}]" 62 | ) 63 | click.echo(f"\tTimebase: {format_number(timebase, 's')} [{channel.timebase}]") 64 | click.echo(f"\tSampling Depth: {channel.sampling_depth}") 65 | click.echo(f"\tSamples per Second: {channel.samples_per_second}") 66 | click.echo( 67 | f"\tTrigger Type: {conversions.TRIGGER_NAME[channel.trigger_type]}" 68 | f" [{channel.trigger_type}]" 69 | ) 70 | click.echo(f"\tTrigger Channel: {channel.trigger_channel}") 71 | click.echo(f"\tTrigger Level: {channel.trigger_level}") 72 | click.echo(f"\tHorizontal Offset: {channel.horizontal_offset}") 73 | 74 | 75 | def print_channel(n: int, channel: Hantek.Channel): 76 | click.secho(f"Channel {n}", fg="magenta") 77 | 78 | if channel.enabled: 79 | volts_per_div = conversions.VOLTS_PER_DIV[channel.volts_per_div] 80 | volts_per_div_str = format_number(volts_per_div * (10**channel.mode), "V") 81 | 82 | click.echo(f"\tSample Count: {channel.sample_count}") 83 | click.echo(f"\tOffset: {channel.offset}") 84 | click.echo( 85 | f"\tVots per Division: {volts_per_div_str} [{channel.volts_per_div}]" 86 | ) 87 | click.echo(f"\tProbe Mode: {10 ** channel.mode}x [{channel.mode}]") 88 | else: 89 | click.echo("\tDisabled") 90 | -------------------------------------------------------------------------------- /python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "hantek_wave_viewer" 7 | authors = [ 8 | {name = "Matt Davis"}, 9 | ] 10 | description = "Viewer for Hantek DSO lwf files" 11 | readme = "README.md" 12 | license = {text ="MIT"} 13 | classifiers = [ 14 | "Programming Language :: Python :: 3" 15 | ] 16 | dependencies = [ 17 | "click == 8.1.7", 18 | "kaitaistruct == 0.10", 19 | "matplotlib == 3.8.2", 20 | ] 21 | dynamic = ["version"] 22 | 23 | [project.scripts] 24 | hantek_wave_viewer = "hantek_wave_viewer:main" 25 | 26 | [tool.setuptools] 27 | packages = ["hantek_wave_viewer"] 28 | 29 | [tool.setuptools.dynamic] 30 | version = {attr = "hantek_wave_viewer._version.VERSION"} 31 | 32 | [tool.isort] 33 | profile = "black" 34 | 35 | [tool.pylint.format] 36 | max-line-length = "88" 37 | 38 | [tool.mypy] 39 | check_untyped_defs = true 40 | 41 | [tool.black] 42 | extend-exclude = "parser/hantek.py" 43 | -------------------------------------------------------------------------------- /python/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | extend-ignore = E203 4 | exclude = hantek_wave_viewer/parser/hantek.py 5 | 6 | [pycodestyle] 7 | ignore = E203 8 | max_line_length = 88 9 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /python/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/python/tests/__init__.py -------------------------------------------------------------------------------- /python/tests/parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/python/tests/parser/__init__.py -------------------------------------------------------------------------------- /python/tests/parser/test_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from hantek_wave_viewer.parser import utils 4 | 5 | 6 | class UtilsTestCase(unittest.TestCase): 7 | def test_format_small(self): 8 | self.assertEqual(utils.format_small("100"), "100") 9 | self.assertEqual(utils.format_small("10"), "10") 10 | self.assertEqual(utils.format_small("1"), "1") 11 | self.assertEqual(utils.format_small("0.1"), "100m") 12 | self.assertEqual(utils.format_small("0.01"), "10m") 13 | self.assertEqual(utils.format_small("0.001"), "1m") 14 | self.assertEqual(utils.format_small("0.0001"), "100u") 15 | self.assertEqual(utils.format_small("0.00001"), "10u") 16 | self.assertEqual(utils.format_small("0.000001"), "1u") 17 | self.assertEqual(utils.format_small("0.0000001"), "100n") 18 | self.assertEqual(utils.format_small("0.00000001"), "10n") 19 | 20 | self.assertEqual(utils.format_small("0.000000016"), "16n") 21 | 22 | def test_format_large(self): 23 | self.assertEqual(utils.format_large("1"), "1") 24 | self.assertEqual(utils.format_large("10"), "10") 25 | self.assertEqual(utils.format_large("100"), "100") 26 | self.assertEqual(utils.format_large("1000"), "1K") 27 | self.assertEqual(utils.format_large("10000"), "10K") 28 | self.assertEqual(utils.format_large("100000"), "100K") 29 | self.assertEqual(utils.format_large("1000000"), "1M") 30 | self.assertEqual(utils.format_large("10000000"), "10M") 31 | self.assertEqual(utils.format_large("100000000"), "100M") 32 | self.assertEqual(utils.format_large("1000000000"), "1G") 33 | self.assertEqual(utils.format_large("10000000000"), "10G") 34 | 35 | self.assertEqual(utils.format_large("1250000000"), "1.25G") 36 | 37 | def test_format_number(self): 38 | self.assertEqual(utils.format_number("1250"), "1.25K") 39 | self.assertEqual(utils.format_number("0"), "0") 40 | self.assertEqual(utils.format_number("0.125"), "125m") 41 | -------------------------------------------------------------------------------- /wavefiles/README.md: -------------------------------------------------------------------------------- 1 | # Wavefile Samples 2 | 3 | To determine the header structure I made some sample captures on a DSO2C10 and observed differences in the header using the [Kaitai Web IDE](https://ide.kaitai.io). 4 | 5 | From this it can be seen that there is a consistent 348 Bytes header. Further inspection of the larger files shows that it is actually 344 Bytes header with a 4 Byte footer (constant 0x78563412 - which implies the file is little-endian). Also of note is that the scope was set to 4000 samples and the file grows by 8000 Bytes per channel, indicating `16-bit signed integer` as the data type. 6 | 7 | I used [Kaitai Struct](https://kaitai.io) to analyse the files and mark the various bytes in the header that aligned to the parameters that I had changes. The `ksy` file is available in the [repo](https://github.com/mattdavis90/hantek-wave-viewer/blob/master/hantek_wave_viewer/kaitai/hantek.ksy). From this file Kaitai generates Python and C++ bindings. 8 | 9 | ### Included Sample Files 10 | 11 | filename | Ch1 VpD, offset | Ch2 VpD, Offset | Timebase | Trigger | Comments | filesize 12 | ---------|-----------------|-----------------|------------|---------|--------------------------|---------- 13 | 26_0 | Off, 1V,0V | Off,1V,0V | 2m, 0 | Ch1,0V | | 348 14 | 26_1 | Off, 1V,0V | Off,1V,0V | 1m, 0 | Ch1,0V | | 348 15 | 26_2 | Off, 1V,0V | Off,1V,0V | 500u, 0 | Ch1,0V | | 348 16 | 26_3 | Off, 1V,0V | Off,1V,0V | 200u, 0 | Ch1,0V | | 348 17 | 26_4 | Off, 1V,0V | Off,1V,0V | 100u, 0 | Ch1,0V | | 348 18 | 26_5 | On, 1V,0V | Off,1V,0V | 200u, 0 | Ch1,0V | | 8348 19 | 26_6 | On, 2V,0V | Off,1V,0V | 200u, 0 | Ch1,0V | | 8348 20 | 26_7 | On, 5V,0V | Off,1V,0V | 200u, 0 | Ch1,0V | | 8348 21 | 26_8 | On,10V,0V | Off,1V,0V | 200u, 0 | Ch1,0V | | 8348 22 | 26_9 | On, 2V,2V | Off,1V,0V | 200u, 0 | Ch1,0V | | 8348 23 | 26_10 | On, 2V,4V | Off,1V,0V | 200u, 0 | Ch1,0V | | 8348 24 | 26_11 | On, 2V,6V | Off,1V,0V | 200u, 0 | Ch1,0V | | 8348 25 | 26_12 | On, 2V,0V | Off,1V,0V | 200u, 0 | Ch1,0V | 5V:1KHz Square on Ch1 | 8348 26 | 26_13 | On, 2V,0V | Off,1V,0V | 200u, 0 | Ch1,2V | 5V:1KHz Square on Ch1 | 8348 27 | 26_14 | Off, 2V,0V | On,2V,0V | 200u, 0 | Ch1,0V | 5V:1KHz Square on Ch2 | 8348 28 | 26_15 | Off, 2V,0V | On,2V,0V | 200u, 0 | Ch2,2V | 5V:1KHz Square on Ch2 | 8348 29 | 26_16 | Off, 2V,0V | On,5V,0V | 200u, 0 | Ch2,2V | 5V:1KHz Square on Ch2 | 8348 30 | 26_17 | Off, 2V,0V | On,2V,2V | 200u, 0 | Ch2,2V | 5V:1KHz Square on Ch2 | 8348 31 | 26_18 | On, 2V,0V | On,2V,0V | 200u, 0 | Ch1,2V | 5V:1KHz Square on Ch1 | 16348 32 | 26_19 | On, 2V,0V | On,2V,0V | 200u, 0 | Ch2,2V | 5V:1KHz Square on Ch2 | 16348 33 | 29_0 | On, 2V,0V | Off,2V,0V | 200u, 0 | Ch1,2V | | 8348 34 | 29_1 | On, 2V,0V | Off,2V,0V | 200u,-200u | Ch1,2V | | 8348 35 | 29_2 | On, 2V,0V | Off,2V,0V | 200u,-400u | Ch1,2V | | 8348 36 | 29_3 | On, 2V,0V | Off,2V,0V | 200u, 200u | Ch1,2V | | 8348 37 | 30_1 | On, 2V,0V | Off,2V,0V | 1u, 0 | Ch1,2V | | 8348 38 | 30_2 | On, 2V,0V | Off,2V,0V | 200u, 0 | Ch1,2V | Gnd Coupling - No change | 8348 39 | 30_3 | On, 2V,0V | Off,2V,0V | 200u, 0 | Ch1,2V | AC coupling - No change | 8348 40 | 30_4 | On, 2V,0V | Off,2V,0V | 200u, 0 | Ch1,2V | Pulse trigger | 8348 41 | 30_5 | On, 2V,0V | Off,2V,0V | 200u, 0 | Ch1,2V | Falling edge - No change | 8348 42 | 30_6 | On, 2V,0V | Off,2V,0V | 200u, 0 | Ch1,2V | Either edge - No change | 8348 43 | 30_7 | On, 2V,0V | Off,2V,0V | 200u, 0 | Ch1,2V | Normal mode - No change | 8348 44 | 30_8 | On, 2V,0V | Off,2V,0V | 200u, 0 | Ch1,2V | 16ns holdoff - No change | 8348 45 | -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_0.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_0.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_1.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_1.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_10.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_10.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_11.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_11.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_12.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_12.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_13.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_13.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_14.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_14.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_15.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_15.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_16.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_16.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_17.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_17.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_18.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_18.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_19.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_19.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_2.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_2.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_3.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_3.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_4.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_4.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_5.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_5.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_6.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_6.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_7.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_7.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_8.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_8.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_26_9.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_26_9.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_29_0.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_29_0.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_29_1.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_29_1.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_29_2.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_29_2.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_29_3.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_29_3.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_30_1.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_30_1.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_30_2.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_30_2.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_30_3.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_30_3.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_30_4.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_30_4.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_30_5.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_30_5.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_30_6.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_30_6.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_30_7.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_30_7.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_30_8.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_30_8.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_35_4.lwf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_35_4.lwf -------------------------------------------------------------------------------- /wavefiles/scope_wave_35_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_35_4.png -------------------------------------------------------------------------------- /wavefiles/scope_wave_35_4_py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdavis90/hantek-wave-viewer/6bd6f5ef1eaa43741163316e1bcae8d834761112/wavefiles/scope_wave_35_4_py.png --------------------------------------------------------------------------------