Repository: PX4/pyulog Branch: main Commit: 4379a77cc6d4 Files: 55 Total size: 302.1 KB Directory structure: gitextract_xzoh4fzy/ ├── .github/ │ └── workflows/ │ ├── deploy.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── LICENSE.md ├── MANIFEST.in ├── README.ko-KR.md ├── README.md ├── gen_expected_output.sh ├── pylintrc ├── pyproject.toml ├── pyulog/ │ ├── __init__.py │ ├── core.py │ ├── db.py │ ├── extract_gps_dump.py │ ├── extract_message.py │ ├── info.py │ ├── messages.py │ ├── migrate_db.py │ ├── params.py │ ├── px4.py │ ├── px4_events.py │ ├── sql/ │ │ ├── pyulog.1.sql │ │ ├── pyulog.2.sql │ │ ├── pyulog.3.sql │ │ ├── pyulog.4.sql │ │ └── pyulog.5.sql │ ├── ulog2csv.py │ ├── ulog2kml.py │ └── ulog2rosbag.py ├── run_tests.sh ├── setup.py └── test/ ├── __init__.py ├── sample.ulg ├── sample_appended.ulg ├── sample_appended_info.txt ├── sample_appended_messages.txt ├── sample_appended_multiple.ulg ├── sample_appended_multiple_info.txt ├── sample_appended_multiple_messages.txt ├── sample_info.txt ├── sample_log_small.ulg ├── sample_log_small_messages.txt ├── sample_logging_tagged_and_default_params.ulg ├── sample_logging_tagged_and_default_params_messages.txt ├── sample_messages.txt ├── sample_px4_events.ulg ├── sample_px4_events_messages.txt ├── test_cli.py ├── test_db.py ├── test_extract_message.py ├── test_migration.py ├── test_px4.py ├── test_px4_events.py └── test_ulog.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/deploy.yml ================================================ name: Deploy on: workflow_dispatch: pull_request: push: branches: - main release: types: - published jobs: build_dist: name: Build Dist runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - name: Build Dist run: pipx run build - name: Check Metadata run: pipx run twine check --strict dist/* - uses: actions/upload-artifact@v4 with: path: dist/ upload_all: name: Upload if release needs: [build_dist] runs-on: ubuntu-latest if: github.event_name == 'release' && github.event.action == 'published' environment: name: pypi url: https://pypi.org/p/pyulog permissions: id-token: write steps: - uses: actions/setup-python@v4 with: python-version: "3.x" - uses: actions/download-artifact@v4 with: name: artifact path: dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@v1.13.0 ================================================ FILE: .github/workflows/test.yml ================================================ name: Run Tests on: push: branches: - 'main' pull_request: branches: - '*' jobs: build: runs-on: ubuntu-latest strategy: fail-fast: false # don't cancel if a job from the matrix fails matrix: # https://devguide.python.org/versions python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 with: submodules: 'recursive' - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - name: Install Dependencies and pyulog run: | pip install pylint pip install --verbose .[test] - name: Running Tests run: | ./run_tests.sh ================================================ FILE: .gitignore ================================================ __pycache__ *.pyc build/ dist/ *.egg-info .eggs/ *.sqlite3 venv/ ================================================ FILE: .gitmodules ================================================ [submodule "3rd_party/libevents"] path = 3rd_party/libevents url = https://github.com/mavlink/libevents.git ================================================ FILE: LICENSE.md ================================================ Copyright (c) 2016, PX4 Pro Drone Autopilot All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of GpsDrivers nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: MANIFEST.in ================================================ include LICENSE.md recursive-include pyulog/sql * recursive-exclude test *.ulg ================================================ FILE: README.ko-KR.md ================================================ # pyulog 이 레포지토리에는 ULog 파일 및 스크립트를 파싱하는 python 패키지가 포함되어 있습니다. ULog는 self-describing 형식을 따르며, 해당 관련 문서는 다음과 같습니다(https://docs.px4.io/main/en/dev_log/ulog_file_format.html). 제공되는 명령어 스크립트는(command line scripts)는 아래와 같습니다: - `ulog_info`: ULog 파일의 정보를 나타냅니다. - `ulog_messages`: ULog 파일에 기록된 로그 메시지(logged messages)를 출력합니다. - `ulog_params`: ULog 파일에 저장된 파라미터들을 추출합니다. - `ulog2csv`: ULog 파일을 CSV 파일로 변환합니다. - `ulog2kml`: ULog 파일을 KML 파일로 변환합니다. ## 설치 패키지 설치: ```bash pip install pyulog ``` 소스코드를 통한 설치: ```bash python setup.py build install ``` ## 추가 개발 코드를 쉽게 변경 및 편집할 수 있는 형식으로 설치하려면 다음 명령 사용(해당 명령은 패키지를 Repo에 대한 링크로 설치합니다): ```bash pip install -e . ``` ## 테스트 ```bash pytest test ``` 또는, ```bash python setup.py test ``` ## 코드 검사(Code Checking) ```bash pylint pyulog/*.py ``` ## 명령어 스크립트 모든 스크립트는 시스템 전체 어플리케이션단에서 설치되며(Python 또는 시스템 경로를 지정하지 않고 커맨드 라인에서 호출), `-h` 플래그를 통해 각 스크립트의 사용법을 확인할 수 있습니다. 아래 섹션에서는 사용 구문 및 샘플 출력을 나타냅니다. (from [test/sample.ulg](test/sample.ulg)): ### ULog 파일로부터 정보 출력 (ulog_info) 사용: ```bash usage: ulog_info [-h] [-v] file.ulg Display information from an ULog file positional arguments: file.ulg ULog input file optional arguments: -h, --help show this help message and exit -v, --verbose Verbose output ``` 결과 예시: ```bash $ ulog_info sample.ulg Logging start time: 0:01:52, duration: 0:01:08 Dropouts: count: 4, total duration: 0.1 s, max: 62 ms, mean: 29 ms Info Messages: sys_name: PX4 time_ref_utc: 0 ver_hw: AUAV_X21 ver_sw: fd483321a5cf50ead91164356d15aa474643aa73 Name (multi id, message size in bytes) number of data points, total bytes actuator_controls_0 (0, 48) 3269 156912 actuator_outputs (0, 76) 1311 99636 commander_state (0, 9) 678 6102 control_state (0, 122) 3268 398696 cpuload (0, 16) 69 1104 ekf2_innovations (0, 140) 3271 457940 estimator_status (0, 309) 1311 405099 sensor_combined (0, 72) 17070 1229040 sensor_preflight (0, 16) 17072 273152 telemetry_status (0, 36) 70 2520 vehicle_attitude (0, 36) 6461 232596 vehicle_attitude_setpoint (0, 55) 3272 179960 vehicle_local_position (0, 123) 678 83394 vehicle_rates_setpoint (0, 24) 6448 154752 vehicle_status (0, 45) 294 13230 ``` ### ULog 파일에 기록된 로그 메시지 출력 (ulog_messages) 사용: ``` usage: ulog_messages [-h] file.ulg Display logged messages from an ULog file positional arguments: file.ulg ULog input file optional arguments: -h, --help show this help message and exit ``` 결과 예시: ``` ubuntu@ubuntu:~/github/pyulog/test$ ulog_messages sample.ulg 0:02:38 ERROR: [sensors] no barometer found on /dev/baro0 (2) 0:02:42 ERROR: [sensors] no barometer found on /dev/baro0 (2) 0:02:51 ERROR: [sensors] no barometer found on /dev/baro0 (2) 0:02:56 ERROR: [sensors] no barometer found on /dev/baro0 (2) ``` ### ULog 파일에 저장된 파라미터 추출 (ulog_params) 사용: ``` usage: ulog_params [-h] [-d DELIMITER] [-i] [-o] file.ulg [params.txt] Extract parameters from an ULog file positional arguments: file.ulg ULog input file params.txt Output filename (default=stdout) optional arguments: -h, --help show this help message and exit -d DELIMITER, --delimiter DELIMITER Use delimiter in CSV (default is ',') -i, --initial Only extract initial parameters -o, --octave Use Octave format ``` 결과 예시 (콘솔 출력): ``` ubuntu@ubuntu:~/github/pyulog/test$ ulog_params sample.ulg ATT_ACC_COMP,1 ATT_BIAS_MAX,0.0500000007451 ATT_EXT_HDG_M,0 ... VT_OPT_RECOV_EN,0 VT_TYPE,0 VT_WV_LND_EN,0 VT_WV_LTR_EN,0 VT_WV_YAWR_SCL,0.15000000596 ``` ### ULog 파일을 CSV 파일로 변환 (ulog2csv) 사용: ``` usage: ulog2csv [-h] [-m MESSAGES] [-d DELIMITER] [-o DIR] file.ulg Convert ULog to CSV positional arguments: file.ulg ULog input file optional arguments: -h, --help show this help message and exit -m MESSAGES, --messages MESSAGES Only consider given messages. Must be a comma- separated list of names, like 'sensor_combined,vehicle_gps_position' -d DELIMITER, --delimiter DELIMITER Use delimiter in CSV (default is ',') -o DIR, --output DIR Output directory (default is same as input file) ``` ### ULog 파일을 KML 파일로 변환 (ulog2kml) > **Note** 모듈 `simplekml` 이 사용자의 PC에 설치되어 있어야 합니다. 만약 설치되어 있지 않다면, 아래 명령어를 통해 설치하십시오. ``` pip install simplekml ``` 사용: ``` usage: ulog2kml [-h] [-o OUTPUT_FILENAME] [--topic TOPIC_NAME] [--camera-trigger CAMERA_TRIGGER] file.ulg Convert ULog to KML positional arguments: file.ulg ULog input file optional arguments: -h, --help show this help message and exit -o OUTPUT_FILENAME, --output OUTPUT_FILENAME output filename --topic TOPIC_NAME topic name with position data (default=vehicle_gps_position) --camera-trigger CAMERA_TRIGGER Camera trigger topic name (e.g. camera_capture) ``` ### ULog 파일을 rosbag 파일로 변환 (ulog2rosbag) > **Note** `px4_msgs`가 설치된 ROS 환경이 필요합니다. 사용: ``` usage: ulog2rosbag [-h] [-m MESSAGES] file.ulg result.bag Convert ULog to rosbag positional arguments: file.ulg ULog input file result.ulg rosbag output file optional arguments: -h, --help show this help message and exit -m MESSAGES, --messages MESSAGES Only consider given messages. Must be a comma- separated list of names, like 'sensor_combined,vehicle_gps_position' ``` ### Migrate/setup the database for use with the DatabaseULog class (ulog_migratedb) 사용: ``` usage: ulog_migratedb [-h] [-d DB_PATH] [-n] [-s SQL_DIR] [-f] Setup the database for DatabaseULog optional arguments: -h, --help show this help message and exit -d DB_PATH, --database DB_PATH Path to the database file -n, --noop Only print results, do not execute migration scripts. -s SQL_DIR, --sql SQL_DIR Directory with migration SQL files -f, --force Run the migration script even if the database is not created with this script. ``` 결과 예시 (콘솔 출력): ``` ubuntu@ubuntu:~/github/pyulog$ ulog_migratedb Using migration files in /home/ubuntu/github/pyulog/pyulog/sql. Database file pyulog.sqlite3 not found, creating it from scratch. Current schema version: 0 (database) and 1 (code). Executing /home/ubuntu/github/pyulog/pyulog/sql/pyulog.1.sql. Migration done. ``` ================================================ FILE: README.md ================================================ # pyulog This repository contains a python package to parse ULog files and scripts to convert and display them. ULog is a self-describing logging format which is documented [here](https://docs.px4.io/main/en/dev_log/ulog_file_format.html). The provided [command line scripts](#scripts) are: - `ulog_info`: display information from an ULog file. - `ulog_messages`: display logged messages from an ULog file. - `ulog_params`: extract parameters from an ULog file. - `ulog2csv`: convert ULog to CSV files. - `ulog2kml`: convert ULog to KML files. ## Installation Installation with package manager: ```bash pip install pyulog ``` Installation from source: ```bash python setup.py build install ``` ## Development To install the code in a format so that it can be easily edited use the following command (this will install the package as a link to the repo): ```bash pip install -e . ``` ## Testing ```bash pytest test ``` or ```bash python setup.py test ``` ## Code Checking ```bash pylint pyulog/*.py ``` ## Command Line Scripts All scripts are installed as system-wide applications (i.e. they be called on the command line without specifying Python or a system path), and support the `-h` flag for getting usage instructions. The sections below show the usage syntax and sample output (from [test/sample.ulg](test/sample.ulg)): ### Display information from an ULog file (ulog_info) Usage: ```bash usage: ulog_info [-h] [-v] file.ulg Display information from an ULog file positional arguments: file.ulg ULog input file optional arguments: -h, --help show this help message and exit -v, --verbose Verbose output ``` Example output: ```bash $ ulog_info sample.ulg Logging start time: 0:01:52, duration: 0:01:08 Dropouts: count: 4, total duration: 0.1 s, max: 62 ms, mean: 29 ms Info Messages: sys_name: PX4 time_ref_utc: 0 ver_hw: AUAV_X21 ver_sw: fd483321a5cf50ead91164356d15aa474643aa73 Name (multi id, message size in bytes) number of data points, total bytes actuator_controls_0 (0, 48) 3269 156912 actuator_outputs (0, 76) 1311 99636 commander_state (0, 9) 678 6102 control_state (0, 122) 3268 398696 cpuload (0, 16) 69 1104 ekf2_innovations (0, 140) 3271 457940 estimator_status (0, 309) 1311 405099 sensor_combined (0, 72) 17070 1229040 sensor_preflight (0, 16) 17072 273152 telemetry_status (0, 36) 70 2520 vehicle_attitude (0, 36) 6461 232596 vehicle_attitude_setpoint (0, 55) 3272 179960 vehicle_local_position (0, 123) 678 83394 vehicle_rates_setpoint (0, 24) 6448 154752 vehicle_status (0, 45) 294 13230 ``` ### Display logged messages from an ULog file (ulog_messages) Usage: ``` usage: ulog_messages [-h] file.ulg Display logged messages from an ULog file positional arguments: file.ulg ULog input file optional arguments: -h, --help show this help message and exit ``` Example output: ``` ubuntu@ubuntu:~/github/pyulog/test$ ulog_messages sample.ulg 0:02:38 ERROR: [sensors] no barometer found on /dev/baro0 (2) 0:02:42 ERROR: [sensors] no barometer found on /dev/baro0 (2) 0:02:51 ERROR: [sensors] no barometer found on /dev/baro0 (2) 0:02:56 ERROR: [sensors] no barometer found on /dev/baro0 (2) ``` ### Extract parameters from an ULog file (ulog_params) Usage: ``` usage: ulog_params [-h] [-d DELIMITER] [-i] [-o] file.ulg [params.txt] Extract parameters from an ULog file positional arguments: file.ulg ULog input file params.txt Output filename (default=stdout) optional arguments: -h, --help show this help message and exit -d DELIMITER, --delimiter DELIMITER Use delimiter in CSV (default is ',') -i, --initial Only extract initial parameters -o, --octave Use Octave format ``` Example output (to console): ``` ubuntu@ubuntu:~/github/pyulog/test$ ulog_params sample.ulg ATT_ACC_COMP,1 ATT_BIAS_MAX,0.0500000007451 ATT_EXT_HDG_M,0 ... VT_OPT_RECOV_EN,0 VT_TYPE,0 VT_WV_LND_EN,0 VT_WV_LTR_EN,0 VT_WV_YAWR_SCL,0.15000000596 ``` ### Convert ULog to CSV files (ulog2csv) Usage: ``` usage: ulog2csv [-h] [-m MESSAGES] [-d DELIMITER] [-o DIR] file.ulg Convert ULog to CSV positional arguments: file.ulg ULog input file optional arguments: -h, --help show this help message and exit -m MESSAGES, --messages MESSAGES Only consider given messages. Must be a comma- separated list of names, like 'sensor_combined,vehicle_gps_position' -d DELIMITER, --delimiter DELIMITER Use delimiter in CSV (default is ',') -o DIR, --output DIR Output directory (default is same as input file) ``` ### Convert ULog to KML files (ulog2kml) > **Note** The `simplekml` module must be installed on your computer. If not already present, you can install it with: ``` pip install simplekml ``` Usage: ``` usage: ulog2kml [-h] [-o OUTPUT_FILENAME] [--topic TOPIC_NAME] [--camera-trigger CAMERA_TRIGGER] file.ulg Convert ULog to KML positional arguments: file.ulg ULog input file optional arguments: -h, --help show this help message and exit -o OUTPUT_FILENAME, --output OUTPUT_FILENAME output filename --topic TOPIC_NAME topic name with position data (default=vehicle_gps_position) --camera-trigger CAMERA_TRIGGER Camera trigger topic name (e.g. camera_capture) ``` ### Convert ULog to rosbag files (ulog2rosbag) > **Note** You need a ROS environment with `px4_msgs` built and sourced. Usage: ``` usage: ulog2rosbag [-h] [-m MESSAGES] file.ulg result.bag Convert ULog to rosbag positional arguments: file.ulg ULog input file result.ulg rosbag output file optional arguments: -h, --help show this help message and exit -m MESSAGES, --messages MESSAGES Only consider given messages. Must be a comma- separated list of names, like 'sensor_combined,vehicle_gps_position' ``` ### Migrate/setup the database for use with the DatabaseULog class (ulog_migratedb) > **Warning** This command must be run whenever the schema changes, otherwise DatabaseULog won't function. > **Warning** Even if you store logs in the database, you should keep the original .ulg files. Otherwise you may lose your data. Usage: ``` usage: ulog_migratedb [-h] [-d DB_PATH] [-n] [-s SQL_DIR] [-f] Setup the database for DatabaseULog. optional arguments: -h, --help show this help message and exit -d DB_PATH, --database DB_PATH Path to the database file -n, --noop Only print results, do not execute migration scripts. -s SQL_DIR, --sql SQL_DIR Directory with migration SQL files -f, --force Run the migration script even if the database is not created with this script. ``` Example output (to console): ``` ubuntu@ubuntu:~/github/pyulog$ ulog_migratedb Using migration files in /home/ubuntu/github/pyulog/pyulog/sql. Database file pyulog.sqlite3 not found, creating it from scratch. Current schema version: 0 (database) and 1 (code). Executing /home/ubuntu/github/pyulog/pyulog/sql/pyulog.1.sql. Migration done. ``` ================================================ FILE: gen_expected_output.sh ================================================ #! /bin/bash # generate the expected output for the unittests for f in test/*.ulg; do echo "Processing $f" ulog_info -v "$f" > "${f%.*}"_info.txt ulog_messages "$f" > "${f%.*}"_messages.txt #ulog_params "$f" > "${f%.*}"_params.txt done ================================================ FILE: pylintrc ================================================ [MAIN] # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no # Clear in-memory caches upon conclusion of linting. Useful if running pylint # in a server-like mode. clear-cache-post-run=no # Load and enable all available extensions. Use --list-extensions to see a list # all available extensions. #enable-all-extensions= # In error mode, messages with a category besides ERROR or FATAL are # suppressed, and no reports are done by default. Error mode is compatible with # disabling specific errors. #errors-only= # Always return a 0 (non-error) status code, even if lint errors are found. # This is primarily useful in continuous integration scripts. #exit-zero= # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code. extension-pkg-allow-list= # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code. (This is an alternative name to extension-pkg-allow-list # for backward compatibility.) extension-pkg-whitelist= # Return non-zero exit code if any of these messages/categories are detected, # even if score is above --fail-under value. Syntax same as enable. Messages # specified are enabled, while categories only check already-enabled messages. fail-on= # Specify a score threshold under which the program will exit with error. fail-under=10 # Interpret the stdin as a python script, whose filename needs to be passed as # the module_or_package argument. #from-stdin= # Files or directories to be skipped. They should be base names, not paths. ignore=CVS # Add files or directories matching the regular expressions patterns to the # ignore-list. The regex matches against paths and can be in Posix or Windows # format. Because '\\' represents the directory delimiter on Windows systems, # it can't be used as an escape character. ignore-paths= # Files or directories matching the regular expression patterns are skipped. # The regex matches against base names, not paths. The default value ignores # Emacs file locks ignore-patterns= # List of module names for which member attributes should not be checked and # will not be imported (useful for modules/projects where namespaces are # manipulated during runtime and thus existing member attributes cannot be # deduced by static analysis). It supports qualified module names, as well as # Unix pattern matching. ignored-modules=numpy # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use, and will cap the count on Windows to # avoid hangs. jobs=1 # Control the amount of potential inferred values when inferring a single # object. This can help the performance when dealing with large functions or # complex, nested conditions. limit-inference-results=100 # List of plugins (as comma separated values of python module names) to load, # usually to register additional checkers. load-plugins= # Pickle collected data for later comparisons. persistent=yes # Resolve imports to .pyi stubs if available. May reduce no-member messages and # increase not-an-iterable messages. prefer-stubs=no # Minimum Python version to use for version dependent checks. Will default to # the version used to run pylint. py-version=3.13 # Discover python modules and packages in the file system subtree. recursive=no # Add paths to the list of the source roots. Supports globbing patterns. The # source root is an absolute path or a path relative to the current working # directory used to determine a package namespace for modules located under the # source root. source-roots= # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no # In verbose mode, extra non-checker-related info will be displayed. #verbose= [BASIC] # Naming style matching correct argument names. argument-naming-style=snake_case # Regular expression matching correct argument names. Overrides argument- # naming-style. If left empty, argument names will be checked with the set # naming style. argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming style matching correct attribute names. attr-naming-style=snake_case # Regular expression matching correct attribute names. Overrides attr-naming- # style. If left empty, attribute names will be checked with the set naming # style. attr-rgx=[a-z_][a-z0-9_]{2,30}$ # Bad variable names which should always be refused, separated by a comma. bad-names=foo, bar, baz, toto, tutu, tata # Bad variable names regexes, separated by a comma. If names match any regex, # they will always be refused bad-names-rgxs= # Naming style matching correct class attribute names. class-attribute-naming-style=any # Regular expression matching correct class attribute names. Overrides class- # attribute-naming-style. If left empty, class attribute names will be checked # with the set naming style. class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Naming style matching correct class constant names. class-const-naming-style=UPPER_CASE # Regular expression matching correct class constant names. Overrides class- # const-naming-style. If left empty, class constant names will be checked with # the set naming style. #class-const-rgx= # Naming style matching correct class names. class-naming-style=PascalCase # Regular expression matching correct class names. Overrides class-naming- # style. If left empty, class names will be checked with the set naming style. class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming style matching correct constant names. const-naming-style=UPPER_CASE # Regular expression matching correct constant names. Overrides const-naming- # style. If left empty, constant names will be checked with the set naming # style. const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 # Naming style matching correct function names. function-naming-style=snake_case # Regular expression matching correct function names. Overrides function- # naming-style. If left empty, function names will be checked with the set # naming style. function-rgx=[a-z_][a-z0-9_]{2,30}$ # Good variable names which should always be accepted, separated by a comma. good-names=i, j, k, ex, Run, _, t, x, q # Good variable names regexes, separated by a comma. If names match any regex, # they will always be accepted good-names-rgxs= # Include a hint for the correct naming format with invalid-name. include-naming-hint=no # Naming style matching correct inline iteration names. inlinevar-naming-style=any # Regular expression matching correct inline iteration names. Overrides # inlinevar-naming-style. If left empty, inline iteration names will be checked # with the set naming style. inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming style matching correct method names. method-naming-style=snake_case # Regular expression matching correct method names. Overrides method-naming- # style. If left empty, method names will be checked with the set naming style. method-rgx=[a-z_][a-z0-9_]{2,40}$ # Naming style matching correct module names. module-naming-style=snake_case # Regular expression matching correct module names. Overrides module-naming- # style. If left empty, module names will be checked with the set naming style. module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. # These decorators are taken in consideration only for invalid-name. property-classes=abc.abstractproperty # Regular expression matching correct type alias names. If left empty, type # alias names will be checked with the set naming style. #typealias-rgx= # Regular expression matching correct type variable names. If left empty, type # variable names will be checked with the set naming style. #typevar-rgx= # Naming style matching correct variable names. variable-naming-style=snake_case # Regular expression matching correct variable names. Overrides variable- # naming-style. If left empty, variable names will be checked with the set # naming style. variable-rgx=[a-z_][a-z0-9_]{2,30}$ [CLASSES] # Warn about protected attribute access inside special methods check-protected-access-in-special-methods=no # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__, __new__, setUp # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict, _fields, _replace, _source, _make # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs [DESIGN] # List of regular expressions of class ancestor names to ignore when counting # public methods (see R0903) exclude-too-few-public-methods= # List of qualified class names to ignore when counting class parents (see # R0901) ignored-parents= # Maximum number of arguments for function / method. max-args=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Maximum number of boolean expressions in an if statement (see R0916). max-bool-expr=5 # Maximum number of branch for function / method body. max-branches=12 # Maximum number of locals for function / method body. max-locals=20 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of positional arguments for function / method. max-positional-arguments=10 # Maximum number of public methods for a class (see R0904). max-public-methods=25 # Maximum number of return / yield for function / method body. max-returns=6 # Maximum number of statements in function / method body. max-statements=100 # Minimum number of public methods for a class (see R0903). min-public-methods=2 [EXCEPTIONS] # Exceptions that will emit a warning when caught. overgeneral-exceptions=builtins.Exception [FORMAT] # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Maximum number of characters on a single line. max-line-length=100 # Maximum number of lines in a module. max-module-lines=1200 # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no [IMPORTS] # List of modules that can be imported at any level, not just the top level # one. allow-any-import-level= # Allow explicit reexports by alias from a package __init__. allow-reexport-from-package=no # Allow wildcard imports from modules that define __all__. allow-wildcard-with-all=no # Deprecated modules which should not be used, separated by a comma. deprecated-modules=regsub, TERMIOS, Bastion, rexec # Output a graph (.gv or any supported image format) of external dependencies # to the given file (report RP0402 must not be disabled). ext-import-graph= # Output a graph (.gv or any supported image format) of all (i.e. internal and # external) dependencies to the given file (report RP0402 must not be # disabled). import-graph= # Output a graph (.gv or any supported image format) of internal dependencies # to the given file (report RP0402 must not be disabled). int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant # Couples of modules and preferred modules, separated by a comma. preferred-modules= [LOGGING] # The type of string formatting that logging methods do. `old` means using % # formatting, `new` is for `{}` formatting. logging-format-style=old # Logging modules to check that the string format arguments are in logging # function parameter format. logging-modules=logging [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, # UNDEFINED. confidence=HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once). You can also use "--disable=all" to # disable everything first and then re-enable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". disable=raw-checker-failed, bad-inline-option, locally-disabled, file-ignored, suppressed-message, useless-suppression, deprecated-pragma, use-symbolic-message-instead, use-implicit-booleaness-not-comparison-to-string, use-implicit-booleaness-not-comparison-to-zero, fixme, trailing-newlines, multiple-statements, too-few-public-methods, use-implicit-booleaness-not-len, useless-object-inheritance, consider-using-f-string, unused-argument # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. enable= [METHOD_ARGS] # List of qualified names (i.e., library.method) which require a timeout # parameter e.g. 'requests.api.get,requests.api.post' timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME, XXX, TODO # Regular expression of note tags to take in consideration. notes-rgx= [REFACTORING] # Maximum number of nested blocks for function / method body max-nested-blocks=6 # Complete name of functions that never returns. When checking for # inconsistent-return-statements if a never returning function is called then # it will be considered as an explicit return statement and no message will be # printed. never-returning-functions=sys.exit,argparse.parse_error # Let 'consider-using-join' be raised when the separator to join on would be # non-empty (resulting in expected fixes of the type: ``"- " + " - # ".join(items)``) suggest-join-with-non-empty-separator=yes [REPORTS] # Python expression which should return a score less than or equal to 10. You # have access to the variables 'fatal', 'error', 'warning', 'refactor', # 'convention', and 'info' which contain the number of messages in each # category, as well as 'statement' which is the total number of statements # analyzed. This score is used by the global evaluation report (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details. msg-template= # Set the output format. Available formats are: text, parseable, colorized, # json2 (improved json format), json (old json format) and msvs (visual # studio). You can also give a reporter class, e.g. # mypackage.mymodule.MyReporterClass. #output-format= # Tells whether to display a full report or only the messages. reports=no # Activate the evaluation score. score=yes [SIMILARITIES] # Comments are removed from the similarity computation ignore-comments=yes # Docstrings are removed from the similarity computation ignore-docstrings=yes # Imports are removed from the similarity computation ignore-imports=yes # Signatures are removed from the similarity computation ignore-signatures=yes # Minimum lines number of a similarity. min-similarity-lines=5 [SPELLING] # Limits count of emitted suggestions for spelling mistakes. max-spelling-suggestions=4 # Spelling dictionary name. No available dictionaries : You need to install # both the python package and the system dependency for enchant to work. spelling-dict= # List of comma separated words that should be considered directives if they # appear at the beginning of a comment and should not be checked. spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains the private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to the private dictionary (see the # --spelling-private-dict-file option) instead of raising a message. spelling-store-unknown-words=no [STRING] # This flag controls whether inconsistent-quotes generates a warning when the # character used as a quote delimiter is used inconsistently within a module. check-quote-consistency=no # This flag controls whether the implicit-str-concat should generate a warning # on implicit string concatenation in sequences defined over several lines. check-str-concat-over-line-jumps=no [TYPECHECK] # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= # Tells whether to warn about missing members when the owner of the attribute # is inferred to be None. ignore-none=yes # This flag controls whether pylint should warn about no-member and similar # checks whenever an opaque object is returned when inferring. The inference # can return multiple potential results while evaluating a Python object, but # some branches might not be evaluated, which results in partial inference. In # that case, it might be useful to still emit no-member and other checks for # the rest of the inferred objects. ignore-on-opaque-inference=yes # List of symbolic message names to ignore for Mixin members. ignored-checks-for-mixins=no-member, not-async-context-manager, not-context-manager, attribute-defined-outside-init # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=optparse.Values,thread._local,_thread._local # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. missing-member-hint=yes # The minimum edit distance a name should have in order to be considered a # similar match for a missing member name. missing-member-hint-distance=1 # The total number of similar names that should be taken in consideration when # showing a hint for a missing member. missing-member-max-choices=1 # Regex pattern to define which classes are considered mixins. mixin-class-rgx=.*[Mm]ixin # List of decorators that change the signature of a decorated function. signature-mutators= [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid defining new builtins when possible. additional-builtins= # Tells whether unused global variables should be treated as a violation. allow-global-unused-variables=yes # List of names allowed to shadow builtins allowed-redefined-builtins= # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_, _cb # A regular expression matching the name of dummy variables (i.e. expected to # not be used). dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy # Argument names that match this expression will be ignored. ignored-argument-names=_.* # Tells whether we should check for unused import in __init__ files. init-import=no # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,future.builtins ================================================ FILE: pyproject.toml ================================================ [build-system] requires = ["setuptools>=77.0.3", "setuptools-scm>=8"] build-backend = "setuptools.build_meta" [project] name = "pyulog" description = "Python log parser for ULog" license = "BSD-3-Clause" dependencies = [ "numpy < 1.25; python_version < '3.9'", "numpy >= 1.25; python_version >= '3.9'", ] dynamic = [ "version", "readme", ] maintainers = [ { name = "James Goppert", email = "james.goppert@gmail.com" }, { name = "Beat Kueng", email = "beat-kueng@gmx.net" }, ] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Science/Research", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Other", "Topic :: Software Development", "Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Scientific/Engineering :: Mathematics", "Topic :: Scientific/Engineering :: Physics", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Operating System :: Unix", "Operating System :: MacOS", ] [project.scripts] ulog_extract_gps_dump = "pyulog.extract_gps_dump:main" ulog_info = "pyulog.info:main" ulog_messages = "pyulog.messages:main" ulog_params = "pyulog.params:main" ulog2csv = "pyulog.ulog2csv:main" ulog2kml = "pyulog.ulog2kml:main" ulog2rosbag = "pyulog.ulog2rosbag:main" ulog_migratedb = "pyulog.migrate_db:main" [project.urls] Homepage = "https://github.com/PX4/pyulog" Repository = "https://github.com/PX4/pyulog" [project.optional-dependencies] test = ['pytest', 'ddt'] [tool.setuptools_scm] ================================================ FILE: pyulog/__init__.py ================================================ """ Wrapper to include the main library modules """ from .core import ULog from . import px4 ================================================ FILE: pyulog/core.py ================================================ """ Main Module to load and parse an ULog file """ import struct import copy import sys import contextlib import numpy as np #pylint: disable=too-many-instance-attributes, unused-argument, missing-docstring #pylint: disable=protected-access, too-many-branches __author__ = "Beat Kueng" # check python version if sys.hexversion >= 0x030000F0: _RUNNING_PYTHON3 = True def _parse_string(cstr, errors='strict'): return str(cstr, 'utf-8', errors) else: _RUNNING_PYTHON3 = False def _parse_string(cstr): return str(cstr) class ULog(object): """ This class parses an ulog file """ ## constants ## HEADER_BYTES = b'\x55\x4c\x6f\x67\x01\x12\x35' SYNC_BYTES = b'\x2F\x73\x13\x20\x25\x0C\xBB\x12' # message types MSG_TYPE_FORMAT = ord('F') MSG_TYPE_DATA = ord('D') MSG_TYPE_INFO = ord('I') MSG_TYPE_INFO_MULTIPLE = ord('M') MSG_TYPE_PARAMETER = ord('P') MSG_TYPE_PARAMETER_DEFAULT = ord('Q') MSG_TYPE_ADD_LOGGED_MSG = ord('A') MSG_TYPE_REMOVE_LOGGED_MSG = ord('R') MSG_TYPE_SYNC = ord('S') MSG_TYPE_DROPOUT = ord('O') MSG_TYPE_LOGGING = ord('L') MSG_TYPE_LOGGING_TAGGED = ord('C') MSG_TYPE_FLAG_BITS = ord('B') _UNPACK_TYPES = { 'int8_t': ['b', 1, np.int8], 'uint8_t': ['B', 1, np.uint8], 'int16_t': ['h', 2, np.int16], 'uint16_t': ['H', 2, np.uint16], 'int32_t': ['i', 4, np.int32], 'uint32_t': ['I', 4, np.uint32], 'int64_t': ['q', 8, np.int64], 'uint64_t': ['Q', 8, np.uint64], 'float': ['f', 4, np.float32], 'double': ['d', 8, np.float64], 'bool': ['?', 1, np.int8], 'char': ['c', 1, np.int8] } @staticmethod def get_field_size(type_str): """ get the field size in bytes. :param type_str: type string, eg. 'int8_t' """ return ULog._UNPACK_TYPES[type_str][1] # pre-init unpack structs for quicker use _unpack_ushort_byte = struct.Struct(' bytes: if isinstance(value, int): value_type = "int32_t" elif isinstance(value, float): value_type = "float" else: raise TypeError("Found unknown parameter value type") key: str = value_type + ' ' + name return self._make_info_message_data(key, value, value_type) def _make_info_message_data(self, key: str, value, value_type: str, continued=None) -> bytes: key_bytes = bytes(key, 'utf-8') data = bytearray() if continued is not None: data.extend(struct.pack(' 0: encoded_field = '%s[%d] %s;' % (field_type, array_size, field_name) else: encoded_field = '%s %s;' % (field_type, field_name) data.extend(bytes(encoded_field, 'utf-8')) header = struct.pack(' 8 + 8 + 3*8: # we can still parse it but might miss some information print('Warning: Flags Bit message is longer than expected') self.compat_flags = list(struct.unpack('<'+'B'*8, data[0:8])) self.incompat_flags = list(struct.unpack('<'+'B'*8, data[8:16])) self.appended_offsets = list(struct.unpack('<'+'Q'*3, data[16:16+3*8])) # remove the 0's at the end while len(self.appended_offsets) > 0 and self.appended_offsets[-1] == 0: self.appended_offsets.pop() class MessageFormat(object): """ ULog message format representation """ def __init__(self, data, header): format_arr = ULog.parse_string(data).split(':') self.name = format_arr[0] types_str = format_arr[1].split(';') self.fields = [] # list of tuples (type, array_size, name) for t in types_str: if len(t) > 0: self.fields.append(self._extract_type(t)) def __eq__(self, other): if not isinstance(other, ULog.MessageFormat): return NotImplemented return self.name == other.name and self.fields == other.fields @staticmethod def _extract_type(field_str): field_str_split = field_str.split(' ') type_str = field_str_split[0] name_str = field_str_split[1] a_pos = type_str.find('[') if a_pos == -1: array_size = 0 type_name = type_str else: b_pos = type_str.find(']') array_size = int(type_str[a_pos+1:b_pos]) type_name = type_str[:a_pos] return type_name, array_size, name_str class MessageLogging(object): """ ULog logged string message representation """ def __init__(self, data, header): self.log_level, = struct.unpack(' 0 and self.field_data[-1].field_name.startswith('_padding')): self.field_data.pop() def _parse_nested_type(self, prefix_str, type_name, message_formats): # we flatten nested types message_format = message_formats[type_name] for (type_name_fmt, array_size, field_name) in message_format.fields: if type_name_fmt in ULog._UNPACK_TYPES: if array_size > 0: for i in range(array_size): self.field_data.append(ULog._FieldData( prefix_str+field_name+'['+str(i)+']', type_name_fmt)) else: self.field_data.append(ULog._FieldData( prefix_str+field_name, type_name_fmt)) if prefix_str+field_name == 'timestamp': self.timestamp_idx = len(self.field_data) - 1 else: # nested type if array_size > 0: for i in range(array_size): self._parse_nested_type(prefix_str+field_name+'['+str(i)+'].', type_name_fmt, message_formats) else: self._parse_nested_type(prefix_str+field_name+'.', type_name_fmt, message_formats) class _MessageData(object): def __init__(self): self.timestamp = 0 def initialize(self, data, header, subscriptions, ulog_object) -> bool: has_corruption = False msg_id, = ULog._unpack_ushort(data[:2]) if msg_id in subscriptions: subscription = subscriptions[msg_id] min_data_size = subscription.dtype.itemsize data_size = len(data) - 2 if data_size < min_data_size or data_size > subscription.max_data_size: # Corrupt data: skip self.timestamp = 0 has_corruption = True else: if data_size > min_data_size: # Strip extra data (_padding bytes) data = data[:2+min_data_size] # accumulate data to a buffer, will be parsed later subscription.buffer += data[2:] t_off = subscription.timestamp_offset # TODO: the timestamp can have another size than uint64 self.timestamp, = ULog._unpack_uint64(data[t_off+2:t_off+10]) else: if not msg_id in ulog_object._filtered_message_ids: # this is an error, but make it non-fatal if not msg_id in ulog_object._missing_message_ids: ulog_object._missing_message_ids.add(msg_id) if ulog_object._debug: print(ulog_object._file_handle.tell()) print('Warning: no subscription found for message id {:}. Continuing,' ' but file is most likely corrupt'.format(msg_id)) has_corruption = True self.timestamp = 0 return has_corruption def _add_parameter_default(self, msg_param): """ add a _MessageParameterDefault object """ default_types = msg_param.default_types while default_types: # iterate over each bit def_type = default_types & (~default_types+1) default_types ^= def_type def_type -= 1 if def_type not in self._default_parameters: self._default_parameters[def_type] = {} self._default_parameters[def_type][msg_param.key] = msg_param.value def _add_message_info_multiple(self, msg_info): """ add a message info multiple to self._msg_info_multiple_dict """ if msg_info.key in self._msg_info_multiple_dict: if msg_info.is_continued: self._msg_info_multiple_dict[msg_info.key][-1].append(msg_info.value) else: self._msg_info_multiple_dict[msg_info.key].append([msg_info.value]) else: self._msg_info_multiple_dict[msg_info.key] = [[msg_info.value]] self._msg_info_multiple_dict_types[msg_info.key] = msg_info.type def _load_file(self, log_file, message_name_filter_list, parse_header_only=False): """ load and parse an ULog file into memory """ if isinstance(log_file, str): self._file_handle = open(log_file, "rb") #pylint: disable=consider-using-with else: self._file_handle = log_file # parse the whole file self._read_file_header() self._last_timestamp = self._start_timestamp self._read_file_definitions() if self._debug: print("header end offset: {:}".format(self._file_handle.tell())) if parse_header_only: self._file_handle.close() del self._file_handle return if self.has_data_appended and len(self._appended_offsets) > 0: if self._debug: print('This file has data appended') for offset in self._appended_offsets: self._read_file_data(message_name_filter_list, read_until=offset) self._file_handle.seek(offset) # read the whole file, or the rest if data appended self._read_file_data(message_name_filter_list) self._file_handle.close() del self._file_handle def _read_file_header(self): header_data = self._file_handle.read(16) if len(header_data) != 16: raise TypeError("Invalid file format (Header too short)") if header_data[:7] != self.HEADER_BYTES: raise TypeError("Invalid file format (Failed to parse header)") self._file_version, = struct.unpack('B', header_data[7:8]) if self._file_version > 1: print("Warning: unknown file version. Will attempt to read it anyway") # read timestamp self._start_timestamp, = ULog._unpack_uint64(header_data[8:]) def _read_file_definitions(self): header = self._MessageHeader() while True: data = self._file_handle.read(3) if not data: break header.initialize(data) data = self._file_handle.read(header.msg_size) try: if header.msg_type == self.MSG_TYPE_INFO: msg_info = self._MessageInfo(data, header) self._msg_info_dict[msg_info.key] = msg_info.value self._msg_info_dict_types[msg_info.key] = msg_info.type elif header.msg_type == self.MSG_TYPE_INFO_MULTIPLE: msg_info = self._MessageInfo(data, header, is_info_multiple=True) self._add_message_info_multiple(msg_info) elif header.msg_type == self.MSG_TYPE_FORMAT: msg_format = self.MessageFormat(data, header) self._message_formats[msg_format.name] = msg_format elif header.msg_type == self.MSG_TYPE_PARAMETER: msg_info = self._MessageInfo(data, header) self._initial_parameters[msg_info.key] = msg_info.value elif header.msg_type == self.MSG_TYPE_PARAMETER_DEFAULT: msg_param = self._MessageParameterDefault(data, header) self._add_parameter_default(msg_param) elif header.msg_type in (self.MSG_TYPE_ADD_LOGGED_MSG, self.MSG_TYPE_LOGGING, self.MSG_TYPE_LOGGING_TAGGED): self._file_handle.seek(-(3+header.msg_size), 1) break # end of section elif header.msg_type == self.MSG_TYPE_FLAG_BITS: # make sure this is the first message in the log if self._file_handle.tell() != 16 + 3 + header.msg_size: print('Error: FLAGS_BITS message must be first message. Offset:', self._file_handle.tell()) msg_flag_bits = self._MessageFlagBits(data, header) self._compat_flags = msg_flag_bits.compat_flags self._incompat_flags = msg_flag_bits.incompat_flags self._appended_offsets = msg_flag_bits.appended_offsets if self._debug: print('compat flags: ', self._compat_flags) print('incompat flags:', self._incompat_flags) print('appended offsets:', self._appended_offsets) # check if there are bits set that we don't know unknown_incompat_flag_msg = \ "Unknown incompatible flag set: cannot parse the log" if self._incompat_flags[0] & ~1: raise ValueError(unknown_incompat_flag_msg) for i in range(1, 8): if self._incompat_flags[i]: raise NotImplementedError(unknown_incompat_flag_msg) else: if self._debug: print('read_file_definitions: unknown message type: %i (%s)' % (header.msg_type, chr(header.msg_type))) file_position = self._file_handle.tell() print('file position: %i (0x%x) msg size: %i' % ( file_position, file_position, header.msg_size)) if self._check_packet_corruption(header): # seek back to advance only by a single byte instead of # skipping the message self._file_handle.seek(-2-header.msg_size, 1) except IndexError: if not self._file_corrupt: print("File corruption detected while reading file definitions!") self._file_corrupt = True def _find_sync(self, last_n_bytes=-1): """ read the file from a given location until the end of sync_byte sequence is found or an end condition is met(reached EOF or searched all last_n_bytes). :param last_n_bytes: optional arg to search only last_n_bytes for sync_bytes. when provided, _find_sync searches for sync_byte sequence in the last_n_bytes from current location, else, from current location till end of file. return true if successful, else return false and seek back to initial position and set _has_sync to false if searched till end of file """ sync_seq_found = False initial_file_position = self._file_handle.tell() current_file_position = initial_file_position search_chunk_size = 512 # number of bytes that are searched at once if last_n_bytes != -1: current_file_position = self._file_handle.seek(-last_n_bytes, 1) search_chunk_size = last_n_bytes chunk = self._file_handle.read(search_chunk_size) while len(chunk) >= len(ULog.SYNC_BYTES): current_file_position += len(chunk) chunk_index = chunk.find(ULog.SYNC_BYTES) if chunk_index >= 0: if self._debug: print("Found sync at %i" % (current_file_position - len(chunk) + chunk_index)) # seek to end of sync sequence and break current_file_position = self._file_handle.seek(current_file_position - len(chunk)\ + chunk_index + len(ULog.SYNC_BYTES), 0) sync_seq_found = True break if last_n_bytes != -1: # we read the whole last_n_bytes and did not find sync break # seek back 7 bytes to handle boundary condition and read next chunk current_file_position = self._file_handle.seek(-(len(ULog.SYNC_BYTES)-1), 1) chunk = self._file_handle.read(search_chunk_size) if not sync_seq_found: current_file_position = self._file_handle.seek(initial_file_position, 0) if last_n_bytes == -1: self._has_sync = False if self._debug: print("Failed to find sync in file from %i" % initial_file_position) else: if self._debug: print("Failed to find sync in (%i, %i)" %\ (initial_file_position - last_n_bytes, initial_file_position)) else: # declare file corrupt if we skipped bytes to sync sequence self._file_corrupt = True return sync_seq_found def _read_file_data(self, message_name_filter_list, read_until=None): """ read the file data section :param read_until: an optional file offset: if set, parse only up to this offset (smaller than) """ if read_until is None: read_until = 1 << 50 # make it larger than any possible log file try: # pre-init reusable objects header = self._MessageHeader() msg_data = self._MessageData() curr_file_pos = self._file_handle.tell() while True: data = self._file_handle.read(3) curr_file_pos += len(data) header.initialize(data) data = self._file_handle.read(header.msg_size) curr_file_pos += len(data) if len(data) < header.msg_size: break # less data than expected. File is most likely cut if curr_file_pos > read_until: if self._debug: print('read until offset=%i done, current pos=%i' % (read_until, curr_file_pos)) break try: if header.msg_type == self.MSG_TYPE_INFO: msg_info = self._MessageInfo(data, header) self._msg_info_dict[msg_info.key] = msg_info.value self._msg_info_dict_types[msg_info.key] = msg_info.type elif header.msg_type == self.MSG_TYPE_INFO_MULTIPLE: msg_info = self._MessageInfo(data, header, is_info_multiple=True) self._add_message_info_multiple(msg_info) elif header.msg_type == self.MSG_TYPE_PARAMETER: msg_info = self._MessageInfo(data, header) self._changed_parameters.append((self._last_timestamp, msg_info.key, msg_info.value)) elif header.msg_type == self.MSG_TYPE_PARAMETER_DEFAULT: msg_param = self._MessageParameterDefault(data, header) self._add_parameter_default(msg_param) elif header.msg_type == self.MSG_TYPE_ADD_LOGGED_MSG: msg_add_logged = self._MessageAddLogged(data, header, self._message_formats) if (message_name_filter_list is None or msg_add_logged.message_name in message_name_filter_list): self._subscriptions[msg_add_logged.msg_id] = msg_add_logged else: self._filtered_message_ids.add(msg_add_logged.msg_id) elif header.msg_type == self.MSG_TYPE_LOGGING: msg_logging = self.MessageLogging(data, header) self._logged_messages.append(msg_logging) elif header.msg_type == self.MSG_TYPE_LOGGING_TAGGED: msg_log_tagged = self.MessageLoggingTagged(data, header) if msg_log_tagged.tag in self._logged_messages_tagged: self._logged_messages_tagged[msg_log_tagged.tag].append(msg_log_tagged) else: self._logged_messages_tagged[msg_log_tagged.tag] = [msg_log_tagged] elif header.msg_type == self.MSG_TYPE_DATA: has_corruption = msg_data.initialize(data, header, self._subscriptions, self) if has_corruption: self._file_corrupt = True elif msg_data.timestamp > self._last_timestamp: self._last_timestamp = msg_data.timestamp elif header.msg_type == self.MSG_TYPE_DROPOUT: msg_dropout = self.MessageDropout(data, header, self._last_timestamp) self._dropouts.append(msg_dropout) elif header.msg_type == self.MSG_TYPE_SYNC: self._sync_seq_cnt = self._sync_seq_cnt + 1 else: if self._debug: print('_read_file_data: unknown message type: %i (%s)' % (header.msg_type, chr(header.msg_type))) print('file position: %i msg size: %i' % ( curr_file_pos, header.msg_size)) if self._check_packet_corruption(header): # seek back to advance only by a single byte instead of # skipping the message curr_file_pos = self._file_handle.seek(-2-header.msg_size, 1) # try recovery with sync sequence in case of unknown msg_type if self._has_sync: self._find_sync() else: # seek back msg_size to look for sync sequence in payload if self._has_sync: self._find_sync(header.msg_size) except IndexError: if not self._file_corrupt: print("File corruption detected while reading file data!") self._file_corrupt = True except struct.error: pass #we read past the end of the file # convert into final representation while self._subscriptions: _, value = self._subscriptions.popitem() if len(value.buffer) > 0: # only add if we have data data_item = ULog.Data(value) self._data_list.append(data_item) # Sorting is necessary to be able to compare two ULogs correctly self.data_list.sort(key=lambda ds: (ds.name, ds.multi_id)) def _check_packet_corruption(self, header): """ check for data corruption based on an unknown message type in the header set _file_corrupt flag to true if a corrupt packet is found We need to handle 2 cases: - corrupt file (we do our best to read the rest of the file) - new ULog message type got added (we just want to skip the message) return true if packet associated with header is corrupt, else return false """ data_corrupt = False if header.msg_type == 0 or header.msg_size == 0 or header.msg_size > 10000: if not self._file_corrupt and self._debug: print('File corruption detected') data_corrupt = True self._file_corrupt = True return data_corrupt def get_version_info(self, key_name='ver_sw_release'): """ get the (major, minor, patch, type) version information as tuple. Returns None if not found definition of type is: >= 0: development >= 64: alpha version >= 128: beta version >= 192: RC version == 255: release version """ if key_name in self._msg_info_dict: val = self._msg_info_dict[key_name] return ((val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff) return None def get_version_info_str(self, key_name='ver_sw_release'): """ get version information in the form 'v1.2.3 (RC)', or None if version tag either not found or it's a development version """ version = self.get_version_info(key_name) if not version is None and version[3] >= 64: type_str = '' if version[3] < 128: type_str = ' (alpha)' elif version[3] < 192: type_str = ' (beta)' elif version[3] < 255: type_str = ' (RC)' return 'v{}.{}.{}{}'.format(version[0], version[1], version[2], type_str) return None ================================================ FILE: pyulog/db.py ================================================ ''' Module containing the DatabaseULog class. ''' import sqlite3 import hashlib import contextlib import numpy as np from pyulog import ULog # pylint: disable=too-many-instance-attributes class DatabaseULog(ULog): ''' This class can be used in place of a ULog, except that it reads from and writes to a sqlite3 database instead of a file. The first time you see a ulog file, instantiate a DatabaseULog directly from a ulog file, and then call save() to write it to the database. Later it can be accessed by providing the primary_key, upon which this class loads all needed fields from the database. If you don't have the primary_key value available, but you have the .ulg file and know it is in the database, you can retrieve the hash with DatabaseULog.calc_sha256sum and DatabaseULog.primary_key_from_sha256sum. This class is currently designed to be write-once only, so you cannot update existing database entries. To do so, first call delete() and then save() again. A weakness of the implementation is that there are some silently failing states if you don't call save() immediately after instantiating from a ULog object. The requirement of explicit save() is to prevent unexpected, sudden side effects, which is considered worse than the current solution. Example usage: > from pyulog.db import DatabaseULog > > db_handle = DatabaseULog.get_db_handle('dbulog.sqlite3') > dbulog = DatabaseULog(db_handle, log_file='example.ulg') # Slow > dbulog.save() > pk = dbulog.primary_key() > # [...] > dbulog = DatabaseULog(db_handle, primary_key=pk) # Fast SCHEMA_VERSION specifies which version of the database schema (as read from "PRAGMA user_version") this file is supposed to match. This is done to ensure consistency between the database operations and the state of the database. If SCHEMA_VERSION is higher than what is found in the database, then it means that the datbaase is has not been updated, and the contsructor will throw an exception. See the documentation of "ulog_migratedb" for more information. ''' SCHEMA_VERSION = 5 @staticmethod def get_db_handle(db_path): ''' Generate a database handle that can be used in subsequent database access. ''' def db_handle(): con = sqlite3.connect( db_path, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES ) # The next line is necessary for sqlite3 to actually respect # FOREIGN KEY constraints and ON DELETE CASCADE. con.execute('PRAGMA foreign_keys=on') return con return db_handle @staticmethod def exists_in_db(db_handle, primary_key): ''' Check whether an ULog row with Id=primary_key exists in the database accessed with db_handle. ''' with db_handle() as con: cur = con.cursor() cur.execute(''' SELECT COUNT(*) FROM ULog WHERE Id = ? ''', (primary_key,)) count, = cur.fetchone() cur.close() return count == 1 @staticmethod def primary_key_from_sha256sum(db_handle, sha256sum): ''' Search the database for a row with the given SHA256 digest and returns the corresponding primary key. Returns None if not found. ''' primary_key = None with db_handle() as con: cur = con.cursor() cur.execute(''' SELECT Id FROM ULog WHERE SHA256Sum = ? ''', (sha256sum,)) row = cur.fetchone() if row: primary_key = row[0] cur.close() return primary_key @staticmethod def calc_sha256sum(log_file): ''' Compute the SHA256 digest of a file, specified as a file or a valid file path. ''' if log_file is None: return None if isinstance(log_file, str): file_context = open(log_file, 'rb') # pylint: disable=consider-using-with elif log_file.closed: file_context = open(log_file.name, 'rb') else: file_context = contextlib.nullcontext(log_file) file_hash = hashlib.sha256() with file_context as open_file: while True: block = open_file.read(4096) file_hash.update(block) if block == b'': break return file_hash.hexdigest() def __init__(self, db_handle, primary_key=None, log_file=None, lazy=True, **kwargs): ''' You always need the database handle (which can be generated with DatabaseULog.get_db_handle), but there are two options for the parameters "primary_key" and "log_file": - For storing a new log in the database, supply the corresponding log_file parameter, but leave the primary_key field at None. - For reading an existing log from the database, supply the desired primary_key parameter, but leave the log_file parameter at None. If you don't know the primary_key, you can try finding it with DatabaseULog.primary_key_from_sha256sum. You cannot supply both of these parameters. Furthermore, the "lazy" parameter specifies whether all data fields should be read on-demand when get_dataset is called, or if they should all be read and populated into data_list when the object is instantiated. The constructor also checks that SCHEMA_VERSION matches the "PRAGMA user_version" found in the database, see the documentation of "PRAGMA user_version" for more information. ''' with db_handle() as con: cur = con.cursor() cur.execute('PRAGMA user_version') (db_version,) = cur.fetchone() if db_version < DatabaseULog.SCHEMA_VERSION: raise ValueError('Database version %d < schema version %d, migration needed.' % ( db_version, DatabaseULog.SCHEMA_VERSION )) if log_file is not None and primary_key is not None: raise ValueError('You cannot provide both primary_key and log_file.') if log_file is None and primary_key is None: raise ValueError('You must provide either a primary_key or log_file.') self._pk = primary_key self._db = db_handle self._lazy_loaded = lazy if log_file is not None: self._sha256sum = DatabaseULog.calc_sha256sum(log_file) super().__init__(log_file, **kwargs) if primary_key is not None: self.load(lazy=lazy) def __eq__(self, other): """ If the other object is a normal ULog, then we just want to compare ULog data, not DatabaseULog specific fields, because we want to compare theULog file contents. """ if type(other) is ULog: # pylint: disable=unidiomatic-typecheck return other.__eq__(self) return super().__eq__(other) def write_ulog(self, log_file): if self._lazy_loaded: raise ValueError('Cannot write after lazy load because it has no datasets.') super().write_ulog(log_file) @property def primary_key(self): '''The primary key of the ulog, pointing to the correct "ULog" row in the database.''' return self._pk @property def sha256sum(self): '''The computed SHA256 digest of the file, stored for later use.''' return self._sha256sum # pylint: disable=too-many-locals,too-many-branches def load(self, lazy=True): ''' Load all necessary data from the database, possibly except for the data series themselves, which can cost unnecessary resources. If lazy=True, then the data series will be left unread, deferred until get_dataset is called. If however lazy=False, then all data series will be read at once. Even if the log was originally saved with append_json=True, this function will always use the faster BLOB column for retrieval. ''' if not DatabaseULog.exists_in_db(self._db, self._pk): raise KeyError(f'No ULog in database with Id={self._pk}') with self._db() as con: cur = con.cursor() # ULog metadata cur.execute(''' SELECT FileVersion, StartTimestamp, LastTimestamp, CompatFlags, IncompatFlags, SyncCount, HasSync, SHA256Sum FROM ULog WHERE Id = ? ''', (self._pk,)) ulog_result = cur.fetchone() self._file_version = ulog_result[0] self._start_timestamp = ulog_result[1] self._last_timestamp = ulog_result[2] self._compat_flags = [ord(c) for c in ulog_result[3]] self._incompat_flags = [ord(c) for c in ulog_result[4]] self._sync_seq_cnt = ulog_result[5] self._has_sync = ulog_result[6] self._sha256sum = ulog_result[7] # appended_offsets cur.execute(''' SELECT Offset FROM ULogAppendedOffsets WHERE ULogId = ? ORDER BY SeriesIndex ''', (self._pk,)) offsets_result = cur.fetchall() for offset, in offsets_result: self._appended_offsets.append(offset) # data_list self._data_list = [] cur.execute(''' SELECT DatasetName, MultiId FROM ULogDataset WHERE ULogId = ? ORDER BY DatasetName, MultiId ''', (self._pk,)) dataset_results = cur.fetchall() for dataset_name, multi_id in dataset_results: dataset = self.get_dataset(dataset_name, multi_instance=multi_id, lazy=lazy, db_cursor=cur, caching=False) self._data_list.append(dataset) # dropouts cur.execute(''' SELECT Timestamp, Duration FROM ULogMessageDropout WHERE ULogId = ? ''', (self._pk,)) for timestamp, duration in cur.fetchall(): self._dropouts.append( DatabaseULog.DatabaseMessageDropout( timestamp=timestamp, duration=duration, ) ) # logged_messages cur.execute(''' SELECT LogLevel, Timestamp, Message FROM ULogMessageLogging WHERE ULogId = ? ''', (self._pk,)) for log_level, timestamp, message in cur.fetchall(): self._logged_messages.append( DatabaseULog.DatabaseMessageLogging( log_level=log_level, timestamp=timestamp, message=message, ) ) # logged_messages_tagged cur.execute(''' SELECT LogLevel, Tag, Timestamp, Message FROM ULogMessageLoggingTagged WHERE ULogId = ? ''', (self._pk,)) for log_level, tag, timestamp, message in cur.fetchall(): if tag not in self._logged_messages_tagged: self._logged_messages_tagged[tag] = [] self._logged_messages_tagged[tag].append( DatabaseULog.DatabaseMessageLoggingTagged( log_level=log_level, tag=tag, timestamp=timestamp, message=message, ) ) # message_formats cur.execute(''' SELECT msg.Name, field.FieldType, field.ArraySize, field.Name FROM ULogMessageFormat msg JOIN ULogMessageFormatField field ON field.MessageId = msg.Id WHERE ULogId = ? ''', (self._pk,)) for row in cur.fetchall(): msg_name = row[0] field_data = row[1:] if msg_name in self._message_formats: self._message_formats[msg_name].fields.append(field_data) else: self._message_formats[msg_name] = DatabaseULog.DatabaseMessageFormat( name=msg_name, fields=[field_data] ) # msg_info_dict cur.execute(''' SELECT Key, Typename, Value FROM ULogMessageInfo WHERE ULogId = ? ''', (self._pk,)) for key, typename, value in cur.fetchall(): self._msg_info_dict[key] = value self._msg_info_dict_types[key] = typename # msg_info_multiple_dict cur.execute(''' SELECT Id, Key, Typename FROM ULogMessageInfoMultiple WHERE ULogId = ? ''', (self._pk,)) for message_id, key, typename in cur.fetchall(): self._msg_info_multiple_dict[key] = [] self._msg_info_multiple_dict_types[key] = typename cur.execute(''' SELECT Id FROM ULogMessageInfoMultipleList WHERE MessageId = ? ORDER BY SeriesIndex ''', (message_id,)) for (list_id,) in cur.fetchall(): cur.execute(''' SELECT Value FROM ULogMessageInfoMultipleListElement WHERE ListId = ? ORDER BY SeriesIndex ''', (list_id,)) self._msg_info_multiple_dict[key].append([value for (value,) in cur.fetchall()]) # initial_parameters cur.execute(''' SELECT Key, Value FROM ULogInitialParameter WHERE ULogId = ? ''', (self._pk,)) for key, value in cur.fetchall(): self._initial_parameters[key] = value # _default_parameters cur.execute(''' SELECT DefaultType, Key, Value FROM ULogDefaultParameter WHERE ULogId = ? ''', (self._pk,)) for default_type, key, value in cur.fetchall(): if default_type not in self._default_parameters: self._default_parameters[default_type] = {} self._default_parameters[default_type][key] = value # changed_parameters cur.execute(''' SELECT Timestamp, Key, Value FROM ULogChangedParameter WHERE ULogId = ? ''', (self._pk,)) for timestamp, key, value in cur.fetchall(): self._changed_parameters.append((timestamp, key, value)) cur.close() self._lazy_loaded = lazy def get_dataset(self, name, multi_instance=0, lazy=False, db_cursor=None, caching=True): ''' Access a specific dataset and its data series from the database. The "lazy" argument specifies whether only dataset metadata should be retrieved from the database, or if the data series arrays should be retrieved too. The optional "db_cursor" argument can be used to avoid re-opening the database connection each time get_dataset is called. Since we don't expect the data to change often, we will normally use self._data_list as a cache, and check there before reading from the database. However, if caching=False, then we will always read anew from the database. ''' if db_cursor is None: db_context = self._db() cur = db_context.cursor() else: db_context = contextlib.nullcontext() cur = db_cursor existing_dataset = None for dataset in self._data_list: if dataset.name == name and dataset.multi_id == multi_instance: existing_dataset = dataset break if (caching and existing_dataset is not None and (lazy or existing_dataset.data)): return existing_dataset with db_context: cur.execute(''' SELECT Id, TimestampIndex, MessageId FROM ULogDataset WHERE DatasetName = ? AND MultiId = ? AND ULogId = ? ''', (name, multi_instance, self._pk)) dataset_result = cur.fetchone() if dataset_result is None: raise KeyError(f'Dataset with name {name} and multi id {multi_instance} not found.') dataset_id, timestamp_idx, msg_id = dataset_result fields = [] data = {} if not lazy: cur.execute(''' SELECT TopicName, DataType, ValueArray FROM ULogField WHERE DatasetId = ? ''', (dataset_id,)) field_results = cur.fetchall() for field_name, data_type, value_bytes in field_results: fields.append(DatabaseULog._FieldData(field_name=field_name,type_str=data_type)) dtype = DatabaseULog._UNPACK_TYPES[data_type][2] data[field_name] = np.frombuffer(value_bytes, dtype=dtype) # If caching=True but there is no existing dataset we could append a # new one to self._data_list, but that could be considered a # non-obvious side effect. if caching and existing_dataset is not None: existing_dataset.msg_id = msg_id existing_dataset.timestamp_idx = timestamp_idx existing_dataset.field_data = fields existing_dataset.data = data dataset = existing_dataset else: dataset = DatabaseULog.DatabaseData( name=name, multi_id=multi_instance, msg_id=msg_id, timestamp_idx=timestamp_idx, field_data=fields, data=data, ) return dataset def save(self, append_json=False): ''' Save the DatabaseULog to the database. Throws a KeyError if the primary key is already in the database. The datasets are stored in a sqlite BLOB for fast retrieval, but if append_json=True, then datasets are additionally stored in a JSON field. This allows them to be directly queried using the sqlite function json_each, but increases the writing time and database size. ''' if self._pk is not None: raise KeyError('Cannot save logs that are already in the database') pk_from_hash = DatabaseULog.primary_key_from_sha256sum(self._db, self._sha256sum) if pk_from_hash is not None: self._pk = pk_from_hash raise KeyError(f'Hash {self._sha256sum} already in database with Id={pk_from_hash}') with self._db() as con: cur = con.cursor() # ULog metadata cur.execute(''' INSERT INTO ULog (FileVersion, StartTimestamp, LastTimestamp, CompatFlags, IncompatFlags, SyncCount, HasSync, SHA256Sum) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''', ( self._file_version, self._start_timestamp, self._last_timestamp, ''.join([chr(n) for n in self._compat_flags]), ''.join([chr(n) for n in self._incompat_flags]), self._sync_seq_cnt, self._has_sync, self._sha256sum, ) ) self._pk = cur.lastrowid # appended_offsets cur.executemany(''' INSERT INTO ULogAppendedOffsets (SeriesIndex, Offset, ULogId) VALUES (?, ?, ?) ''', [( list_index, offset, self._pk, ) for list_index, offset in enumerate(self._appended_offsets)]) # data_list for dataset in self.data_list: cur.execute(''' INSERT INTO ULogDataSet (DatasetName, MultiId, MessageId, TimestampIndex, ULogId) VALUES (?, ?, ?, ?, ?) ''', ( dataset.name, dataset.multi_id, dataset.msg_id, dataset.timestamp_idx, self._pk, ) ) dataset_id = cur.lastrowid for field in dataset.field_data: values = dataset.data[field.field_name] values_bytes = values.tobytes() if append_json: # Precision is only good enough up to a few decimals, # depending on the default float formatter. The # function np.array2string was tested, but was slower. # Also note that doing the slow json.dumps is also # unnecessary since we know that the object to be # formatted is an array. timestamp_list = [str(s) for s in dataset.data['timestamp'].tolist()] values_json = str( dict(zip(timestamp_list, values.tolist())) ).replace('nan', 'null').replace('inf', 'null').replace("'", '"') json_placeholder = 'json(?)' # Saves some space else: values_json = None json_placeholder = '?' cur.execute(f''' INSERT INTO ULogField (TopicName, DataType, ValueArray, ValueJson, DatasetId) VALUES (?, ?, ?, {json_placeholder}, ?) ''', ( field.field_name, field.type_str, values_bytes, values_json, dataset_id, ) ) # dropouts cur.executemany(''' INSERT INTO ULogMessageDropout (Timestamp, Duration, ULogId) VALUES (?, ?, ?) ''', [( dropout.timestamp, dropout.duration, self._pk, ) for dropout in self._dropouts]) # logged_messages cur.executemany(''' INSERT INTO ULogMessageLogging (LogLevel, Timestamp, Message, ULogId) VALUES (?, ?, ?, ?) ''', [( message.log_level, message.timestamp, message.message, self._pk, ) for message in self._logged_messages]) # logged_messages_tagged for tag, messages in self._logged_messages_tagged.items(): cur.executemany(''' INSERT INTO ULogMessageLoggingTagged (LogLevel, Timestamp, Tag, Message, ULogId) VALUES (?, ?, ?, ?, ?) ''', [( message.log_level, message.timestamp, tag, message.message, self._pk, ) for message in messages]) # message_formats for name, message_format in self._message_formats.items(): cur.execute(''' INSERT INTO ULogMessageFormat (Name, ULogId) VALUES (?, ?) ''', (name, self._pk)) format_id = cur.lastrowid cur.executemany(''' INSERT INTO ULogMessageFormatField (FieldType, ArraySize, Name, MessageId) VALUES (?, ?, ?, ?) ''', [(*field, format_id) for field in message_format.fields]) # msg_info_dict cur.executemany(''' INSERT INTO ULogMessageInfo (Key, Value, Typename, ULogId) VALUES (?, ?, ?, ?) ''', [( key, value, self._msg_info_dict_types[key], self._pk, ) for key, value in self.msg_info_dict.items()]) # msg_info_multiple_dict for key, lists in self.msg_info_multiple_dict.items(): cur.execute(''' INSERT INTO ULogMessageInfoMultiple (Key, Typename, ULogId) VALUES (?, ?, ?) ''', (key, self._msg_info_multiple_dict_types[key], self._pk)) message_id = cur.lastrowid for list_index, message_list in enumerate(lists): cur.execute(''' INSERT INTO ULogMessageInfoMultipleList (SeriesIndex, MessageId) VALUES (?, ?) ''', (list_index, message_id)) list_id = cur.lastrowid cur.executemany(''' INSERT INTO ULogMessageInfoMultipleListElement (SeriesIndex, Value, ListId) VALUES (?, ?, ?) ''', [( series_index, value, list_id, ) for series_index, value in enumerate(message_list)]) # initial_parameters cur.executemany(''' INSERT INTO ULogInitialParameter (Key, Value, ULogId) VALUES (?, ?, ?) ''', [( key, value, self._pk, ) for key, value in self.initial_parameters.items()]) # _default_parameters for default_type, parameters in self._default_parameters.items(): cur.executemany(''' INSERT INTO ULogDefaultParameter (DefaultType, Key, Value, ULogId) VALUES (?, ?, ?, ?) ''', [( default_type, key, value, self._pk, ) for key, value in parameters.items()]) # changed_parameters cur.executemany(''' INSERT INTO ULogChangedParameter (Timestamp, Key, Value, ULogId) VALUES (?, ?, ?, ?) ''', [( timestamp, key, value, self._pk, ) for timestamp, key, value in self.changed_parameters]) cur.close() def delete(self): ''' Deletes the ULog row and cascading rows from the database. ''' if self._pk is None: raise KeyError('Cannot delete logs that are not in the database') with self._db() as con: cur = con.cursor() cur.execute(''' DELETE FROM ULog WHERE Id = ? ''', (self._pk,) ) cur.close() self._pk = None class DatabaseData(ULog.Data): ''' Overrides the ULog.Data class since its constructor only reads ULog.MessageLogAdded objects, and we want to specify the fields directly. ''' # pylint: disable=super-init-not-called,too-many-arguments def __init__(self, name, multi_id, msg_id, timestamp_idx, field_data, data=None): self.name = name self.multi_id = multi_id self.msg_id = msg_id self.timestamp_idx = timestamp_idx self.field_data = field_data self.data = data class DatabaseMessageDropout(ULog.MessageDropout): ''' Overrides the ULog.MessageDropout class since its constructor is not suitable for out purpose. ''' # pylint: disable=super-init-not-called def __init__(self, timestamp, duration): self.timestamp = timestamp self.duration = duration class DatabaseMessageFormat(ULog.MessageFormat): ''' Overrides the ULog.MessageFormat class since its constructor is not suitable for out purpose. ''' # pylint: disable=super-init-not-called def __init__(self, name, fields): self.name = name self.fields = fields class DatabaseMessageLogging(ULog.MessageLogging): ''' Overrides the ULog.MessageLogging class since its constructor is not suitable for out purpose. ''' # pylint: disable=super-init-not-called def __init__(self, log_level, timestamp, message): self.log_level = log_level self.timestamp = timestamp self.message = message class DatabaseMessageLoggingTagged(ULog.MessageLoggingTagged): ''' Overrides the ULog.MessageLoggingTagged class since its constructor is not suitable for our purpose. ''' # pylint: disable=super-init-not-called def __init__(self, log_level, tag, timestamp, message): self.log_level = log_level self.tag = tag self.timestamp = timestamp self.message = message ================================================ FILE: pyulog/extract_gps_dump.py ================================================ #! /usr/bin/env python """ Extract the raw gps communication from an ULog file. """ import argparse import os import sys import numpy as np from .core import ULog #pylint: disable=too-many-locals, unused-wildcard-import, wildcard-import def main(): """ Command line interface """ parser = argparse.ArgumentParser( description='Extract the raw gps communication from an ULog file') parser.add_argument('filename', metavar='file.ulg', help='ULog input file') def is_valid_directory(parser, arg): """Check if valid directory""" if not os.path.isdir(arg): parser.error('The directory {} does not exist'.format(arg)) # File exists so return the directory return arg parser.add_argument('-o', '--output', dest='output', action='store', help='Output directory (default is CWD)', metavar='DIR', type=lambda x: is_valid_directory(parser, x)) parser.add_argument('-x', '--ignore', dest='ignore', action='store_true', help='Ignore string parsing exceptions', default=False) parser.add_argument('-i', '--instance', dest='required_instance', action='store', help='GPS instance. Use 0 (default)' + 'for main GPS, 1 for secondary GPS reciever.', default=0) args = parser.parse_args() ulog_file_name = args.filename disable_str_exceptions = args.ignore required_instance = int(args.required_instance) msg_filter = ['gps_dump'] ulog = ULog(ulog_file_name, msg_filter, disable_str_exceptions) data = ulog.data_list output_file_prefix = os.path.basename(ulog_file_name) # strip '.ulg' if output_file_prefix.lower().endswith('.ulg'): output_file_prefix = output_file_prefix[:-4] # write to different output path? if args.output is not None: output_file_prefix = os.path.join(args.output, output_file_prefix) to_dev_filename = output_file_prefix + '_' + str(required_instance) + '_to_device.dat' from_dev_filename = output_file_prefix + '_' + str(required_instance) + '_from_device.dat' if len(data) == 0: print("File {0} does not contain gps_dump messages!".format(ulog_file_name)) sys.exit(0) gps_dump_data = data[0] # message format check field_names = [f.field_name for f in gps_dump_data.field_data] if not 'len' in field_names or not 'data[0]' in field_names: print('Error: gps_dump message has wrong format') sys.exit(-1) if len(ulog.dropouts) > 0: print("Warning: file contains {0} dropouts".format(len(ulog.dropouts))) print("Creating files {0} and {1}".format(to_dev_filename, from_dev_filename)) with open(to_dev_filename, 'wb') as to_dev_file: with open(from_dev_filename, 'wb') as from_dev_file: msg_lens = gps_dump_data.data['len'] instances = gps_dump_data.data.get('instance', [0]*len(msg_lens)) for i in range(len(gps_dump_data.data['timestamp'])): instance = instances[i] msg_len = msg_lens[i] if instance == required_instance: if msg_len & (1<<7): msg_len = msg_len & ~(np.uint8(1) << 7) file_handle = to_dev_file else: file_handle = from_dev_file for k in range(msg_len): file_handle.write(gps_dump_data.data['data['+str(k)+']'][i]) ================================================ FILE: pyulog/extract_message.py ================================================ """ Extract values from a ULog file message to use in scripting """ from typing import List import numpy as np from .core import ULog def extract_message(ulog_file_name: str, message: str, time_s: "int | None" = None, time_e: "int | None" = None, disable_str_exceptions: bool = False) -> List[dict]: """ Extract values from a ULog file :param ulog_file_name: (str) The ULog filename to open and read :param message: (str) A ULog message to return values from :param time_s: (int) Offset time for conversion in seconds :param time_e: (int) Limit until time for conversion in seconds :return: (List[dict]) A list of each record from the ULog as key-value pairs """ if not isinstance(message, str): raise AttributeError("Must provide a message to pull from ULog file") ulog = ULog(ulog_file_name, message, disable_str_exceptions) try: data = ulog.get_dataset(message) except Exception as exc: raise AttributeError("Provided message is not in the ULog file") from exc values = [] # use same field order as in the log, except for the timestamp data_keys = [f.field_name for f in data.field_data] data_keys.remove('timestamp') data_keys.insert(0, 'timestamp') # we want timestamp at first position #get the index for row where timestamp exceeds or equals the required value time_s_i = np.where(data.data['timestamp'] >= time_s * 1e6)[0][0] \ if time_s else 0 #get the index for row upto the timestamp of the required value time_e_i = np.where(data.data['timestamp'] >= time_e * 1e6)[0][0] \ if time_e else len(data.data['timestamp']) # write the data for i in range(time_s_i, time_e_i): row = {} for key in data_keys: row[key] = data.data[key][i] values.append(row) return values ================================================ FILE: pyulog/info.py ================================================ #! /usr/bin/env python """ Display information from an ULog file """ import argparse from .core import ULog #pylint: disable=too-many-locals, unused-wildcard-import, wildcard-import #pylint: disable=invalid-name def show_info(ulog, verbose): """Show general information from an ULog""" if ulog.file_corruption: print("Warning: file has data corruption(s)") m1, s1 = divmod(int(ulog.start_timestamp/1e6), 60) h1, m1 = divmod(m1, 60) m2, s2 = divmod(int((ulog.last_timestamp - ulog.start_timestamp)/1e6), 60) h2, m2 = divmod(m2, 60) print("Logging start time: {:d}:{:02d}:{:02d}, duration: {:d}:{:02d}:{:02d}".format( h1, m1, s1, h2, m2, s2)) dropout_durations = [dropout.duration for dropout in ulog.dropouts] if len(dropout_durations) == 0: print("No Dropouts") else: print("Dropouts: count: {:}, total duration: {:.1f} s, max: {:} ms, mean: {:} ms" .format(len(dropout_durations), sum(dropout_durations)/1000., max(dropout_durations), int(sum(dropout_durations)/len(dropout_durations)))) version = ulog.get_version_info_str() if not version is None: print('SW Version: {}'.format(version)) print("Info Messages:") for k in sorted(ulog.msg_info_dict): if not k.startswith('perf_') or verbose: print(" {0}: {1}".format(k, ulog.msg_info_dict[k])) if len(ulog.msg_info_multiple_dict) > 0: if verbose: print("Info Multiple Messages:") for k in sorted(ulog.msg_info_multiple_dict): print(" {0}: {1}".format(k, ulog.msg_info_multiple_dict[k])) else: print("Info Multiple Messages: {}".format( ", ".join(["[{}: {}]".format(k, len(ulog.msg_info_multiple_dict[k])) for k in sorted(ulog.msg_info_multiple_dict)]))) print("") print("{:<41} {:7}, {:10}".format("Name (multi id, message size in bytes)", "number of data points", "total bytes")) data_list_sorted = sorted(ulog.data_list, key=lambda d: d.name + str(d.multi_id)) for d in data_list_sorted: message_size = sum(ULog.get_field_size(f.type_str) for f in d.field_data) num_data_points = len(d.data['timestamp']) name_id = "{:} ({:}, {:})".format(d.name, d.multi_id, message_size) print(" {:<40} {:7d} {:10d}".format(name_id, num_data_points, message_size * num_data_points)) def main(): """Commande line interface""" parser = argparse.ArgumentParser(description='Display information from an ULog file') parser.add_argument('filename', metavar='file.ulg', help='ULog input file') parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', help='Verbose output', default=False) parser.add_argument('-m', '--message', dest='message', help='Show a specific Info Multiple Message') parser.add_argument('-n', '--newline', dest='newline', action='store_true', help='Add newline separators (only with --message)', default=False) parser.add_argument('-i', '--ignore', dest='ignore', action='store_true', help='Ignore string parsing exceptions', default=False) args = parser.parse_args() ulog_file_name = args.filename disable_str_exceptions = args.ignore ulog = ULog(ulog_file_name, None, disable_str_exceptions) message = args.message if message: separator = "" if args.newline: separator = "\n" if len(ulog.msg_info_multiple_dict) > 0 and message in ulog.msg_info_multiple_dict: message_info_multiple = ulog.msg_info_multiple_dict[message] for i, m in enumerate(message_info_multiple): if len(m) > 0 and isinstance(m[0], (bytes, bytearray)): print("# {} {} (len: {:}):".format(message, i, sum(len(item) for item in m))) print(separator.join(' '.join('{:02x}'.format(x) for x in item) for item in m)) else: print("# {} {}:".format(message, i)) print(separator.join(m)) else: print("message {} not found".format(message)) else: show_info(ulog, args.verbose) ================================================ FILE: pyulog/messages.py ================================================ #! /usr/bin/env python """ Display logged messages from an ULog file """ import argparse from .core import ULog from .px4_events import PX4Events #pylint: disable=invalid-name def main(): """Commande line interface""" parser = argparse.ArgumentParser(description='Display logged messages from an ULog file') parser.add_argument('filename', metavar='file.ulg', help='ULog input file') parser.add_argument('-i', '--ignore', dest='ignore', action='store_true', help='Ignore string parsing exceptions', default=False) args = parser.parse_args() ulog_file_name = args.filename disable_str_exceptions = args.ignore msg_filter = ['event'] ulog = ULog(ulog_file_name, msg_filter, disable_str_exceptions) logged_messages = [(m.timestamp, m.log_level_str(), m.message) for m in ulog.logged_messages] # If this is a PX4 log, try to get the events too if ulog.msg_info_dict.get('sys_name', '') == 'PX4': px4_events = PX4Events() events = px4_events.get_logged_events(ulog) for t, log_level, message in logged_messages: # backwards compatibility: a string message with appended tab is output # in addition to an event with the same message so we can ignore those if message[-1] == '\t': continue events.append((t, log_level, message)) logged_messages = sorted(events, key=lambda m: m[0]) for t, log_level, message in logged_messages: m1, s1 = divmod(int(t/1e6), 60) h1, m1 = divmod(m1, 60) print("{:d}:{:02d}:{:02d} {:}: {:}".format(h1, m1, s1, log_level, message)) ================================================ FILE: pyulog/migrate_db.py ================================================ ''' Tool for handling changes in the database schema. This is necessary for avoiding breaking backwards compatibility whenver bugs are discovered in the database model, or if the ULog format changes. There are some options available, such as "alembic" or "migrations', but these seem like overkill for us. For instance, we don't really need to migrate both up and down, just up. ''' import os import argparse from pyulog.db import DatabaseULog def main(): ''' Entry point for the console script. ''' parser = argparse.ArgumentParser(description='Setup the database for DatabaseULog') parser.add_argument('-d', '--database', dest='db_path', action='store', help='Path to the database file', default='pyulog.sqlite3') # The noop flag actually has a side effect if it is called on an uncreated # database, since the "PRAGMA user_version" command implicitly creates the # database. The created database will have user_version = 0, which will # later confuse the migration tool. however, this edge case will mostly be # relevant for advanced users, and can be handled with the -f flag. parser.add_argument('-n', '--noop', dest='noop', action='store_true', help='Only print results, do not execute migration scripts.', default=False) parser.add_argument('-s', '--sql', dest='sql_dir', action='store', help='Directory with migration SQL files', default=None) parser.add_argument('-f', '--force', dest='force', action='store_true', help=('Run the migration script even if the database is not created' 'with this script.'), default=False) args = parser.parse_args() migrate_db(args.db_path, sql_dir=args.sql_dir, noop=args.noop, force=args.force) def _read_db_schema_version(db_path, force): ''' Read and validate the schema version defined by the "PRAGMA user_version" field in the database. If the database file exists and schema version is 0, then this means that the database was not created with the migration tool. This means that the database is in a state unknown to the migration tool, and hence a migration could cause schema corruption. The default behavior in this case is to reject the migration, but it can be overriden with force=True. ''' db_handle = DatabaseULog.get_db_handle(db_path) if not os.path.isfile(db_path): print(f'Database file {db_path} not found, creating it from scratch.') return 0 print(f'Found database file {db_path}.') with db_handle() as con: cur = con.cursor() cur.execute('PRAGMA user_version') (db_schema_version,) = cur.fetchone() cur.close() if db_schema_version is None: raise ValueError(f'Could not fetch database schema version for {db_path}.') if db_schema_version == 0 and not force: raise FileExistsError('Database has user_version = 0, rejecting migration.' 'Use the "force" flag to migrate anyway.') if not isinstance(db_schema_version, int) or db_schema_version < 0: raise ValueError(f'Invalid database schema version {db_schema_version}.') return db_schema_version def _read_migration_file(migration_id, sql_dir): ''' Read the migration file with id "migration_id" in directory "sql_dir", and check that it handles transactions strictly. ''' migration_filename_format = os.path.join(sql_dir, 'pyulog.{migration_id}.sql') migration_filename = migration_filename_format.format(migration_id=migration_id) if not os.path.exists(migration_filename): raise FileNotFoundError(f'Migration file {migration_filename} does not exist. ' f'Stopped after migration {migration_id}.') with open(migration_filename, 'r', encoding='utf8') as migration_file: migration_lines = migration_file.read() if not migration_lines.strip().startswith('BEGIN;'): raise ValueError(f'Migration file {migration_filename} must start with "BEGIN;"') if not migration_lines.strip().endswith('COMMIT;'): raise ValueError(f'Migration file {migration_filename} must end with "COMMIT;"') migration_lines += f'\nPRAGMA user_version = {migration_id};' return migration_filename, migration_lines def migrate_db(db_path, sql_dir=None, noop=False, force=False): ''' Apply database migrations that have not yet been applied. Compares "PRAGMA user_version" from the sqlite3 database at "db_path" with the SCHEMA_VERSION in the DatabaseULog class. If the former is larger than the latter, then migration scripts will be read and executed from files in "sql_dir", and the user_version will be incremented, until the database is up to date. ''' if sql_dir is None: module_dir = os.path.dirname(os.path.realpath(os.path.abspath(__file__))) sql_dir = os.path.join(module_dir, 'sql') if not os.path.isdir(sql_dir): raise NotADirectoryError(f'{sql_dir} is not a directory.') print(f'Using migration files in {sql_dir}.') db_schema_version = _read_db_schema_version(db_path, force) class_schema_version = DatabaseULog.SCHEMA_VERSION print('Current schema version: {} (database) and {} (code).'.format( db_schema_version, class_schema_version, )) db_handle = DatabaseULog.get_db_handle(db_path) with db_handle() as con: cur = con.cursor() for migration_id in range(db_schema_version+1, DatabaseULog.SCHEMA_VERSION+1): migration_filename, migration_lines = _read_migration_file(migration_id, sql_dir) print(f'Executing {migration_filename}.') if noop: print(migration_lines) else: cur.executescript(migration_lines) cur.close() print('Migration done.') return db_path if __name__ == '__main__': raise SystemExit(main()) ================================================ FILE: pyulog/params.py ================================================ #! /usr/bin/env python """ Extract parameters from an ULog file """ import argparse import sys from .core import ULog #pylint: disable=unused-variable, too-many-branches def get_defaults(ulog, default): """ get default params from ulog """ assert ulog.has_default_parameters, "Log does not contain default parameters" if default == 'system': return ulog.get_default_parameters(0) if default == 'current_setup': return ulog.get_default_parameters(1) raise ValueError('invalid value \'{}\' for --default'.format(default)) def main(): """Commande line interface""" parser = argparse.ArgumentParser(description='Extract parameters from an ULog file') parser.add_argument('filename', metavar='file.ulg', help='ULog input file') parser.add_argument('-l', '--delimiter', dest='delimiter', action='store', help='Use delimiter in CSV (default is \',\')', default=',') parser.add_argument('-i', '--initial', dest='initial', action='store_true', help='Only extract initial parameters. (octave|csv)', default=False) parser.add_argument('-t', '--timestamps', dest='timestamps', action='store_true', help='Extract changed parameters with timestamps. (csv)', default=False) parser.add_argument('-f', '--format', dest='format', action='store', type=str, help='csv|octave|qgc', default='csv') parser.add_argument('output_filename', metavar='params.txt', type=argparse.FileType('w'), nargs='?', help='Output filename (default=stdout)', default=sys.stdout) parser.add_argument('--ignore', dest='ignore', action='store_true', help='Ignore string parsing exceptions', default=False) parser.add_argument('-d', '--default', dest='default', action='store', type=str, help='Select default param values instead of configured ' 'values (implies --initial). Valid values: system|current_setup', default=None) args = parser.parse_args() ulog_file_name = args.filename disable_str_exceptions = args.ignore message_filter = [] if not args.initial: message_filter = None ulog = ULog(ulog_file_name, message_filter, disable_str_exceptions) params = ulog.initial_parameters if args.default is not None: params = get_defaults(ulog, args.default) args.initial = True param_keys = sorted(params.keys()) delimiter = args.delimiter output_file = args.output_filename if args.format == "csv": for param_key in param_keys: output_file.write(param_key) if args.timestamps: output_file.write(delimiter) output_file.write(str(params[param_key])) for t, name, value in ulog.changed_parameters: if name == param_key: output_file.write(delimiter) output_file.write(str(value)) output_file.write('\n') output_file.write("timestamp") output_file.write(delimiter) output_file.write('0') for t, name, value in ulog.changed_parameters: if name == param_key: output_file.write(delimiter) output_file.write(str(t)) output_file.write('\n') else: output_file.write(delimiter) output_file.write(str(params[param_key])) if not args.initial: for t, name, value in ulog.changed_parameters: if name == param_key: output_file.write(delimiter) output_file.write(str(value)) output_file.write('\n') elif args.format == "octave": for param_key in param_keys: output_file.write('# name ') output_file.write(param_key) values = [params[param_key]] if not args.initial: for t, name, value in ulog.changed_parameters: if name == param_key: values += [value] if len(values) > 1: output_file.write('\n# type: matrix\n') output_file.write('# rows: 1\n') output_file.write('# columns: ') output_file.write(str(len(values)) + '\n') for value in values: output_file.write(str(value) + ' ') else: output_file.write('\n# type: scalar\n') output_file.write(str(values[0])) output_file.write('\n') elif args.format == "qgc": for param_key in param_keys: sys_id = 1 comp_id = 1 delimiter = '\t' param_value = params[param_key] output_file.write(str(sys_id)) output_file.write(delimiter) output_file.write(str(comp_id)) output_file.write(delimiter) output_file.write(param_key) output_file.write(delimiter) output_file.write(str(param_value)) output_file.write(delimiter) if isinstance(param_value, float): # Float param_type = 9 else: # Int param_type = 6 output_file.write(str(param_type)) output_file.write('\n') ================================================ FILE: pyulog/px4.py ================================================ """ PX4-specific ULog helper """ import numpy as np __author__ = "Beat Kueng" class PX4ULog(object): """ This class contains PX4-specific ULog things (field names, etc.) """ def __init__(self, ulog_object): """ @param ulog_object: ULog instance """ self._ulog = ulog_object def get_mav_type(self): """ return the MAV type as string from initial parameters """ mav_type = self._ulog.initial_parameters.get('MAV_TYPE', None) return {0: 'Generic', 1: 'Fixed Wing', 2: 'Quadrotor', 3: 'Coaxial helicopter', 4: 'Normal helicopter with tail rotor', 5: 'Ground installation', 6: 'Ground Control Station', 7: 'Airship, controlled', 8: 'Free balloon, uncontrolled', 9: 'Rocket', 10: 'Ground Rover', 11: 'Surface Vessel, Boat, Ship', 12: 'Submarine', 13: 'Hexarotor', 14: 'Octorotor', 15: 'Tricopter', 16: 'Flapping wing', 17: 'Kite', 18: 'Onboard Companion Controller', 19: 'Two-rotor VTOL (Tailsitter)', 20: 'Quad-rotor VTOL (Tailsitter)', 21: 'Tiltrotor VTOL', 22: 'VTOL Standard', 23: 'VTOL Tailsitter', 24: 'VTOL reserved 4', 25: 'VTOL reserved 5', 26: 'Onboard Gimbal', 27: 'Onboard ADSB Peripheral'}.get(mav_type, 'unknown type') def get_estimator(self): """return the configured estimator as string from initial parameters""" mav_type = self._ulog.initial_parameters.get('MAV_TYPE', None) if mav_type == 1: # fixed wing always uses EKF2 return 'EKF2' mc_est_group = self._ulog.initial_parameters.get('SYS_MC_EST_GROUP', 2) return {0: 'INAV', 1: 'LPE', 2: 'EKF2', 3: 'Q'}.get(mc_est_group, 'unknown ({})'.format(mc_est_group)) def add_roll_pitch_yaw(self, messages=None): """ convenience method to add the fields 'roll', 'pitch', 'yaw' to the loaded data using the quaternion fields (does not update field_data). By default, messages are: 'vehicle_attitude.q', 'vehicle_attitude_setpoint.q_d', 'vehicle_attitude_groundtruth.q' and 'vehicle_vision_attitude.q' """ if messages is None: messages = ['vehicle_attitude', 'vehicle_vision_attitude', 'vehicle_attitude_groundtruth', 'vehicle_attitude_setpoint:_d'] for message in messages: if message.endswith(':_d'): suffix = '_d' message = message[:-3] else: suffix = '' self._add_roll_pitch_yaw_to_message(message, suffix) def _add_roll_pitch_yaw_to_message(self, message_name, field_name_suffix=''): message_data_all = [elem for elem in self._ulog.data_list if elem.name == message_name] for message_data in message_data_all: q = [message_data.data['q'+field_name_suffix+'['+str(i)+']'] for i in range(4)] roll = np.arctan2(2.0 * (q[0] * q[1] + q[2] * q[3]), 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2])) pitch = np.arcsin(2.0 * (q[0] * q[2] - q[3] * q[1])) yaw = np.arctan2(2.0 * (q[0] * q[3] + q[1] * q[2]), 1.0 - 2.0 * (q[2] * q[2] + q[3] * q[3])) message_data.data['roll'+field_name_suffix] = roll message_data.data['pitch'+field_name_suffix] = pitch message_data.data['yaw'+field_name_suffix] = yaw def get_configured_rc_input_names(self, channel): """ find all RC mappings to a given channel and return their names :param channel: input channel (0=first) :return: list of strings or None """ ret_val = [] for key in self._ulog.initial_parameters: param_val = self._ulog.initial_parameters[key] if key.startswith('RC_MAP_') and param_val == channel + 1: ret_val.append(key[7:].capitalize()) if len(ret_val) > 0: return ret_val return None ================================================ FILE: pyulog/px4_events.py ================================================ """ Event parsing """ import json import lzma import urllib.request from typing import Optional, Callable, Any, List, Tuple from .libevents_parse.parser import Parser from .core import ULog class PX4Events: """ class to extract events from logs and combine them with metadata to get the messages """ DEFAULT_EVENTS_URL = \ 'https://px4-travis.s3.amazonaws.com/Firmware/master/_general/all_events.json.xz' def __init__(self): self._events_profile = 'dev' self._default_parser: Optional[Parser] = None self._get_default_json_def_cb = self._get_default_json_definitions @staticmethod def _get_default_json_definitions(already_has_default_parser: bool) -> Optional[Any]: """ Default implementation for retrieving the default json event definitions """ # If it already exists, return it to avoid re-downloading if already_has_default_parser: return None with urllib.request.urlopen(PX4Events.DEFAULT_EVENTS_URL, timeout=4) as response: data = response.read() return json.loads(lzma.decompress(data)) def set_default_json_definitions_cb(self, default_json_definitions_cb: Callable[[bool], Optional[Any]]): """ Set the callback to retrieve the default event definitions json data (can be used for caching) """ self._get_default_json_def_cb = default_json_definitions_cb def _get_event_parser(self, ulog: ULog) -> Optional[Parser]: """ get event parser instance or None on error """ if 'metadata_events' in ulog.msg_info_multiple_dict and \ 'metadata_events_sha256' in ulog.msg_info_dict: file_hash = ulog.msg_info_dict['metadata_events_sha256'] if len(file_hash) <= 64 and file_hash.isalnum(): events_metadata = ulog.msg_info_multiple_dict['metadata_events'][0] event_definitions_json = json.loads(lzma.decompress(b''.join(events_metadata))) parser = Parser() parser.load_definitions(event_definitions_json) parser.set_profile(self._events_profile) return parser # No json definitions in the log -> use default definitions json_definitions = self._get_default_json_def_cb( self._default_parser is not None) if json_definitions is not None: self._default_parser = Parser() self._default_parser.load_definitions(json_definitions) self._default_parser.set_profile(self._events_profile) return self._default_parser def get_logged_events(self, ulog: ULog) -> List[Tuple[int, str, str]]: """ Get the events as list of messages :return: list of (timestamp, log level str, message) tuples """ def event_log_level_str(log_level: int): return {0: 'EMERGENCY', 1: 'ALERT', 2: 'CRITICAL', 3: 'ERROR', 4: 'WARNING', 5: 'NOTICE', 6: 'INFO', 7: 'DEBUG', 8: 'PROTOCOL', 9: 'DISABLED'}.get(log_level, 'UNKNOWN') # Parse events messages = [] try: events = ulog.get_dataset('event') all_ids = events.data['id'] if len(all_ids) == 0: return [] # Get the parser try: event_parser = self._get_event_parser(ulog) except Exception as exception: # pylint: disable=broad-exception-caught print('Failed to get event parser: {}'.format(exception)) return [] for event_idx, event_id in enumerate(all_ids): log_level = (events.data['log_levels'][event_idx] >> 4) & 0xf if log_level >= 8: continue args = [] i = 0 while True: arg_str = 'arguments[{}]'.format(i) if arg_str not in events.data: break arg = events.data[arg_str][event_idx] args.append(arg) i += 1 log_level_str = event_log_level_str(log_level) t = events.data['timestamp'][event_idx] event = None if event_parser is not None: event = event_parser.parse(event_id, bytes(args)) if event is None: messages.append((t, log_level_str, '[Unknown event with ID {:}]'.format(event_id))) else: # only show default group if event.group() == "default": messages.append((t, log_level_str, event.message())) # we could expand this a bit for events: # - show the description too # - handle url's as link (currently it's shown as text, and all tags are escaped) except (KeyError, IndexError, ValueError): # no events in log pass return messages ================================================ FILE: pyulog/sql/pyulog.1.sql ================================================ BEGIN; CREATE TABLE IF NOT EXISTS ULog ( Id INTEGER PRIMARY KEY AUTOINCREMENT, FileVersion INT, StartTimestamp REAL, LastTimestamp REAL, CompatFlags TEXT, IncompatFlags TEXT, SyncCount INT, HasSync BOOLEAN ); CREATE TABLE IF NOT EXISTS ULogAppendedOffsets ( SeriesIndex INTEGER, Offset INTEGER, ULogId INT REFERENCES ULog (Id) ); CREATE TABLE IF NOT EXISTS ULogDataset ( Id INTEGER PRIMARY KEY AUTOINCREMENT, DatasetName TEXT, MultiId INT, MessageId INT, TimestampIndex INT, ULogId INT REFERENCES ULog (Id), UNIQUE (ULogId, DatasetName, MultiId) ); CREATE TABLE IF NOT EXISTS ULogField ( Id INTEGER PRIMARY KEY AUTOINCREMENT, TopicName TEXT, DataType TEXT, ValueArray BLOB, DatasetId INTEGER REFERENCES ULogDataset (Id) ); CREATE INDEX IF NOT EXISTS btree_ulogfield_datasetid ON ULogField(DatasetId); CREATE TABLE IF NOT EXISTS ULogMessageDropout ( Timestamp REAL, Duration FLOAT, ULogId INT REFERENCES ULog (Id) ); CREATE TABLE IF NOT EXISTS ULogMessageFormat ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT, ULogId INT REFERENCES ULog (Id) ); CREATE TABLE IF NOT EXISTS ULogMessageFormatField ( FieldType TEXT, ArraySize INT, Name TEXT, MessageId INT REFERENCES ULogMessageFormat (Id) ); CREATE TABLE IF NOT EXISTS ULogMessageLogging ( LogLevel INT, Timestamp REAL, Message TEXT, ULogId INT REFERENCES ULog (Id) ); CREATE TABLE IF NOT EXISTS ULogMessageLoggingTagged ( LogLevel INT, Timestamp REAL, Tag INT, Message TEXT, ULogId INT REFERENCES ULog (Id) ); CREATE TABLE IF NOT EXISTS ULogMessageInfo ( Key TEXT, Typename TEXT, Value BLOB, ULogId INT REFERENCES ULog (Id) ); CREATE TABLE IF NOT EXISTS ULogMessageInfoMultiple ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Key TEXT, Typename TEXT, ULogId INT REFERENCES ULog (Id) ); CREATE TABLE IF NOT EXISTS ULogMessageInfoMultipleList ( Id INTEGER PRIMARY KEY AUTOINCREMENT, SeriesIndex INTEGER, MessageId TEXT REFERENCES ULogMessageInfoMultiple (Id) ); CREATE TABLE IF NOT EXISTS ULogMessageInfoMultipleListElement ( Id INTEGER PRIMARY KEY AUTOINCREMENT, SeriesIndex INTEGER, Value TEXT, ListId TEXT REFERENCES ULogMessageInfoMultipleList (Id) ); CREATE TABLE IF NOT EXISTS ULogInitialParameter ( Key TEXT, Value BLOB, ULogId INT REFERENCES ULog (Id) ); CREATE TABLE IF NOT EXISTS ULogChangedParameter ( Timestamp REAL, Key TEXT, Value BLOB, ULogId INT REFERENCES ULog (Id) ); CREATE TABLE IF NOT EXISTS ULogDefaultParameter ( DefaultType INT, Key TEXT, Value BLOB, ULogId INT REFERENCES ULog (Id) ); COMMIT; ================================================ FILE: pyulog/sql/pyulog.2.sql ================================================ BEGIN; PRAGMA foreign_keys=off; CREATE TABLE IF NOT EXISTS ULog_tmp ( Id INTEGER PRIMARY KEY AUTOINCREMENT, SHA256Sum TEXT UNIQUE, FileVersion INT, StartTimestamp REAL, LastTimestamp REAL, CompatFlags TEXT, IncompatFlags TEXT, SyncCount INT, HasSync BOOLEAN ); INSERT OR IGNORE INTO ULog_tmp (Id, FileVersion, StartTimestamp, LastTimestamp, CompatFlags, IncompatFlags, SyncCount, HasSync) SELECT Id, FileVersion, StartTimestamp, LastTimestamp, CompatFlags, IncompatFlags, SyncCount, HasSync FROM ULog; CREATE TABLE IF NOT EXISTS ULogAppendedOffsets_tmp ( SeriesIndex INTEGER, Offset INTEGER, ULogId INT REFERENCES ULog_tmp (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogAppendedOffsets_tmp SELECT * FROM ULogAppendedOffsets; CREATE TABLE IF NOT EXISTS ULogDataset_tmp ( Id INTEGER PRIMARY KEY AUTOINCREMENT, DatasetName TEXT, MultiId INT, MessageId INT, TimestampIndex INT, ULogId INT REFERENCES ULog (Id) ON DELETE CASCADE, UNIQUE (ULogId, DatasetName, MultiId) ); INSERT OR IGNORE INTO ULogDataset_tmp SELECT * FROM ULogDataset; CREATE TABLE IF NOT EXISTS ULogField_tmp ( Id INTEGER PRIMARY KEY AUTOINCREMENT, TopicName TEXT, DataType TEXT, ValueArray BLOB, DatasetId INTEGER REFERENCES ULogDataset (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogField_tmp SELECT * FROM ULogField; CREATE INDEX IF NOT EXISTS btree_ulogfield_datasetid ON ULogField_tmp(DatasetId); CREATE TABLE IF NOT EXISTS ULogMessageDropout_tmp ( Timestamp REAL, Duration FLOAT, ULogId INT REFERENCES ULog (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogMessageDropout_tmp SELECT * FROM ULogMessageDropout; CREATE TABLE IF NOT EXISTS ULogMessageFormat_tmp ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT, ULogId INT REFERENCES ULog (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogMessageFormat_tmp SELECT * FROM ULogMessageFormat; CREATE TABLE IF NOT EXISTS ULogMessageFormatField_tmp ( FieldType TEXT, ArraySize INT, Name TEXT, MessageId INT REFERENCES ULogMessageFormat (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogMessageFormatField_tmp SELECT * FROM ULogMessageFormatField; CREATE TABLE IF NOT EXISTS ULogMessageLogging_tmp ( LogLevel INT, Timestamp REAL, Message TEXT, ULogId INT REFERENCES ULog (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogMessageLogging_tmp SELECT * FROM ULogMessageLogging; CREATE TABLE IF NOT EXISTS ULogMessageLoggingTagged_tmp ( LogLevel INT, Timestamp REAL, Tag INT, Message TEXT, ULogId INT REFERENCES ULog (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogMessageLoggingTagged_tmp SELECT * FROM ULogMessageLoggingTagged; CREATE TABLE IF NOT EXISTS ULogMessageInfo_tmp ( Key TEXT, Typename TEXT, Value BLOB, ULogId INT REFERENCES ULog (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogMessageInfo_tmp SELECT * FROM ULogMessageInfo; CREATE TABLE IF NOT EXISTS ULogMessageInfoMultiple_tmp ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Key TEXT, Typename TEXT, ULogId INT REFERENCES ULog (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogMessageInfoMultiple_tmp SELECT * FROM ULogMessageInfoMultiple; CREATE TABLE IF NOT EXISTS ULogMessageInfoMultipleList_tmp ( Id INTEGER PRIMARY KEY AUTOINCREMENT, SeriesIndex INTEGER, MessageId TEXT REFERENCES ULogMessageInfoMultiple (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogMessageInfoMultipleList_tmp SELECT * FROM ULogMessageInfoMultipleList; CREATE TABLE IF NOT EXISTS ULogMessageInfoMultipleListElement_tmp ( Id INTEGER PRIMARY KEY AUTOINCREMENT, SeriesIndex INTEGER, Value TEXT, ListId TEXT REFERENCES ULogMessageInfoMultipleList (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogMessageInfoMultipleListElement_tmp SELECT * FROM ULogMessageInfoMultipleListElement; CREATE TABLE IF NOT EXISTS ULogInitialParameter_tmp ( Key TEXT, Value BLOB, ULogId INT REFERENCES ULog (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogInitialParameter_tmp SELECT * FROM ULogInitialParameter; CREATE TABLE IF NOT EXISTS ULogChangedParameter_tmp ( Timestamp REAL, Key TEXT, Value BLOB, ULogId INT REFERENCES ULog (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogChangedParameter_tmp SELECT * FROM ULogChangedParameter; CREATE TABLE IF NOT EXISTS ULogDefaultParameter_tmp ( DefaultType INT, Key TEXT, Value BLOB, ULogId INT REFERENCES ULog (Id) ON DELETE CASCADE ); INSERT OR IGNORE INTO ULogDefaultParameter_tmp SELECT * FROM ULogDefaultParameter; DROP TABLE IF EXISTS ULog; DROP TABLE IF EXISTS ULogAppendedOffsets; DROP TABLE IF EXISTS ULogDataset; DROP TABLE IF EXISTS ULogField; DROP TABLE IF EXISTS ULogMessageDropout; DROP TABLE IF EXISTS ULogMessageFormat; DROP TABLE IF EXISTS ULogMessageFormatField; DROP TABLE IF EXISTS ULogMessageLogging; DROP TABLE IF EXISTS ULogMessageLoggingTagged; DROP TABLE IF EXISTS ULogMessageInfo; DROP TABLE IF EXISTS ULogMessageInfoMultiple; DROP TABLE IF EXISTS ULogMessageInfoMultipleList; DROP TABLE IF EXISTS ULogMessageInfoMultipleListElement; DROP TABLE IF EXISTS ULogInitialParameter; DROP TABLE IF EXISTS ULogChangedParameter; DROP TABLE IF EXISTS ULogDefaultParameter; ALTER TABLE ULog_tmp RENAME TO ULog; ALTER TABLE ULogAppendedOffsets_tmp RENAME TO ULogAppendedOffsets; ALTER TABLE ULogDataset_tmp RENAME TO ULogDataset; ALTER TABLE ULogField_tmp RENAME TO ULogField; ALTER TABLE ULogMessageDropout_tmp RENAME TO ULogMessageDropout; ALTER TABLE ULogMessageFormat_tmp RENAME TO ULogMessageFormat; ALTER TABLE ULogMessageFormatField_tmp RENAME TO ULogMessageFormatField; ALTER TABLE ULogMessageLogging_tmp RENAME TO ULogMessageLogging; ALTER TABLE ULogMessageLoggingTagged_tmp RENAME TO ULogMessageLoggingTagged; ALTER TABLE ULogMessageInfo_tmp RENAME TO ULogMessageInfo; ALTER TABLE ULogMessageInfoMultiple_tmp RENAME TO ULogMessageInfoMultiple; ALTER TABLE ULogMessageInfoMultipleList_tmp RENAME TO ULogMessageInfoMultipleList; ALTER TABLE ULogMessageInfoMultipleListElement_tmp RENAME TO ULogMessageInfoMultipleListElement; ALTER TABLE ULogInitialParameter_tmp RENAME TO ULogInitialParameter; ALTER TABLE ULogChangedParameter_tmp RENAME TO ULogChangedParameter; ALTER TABLE ULogDefaultParameter_tmp RENAME TO ULogDefaultParameter; PRAGMA foreign_keys=on; COMMIT; ================================================ FILE: pyulog/sql/pyulog.3.sql ================================================ BEGIN; ALTER TABLE ULogField ADD COLUMN ValueJson JSON; COMMIT; ================================================ FILE: pyulog/sql/pyulog.4.sql ================================================ BEGIN; PRAGMA foreign_keys=off; -- Change REAL timestamps to INT. SQLITE only supports INT64, but ULog -- changed from REAL -- timestamps are UINT64. We accept losing 1 bit at the top end, since 2^63 -- microseconds = 400,000 years. which should be enough. ALTER TABLE ULog RENAME COLUMN StartTimestamp TO StartTimestamp_old; ALTER TABLE ULog ADD COLUMN StartTimestamp INT; UPDATE ULog SET StartTimestamp = CAST(StartTimestamp_old AS INT); ALTER TABLE ULog RENAME COLUMN LastTimestamp TO LastTimestamp_old; ALTER TABLE ULog ADD COLUMN LastTimestamp INT; UPDATE ULog SET LastTimestamp = CAST(LastTimestamp_old AS INT); ALTER TABLE ULogMessageDropout RENAME COLUMN Timestamp TO Timestamp_old; ALTER TABLE ULogMessageDropout ADD COLUMN Timestamp INT; UPDATE ULogMessageDropout SET Timestamp = CAST(Timestamp_old AS INT); ALTER TABLE ULogMessageDropout RENAME COLUMN Duration TO Duration_old; ALTER TABLE ULogMessageDropout ADD COLUMN Duration INT; UPDATE ULogMessageDropout SET Duration = CAST(Duration_old AS INT); ALTER TABLE ULogMessageLogging RENAME COLUMN Timestamp TO Timestamp_old; ALTER TABLE ULogMessageLogging ADD COLUMN Timestamp INT; UPDATE ULogMessageLogging SET Timestamp = CAST(Timestamp_old AS INT); ALTER TABLE ULogMessageLoggingTagged RENAME COLUMN Timestamp TO Timestamp_old; ALTER TABLE ULogMessageLoggingTagged ADD COLUMN Timestamp INT; UPDATE ULogMessageLoggingTagged SET Timestamp = CAST(Timestamp_old AS INT); ALTER TABLE ULogChangedParameter RENAME COLUMN Timestamp TO Timestamp_old; ALTER TABLE ULogChangedParameter ADD COLUMN Timestamp INT; UPDATE ULogChangedParameter SET Timestamp = CAST(Timestamp_old AS INT); PRAGMA foreign_keys=on; COMMIT; ================================================ FILE: pyulog/sql/pyulog.5.sql ================================================ BEGIN; CREATE INDEX IF NOT EXISTS btree_ULogAppendedOffsets_ULogId ON ULogAppendedOffsets(ULogId); CREATE INDEX IF NOT EXISTS btree_ULogDataset_ULogId ON ULogDataset(ULogId); CREATE INDEX IF NOT EXISTS btree_ULogField_DatasetId ON ULogField(DatasetId); CREATE INDEX IF NOT EXISTS btree_ULogMessageDropout_ULogId ON ULogMessageDropout(ULogId); CREATE INDEX IF NOT EXISTS btree_ULogMessageFormat_ULogId ON ULogMessageFormat(ULogId); CREATE INDEX IF NOT EXISTS btree_ULogMessageFormatField_MessageId ON ULogMessageFormatField(MessageId); CREATE INDEX IF NOT EXISTS btree_ULogMessageLogging_ULogId ON ULogMessageLogging(ULogId); CREATE INDEX IF NOT EXISTS btree_ULogMessageLoggingTagged_ULogId ON ULogMessageLoggingTagged(ULogId); CREATE INDEX IF NOT EXISTS btree_ULogMessageInfo_ULogId ON ULogMessageInfo(ULogId); CREATE INDEX IF NOT EXISTS btree_ULogMessageInfoMultiple_ULogId ON ULogMessageInfoMultiple(ULogId); CREATE INDEX IF NOT EXISTS btree_ULogMessageInfoMultipleList_MessageId ON ULogMessageInfoMultipleList(MessageId); CREATE INDEX IF NOT EXISTS btree_ULogMessageInfoMultipleListElement_ListId ON ULogMessageInfoMultipleListElement(ListId); CREATE INDEX IF NOT EXISTS btree_ULogInitialParameter_ULogId ON ULogInitialParameter(ULogId); CREATE INDEX IF NOT EXISTS btree_ULogChangedParameter_ULogId ON ULogChangedParameter(ULogId); CREATE INDEX IF NOT EXISTS btree_ULogDefaultParameter_ULogId ON ULogDefaultParameter(ULogId); COMMIT; ================================================ FILE: pyulog/ulog2csv.py ================================================ #! /usr/bin/env python """ Convert a ULog file into CSV file(s) """ import argparse import os import re import numpy as np from .core import ULog #pylint: disable=too-many-locals, invalid-name, consider-using-enumerate def main(): """Command line interface""" parser = argparse.ArgumentParser(description='Convert ULog to CSV') parser.add_argument('filename', metavar='file.ulg', help='ULog input file') parser.add_argument( '-m', '--messages', dest='messages', help=("Only consider given messages. Must be a comma-separated list of" " names, like 'sensor_combined,vehicle_gps_position'")) parser.add_argument('-d', '--delimiter', dest='delimiter', action='store', help="Use delimiter in CSV (default is ',')", default=',') parser.add_argument('-o', '--output', dest='output', action='store', help='Output directory (default is same as input file)', metavar='DIR') parser.add_argument('-i', '--ignore', dest='ignore', action='store_true', help='Ignore string parsing exceptions', default=False) parser.add_argument( '-ts', '--time_s', dest='time_s', type = int, help="Only convert data after this timestamp (in seconds)") parser.add_argument( '-te', '--time_e', dest='time_e', type=int, help="Only convert data upto this timestamp (in seconds)") args = parser.parse_args() if args.output and not os.path.isdir(args.output): print('Creating output directory {:}'.format(args.output)) os.mkdir(args.output) convert_ulog2csv(args.filename, args.messages, args.output, args.delimiter, args.time_s, args.time_e, args.ignore) def read_string_data(data: ULog.Data, field_name: str, array_size: int, data_index: int) -> str: """ Parse a data field as string """ s = '' for index in range(array_size): character = data.data[f'{field_name}[{index}]'][data_index] if character == 0: break s += chr(character) return s def convert_ulog2csv(ulog_file_name, messages, output, delimiter, time_s, time_e, disable_str_exceptions=False): """ Coverts and ULog file to a CSV file. :param ulog_file_name: The ULog filename to open and read :param messages: A list of message names :param output: Output file path :param delimiter: CSV delimiter :param time_s: Offset time for conversion in seconds :param time_e: Limit until time for conversion in seconds :return: None """ msg_filter = messages.split(',') if messages else None ulog = ULog(ulog_file_name, msg_filter, disable_str_exceptions) data = ulog.data_list output_file_prefix = ulog_file_name # strip '.ulg' if output_file_prefix.lower().endswith('.ulg'): output_file_prefix = output_file_prefix[:-4] # write to different output path? if output: base_name = os.path.basename(output_file_prefix) output_file_prefix = os.path.join(output, base_name) array_pattern = re.compile(r"(.*)\[(.*?)\]") def get_fields(data: ULog.Data) -> tuple[list[str], dict[str, int]]: # use same field order as in the log, except for the timestamp data_keys = [] string_array_sizes = {} for f in data.field_data: if f.field_name.startswith('_padding'): continue result = array_pattern.fullmatch(f.field_name) if result and f.type_str == 'char': # string (array of char's) field, array_index = result.groups() array_index = int(array_index) string_array_sizes[field] = max(array_index + 1, string_array_sizes.get(field, 0)) if array_index == 0: data_keys.append(field) else: data_keys.append(f.field_name) data_keys.remove('timestamp') data_keys.insert(0, 'timestamp') # we want timestamp at first position return data_keys, string_array_sizes for d in data: name_without_slash = d.name.replace('/', '_') output_file_name = f'{output_file_prefix}_{name_without_slash}_{d.multi_id}.csv' num_data_points = len(d.data['timestamp']) print(f'Writing {output_file_name} ({num_data_points} data points)') with open(output_file_name, 'w', encoding='utf-8') as csvfile: data_keys, string_array_sizes = get_fields(d) # we don't use np.savetxt, because we have multiple arrays with # potentially different data types. However the following is quite # slow... # write the header csvfile.write(delimiter.join(data_keys) + '\n') #get the index for row where timestamp exceeds or equals the required value time_s_i = np.where(d.data['timestamp'] >= time_s * 1e6)[0][0] \ if time_s else 0 #get the index for row upto the timestamp of the required value time_e_i = np.where(d.data['timestamp'] >= time_e * 1e6)[0][0] \ if time_e else len(d.data['timestamp']) # write the data last_elem = len(data_keys)-1 for i in range(time_s_i, time_e_i): for k in range(len(data_keys)): if data_keys[k] in string_array_sizes: # string s = read_string_data(d, data_keys[k], string_array_sizes[data_keys[k]], i) csvfile.write(s) else: csvfile.write(str(d.data[data_keys[k]][i])) if k != last_elem: csvfile.write(delimiter) csvfile.write('\n') ================================================ FILE: pyulog/ulog2kml.py ================================================ #! /usr/bin/env python """ Convert a ULog file into a KML file (positioning information) """ import argparse import simplekml # pylint: disable=import-error from .core import ULog #pylint: disable=too-many-locals, invalid-name, consider-using-enumerate, too-many-arguments #pylint: disable=unused-variable def main(): """Command line interface""" parser = argparse.ArgumentParser(description='Convert ULog to KML') parser.add_argument('filename', metavar='file.ulg', help='ULog input file') parser.add_argument('-o', '--output', dest='output_filename', help="output filename", default='track.kml') parser.add_argument('--topic', dest='topic_name', help="topic name with position data (default=vehicle_gps_position)", default='vehicle_gps_position') parser.add_argument('--camera-trigger', dest='camera_trigger', help="Camera trigger topic name (e.g. camera_capture)", default=None) parser.add_argument('-i', '--ignore', dest='ignore', action='store_true', help='Ignore string parsing exceptions', default=False) args = parser.parse_args() convert_ulog2kml(args.filename, args.output_filename, position_topic_name=args.topic_name, camera_trigger_topic_name=args.camera_trigger, disable_str_exceptions=args.ignore) # alternative example call: # convert_ulog2kml(args.filename, 'test.kml', ['vehicle_global_position', # 'vehicle_gps_position'], [_kml_default_colors, lambda x: simplekml.Color.green]) def _kml_default_colors(x): """ flight mode to color conversion """ x = max([x, 0]) colors_arr = [simplekml.Color.red, simplekml.Color.green, simplekml.Color.blue, simplekml.Color.violet, simplekml.Color.yellow, simplekml.Color.orange, simplekml.Color.burlywood, simplekml.Color.azure, simplekml.Color.lightblue, simplekml.Color.lawngreen, simplekml.Color.indianred, simplekml.Color.hotpink, simplekml.Color.bisque, simplekml.Color.cyan, simplekml.Color.darksalmon, simplekml.Color.deepskyblue, simplekml.Color.lime, simplekml.Color.orchid] return colors_arr[x] def convert_ulog2kml(ulog_file_name, output_file_name, position_topic_name= 'vehicle_gps_position', colors=_kml_default_colors, altitude_offset=0, minimum_interval_s=0.1, style=None, camera_trigger_topic_name=None, disable_str_exceptions=False): """ Coverts and ULog file to a CSV file. :param ulog_file_name: The ULog filename to open and read :param output_file_name: KML Output file name :param position_topic_name: either name of a topic (must have 'lon', 'lat' & 'alt' fields), or a list of topic names :param colors: lambda function with flight mode (int) (or -1) as input and returns a color (eg 'fffff8f0') (or list of lambda functions if multiple position_topic_name's) :param altitude_offset: add this offset to the altitude [m] :param minimum_interval_s: minimum time difference between two datapoints (drop if more points) :param style: dictionary with rendering options: 'extrude': Bool 'line_width': int :param camera_trigger_topic_name: name of the camera trigger topic (must have 'lon', 'lat' & 'seq') :return: None """ default_style = { 'extrude': False, 'line_width': 3 } used_style = default_style if style is not None: for key in style: used_style[key] = style[key] if not isinstance(position_topic_name, list): position_topic_name = [position_topic_name] colors = [colors] kml = simplekml.Kml() load_topic_names = position_topic_name + ['vehicle_status'] if camera_trigger_topic_name is not None: load_topic_names.append(camera_trigger_topic_name) ulog = ULog(ulog_file_name, load_topic_names, disable_str_exceptions) # get flight modes try: cur_dataset = ulog.get_dataset('vehicle_status') flight_mode_changes = cur_dataset.list_value_changes('nav_state') flight_mode_changes.append((ulog.last_timestamp, -1)) except (KeyError, IndexError) as error: flight_mode_changes = [] # add the graphs for topic, cur_colors in zip(position_topic_name, colors): _kml_add_position_data(kml, ulog, topic, cur_colors, used_style, altitude_offset, minimum_interval_s, flight_mode_changes) # camera triggers _kml_add_camera_triggers(kml, ulog, camera_trigger_topic_name, altitude_offset) kml.save(output_file_name) def _kml_add_camera_triggers(kml, ulog, camera_trigger_topic_name, altitude_offset): """ Add camera trigger points to the map """ data = ulog.data_list topic_instance = 0 cur_dataset = [elem for elem in data if elem.name == camera_trigger_topic_name and elem.multi_id == topic_instance] if len(cur_dataset) > 0: cur_dataset = cur_dataset[0] pos_lon = cur_dataset.data['lon'] pos_lat = cur_dataset.data['lat'] pos_alt = cur_dataset.data['alt'] sequence = cur_dataset.data['seq'] for i in range(len(pos_lon)): pnt = kml.newpoint(name='Camera Trigger '+str(sequence[i])) pnt.coords = [(pos_lon[i], pos_lat[i], pos_alt[i] + altitude_offset)] # Balloons instead of text does not work #pnt.style.balloonstyle.text = 'Camera Trigger '+str(sequence[i]) def _kml_add_position_data(kml, ulog, position_topic_name, colors, style, altitude_offset=0, minimum_interval_s=0.1, flight_mode_changes=None): data = ulog.data_list topic_instance = 0 if flight_mode_changes is None: flight_mode_changes = [] cur_dataset = [elem for elem in data if elem.name == position_topic_name and elem.multi_id == topic_instance] if len(cur_dataset) == 0: raise KeyError(position_topic_name+' not found in data') cur_dataset = cur_dataset[0] pos_lon = cur_dataset.data['lon'] pos_lat = cur_dataset.data['lat'] pos_alt = cur_dataset.data['alt'] pos_t = cur_dataset.data['timestamp'] if 'fix_type' in cur_dataset.data: indices = cur_dataset.data['fix_type'] > 2 # use only data with a fix pos_lon = pos_lon[indices] pos_lat = pos_lat[indices] pos_alt = pos_alt[indices] pos_t = pos_t[indices] # scale if it's an integer type lon_type = [f.type_str for f in cur_dataset.field_data if f.field_name == 'lon'] if len(lon_type) > 0 and lon_type[0] == 'int32_t': pos_lon = pos_lon / 1e7 # to degrees pos_lat = pos_lat / 1e7 pos_alt = pos_alt / 1e3 # to meters current_flight_mode = 0 current_flight_mode_idx = 0 if len(flight_mode_changes) > 0: current_flight_mode = flight_mode_changes[0][1] def create_linestring(): """ create a new kml linestring and set rendering options """ name = position_topic_name + ":" + str(current_flight_mode) new_linestring = kml.newlinestring(name=name, altitudemode='absolute') # set rendering options if style['extrude']: new_linestring.extrude = 1 new_linestring.style.linestyle.color = colors(current_flight_mode) new_linestring.style.linestyle.width = style['line_width'] return new_linestring current_kml_linestring = create_linestring() last_t = 0 for i in range(len(pos_lon)): cur_t = pos_t[i] if (cur_t - last_t)/1e6 > minimum_interval_s: # assume timestamp is in [us] pos_data = [pos_lon[i], pos_lat[i], pos_alt[i] + altitude_offset] current_kml_linestring.coords.addcoordinates([pos_data]) last_t = cur_t # flight mode change? while current_flight_mode_idx < len(flight_mode_changes)-1 and \ flight_mode_changes[current_flight_mode_idx+1][0] <= cur_t: current_flight_mode_idx += 1 current_flight_mode = flight_mode_changes[current_flight_mode_idx][1] current_kml_linestring = create_linestring() current_kml_linestring.coords.addcoordinates([pos_data]) ================================================ FILE: pyulog/ulog2rosbag.py ================================================ #! /usr/bin/env python """ Convert a ULog file into rosbag file(s) """ from collections import defaultdict import argparse import re import rospy # pylint: disable=import-error import rosbag # pylint: disable=import-error from px4_msgs import msg as px4_msgs # pylint: disable=import-error from .core import ULog #pylint: disable=too-many-locals, invalid-name def main(): """Command line interface""" parser = argparse.ArgumentParser(description='Convert ULog to rosbag') parser.add_argument('filename', metavar='file.ulg', help='ULog input file') parser.add_argument('bag', metavar='file.bag', help='rosbag output file') parser.add_argument( '-m', '--messages', dest='messages', help=("Only consider given messages. Must be a comma-separated list of" " names, like 'sensor_combined,vehicle_gps_position'")) parser.add_argument('-i', '--ignore', dest='ignore', action='store_true', help='Ignore string parsing exceptions', default=False) args = parser.parse_args() convert_ulog2rosbag(args.filename, args.bag, args.messages, args.ignore) # https://stackoverflow.com/questions/19053707/converting-snake-case-to-lower-camel-case-lowercamelcase def to_camel_case(snake_str): """ Convert snake case string to camel case """ components = snake_str.split("_") return ''.join(x.title() for x in components) def convert_ulog2rosbag(ulog_file_name, rosbag_file_name, messages, disable_str_exceptions=False): """ Coverts and ULog file to a CSV file. :param ulog_file_name: The ULog filename to open and read :param rosbag_file_name: The rosbag filename to open and write :param messages: A list of message names :return: No """ array_pattern = re.compile(r"(.*?)\[(.*?)\]") msg_filter = messages.split(',') if messages else None ulog = ULog(ulog_file_name, msg_filter, disable_str_exceptions) data = ulog.data_list multiids = defaultdict(set) for d in data: multiids[d.name].add(d.multi_id) with rosbag.Bag(rosbag_file_name, 'w') as bag: items = [] for d in data: if multiids[d.name] == {0}: topic = "/px4/{}".format(d.name) else: topic = "/px4/{}_{}".format(d.name, d.multi_id) msg_type = getattr(px4_msgs, to_camel_case(d.name)) for i in range(len(d.data['timestamp'])): msg = msg_type() for f in d.field_data: result = array_pattern.match(f.field_name) value = d.data[f.field_name][i] if result: field, array_index = result.groups() array_index = int(array_index) if isinstance(getattr(msg, field), bytes): attr = bytearray(getattr(msg, field)) attr[array_index] = value setattr(msg, field, bytes(attr)) else: getattr(msg, field)[array_index] = value else: setattr(msg, f.field_name, value) ts = rospy.Time(nsecs=d.data['timestamp'][i]*1000) items.append((topic, msg, ts)) items.sort(key=lambda x: x[2]) for topic, msg, ts in items: bag.write(topic, msg, ts) ================================================ FILE: run_tests.sh ================================================ #! /bin/bash pytest test && pylint pyulog/*.py test/*.py ================================================ FILE: setup.py ================================================ #!/usr/bin/env python """ This module allows you to parse ULog files, which are used within the PX4 autopilot middleware. The file format is documented on https://docs.px4.io/main/en/dev_log/ulog_file_format.html """ from setuptools import setup DOCLINES = __doc__.split("\n") # pylint: disable=invalid-name setup( long_description="\n".join(DOCLINES), long_description_content_type='text/x-rst', platforms=["Windows", "Linux", "Solaris", "Mac OS-X", "Unix"], ) ================================================ FILE: test/__init__.py ================================================ ================================================ FILE: test/sample_appended_info.txt ================================================ Logging start time: 0:00:05, duration: 0:01:54 Dropouts: count: 1, total duration: 0.0 s, max: 10 ms, mean: 10 ms Info Messages: perf_counter_preflight-00: navigator: 3 events, 80us elapsed, 26us avg, min 25us max 28us 1.528us rms perf_counter_preflight-01: mc_att_control: 766 events, 38087us elapsed, 49us avg, min 23us max 395us 32.174us rms perf_counter_preflight-02: logger_sd_fsync: 0 events, 0us elapsed, 0us avg, min 0us max 0us 0.000us rms perf_counter_preflight-03: logger_sd_write: 3 events, 72442us elapsed, 24147us avg, min 10us max 36356us 20904.014us rms perf_counter_preflight-04: mavlink_txe: 226 events perf_counter_preflight-05: mavlink_el: 1016 events, 163693us elapsed, 161us avg, min 84us max 2478us 191.598us rms perf_counter_preflight-06: mavlink_txe: 0 events perf_counter_preflight-07: mavlink_el: 286 events, 33394us elapsed, 116us avg, min 46us max 1851us 166.045us rms perf_counter_preflight-08: mavlink_txe: 0 events perf_counter_preflight-09: mavlink_el: 318 events, 48587us elapsed, 152us avg, min 66us max 2327us 259.293us rms perf_counter_preflight-10: mavlink_txe: 0 events perf_counter_preflight-11: mavlink_el: 1030 events, 214017us elapsed, 207us avg, min 79us max 4163us 310.871us rms perf_counter_preflight-12: ctl_lat: 321 events, 13187us elapsed, 41us avg, min 38us max 111us 11.183us rms perf_counter_preflight-13: stack_check: 7 events, 69us elapsed, 9us avg, min 2us max 16us 4.488us rms perf_counter_preflight-14: sensors: 826 events, 93853us elapsed, 113us avg, min 65us max 5118us 179.764us rms perf_counter_preflight-15: ctrl_latency: 321 events, 40037us elapsed, 124us avg, min 103us max 3022us 166.815us rms perf_counter_preflight-16: mpu9250_dupe: 898 events perf_counter_preflight-17: mpu9250_reset: 0 events perf_counter_preflight-18: mpu9250_good_trans: 3443 events perf_counter_preflight-19: mpu9250_bad_reg: 0 events perf_counter_preflight-20: mpu9250_bad_trans: 0 events perf_counter_preflight-21: mpu9250_read: 4342 events, 269357us elapsed, 62us avg, min 41us max 91us 13.632us rms perf_counter_preflight-22: mpu9250_gyro_read: 0 events perf_counter_preflight-23: mpu9250_acc_read: 2 events perf_counter_preflight-24: mpu9250_mag_duplicates: 3066 events perf_counter_preflight-25: mpu9250_mag_overflows: 0 events perf_counter_preflight-26: mpu9250_mag_overruns: 51 events perf_counter_preflight-27: mpu9250_mag_errors: 0 events perf_counter_preflight-28: mpu9250_mag_reads: 0 events perf_counter_preflight-29: adc_samples: 3024 events, 8046us elapsed, 2us avg, min 2us max 3us 0.474us rms perf_counter_preflight-30: ms5611_com_err: 0 events perf_counter_preflight-31: ms5611_measure: 321 events, 5603us elapsed, 17us avg, min 8us max 679us 54.355us rms perf_counter_preflight-32: ms5611_read: 320 events, 23168us elapsed, 72us avg, min 13us max 543us 49.197us rms perf_counter_preflight-33: dma_alloc: 4 events perf_top_preflight-00: PID COMMAND CPU(ms) CPU(%) USED/STACK PRIO(BASE) STATE perf_top_preflight-01: 0 Idle Task 2922 53.339 596/ 748 0 ( 0) READY perf_top_preflight-02: 1 hpwork 207 4.486 928/ 1780 192 (192) w:sig perf_top_preflight-03: 2 lpwork 3 0.099 640/ 1780 50 ( 50) w:sig perf_top_preflight-04: 3 init 4816 0.000 1720/ 2580 100 (100) w:sem perf_top_preflight-05: 228 px4flow 1 0.000 728/ 1164 100 (100) w:sig perf_top_preflight-06: 104 gps 10 0.099 1032/ 1524 220 (220) w:sem perf_top_preflight-07: 108 dataman 1 0.000 720/ 1180 90 ( 90) w:sem perf_top_preflight-08: 152 sensors 161 3.389 1440/ 1980 250 (250) READY perf_top_preflight-09: 154 commander 59 2.093 2776/ 3652 140 (140) w:sig perf_top_preflight-10: 169 mavlink_if0 261 6.281 1648/ 2380 100 (100) READY perf_top_preflight-11: 170 mavlink_rcv_if0 14 0.299 1320/ 2140 175 (175) w:sem perf_top_preflight-12: 175 mavlink_if1 63 1.395 1616/ 2420 100 (100) w:sig perf_top_preflight-13: 176 mavlink_rcv_if1 16 0.398 1496/ 2140 175 (175) w:sem perf_top_preflight-14: 187 mavlink_if2 48 1.096 1632/ 2388 100 (100) w:sig perf_top_preflight-15: 188 mavlink_rcv_if2 15 0.299 1316/ 2140 175 (175) w:sem perf_top_preflight-16: 217 frsky_telemetry 0 0.000 544/ 1188 200 (200) w:sem perf_top_preflight-17: 254 log_writer_file 13 0.997 544/ 1060 60 ( 60) w:sem perf_top_preflight-18: 235 mavlink_if3 249 4.287 1576/ 2388 100 (100) READY perf_top_preflight-19: 237 mavlink_rcv_if3 62 2.093 1316/ 2140 175 (175) READY perf_top_preflight-20: 253 logger 115 6.779 3104/ 3532 250 (250) RUN perf_top_preflight-21: 341 commander_low_prio 0 0.000 592/ 2996 50 ( 50) w:sem perf_top_preflight-22: 295 ekf2 331 9.870 5056/ 5780 250 (250) w:sem perf_top_preflight-23: 305 mc_att_control 97 2.991 1136/ 1676 250 (250) READY perf_top_preflight-24: 307 mc_pos_control 12 0.299 552/ 1876 250 (250) w:sem perf_top_preflight-25: 310 navigator 4 0.000 904/ 1772 105 (105) w:sem perf_top_preflight-26: perf_top_preflight-27: Processes: 25 total, 7 running, 18 sleeping perf_top_preflight-28: CPU usage: 46.66% tasks, 0.00% sched, 53.34% idle perf_top_preflight-29: DMA Memory: 5120 total, 1536 used 1536 peak perf_top_preflight-30: Uptime: 6.236s total, 2.922s idle sys_mcu: STM32F42x, rev. 3 sys_name: PX4 sys_os_name: NuttX sys_os_ver: 8b81cf5c7ece0c228eaaea3e9d8e667fc4d21a06 sys_os_ver_release: 192 sys_toolchain: GNU GCC sys_toolchain_ver: 5.4.1 20160919 (release) [ARM/embedded-5-branch revision 240496] sys_uuid: 004F00413335510D30383336 time_ref_utc: 0 ver_hw: PX4FMU_V4 ver_sw: f54a6c2999e1e2fcbf56dd89de06b615b4186a6e ver_sw_branch: ulog_crash_dump ver_sw_release: 17170432 Info Multiple Messages: hardfault_plain: [['[hardfault_log] -- 2000-01-01-00:06:01 Begin Fault Log --\nSystem fault Occurred on: 2000-01-01-00:06:01\n Type:Hard Fault in file:armv7-m/up_hardfault.c at line: 171 running task: hardfault_log\n FW git-hash: f54a6c2999e1e2fcbf56dd89de06b615b4186a6e\n Build datetime: Jul 3 2017 17:04:33\n Build url: localhost \n Processor registers: from 0x2002b8e4\n r0:0x00000000 r1:0x00000000 r2:0x00000001 r3:0xe000ed14 r4:0x00000000 r5:0x20002854 r6:0x2002bba6 r7:0x2002bb8c\n r8:0x00000000 r9:0x00000000 r10:0x00000000 r11:0x00000000 r12:0x00000000 sp:0x2002b9b8 lr:0x080353ad pc:0x080353dc\n xpsr:0x21000000 basepri:0x000000f0 control:0x00000004\n exe return:0xffffffe9\n IRQ stack: \n top: 0x200068f0\n sp: 0x200068a0 Valid\n bottom: 0x20006604\n size: 0x000002ec\n used: 000000e0\n User stack: \n top: 0x2002bb88\n sp: 0x2002b9b8 Valid\n bottom: 0x2002b3ac\n size: 0x000007dc\n used: 000007dc\nInterrupt sp memory region, stack pointer lies within stack\n0x20006952 0x00000000\n0x20006951 0x00000000\n0x20006950 0x00000000\n0x2000694f 0x00000000\n0x2000694e 0x00000000\n0x2000694d 0x00000000\n0x2000694c 0x00000000\n0x2000694b 0x00000000\n0x2000694a 0x00000000\n0x20006949 0x00000000\n0x20006948 0x00000000\n0x20006947 0x00000000\n0x20006946 0x00000000\n0x20006945 0x00000000\n0x20006944 0x00000000\n0x20006943 0x00000000\n0x20006942 0x00000000\n0x20006941 0x00000000\n0x20006940 0x00000000\n0x2000693f 0x00000000\n0x2000693e 0x00000000\n0x2000693d 0x00000000\n0x2000693c 0x00000000\n0x2000693b 0x00000000\n0x2000693a 0x00000000\n0x20006939 0x2001ba90\n0x20006938 0x00000000\n0x20006937 0x00000000\n0x20006936 0x2001bac0\n0x20006935 0x00000000\n0x20006934 0x20007758\n0x20006933 0x20007640\n0x20006932 0x20007608\n0x20006931 0x20007590\n0x20006930 0x20007790\n0x2000692f 0x20007790\n0x2000692e 0x200078a8\n0x2000692d 0x20007794\n0x2000692c 0x00000000\n0x2000692b 0x00000603\n0x2000692a 0x100061b0\n0x20006929 0x10006060\n0x20006928 0x20006990\n0x20006927 0x10005460\n0x20006926 0x00400003\n0x20006925 0x0813b450\n0x20006924 0x00000002\n0x20006923 0x00000000\n0x20006922 0x00000000\n0x20006921 0x20006990\n0x20006920 0x00000000\n0x2000691f 0x00400002\n0x2000691e 0x0813b450\n0x2000691d 0x00000001\n0x2000691c 0x00000000\n0x2000691b 0x00000000\n0x2000691a 0x20006990\n0x20006919 0x00000000\n0x20006918 0x00400001\n0x20006917 0x0813b450\n0x20006916 0x00000000\n0x20006915 0x00000000\n0x20006914 0x00000000\n0x20006913 0x20006990\n0x20006912 0x20006990\n0x20006911 0x00400000\n0x20006910 0x0813b450\n0x2000690f 0x00001003\n0x2000690e 0x00000000\n0x2000690d 0x00000000\n0x2000690c 0x20006990\n0x2000690b 0x00000000\n0x2000690a 0x00400083\n0x20006909 0x0813b450\n0x20006908 0x00001602\n0x20006907 0x10006220\n0x20006906 0x100062b0\n0x20006905 0x20006990\n0x20006904 0x10005460\n0x20006903 0x00400082\n0x20006902 0x0813b450\n0x20006901 0x00001301\n0x20006900 0x00000000\n0x200068ff 0x00000000\n0x200068fe 0x20006990\n0x200068fd 0x10005460\n0x200068fc 0x00400081\n0x200068fb 0x0813b450\n0x200068fa 0x00001000\n0x200068f9 0x00000000\n0x200068f8 0x00000000\n0x200068f7 0x20006990\n0x200068f6 0x10005460\n0x200068f5 0x00400080\n0x200068f4 0x0813b450\n0x200068f3 0x00000000\n0x200068f2 0x00000000\n0x200068f1 0x00000000\n0x200068f0 0x00000000<-- Interrupt sp top\n0x200068ef 0x00000000\n0x200068ee 0x00000000\n0x200068ed 0x00000000\n0x200068ec 0x00000000\n0x200068eb 0x00000000\n0x200068ea 0x00000000\n0x200068e9 0x00000000\n0x200068e8 0x00000000\n0x200068e7 0x00000000\n0x200068e6 0x00000000\n0x200068e5 0x00000063\n0x200068e4 0x08000000\n0x200068e3 0x00258000\n0x200068e2 0x07000000\n0x200068e1 0x0020210e\n0x200068e0 0x0e00020c\n0x200068df 0x10005fa4\n0x200068de 0x00000002\n0x200068dd 0x200069f0\n0x200068dc 0x0813b434\n0x200068db 0x00000000\n0x200068da 0x00000000\n0x200068d9 0x00000000\n0x200068d8 0x00000000\n0x200068d7 0x00000000\n0x200068d6 0x00000000\n0x200068d5 0x00000000\n0x200068d4 0x00000000\n0x200068d3 0x00000000\n0x200068d2 0x00000000\n0x200068d1 0x00000000\n0x200068d0 0x00000000\n0x200068cf 0x00000000\n0x200068ce 0x00000000\n0x200068cd 0x00000000\n0x200068cc 0x00000000\n0x200068cb 0x00000000\n0x200068ca 0x00000000\n0x200068c9 0x10005130\n0x200068c8 0x00000001\n0x200068c7 0x20007570\n0x200068c6 0x20007500\n0x200068c5 0x200074e0\n0x200068c4 0x200073f0\n0x200068c3 0x200071a8\n0x200068c2 0x20007040\n0x200068c1 0x200073cc\n0x200068c0 0x200072a0\n0x200068bf 0x20007278\n0x200068be 0x200071d0\n0x200068bd 0x00000005\n0x200068bc 0x10004460\n0x200068bb 0x0000ffff\n0x200068ba 0x00000000\n0x200068b9 0x00000000\n0x200068b8 0x00000000\n0x200068b7 0x00000000\n0x200068b6 0x00000001\n0x200068b5 0x10000010\n0x200068b4 0x00000000\n0x200068b3 0x080f32b7\n0x200068b2 0x2002bb8c\n0x200068b1 0x2002bba6\n0x200068b0 0x20002854\n0x200068af 0x2002b8e4\n0x200068ae 0x000000f0\n0x200068ad 0x080fbca5\n0x200068ac 0x080f3331\n0x200068ab 0x080f3353\n0x200068aa 0x00000000\n0x200068a9 0x00000003\n0x200068a8 0x080f334b\n0x200068a7 0x200068c8\n0x200068a6 0x080353dc\n0x200068a5 0x080353ad\n0x200068a4 0x2002b9b8\n0x200068a3 0x00000000\n0x200068a2 0x00000000\n0x200068a1 0x00000000\n0x200068a0 0x000000f0<-- Interrupt sp\n0x2000689f 0x080f309d\n0x2000689e 0x000000ab\n0x2000689d 0x0813ad13\n0x2000689c 0x20003d88\n0x2000689b 0x20004628\n0x2000689a 0x00000000\n0x20006899 0x20004628\n0x20006898 0x000000ab\n0x20006897 0x08109da9\n0x20006896 0x20000010\n0x20006895 0x080f3429\n0x20006894 0x2002b9b8\n0x20006893 0x2001bb00\n0x20006892 0x080353ad\n0x20006891 0x0000015f\n0x20006890 0x200068a0\n0x2000688f 0x2002b8e4\n0x2000688e 0x2001bb00\n0x2000688d 0x20000000\n0x2000688c 0x20000000\n0x2000688b 0x080f0571\n0x2000688a 0x20006860\n0x20006889 0x20006860\n0x20006888 0x00000000\n0x20006887 0x080f0553\n0x20006886 0x0000015f\n0x20006885 0x2001bb00\n0x20006884 0x00000000\n0x20006883 0x0000003b\n0x20006882 0x080ff849\n0x20006881 0x080fe621\n0x20006880 0x080ff849\n0x2000687f 0x080f8ee1\n0x2000687e 0x2001bb00\n0x2000687d 0x080353ad\n0x2000687c 0x000000ab\n0x2000687b 0x20003c84\n0x2000687a 0x00000000\n0x20006879 0x080f2ec9\n0x20006878 0x20006844\n0x20006877 0x0813ab5a\n0x20006876 0x00000000\n0x20006875 0x00000000\n0x20006874 0x00000000\n0x20006873 0x00000004\n0x20006872 0x080ff849\n0x20006871 0x080f9a59\n0x20006870 0x080fe621\n0x2000686f 0x00000007\n0x2000686e 0x0813b3fe\n0x2000686d 0x080f9603\n0x2000686c 0x00000004\n0x2000686b 0x080f9603\n0x2000686a 0x20006824\n0x20006869 0x00000020\n0x20006868 0x080fe621\n0x20006867 0x080f90f3\n0x20006866 0x20006824\n0x20006865 0x00000002\n0x20006864 0x080fe621\n0x20006863 0x080f90f3\n0x20006862 0x20006824\n0x20006861 0x00000001\n0x20006860 0x080fe621\n0x2000685f 0x080f90f3\n0x2000685e 0x20006824\n0x2000685d 0x00000000\n0x2000685c 0x080fe621\n0x2000685b 0xfbff0f00\n0x2000685a 0xe2ffe425\n0x20006859 0xe4f7d600\n0x20006858 0xbdff01ff\n0x20006857 0xdeadfffb\n0x20006856 0x001e000f\n0x20006855 0x25e4f7e4\n0x20006854 0x004300d6\n0x20006853 0xc0a00000\n0x20006852 0x41700000\n0x20006851 0xc1f00000\n0x20006850 0xdeadbeef\n0x2000684f 0xdeadbeef\n0x2000684e 0x20006848\n0x2000684d 0x200067e0\n0x2000684c 0x200067c8\n0x2000684b 0x0800c071\n0x2000684a 0x00000000\n0x20006849 0x00000000\n0x20006848 0x10007f20\n0x20006847 0x00000000\n0x20006846 0x3a534ee0\n0x20006845 0x3c089f4a\n0x20006844 0x3a3737e0\n0x20006843 0x080accaf\n0x20006842 0x200067f8\n0x20006841 0x1000a730\n0x20006840 0x1000a9a0\n0x2000683f 0x080f3429\n0x2000683e 0x00000000\n0x2000683d 0x00000000\n0x2000683c 0x00000000\n0x2000683b 0x00000000\n0x2000683a 0x200067e0\n0x20006839 0x00000000\n0x20006838 0x00000000\n0x20006837 0x00000002\n0x20006836 0x1000a980\n0x20006835 0xa91ce0dd\n0x20006834 0x2ac590bd\n0x20006833 0x200066d8\n0x20006832 0x00000001\n0x20006831 0x00000003\n0x20006830 0xb34915f7\n0x2000682f 0x351ebdee\n0x2000682e 0x3316cf97\n0x2000682d 0x200066c0\n0x2000682c 0x00000001\n0x2000682b 0x00000003\n0x2000682a 0xb34915f7\n0x20006829 0x351ebdee\n0x20006828 0x3316cf97\n0x20006827 0x200066a8\n0x20006826 0x00000001\n0x20006825 0x00000003\n0x20006824 0x340f1c97\n0x20006823 0x36c307c3\n0x20006822 0x352ce320\n0x20006821 0x08107ccf\n0x20006820 0x075957d3\n0x2000681f 0x200067c8\n0x2000681e 0x10008178\n0x2000681d 0xdeadbeef\n0x2000681c 0xdeadbeef\n0x2000681b 0xdeadbeef\n0x2000681a 0xdeadbeef\n0x20006819 0xdeadbeef\n0x20006818 0xdeadbeef\n0x20006817 0xdeadbeef\n0x20006816 0xdeadbeef\n0x20006815 0xdeadbeef\n0x20006814 0xdeadbeef\n0x20006813 0xdeadbeef\n0x20006812 0xdeadbeef\n0x20006811 0xdeadbeef\n0x20006810 0xdeadbeef\n0x2000680f 0xdeadbeef\n0x2000680e 0xdeadbeef\n0x2000680d 0xdeadbeef\n0x2000680c 0xdeadbeef\n0x2000680b 0xdeadbeef\n0x2000680a 0xdeadbeef\n0x20006809 0xdeadbeef\n0x20006808 0xdeadbeef\n0x20006807 0xdeadbeef\n0x20006806 0xdeadbeef\n0x20006805 0xdeadbeef\n0x20006804 0xdeadbeef\n0x20006803 0xdeadbeef\n0x20006802 0xdeadbeef\n0x20006801 0xdeadbeef\n0x20006800 0xdeadbeef\n0x200067ff 0xdeadbeef\n0x200067fe 0xdeadbeef\n0x200067fd 0xdeadbeef\n0x200067fc 0xdeadbeef\n0x200067fb 0xdeadbeef\n0x200067fa 0xdeadbeef\n0x200067f9 0xdeadbeef\n0x200067f8 0xdeadbeef\n0x200067f7 0x00000000\n0x200067f6 0x00000000\n0x200067f5 0x00000000\n0x200067f4 0x00000000\n0x200067f3 0x00000000\n0x200067f2 0x00000000\n0x200067f1 0x00000000\n0x200067f0 0x00000000\n0x200067ef 0x00000000\n0x200067ee 0x00000000\n0x200067ed 0xfb98a0bf\n0x200067ec 0xda7d080a\n0x200067eb 0xe6b029e7\n0x200067ea 0x55a55b8b\n0x200067e9 0x26aff8f1\n0x200067e8 0x3a57dc4d\n0x200067e7 0xaa08710c\n0x200067e6 0x48cedaff\n0x200067e5 0xbac645a8\n0x200067e4 0xef1e6244\n0x200067e3 0x2528f8a6\n0x200067e2 0xd4a95a6b\n0x200067e1 0x1f3500b1\n0x200067e0 0xb69bbf87\n0x200067df 0xc97b8e02\n0x200067de 0xdcd13ab8\n0x200067dd 0x1d934129\n0x200067dc 0x1670d1c7\n0x200067db 0x858b8458\n0x200067da 0xb5dc6b2e\n0x200067d9 0x007cb39d\n0x200067d8 0x36910361\n0x200067d7 0x73110452\n0x200067d6 0xf2dccad0\n0x200067d5 0x26161caf\n0x200067d4 0xb8b9cbb7\n0x200067d3 0x25068328\nUser sp memory region, stack pointer lies within stack\n0x2002ba6a 0xfb98a0bf\n0x2002ba69 0xda7d080a\n0x2002ba68 0xe6b029e7\n0x2002ba67 0x55a55b8b\n0x2002ba66 0x26aff8f1\n0x2002ba65 0x3a57dc4d\n0x2002ba64 0xaa08710c\n0x2002ba63 0x48cedaff\n0x2002ba62 0xbac645a8\n0x2002ba61 0xef1e6244\n0x2002ba60 0x2528f8a6\n0x2002ba5f 0xd4a95a6b\n0x2002ba5e 0x1f3500b1\n0x2002ba5d 0xb69bbf87\n0x2002ba5c 0xc97b8e02\n0x2002ba5b 0xdcd13ab8\n0x2002ba5a 0x1d934129\n0x2002ba59 0x1670d1c7\n0x2002ba58 0x858b8458\n0x2002ba57 0xb5dc6b2e\n0x2002ba56 0x007cb39d\n0x2002ba55 0x36910361\n0x2002ba54 0x73110452\n0x2002ba53 0xf2dccad0\n0x2002ba52 0x26161caf\n0x2002ba51 0xb8b9cbb7\n0x2002ba50 0x25068328\n0x2002ba4f 0x487f6c1a\n0x2002ba4e 0x010301d1\n0x2002ba4d 0xbdab57fc\n0x2002ba4c 0x28f32129\n0x2002ba4b 0xd817af66\n0x2002ba4a 0x0a2be006\n0x2002ba49 0x13965cbd\n0x2002ba48 0x8fea0b38\n0x2002ba47 0x6dc4ad61\n0x2002ba46 0x0ccce009\n0x2002ba45 0x466bb2df\n0x2002ba44 0xeb861cb7\n0x2002ba43 0x35b476ef\n0x2002ba42 0x9fc05ab4\n0x2002ba41 0xeecbf38e\n0x2002ba40 0x721d10e7\n0x2002ba3f 0x79fefa9f\n0x2002ba3e 0x600a345f\n0x2002ba3d 0x41f054ed\n0x2002ba3c 0x213f5fcb\n0x2002ba3b 0x20004508\n0x2002ba3a 0x20004518\n0x2002ba39 0x00000810\n0x2002ba38 0x00004440\n0x2002ba37 0x4a3797f4\n0x2002ba36 0x1ce8cada\n0x2002ba35 0xbe0385f1\n0x2002ba34 0x00746c75\n0x2002ba33 0x61660067\n0x2002ba32 0x6f6c5f74\n0x2002ba31 0x6c756166\n0x2002ba30 0x64726168\n0x2002ba2f 0x00000000\n0x2002ba2e 0x2002bba6\n0x2002ba2d 0x2002bb98\n0x2002ba2c 0xdeadbeef\n0x2002ba2b 0x00000000\n0x2002ba2a 0x00000000\n0x2002ba29 0x080ecea5\n0x2002ba28 0x00000000\n0x2002ba27 0x00000000\n0x2002ba26 0x00000000\n0x2002ba25 0x00000000\n0x2002ba24 0x00000000\n0x2002ba23 0x00000000\n0x2002ba22 0x00000101\n0x2002ba21 0x00000000\n0x2002ba20 0x00000000\n0x2002ba1f 0x00000000\n0x2002ba1e 0x00000000\n0x2002ba1d 0x00000000\n0x2002ba1c 0x00000000\n0x2002ba1b 0x00000000\n0x2002ba1a 0x00000000\n0x2002ba19 0x01000000\n0x2002ba18 0x080ece80\n0x2002ba17 0x00000000\n0x2002ba16 0x00000000\n0x2002ba15 0x00000000\n0x2002ba14 0x00000000\n0x2002ba13 0x00000000\n0x2002ba12 0x00000000\n0x2002ba11 0xdeadbeef\n0x2002ba10 0xdeadbeef\n0x2002ba0f 0xdeadbeef\n0x2002ba0e 0xdeadbeef\n0x2002ba0d 0xdeadbeef\n0x2002ba0c 0xdeadbeef\n0x2002ba0b 0xdeadbeef\n0x2002ba0a 0xdeadbeef\n0x2002ba09 0xdeadbeef\n0x2002ba08 0xdeadbeef\n0x2002ba07 0xdeadbeef\n0x2002ba06 0xdeadbeef\n0x2002ba05 0xdeadbeef\n0x2002ba04 0xdeadbeef\n0x2002ba03 0xdeadbeef\n0x2002ba02 0xdeadbeef\n0x2002ba01 0xdeadbeef\n0x2002ba00 0xdeadbeef\n0x2002b9ff 0xdeadbeef\n0x2002b9fe 0xdeadbeef\n0x2002b9fd 0xdeadbeef\n0x2002b9fc 0xdeadbeef\n0x2002b9fb 0xdeadbeef\n0x2002b9fa 0xdeadbeef\n0x2002b9f9 0xdeadbeef\n0x2002b9f8 0xdeadbeef\n0x2002b9f7 0xdeadbeef\n0x2002b9f6 0xdeadbeef\n0x2002b9f5 0xdeadbeef\n0x2002b9f4 0xdeadbeef\n0x2002b9f3 0xdeadbeef\n0x2002b9f2 0xdeadbeef\n0x2002b9f1 0xdeadbeef\n0x2002b9f0 0xdeadbeef\n0x2002b9ef 0xdeadbeef\n0x2002b9ee 0xdeadbeef\n0x2002b9ed 0xdeadbeef\n0x2002b9ec 0xdeadbeef\n0x2002b9eb 0xdeadbeef\n0x2002b9ea 0xdeadbeef\n0x2002b9e9 0xdeadbeef\n0x2002b9e8 0xdeadbeef\n0x2002b9e7 0xdeadbeef\n0x2002b9e6 0xdeadbeef\n0x2002b9e5 0xdeadbeef\n0x2002b9e4 0xdeadbeef\n0x2002b9e3 0xdeadbeef\n0x2002b9e2 0xdeadbeef\n0x2002b9e1 0xdeadbeef\n0x2002b9e0 0xdeadbeef\n0x2002b9df 0xdeadbeef\n0x2002b9de 0xdeadbeef\n0x2002b9dd 0xdeadbeef\n0x2002b9dc 0xdeadbeef\n0x2002b9db 0xdeadbeef\n0x2002b9da 0xdeadbeef\n0x2002b9d9 0xdeadbeef\n0x2002b9d8 0xdeadbeef\n0x2002b9d7 0xdeadbeef\n0x2002b9d6 0xdeadbeef\n0x2002b9d5 0xdeadbeef\n0x2002b9d4 0xdeadbeef\n0x2002b9d3 0xdeadbeef\n0x2002b9d2 0xdeadbeef\n0x2002b9d1 0xdeadbeef\n0x2002b9d0 0xdeadbeef\n0x2002b9cf 0xdeadbeef\n0x2002b9ce 0xdeadbeef\n0x2002b9cd 0xdeadbeef\n0x2002b9cc 0xdeadbeef\n0x2002b9cb 0xdeadbeef\n0x2002b9ca 0xdeadbeef\n0x2002b9c9 0xdeadbeef\n0x2002b9c8 0xdeadbeef\n0x2002b9c7 0xdeadbeef\n0x2002b9c6 0xdeadbeef\n0x2002b9c5 0xdeadbeef\n0x2002b9c4 0xdeadbeef\n0x2002b9c3 0xdeadbeef\n0x2002b9c2 0xdeadbeef\n0x2002b9c1 0xdeadbeef\n0x2002b9c0 0xdeadbeef\n0x2002b9bf 0xdeadbeef\n0x2002b9be 0xdeadbeef\n0x2002b9bd 0xdeadbeef\n0x2002b9bc 0xdeadbeef\n0x2002b9bb 0xdeadbeef\n0x2002b9ba 0xdeadbeef\n0x2002b9b9 0xdeadbeef\n0x2002b9b8 0xdeadbeef<-- User sp\n0x2002b9b7 0xdeadbeef\n0x2002b9b6 0x00000000\n0x2002b9b5 0x00000000\n0x2002b9b4 0x00000000\n0x2002b9b3 0x00000000\n0x2002b9b2 0x00000000\n0x2002b9b1 0x00000000\n0x2002b9b0 0x00000000\n0x2002b9af 0x00000000\n0x2002b9ae 0x00000000\n0x2002b9ad 0x00000000\n0x2002b9ac 0x00000000\n0x2002b9ab 0x00000000\n0x2002b9aa 0x00000000\n0x2002b9a9 0x00000000\n0x2002b9a8 0x00000000\n0x2002b9a7 0x00000000\n0x2002b9a6 0x00000000\n0x2002b9a5 0x21000000\n0x2002b9a4 0x080353dc\n0x2002b9a3 0x080353ad\n0x2002b9a2 0x00000000\n0x2002b9a1 0xe000ed14\n0x2002b9a0 0x00000001\n0x2002b99f 0x00000000\n0x2002b99e 0x00000000\n0x2002b99d 0x00000000\n0x2002b99c 0x00000000\n0x2002b99b 0x00000000\n0x2002b99a 0x00000000\n0x2002b999 0x00000000\n0x2002b998 0x00000000\n0x2002b997 0x00000000\n0x2002b996 0x00000000\n0x2002b995 0x00000000\n0x2002b994 0x00000000\n0x2002b993 0x00000000\n0x2002b992 0x00000000\n0x2002b991 0x00000000\n0x2002b990 0x00000000\n0x2002b98f 0x00000000\n0x2002b98e 0x00000000\n0x2002b98d 0xffffffe9\n0x2002b98c 0x00000000\n0x2002b98b 0x00000000\n0x2002b98a 0x00000000\n0x2002b989 0x00000000\n0x2002b988 0x2002bb8c\n0x2002b987 0x2002bba6\n0x2002b986 0x20002854\n0x2002b985 0x00000000\n0x2002b984 0x000000f0\n0x2002b983 0x2002b9b8\n0x2002b982 0xdeadbeef\n0x2002b981 0xdeadbeef\n0x2002b980 0xdeadbeef\n0x2002b97f 0xdeadbeef\n0x2002b97e 0xdeadbeef\n0x2002b97d 0xdeadbeef\n0x2002b97c 0xdeadbeef\n0x2002b97b 0xdeadbeef\n0x2002b97a 0xdeadbeef\n0x2002b979 0xdeadbeef\n0x2002b978 0xdeadbeef\n0x2002b977 0xdeadbeef\n0x2002b976 0xdeadbeef\n0x2002b975 0xdeadbeef\n0x2002b974 0xdeadbeef\n0x2002b973 0xdeadbeef\n0x2002b972 0xdeadbeef\n0x2002b971 0xdeadbeef\n0x2002b970 0xdeadbeef\n0x2002b96f 0xdeadbeef\n0x2002b96e 0xdeadbeef\n0x2002b96d 0xdeadbeef\n0x2002b96c 0xdeadbeef\n0x2002b96b 0xdeadbeef\n0x2002b96a 0xdeadbeef\n0x2002b969 0xdeadbeef\n0x2002b968 0xdeadbeef\n0x2002b967 0xdeadbeef\n0x2002b966 0xdeadbeef\n0x2002b965 0xdeadbeef\n0x2002b964 0xdeadbeef\n0x2002b963 0xdeadbeef\n0x2002b962 0xdeadbeef\n0x2002b961 0xdeadbeef\n0x2002b960 0xdeadbeef\n0x2002b95f 0xdeadbeef\n0x2002b95e 0xdeadbeef\n0x2002b95d 0xdeadbeef\n0x2002b95c 0xdeadbeef\n0x2002b95b 0xdeadbeef\n0x2002b95a 0xdeadbeef\n0x2002b959 0xdeadbeef\n0x2002b958 0xdeadbeef\n0x2002b957 0xdeadbeef\n0x2002b956 0xdeadbeef\n0x2002b955 0xdeadbeef\n0x2002b954 0xdeadbeef\n0x2002b953 0xdeadbeef\n0x2002b952 0xdeadbeef\n0x2002b951 0xdeadbeef\n0x2002b950 0xdeadbeef\n0x2002b94f 0xdeadbeef\n0x2002b94e 0xdeadbeef\n0x2002b94d 0xdeadbeef\n0x2002b94c 0xdeadbeef\n0x2002b94b 0xdeadbeef\n0x2002b94a 0xdeadbeef\n0x2002b949 0xdeadbeef\n0x2002b948 0xdeadbeef\n0x2002b947 0xdeadbeef\n0x2002b946 0xdeadbeef\n0x2002b945 0xdeadbeef\n0x2002b944 0xdeadbeef\n0x2002b943 0xdeadbeef\n0x2002b942 0xdeadbeef\n0x2002b941 0xdeadbeef\n0x2002b940 0xdeadbeef\n0x2002b93f 0xdeadbeef\n0x2002b93e 0xdeadbeef\n0x2002b93d 0xdeadbeef\n0x2002b93c 0xdeadbeef\n0x2002b93b 0xdeadbeef\n0x2002b93a 0xdeadbeef\n0x2002b939 0xdeadbeef\n0x2002b938 0xdeadbeef\n0x2002b937 0xdeadbeef\n0x2002b936 0xdeadbeef\n0x2002b935 0xdeadbeef\n0x2002b934 0xdeadbeef\n0x2002b933 0xdeadbeef\n0x2002b932 0xdeadbeef\n0x2002b931 0xdeadbeef\n0x2002b930 0xdeadbeef\n0x2002b92f 0xdeadbeef\n0x2002b92e 0xdeadbeef\n0x2002b92d 0xdeadbeef\n0x2002b92c 0xdeadbeef\n0x2002b92b 0xdeadbeef\n0x2002b92a 0xdeadbeef\n0x2002b929 0xdeadbeef\n0x2002b928 0xdeadbeef\n0x2002b927 0xdeadbeef\n0x2002b926 0xdeadbeef\n0x2002b925 0xdeadbeef\n0x2002b924 0xdeadbeef\n0x2002b923 0xdeadbeef\n0x2002b922 0xdeadbeef\n0x2002b921 0xdeadbeef\n0x2002b920 0xdeadbeef\n0x2002b91f 0xdeadbeef\n0x2002b91e 0xdeadbeef\n0x2002b91d 0xdeadbeef\n0x2002b91c 0xdeadbeef\n0x2002b91b 0xdeadbeef\n0x2002b91a 0xdeadbeef\n0x2002b919 0xdeadbeef\n0x2002b918 0xdeadbeef\n0x2002b917 0xdeadbeef\n0x2002b916 0xdeadbeef\n0x2002b915 0xdeadbeef\n0x2002b914 0xdeadbeef\n0x2002b913 0xdeadbeef\n0x2002b912 0xdeadbeef\n0x2002b911 0xdeadbeef\n0x2002b910 0xdeadbeef\n0x2002b90f 0xdeadbeef\n0x2002b90e 0xdeadbeef\n0x2002b90d 0xdeadbeef\n0x2002b90c 0xdeadbeef\n0x2002b90b 0xdeadbeef\n0x2002b90a 0xdeadbeef\n0x2002b909 0xdeadbeef\n0x2002b908 0xdeadbeef\n0x2002b907 0xdeadbeef\n0x2002b906 0xdeadbeef\n[hardfault_log] -- 2000-01-01-00:06:01 END Fault Log --\n']] Name (multi id, message size in bytes) number of data points, total bytes actuator_controls_0 (0, 48) 1132 54336 actuator_outputs (0, 76) 1132 86032 battery_status (0, 42) 382 16044 commander_state (0, 9) 1132 10188 control_state (0, 134) 1110 148740 cpuload (0, 16) 115 1840 ekf2_innovations (0, 140) 2148 300720 ekf2_timestamps (0, 20) 28585 571700 estimator_status (0, 267) 556 148452 sensor_combined (0, 72) 28584 2058048 sensor_preflight (0, 16) 2189 35024 system_power (0, 17) 382 6494 task_stack_info (0, 26) 230 5980 vehicle_attitude (0, 36) 3576 128736 vehicle_attitude_setpoint (0, 55) 3645 200475 vehicle_land_detected (0, 15) 1 15 vehicle_local_position (0, 148) 1110 164280 vehicle_rates_setpoint (0, 24) 3645 87480 vehicle_status (0, 45) 493 22185 wind_estimate (0, 24) 1110 26640 ================================================ FILE: test/sample_appended_messages.txt ================================================ 0:00:11 WARNING: [commander_tests] Not ready to fly: Sensors not set up correctly ================================================ FILE: test/sample_appended_multiple_info.txt ================================================ Logging start time: 0:00:12, duration: 0:00:09 No Dropouts Info Messages: perf_counter_preflight-00: navigator: 9 events, 275us elapsed, 30us avg, min 21us max 75us 16.883us rms perf_counter_preflight-01: mc_att_control: 2465 events, 188639us elapsed, 76us avg, min 18us max 392us 31.226us rms perf_counter_preflight-02: ctl_lat: 0 events, 0us elapsed, 0us avg, min 0us max 0us 0.000us rms perf_counter_preflight-03: logger_sd_fsync: 0 events, 0us elapsed, 0us avg, min 0us max 0us 0.000us rms perf_counter_preflight-04: logger_sd_write: 3 events, 73113us elapsed, 24371us avg, min 9us max 37955us 21144.707us rms perf_counter_preflight-05: mavlink_txe: 569 events perf_counter_preflight-06: mavlink_el: 3104 events, 474097us elapsed, 152us avg, min 78us max 3448us 209.233us rms perf_counter_preflight-07: mavlink_txe: 12 events perf_counter_preflight-08: mavlink_el: 883 events, 113212us elapsed, 128us avg, min 44us max 2234us 210.405us rms perf_counter_preflight-09: mavlink_txe: 0 events perf_counter_preflight-10: mavlink_el: 995 events, 174125us elapsed, 175us avg, min 62us max 4784us 329.831us rms perf_counter_preflight-11: io latency: 2044 events, 5113031102us elapsed, 2501482us avg, min 987us max 7524739us 2916104.750us rms perf_counter_preflight-12: io write: 0 events, 0us elapsed, 0us avg, min 0us max 0us 0.000us rms perf_counter_preflight-13: io update: 2132 events, 1319684us elapsed, 618us avg, min 256us max 18515us 733.727us rms perf_counter_preflight-14: io_txns: 3950 events, 1243786us elapsed, 314us avg, min 131us max 2714us 139.806us rms perf_counter_preflight-15: stack_check: 21 events, 252us elapsed, 12us avg, min 1us max 29us 6.380us rms perf_counter_preflight-16: sensors: 2543 events, 361486us elapsed, 142us avg, min 78us max 6150us 126.879us rms perf_counter_preflight-17: lis3mdl_conf_errors: 3 events perf_counter_preflight-18: lis3mdl_range_errors: 0 events perf_counter_preflight-19: lis3mdl_comms_errors: 0 events perf_counter_preflight-20: lis3mdl_read: 475 events, 16303us elapsed, 34us avg, min 28us max 259us 27.699us rms perf_counter_preflight-21: mpu9250_dupe: 2848 events perf_counter_preflight-22: mpu9250_reset: 0 events perf_counter_preflight-23: mpu9250_good_trans: 10151 events perf_counter_preflight-24: mpu9250_bad_reg: 0 events perf_counter_preflight-25: mpu9250_bad_trans: 0 events perf_counter_preflight-26: mpu9250_read: 13000 events, 973697us elapsed, 74us avg, min 58us max 91us 9.448us rms perf_counter_preflight-27: mpu9250_gyro_read: 0 events perf_counter_preflight-28: mpu9250_acc_read: 9 events perf_counter_preflight-29: mpu9250_mag_duplicates: 9077 events perf_counter_preflight-30: mpu9250_mag_overflows: 0 events perf_counter_preflight-31: mpu9250_mag_overruns: 142 events perf_counter_preflight-32: mpu9250_mag_errors: 0 events perf_counter_preflight-33: mpu9250_mag_reads: 0 events perf_counter_preflight-34: ctrl_latency: 2044 events, 583836us elapsed, 285us avg, min 198us max 904us 53.847us rms perf_counter_preflight-35: mpu6k_duplicates: 2925 events perf_counter_preflight-36: mpu6k_reset: 0 events perf_counter_preflight-37: mpu6k_good_trans: 10111 events perf_counter_preflight-38: mpu6k_bad_reg: 0 events perf_counter_preflight-39: mpu6k_bad_trans: 0 events perf_counter_preflight-40: mpu6k_read: 13036 events, 791547us elapsed, 60us avg, min 43us max 82us 11.774us rms perf_counter_preflight-41: mpu6k_gyro_read: 0 events perf_counter_preflight-42: mpu6k_acc_read: 9 events perf_counter_preflight-43: adc_samples: 9324 events, 23193us elapsed, 2us avg, min 2us max 3us 0.500us rms perf_counter_preflight-44: ms5611_com_err: 0 events perf_counter_preflight-45: ms5611_measure: 950 events, 7047us elapsed, 7us avg, min 5us max 240us 16.192us rms perf_counter_preflight-46: ms5611_read: 949 events, 63895us elapsed, 67us avg, min 10us max 331us 49.056us rms perf_counter_preflight-47: dma_alloc: 7 events perf_top_preflight-00: PID COMMAND CPU(ms) CPU(%) USED/STACK PRIO(BASE) STATE perf_top_preflight-01: 0 Idle Task 7229 49.950 604/ 748 0 ( 0) READY perf_top_preflight-02: 1 hpwork 375 3.396 816/ 1780 192 (192) w:sig perf_top_preflight-03: 2 lpwork 28 0.099 640/ 1780 50 ( 50) w:sig perf_top_preflight-04: 3 init 3747 0.000 1680/ 2484 100 (100) w:sem perf_top_preflight-05: 239 log_writer_file 64 3.396 544/ 1060 60 ( 60) w:sem perf_top_preflight-06: 106 gps 91 0.099 944/ 1524 220 (220) w:sem perf_top_preflight-07: 110 dataman 142 0.000 704/ 1180 90 ( 90) w:sem perf_top_preflight-08: 147 sensors 511 4.295 1464/ 1980 250 (250) w:sem perf_top_preflight-09: 149 commander 222 2.497 2792/ 3652 140 (140) w:sig perf_top_preflight-10: 156 px4io 324 2.997 1072/ 1380 240 (240) w:sem perf_top_preflight-11: 168 mavlink_if0 201 1.398 1656/ 2436 100 (100) READY perf_top_preflight-12: 169 mavlink_rcv_if0 39 0.299 1320/ 2140 175 (175) READY perf_top_preflight-13: 182 mavlink_if1 138 1.098 1640/ 2388 100 (100) w:sig perf_top_preflight-14: 183 mavlink_rcv_if1 39 0.399 1496/ 2140 175 (175) READY perf_top_preflight-15: 219 mavlink_if2 522 3.896 1600/ 2388 100 (100) READY perf_top_preflight-16: 221 mavlink_rcv_if2 190 1.898 1320/ 2140 175 (175) READY perf_top_preflight-17: 237 logger 195 5.794 3104/ 3532 250 (250) RUN perf_top_preflight-18: 318 commander_low_prio 0 0.000 592/ 2996 50 ( 50) w:sem perf_top_preflight-19: 282 ekf2 1091 11.588 5056/ 5780 250 (250) w:sem perf_top_preflight-20: 287 mc_att_control 287 3.196 1176/ 1676 250 (250) READY perf_top_preflight-21: 293 mc_pos_control 145 2.397 584/ 1876 250 (250) w:sem perf_top_preflight-22: 299 navigator 4 0.000 872/ 1772 105 (105) w:sem perf_top_preflight-23: perf_top_preflight-24: Processes: 22 total, 8 running, 14 sleeping perf_top_preflight-25: CPU usage: 48.75% tasks, 1.30% sched, 49.95% idle perf_top_preflight-26: DMA Memory: 5120 total, 1536 used 1536 peak perf_top_preflight-27: Uptime: 13.266s total, 7.229s idle sys_mcu: STM32F???, rev. A sys_name: PX4 sys_os_name: NuttX sys_os_ver: 8b81cf5c7ece0c228eaaea3e9d8e667fc4d21a06 sys_os_ver_release: 192 sys_toolchain: GNU GCC sys_toolchain_ver: 5.4.1 20160919 (release) [ARM/embedded-5-branch revision 240496] sys_uuid: 0035002B3434511732343031 time_ref_utc: 0 ver_hw: PX4FMU_V4PRO ver_sw: f54a6c2999e1e2fcbf56dd89de06b615b4186a6e ver_sw_branch: ulog_crash_dump ver_sw_release: 17170432 Info Multiple Messages: hardfault_plain: [['[hardfault_log] -- 2000-01-01-00:00:36 Begin Fault Log --\nSystem fault Occurred on: 2000-01-01-00:00:36\n Type:Hard Fault in file:armv7-m/up_hardfault.c at line: 171 running task: hardfault_log\n FW git-hash: f54a6c2999e1e2fcbf56dd89de06b615b4186a6e\n Build datetime: Jul 3 2017 16:02:41\n Build url: localhost \n Processor registers: from 0x20029c64\n r0:0x00000000 r1:0x00000000 r2:0x00000001 r3:0xe000ed14 r4:0x00000000 r5:0x20001800 r6:0x20029f26 r7:0x20029f0c\n r8:0x00000000 r9:0x00000000 r10:0x00000000 r11:0x00000000 r12:0x00000000 sp:0x20029d38 lr:0x080381f5 pc:0x08038224\n xpsr:0x21000000 basepri:0x000000f0 control:0x00000004\n exe return:0xffffffe9\n IRQ stack: \n top: 0x200037f0\n sp: 0x200037a0 Valid\n bottom: 0x20003504\n size: 0x000002ec\n used: 000000c8\n User stack: \n top: 0x20029f08\n sp: 0x20029d38 Valid\n bottom: 0x2002972c\n size: 0x000007dc\n used: 000007dc\nInterrupt sp memory region, stack pointer lies within stack\n0x20003852 0x00000000\n0x20003851 0x00000000\n0x20003850 0x00000000\n0x2000384f 0x00000000\n0x2000384e 0x00000000\n0x2000384d 0x00000000\n0x2000384c 0x00000000\n0x2000384b 0x00000000\n0x2000384a 0x00000000\n0x20003849 0x00000000\n0x20003848 0x00000000\n0x20003847 0x00000000\n0x20003846 0x00000000\n0x20003845 0x00000000\n0x20003844 0x00000000\n0x20003843 0x00000000\n0x20003842 0x00000000\n0x20003841 0x00000000\n0x20003840 0x00000000\n0x2000383f 0x00000000\n0x2000383e 0x00000000\n0x2000383d 0x00000000\n0x2000383c 0x00000000\n0x2000383b 0x00000000\n0x2000383a 0x00000000\n0x20003839 0x00000000\n0x20003838 0x00000000\n0x20003837 0x00000000\n0x20003836 0x00000000\n0x20003835 0x00000000\n0x20003834 0x00000000\n0x20003833 0x00000000\n0x20003832 0x00000000\n0x20003831 0x00000000\n0x20003830 0x00000000\n0x2000382f 0x00000000\n0x2000382e 0x00000000\n0x2000382d 0x00000000\n0x2000382c 0x00000000\n0x2000382b 0x00000000\n0x2000382a 0x00000000\n0x20003829 0x00000000\n0x20003828 0x00000000\n0x20003827 0x00000603\n0x20003826 0x100061a0\n0x20003825 0x10006050\n0x20003824 0x20003880\n0x20003823 0x10005450\n0x20003822 0x00400003\n0x20003821 0x0812db88\n0x20003820 0x00000002\n0x2000381f 0x00000000\n0x2000381e 0x00000000\n0x2000381d 0x20003880\n0x2000381c 0x00000000\n0x2000381b 0x00400002\n0x2000381a 0x0812db88\n0x20003819 0x00000001\n0x20003818 0x00000000\n0x20003817 0x00000000\n0x20003816 0x20003880\n0x20003815 0x00000000\n0x20003814 0x00400001\n0x20003813 0x0812db88\n0x20003812 0x00000000\n0x20003811 0x00000000\n0x20003810 0x00000000\n0x2000380f 0x20003880\n0x2000380e 0x20003880\n0x2000380d 0x00400000\n0x2000380c 0x0812db88\n0x2000380b 0x00001003\n0x2000380a 0x00000000\n0x20003809 0x00000000\n0x20003808 0x20003880\n0x20003807 0x00000000\n0x20003806 0x00400083\n0x20003805 0x0812db88\n0x20003804 0x00001602\n0x20003803 0x10006210\n0x20003802 0x100062a0\n0x20003801 0x20003880\n0x20003800 0x10005450\n0x200037ff 0x00400082\n0x200037fe 0x0812db88\n0x200037fd 0x00001301\n0x200037fc 0x00000000\n0x200037fb 0x00000000\n0x200037fa 0x20003880\n0x200037f9 0x10005450\n0x200037f8 0x00400081\n0x200037f7 0x0812db88\n0x200037f6 0x00001000\n0x200037f5 0x00000000\n0x200037f4 0x00000000\n0x200037f3 0x20003880\n0x200037f2 0x10005450\n0x200037f1 0x00400080\n0x200037f0 0x0812db88<-- Interrupt sp top\n0x200037ef 0x00000000\n0x200037ee 0x00000000\n0x200037ed 0x00000000\n0x200037ec 0x00000000\n0x200037eb 0x00000000\n0x200037ea 0x00000000\n0x200037e9 0x00000000\n0x200037e8 0x00000000\n0x200037e7 0x00000000\n0x200037e6 0x00000000\n0x200037e5 0x00000000\n0x200037e4 0x00000000\n0x200037e3 0x00000000\n0x200037e2 0x00000000\n0x200037e1 0x00000063\n0x200037e0 0x08000000\n0x200037df 0x00258000\n0x200037de 0x07000000\n0x200037dd 0x0020210e\n0x200037dc 0x0e00020c\n0x200037db 0x10005f94\n0x200037da 0x00000002\n0x200037d9 0x200038e0\n0x200037d8 0x0812db6c\n0x200037d7 0x10000830\n0x200037d6 0x00000000\n0x200037d5 0x10000070\n0x200037d4 0x00000000\n0x200037d3 0x00000002\n0x200037d2 0x0000ffff\n0x200037d1 0x00000000\n0x200037d0 0x00000000\n0x200037cf 0x00000000\n0x200037ce 0x00000000\n0x200037cd 0x00000001\n0x200037cc 0x01a40204\n0x200037cb 0x00000000\n0x200037ca 0x00000000\n0x200037c9 0x100050f0\n0x200037c8 0x00000001\n0x200037c7 0x20007630\n0x200037c6 0x200075c0\n0x200037c5 0x200075a0\n0x200037c4 0x200074b0\n0x200037c3 0x20007268\n0x200037c2 0x20007100\n0x200037c1 0x2000748c\n0x200037c0 0x20007360\n0x200037bf 0x20007338\n0x200037be 0x20007290\n0x200037bd 0x00000005\n0x200037bc 0x10004450\n0x200037bb 0x0000ffff\n0x200037ba 0x00000000\n0x200037b9 0x00000000\n0x200037b8 0x00000000\n0x200037b7 0x00000000\n0x200037b6 0x00000001\n0x200037b5 0x10000010\n0x200037b4 0x00000000\n0x200037b3 0x080e50a7\n0x200037b2 0x20029f0c\n0x200037b1 0x20029f26\n0x200037b0 0x20001800\n0x200037af 0x20029c64\n0x200037ae 0x000000f0\n0x200037ad 0x080edf69\n0x200037ac 0x080e5121\n0x200037ab 0x080e5143\n0x200037aa 0x00000000\n0x200037a9 0x00000003\n0x200037a8 0x080e513b\n0x200037a7 0x200037c8\n0x200037a6 0x08038224\n0x200037a5 0x080381f5\n0x200037a4 0x20029d38\n0x200037a3 0x00000000\n0x200037a2 0x00000000\n0x200037a1 0x00000000\n0x200037a0 0x000000f0<-- Interrupt sp\n0x2000379f 0x080e4e8d\n0x2000379e 0x000000ab\n0x2000379d 0x0812d43b\n0x2000379c 0x20002c58\n0x2000379b 0x20003468\n0x2000379a 0x00000000\n0x20003799 0x20003468\n0x20003798 0x000000ab\n0x20003797 0x080fa4bd\n0x20003796 0x20000010\n0x20003795 0x080e5219\n0x20003794 0x20029d38\n0x20003793 0x20028ed0\n0x20003792 0x080381f5\n0x20003791 0x00000147\n0x20003790 0x200037a0\n0x2000378f 0x20029c64\n0x2000378e 0x20028ed0\n0x2000378d 0x20000000\n0x2000378c 0x20000000\n0x2000378b 0x080e3091\n0x2000378a 0x20003760\n0x20003789 0x20003760\n0x20003788 0x00000000\n0x20003787 0x080e3073\n0x20003786 0x0000011f\n0x20003785 0x20019680\n0x20003784 0x00000000\n0x20003783 0x0000003d\n0x20003782 0x080f1cd9\n0x20003781 0x080f0aa9\n0x20003780 0x080f1cd9\n0x2000377f 0x080ead1f\n0x2000377e 0x20028ed0\n0x2000377d 0x080381f5\n0x2000377c 0x000000ab\n0x2000377b 0x20002b54\n0x2000377a 0x00000000\n0x20003779 0x080e4cb9\n0x20003778 0x20003744\n0x20003777 0x0812d286\n0x20003776 0x00000000\n0x20003775 0x00000000\n0x20003774 0x2000375c\n0x20003773 0x00000004\n0x20003772 0x080f1cd9\n0x20003771 0x080eb8b5\n0x20003770 0x0000000a\n0x2000376f 0x080ed765\n0x2000376e 0x0812db4c\n0x2000376d 0x00000039\n0x2000376c 0x00000004\n0x2000376b 0x080eb45f\n0x2000376a 0x20003724\n0x20003769 0x00000020\n0x20003768 0x080f0aa9\n0x20003767 0x080f0ab5\n0x20003766 0x20003724\n0x20003765 0x0000000a\n0x20003764 0x0000000a\n0x20003763 0x080edef5\n0x20003762 0x0000000a\n0x20003761 0x080ed765\n0x20003760 0x00000010\n0x2000375f 0x080edef5\n0x2000375e 0x00000037\n0x2000375d 0x080ed765\n0x2000375c 0x00000010\n0x2000375b 0x080edef5\n0x2000375a 0x00000036\n0x20003759 0x080ed765\n0x20003758 0x00000010\n0x20003757 0x080edef5\n0x20003756 0x00000031\n0x20003755 0x080ed765\n0x20003754 0x00000010\n0x20003753 0xc22c0000\n0x20003752 0xc1f00000\n0x20003751 0xffffff45\n0x20003750 0xdeadbeef\n0x2000374f 0x00000000\n0x2000374e 0x00000000\n0x2000374d 0x200036e0\n0x2000374c 0x20003738\n0x2000374b 0x200036d0\n0x2000374a 0x200036b8\n0x20003749 0x0801be95\n0x20003748 0x00000000\n0x20003747 0x200036d0\n0x20003746 0x100099f0\n0x20003745 0x00000000\n0x20003744 0xbd2a126a\n0x20003743 0xbc6508ee\n0x20003742 0xbb5e8c88\n0x20003741 0x080b18c7\n0x20003740 0x00000000\n0x2000373f 0x1000a4b0\n0x2000373e 0x1000a7b0\n0x2000373d 0x080e5219\n0x2000373c 0x00000000\n0x2000373b 0x00000ac9\n0x2000373a 0x000000dd\n0x20003739 0x0000002b\n0x20003738 0x200036a0\n0x20003737 0x00000000\n0x20003736 0x00000002\n0x20003735 0x00000001\n0x20003734 0x1000a700\n0x20003733 0x2cd0851c\n0x20003732 0xac5200a5\n0x20003731 0x200035d0\n0x20003730 0x00000001\n0x2000372f 0x00000003\n0x2000372e 0xb532ad1f\n0x2000372d 0xb60b5e08\n0x2000372c 0xb6d8b642\n0x2000372b 0x200035b8\n0x2000372a 0x00000001\n0x20003729 0x00000003\n0x20003728 0xb532ad1f\n0x20003727 0xb60b5e08\n0x20003726 0xb6d8b642\n0x20003725 0x200035a0\n0x20003724 0x00000001\n0x20003723 0x00000003\n0x20003722 0xb6d3ef16\n0x20003721 0xb7caeb2c\n0x20003720 0xb8933275\n0x2000371f 0x08009c6f\n0x2000371e 0x0153278e\n0x2000371d 0x200036b8\n0x2000371c 0x10009c48\n0x2000371b 0xdeadbeef\n0x2000371a 0xdeadbeef\n0x20003719 0xdeadbeef\n0x20003718 0xdeadbeef\n0x20003717 0xdeadbeef\n0x20003716 0xdeadbeef\n0x20003715 0xdeadbeef\n0x20003714 0xdeadbeef\n0x20003713 0xdeadbeef\n0x20003712 0xdeadbeef\n0x20003711 0xdeadbeef\n0x20003710 0xdeadbeef\n0x2000370f 0xdeadbeef\n0x2000370e 0xdeadbeef\n0x2000370d 0xdeadbeef\n0x2000370c 0xdeadbeef\n0x2000370b 0xdeadbeef\n0x2000370a 0xdeadbeef\n0x20003709 0xdeadbeef\n0x20003708 0xdeadbeef\n0x20003707 0xdeadbeef\n0x20003706 0xdeadbeef\n0x20003705 0xdeadbeef\n0x20003704 0xdeadbeef\n0x20003703 0xdeadbeef\n0x20003702 0xdeadbeef\n0x20003701 0xdeadbeef\n0x20003700 0xdeadbeef\n0x200036ff 0xdeadbeef\n0x200036fe 0xdeadbeef\n0x200036fd 0xdeadbeef\n0x200036fc 0xdeadbeef\n0x200036fb 0xdeadbeef\n0x200036fa 0xdeadbeef\n0x200036f9 0xdeadbeef\n0x200036f8 0xdeadbeef\n0x200036f7 0x00000000\n0x200036f6 0x00000000\n0x200036f5 0x00000000\n0x200036f4 0x00000000\n0x200036f3 0x00000000\n0x200036f2 0x00000000\n0x200036f1 0x00000000\n0x200036f0 0x00000000\n0x200036ef 0x00000000\n0x200036ee 0x00000000\n0x200036ed 0xacc3dceb\n0x200036ec 0x1974741c\n0x200036eb 0xd57bc68f\n0x200036ea 0x161002aa\n0x200036e9 0x19749b3c\n0x200036e8 0xb2c87a55\n0x200036e7 0xe3c5b19f\n0x200036e6 0x34814b92\n0x200036e5 0x547525f9\n0x200036e4 0x0d7c6875\n0x200036e3 0x370d6b04\n0x200036e2 0x80459c98\n0x200036e1 0x70e2f10e\n0x200036e0 0x01ba6748\n0x200036df 0x368a881d\n0x200036de 0xf3da0521\n0x200036dd 0xfcd7f19c\n0x200036dc 0x1ef20172\n0x200036db 0x737caabc\n0x200036da 0x01229a1d\n0x200036d9 0xa84718df\n0x200036d8 0x21dce316\n0x200036d7 0x82941d7a\n0x200036d6 0x808a9014\n0x200036d5 0x69770529\n0x200036d4 0xcb14de92\n0x200036d3 0x0e67affc\nUser sp memory region, stack pointer lies within stack\n0x20029dea 0xacc3dceb\n0x20029de9 0x1974741c\n0x20029de8 0xd57bc68f\n0x20029de7 0x161002aa\n0x20029de6 0x19749b3c\n0x20029de5 0xb2c87a55\n0x20029de4 0xe3c5b19f\n0x20029de3 0x34814b92\n0x20029de2 0x547525f9\n0x20029de1 0x0d7c6875\n0x20029de0 0x370d6b04\n0x20029ddf 0x80459c98\n0x20029dde 0x70e2f10e\n0x20029ddd 0x01ba6748\n0x20029ddc 0x368a881d\n0x20029ddb 0xf3da0521\n0x20029dda 0xfcd7f19c\n0x20029dd9 0x1ef20172\n0x20029dd8 0x737caabc\n0x20029dd7 0x01229a1d\n0x20029dd6 0xa84718df\n0x20029dd5 0x21dce316\n0x20029dd4 0x82941d7a\n0x20029dd3 0x808a9014\n0x20029dd2 0x69770529\n0x20029dd1 0xcb14de92\n0x20029dd0 0x0e67affc\n0x20029dcf 0x8b00640c\n0x20029dce 0xd428dca5\n0x20029dcd 0x90e27e77\n0x20029dcc 0xb9e06175\n0x20029dcb 0x229c4241\n0x20029dca 0xfa9ef04e\n0x20029dc9 0x389b3c63\n0x20029dc8 0xe09e3de1\n0x20029dc7 0x0b27a02a\n0x20029dc6 0xbb9d1572\n0x20029dc5 0x28b65c8d\n0x20029dc4 0x9ea88bda\n0x20029dc3 0x253c280d\n0x20029dc2 0x84f8875e\n0x20029dc1 0x385196d7\n0x20029dc0 0xbe812788\n0x20029dbf 0xeea9e62c\n0x20029dbe 0xe67de43e\n0x20029dbd 0xd1d02b76\n0x20029dbc 0xada3063a\n0x20029dbb 0x20003408\n0x20029dba 0x20003418\n0x20029db9 0x00000810\n0x20029db8 0x000260c0\n0x20029db7 0xac037a13\n0x20029db6 0x3ee2b20b\n0x20029db5 0x3c177304\n0x20029db4 0x00746c75\n0x20029db3 0x61660067\n0x20029db2 0x6f6c5f74\n0x20029db1 0x6c756166\n0x20029db0 0x64726168\n0x20029daf 0x00000000\n0x20029dae 0x20029f26\n0x20029dad 0x20029f18\n0x20029dac 0xdeadbeef\n0x20029dab 0x00000000\n0x20029daa 0x00000000\n0x20029da9 0x080e02f9\n0x20029da8 0x00000000\n0x20029da7 0x00000000\n0x20029da6 0x00000000\n0x20029da5 0x00000000\n0x20029da4 0x00000000\n0x20029da3 0x00000000\n0x20029da2 0x00000101\n0x20029da1 0x00000000\n0x20029da0 0x00000000\n0x20029d9f 0x00000000\n0x20029d9e 0x00000000\n0x20029d9d 0x00000000\n0x20029d9c 0x00000000\n0x20029d9b 0x00000000\n0x20029d9a 0x00000000\n0x20029d99 0x01000000\n0x20029d98 0x080e02d4\n0x20029d97 0x00000000\n0x20029d96 0x00000000\n0x20029d95 0x00000000\n0x20029d94 0x00000000\n0x20029d93 0x00000000\n0x20029d92 0x00000000\n0x20029d91 0xdeadbeef\n0x20029d90 0xdeadbeef\n0x20029d8f 0xdeadbeef\n0x20029d8e 0xdeadbeef\n0x20029d8d 0xdeadbeef\n0x20029d8c 0xdeadbeef\n0x20029d8b 0xdeadbeef\n0x20029d8a 0xdeadbeef\n0x20029d89 0xdeadbeef\n0x20029d88 0xdeadbeef\n0x20029d87 0xdeadbeef\n0x20029d86 0xdeadbeef\n0x20029d85 0xdeadbeef\n0x20029d84 0xdeadbeef\n0x20029d83 0xdeadbeef\n0x20029d82 0xdeadbeef\n0x20029d81 0xdeadbeef\n0x20029d80 0xdeadbeef\n0x20029d7f 0xdeadbeef\n0x20029d7e 0xdeadbeef\n0x20029d7d 0xdeadbeef\n0x20029d7c 0xdeadbeef\n0x20029d7b 0xdeadbeef\n0x20029d7a 0xdeadbeef\n0x20029d79 0xdeadbeef\n0x20029d78 0xdeadbeef\n0x20029d77 0xdeadbeef\n0x20029d76 0xdeadbeef\n0x20029d75 0xdeadbeef\n0x20029d74 0xdeadbeef\n0x20029d73 0xdeadbeef\n0x20029d72 0xdeadbeef\n0x20029d71 0xdeadbeef\n0x20029d70 0xdeadbeef\n0x20029d6f 0xdeadbeef\n0x20029d6e 0xdeadbeef\n0x20029d6d 0xdeadbeef\n0x20029d6c 0xdeadbeef\n0x20029d6b 0xdeadbeef\n0x20029d6a 0xdeadbeef\n0x20029d69 0xdeadbeef\n0x20029d68 0xdeadbeef\n0x20029d67 0xdeadbeef\n0x20029d66 0xdeadbeef\n0x20029d65 0xdeadbeef\n0x20029d64 0xdeadbeef\n0x20029d63 0xdeadbeef\n0x20029d62 0xdeadbeef\n0x20029d61 0xdeadbeef\n0x20029d60 0xdeadbeef\n0x20029d5f 0xdeadbeef\n0x20029d5e 0xdeadbeef\n0x20029d5d 0xdeadbeef\n0x20029d5c 0xdeadbeef\n0x20029d5b 0xdeadbeef\n0x20029d5a 0xdeadbeef\n0x20029d59 0xdeadbeef\n0x20029d58 0xdeadbeef\n0x20029d57 0xdeadbeef\n0x20029d56 0xdeadbeef\n0x20029d55 0xdeadbeef\n0x20029d54 0xdeadbeef\n0x20029d53 0xdeadbeef\n0x20029d52 0xdeadbeef\n0x20029d51 0xdeadbeef\n0x20029d50 0xdeadbeef\n0x20029d4f 0xdeadbeef\n0x20029d4e 0xdeadbeef\n0x20029d4d 0xdeadbeef\n0x20029d4c 0xdeadbeef\n0x20029d4b 0xdeadbeef\n0x20029d4a 0xdeadbeef\n0x20029d49 0xdeadbeef\n0x20029d48 0xdeadbeef\n0x20029d47 0xdeadbeef\n0x20029d46 0xdeadbeef\n0x20029d45 0xdeadbeef\n0x20029d44 0xdeadbeef\n0x20029d43 0xdeadbeef\n0x20029d42 0xdeadbeef\n0x20029d41 0xdeadbeef\n0x20029d40 0xdeadbeef\n0x20029d3f 0xdeadbeef\n0x20029d3e 0xdeadbeef\n0x20029d3d 0xdeadbeef\n0x20029d3c 0xdeadbeef\n0x20029d3b 0xdeadbeef\n0x20029d3a 0xdeadbeef\n0x20029d39 0xdeadbeef\n0x20029d38 0xdeadbeef<-- User sp\n0x20029d37 0xdeadbeef\n0x20029d36 0x00000000\n0x20029d35 0x00000000\n0x20029d34 0x00000000\n0x20029d33 0x00000000\n0x20029d32 0x00000000\n0x20029d31 0x00000000\n0x20029d30 0x00000000\n0x20029d2f 0x00000000\n0x20029d2e 0x00000000\n0x20029d2d 0x00000000\n0x20029d2c 0x00000000\n0x20029d2b 0x00000000\n0x20029d2a 0x00000000\n0x20029d29 0x00000000\n0x20029d28 0x00000000\n0x20029d27 0x00000000\n0x20029d26 0x00000000\n0x20029d25 0x21000000\n0x20029d24 0x08038224\n0x20029d23 0x080381f5\n0x20029d22 0x00000000\n0x20029d21 0xe000ed14\n0x20029d20 0x00000001\n0x20029d1f 0x00000000\n0x20029d1e 0x00000000\n0x20029d1d 0x00000000\n0x20029d1c 0x00000000\n0x20029d1b 0x00000000\n0x20029d1a 0x00000000\n0x20029d19 0x00000000\n0x20029d18 0x00000000\n0x20029d17 0x00000000\n0x20029d16 0x00000000\n0x20029d15 0x00000000\n0x20029d14 0x00000000\n0x20029d13 0x00000000\n0x20029d12 0x00000000\n0x20029d11 0x00000000\n0x20029d10 0x00000000\n0x20029d0f 0x00000000\n0x20029d0e 0x00000000\n0x20029d0d 0xffffffe9\n0x20029d0c 0x00000000\n0x20029d0b 0x00000000\n0x20029d0a 0x00000000\n0x20029d09 0x00000000\n0x20029d08 0x20029f0c\n0x20029d07 0x20029f26\n0x20029d06 0x20001800\n0x20029d05 0x00000000\n0x20029d04 0x000000f0\n0x20029d03 0x20029d38\n0x20029d02 0xdeadbeef\n0x20029d01 0xdeadbeef\n0x20029d00 0xdeadbeef\n0x20029cff 0xdeadbeef\n0x20029cfe 0xdeadbeef\n0x20029cfd 0xdeadbeef\n0x20029cfc 0xdeadbeef\n0x20029cfb 0xdeadbeef\n0x20029cfa 0xdeadbeef\n0x20029cf9 0xdeadbeef\n0x20029cf8 0xdeadbeef\n0x20029cf7 0xdeadbeef\n0x20029cf6 0xdeadbeef\n0x20029cf5 0xdeadbeef\n0x20029cf4 0xdeadbeef\n0x20029cf3 0xdeadbeef\n0x20029cf2 0xdeadbeef\n0x20029cf1 0xdeadbeef\n0x20029cf0 0xdeadbeef\n0x20029cef 0xdeadbeef\n0x20029cee 0xdeadbeef\n0x20029ced 0xdeadbeef\n0x20029cec 0xdeadbeef\n0x20029ceb 0xdeadbeef\n0x20029cea 0xdeadbeef\n0x20029ce9 0xdeadbeef\n0x20029ce8 0xdeadbeef\n0x20029ce7 0xdeadbeef\n0x20029ce6 0xdeadbeef\n0x20029ce5 0xdeadbeef\n0x20029ce4 0xdeadbeef\n0x20029ce3 0xdeadbeef\n0x20029ce2 0xdeadbeef\n0x20029ce1 0xdeadbeef\n0x20029ce0 0xdeadbeef\n0x20029cdf 0xdeadbeef\n0x20029cde 0xdeadbeef\n0x20029cdd 0xdeadbeef\n0x20029cdc 0xdeadbeef\n0x20029cdb 0xdeadbeef\n0x20029cda 0xdeadbeef\n0x20029cd9 0xdeadbeef\n0x20029cd8 0xdeadbeef\n0x20029cd7 0xdeadbeef\n0x20029cd6 0xdeadbeef\n0x20029cd5 0xdeadbeef\n0x20029cd4 0xdeadbeef\n0x20029cd3 0xdeadbeef\n0x20029cd2 0xdeadbeef\n0x20029cd1 0xdeadbeef\n0x20029cd0 0xdeadbeef\n0x20029ccf 0xdeadbeef\n0x20029cce 0xdeadbeef\n0x20029ccd 0xdeadbeef\n0x20029ccc 0xdeadbeef\n0x20029ccb 0xdeadbeef\n0x20029cca 0xdeadbeef\n0x20029cc9 0xdeadbeef\n0x20029cc8 0xdeadbeef\n0x20029cc7 0xdeadbeef\n0x20029cc6 0xdeadbeef\n0x20029cc5 0xdeadbeef\n0x20029cc4 0xdeadbeef\n0x20029cc3 0xdeadbeef\n0x20029cc2 0xdeadbeef\n0x20029cc1 0xdeadbeef\n0x20029cc0 0xdeadbeef\n0x20029cbf 0xdeadbeef\n0x20029cbe 0xdeadbeef\n0x20029cbd 0xdeadbeef\n0x20029cbc 0xdeadbeef\n0x20029cbb 0xdeadbeef\n0x20029cba 0xdeadbeef\n0x20029cb9 0xdeadbeef\n0x20029cb8 0xdeadbeef\n0x20029cb7 0xdeadbeef\n0x20029cb6 0xdeadbeef\n0x20029cb5 0xdeadbeef\n0x20029cb4 0xdeadbeef\n0x20029cb3 0xdeadbeef\n0x20029cb2 0xdeadbeef\n0x20029cb1 0xdeadbeef\n0x20029cb0 0xdeadbeef\n0x20029caf 0xdeadbeef\n0x20029cae 0xdeadbeef\n0x20029cad 0xdeadbeef\n0x20029cac 0xdeadbeef\n0x20029cab 0xdeadbeef\n0x20029caa 0xdeadbeef\n0x20029ca9 0xdeadbeef\n0x20029ca8 0xdeadbeef\n0x20029ca7 0xdeadbeef\n0x20029ca6 0xdeadbeef\n0x20029ca5 0xdeadbeef\n0x20029ca4 0xdeadbeef\n0x20029ca3 0xdeadbeef\n0x20029ca2 0xdeadbeef\n0x20029ca1 0xdeadbeef\n0x20029ca0 0xdeadbeef\n0x20029c9f 0xdeadbeef\n0x20029c9e 0xdeadbeef\n0x20029c9d 0xdeadbeef\n0x20029c9c 0xdeadbeef\n0x20029c9b 0xdeadbeef\n0x20029c9a 0xdeadbeef\n0x20029c99 0xdeadbeef\n0x20029c98 0xdeadbeef\n0x20029c97 0xdeadbeef\n0x20029c96 0xdeadbeef\n0x20029c95 0xdeadbeef\n0x20029c94 0xdeadbeef\n0x20029c93 0xdeadbeef\n0x20029c92 0xdeadbeef\n0x20029c91 0xdeadbeef\n0x20029c90 0xdeadbeef\n0x20029c8f 0xdeadbeef\n0x20029c8e 0xdeadbeef\n0x20029c8d 0xdeadbeef\n0x20029c8c 0xdeadbeef\n0x20029c8b 0xdeadbeef\n0x20029c8a 0xdeadbeef\n0x20029c89 0xdeadbeef\n0x20029c88 0xdeadbeef\n0x20029c87 0xdeadbeef\n0x20029c86 0xdeadbeef\n[hardfault_log] -- 2000-01-01-00:00:36 END Fault Log --\n'], ['[hardfault_log] -- 2000-01-01-00:00:57 Begin Fault Log --\nSystem fault Occurred on: 2000-01-01-00:00:57\n Type:Hard Fault in file:armv7-m/up_hardfault.c at line: 171 running task: hardfault_log\n FW git-hash: f54a6c2999e1e2fcbf56dd89de06b615b4186a6e\n Build datetime: Jul 3 2017 16:02:41\n Build url: localhost \n Processor registers: from 0x20025b84\n r0:0x00000000 r1:0x00000000 r2:0x00000001 r3:0xe000ed14 r4:0x00000000 r5:0x20001800 r6:0x20025e46 r7:0x20025e2c\n r8:0x00000000 r9:0x00000000 r10:0x00000000 r11:0x00000000 r12:0x00000000 sp:0x20025c58 lr:0x080381f5 pc:0x08038224\n xpsr:0x21000000 basepri:0x000000f0 control:0x00000004\n exe return:0xffffffe9\n IRQ stack: \n top: 0x200037f0\n sp: 0x200037a0 Valid\n bottom: 0x20003504\n size: 0x000002ec\n used: 000000c8\n User stack: \n top: 0x20025e28\n sp: 0x20025c58 Valid\n bottom: 0x2002564c\n size: 0x000007dc\n used: 000007dc\nInterrupt sp memory region, stack pointer lies within stack\n0x20003852 0x00000000\n0x20003851 0x00000000\n0x20003850 0x00000000\n0x2000384f 0x00000000\n0x2000384e 0x00000000\n0x2000384d 0x00000000\n0x2000384c 0x00000000\n0x2000384b 0x00000000\n0x2000384a 0x00000000\n0x20003849 0x00000000\n0x20003848 0x00000000\n0x20003847 0x00000000\n0x20003846 0x00000000\n0x20003845 0x00000000\n0x20003844 0x00000000\n0x20003843 0x00000000\n0x20003842 0x00000000\n0x20003841 0x00000000\n0x20003840 0x00000000\n0x2000383f 0x00000000\n0x2000383e 0x00000000\n0x2000383d 0x00000000\n0x2000383c 0x00000000\n0x2000383b 0x00000000\n0x2000383a 0x00000000\n0x20003839 0x00000000\n0x20003838 0x00000000\n0x20003837 0x00000000\n0x20003836 0x00000000\n0x20003835 0x00000000\n0x20003834 0x00000000\n0x20003833 0x00000000\n0x20003832 0x00000000\n0x20003831 0x00000000\n0x20003830 0x00000000\n0x2000382f 0x00000000\n0x2000382e 0x00000000\n0x2000382d 0x00000000\n0x2000382c 0x00000000\n0x2000382b 0x00000000\n0x2000382a 0x00000000\n0x20003829 0x00000000\n0x20003828 0x00000000\n0x20003827 0x00000603\n0x20003826 0x100061a0\n0x20003825 0x10006050\n0x20003824 0x20003880\n0x20003823 0x10005450\n0x20003822 0x00400003\n0x20003821 0x0812db88\n0x20003820 0x00000002\n0x2000381f 0x00000000\n0x2000381e 0x00000000\n0x2000381d 0x20003880\n0x2000381c 0x00000000\n0x2000381b 0x00400002\n0x2000381a 0x0812db88\n0x20003819 0x00000001\n0x20003818 0x00000000\n0x20003817 0x00000000\n0x20003816 0x20003880\n0x20003815 0x00000000\n0x20003814 0x00400001\n0x20003813 0x0812db88\n0x20003812 0x00000000\n0x20003811 0x00000000\n0x20003810 0x00000000\n0x2000380f 0x20003880\n0x2000380e 0x20003880\n0x2000380d 0x00400000\n0x2000380c 0x0812db88\n0x2000380b 0x00001003\n0x2000380a 0x00000000\n0x20003809 0x00000000\n0x20003808 0x20003880\n0x20003807 0x00000000\n0x20003806 0x00400083\n0x20003805 0x0812db88\n0x20003804 0x00001602\n0x20003803 0x10006210\n0x20003802 0x100062a0\n0x20003801 0x20003880\n0x20003800 0x10005450\n0x200037ff 0x00400082\n0x200037fe 0x0812db88\n0x200037fd 0x00001301\n0x200037fc 0x00000000\n0x200037fb 0x00000000\n0x200037fa 0x20003880\n0x200037f9 0x10005450\n0x200037f8 0x00400081\n0x200037f7 0x0812db88\n0x200037f6 0x00001000\n0x200037f5 0x00000000\n0x200037f4 0x00000000\n0x200037f3 0x20003880\n0x200037f2 0x10005450\n0x200037f1 0x00400080\n0x200037f0 0x0812db88<-- Interrupt sp top\n0x200037ef 0x00000000\n0x200037ee 0x00000000\n0x200037ed 0x00000000\n0x200037ec 0x00000000\n0x200037eb 0x00000000\n0x200037ea 0x00000000\n0x200037e9 0x00000000\n0x200037e8 0x00000000\n0x200037e7 0x00000000\n0x200037e6 0x00000000\n0x200037e5 0x00000000\n0x200037e4 0x00000000\n0x200037e3 0x00000000\n0x200037e2 0x00000000\n0x200037e1 0x00000063\n0x200037e0 0x08000000\n0x200037df 0x00258000\n0x200037de 0x07000000\n0x200037dd 0x0020210e\n0x200037dc 0x0e00020c\n0x200037db 0x10005f94\n0x200037da 0x00000002\n0x200037d9 0x200038e0\n0x200037d8 0x0812db6c\n0x200037d7 0x10000830\n0x200037d6 0x00000000\n0x200037d5 0x10000070\n0x200037d4 0x00000000\n0x200037d3 0x00000002\n0x200037d2 0x0000ffff\n0x200037d1 0x00000000\n0x200037d0 0x00000000\n0x200037cf 0x00000000\n0x200037ce 0x00000000\n0x200037cd 0x00000001\n0x200037cc 0x01a40204\n0x200037cb 0x00000000\n0x200037ca 0x00000000\n0x200037c9 0x100050f0\n0x200037c8 0x00000001\n0x200037c7 0x20007630\n0x200037c6 0x200075c0\n0x200037c5 0x200075a0\n0x200037c4 0x200074b0\n0x200037c3 0x20007268\n0x200037c2 0x20007100\n0x200037c1 0x2000748c\n0x200037c0 0x20007360\n0x200037bf 0x20007338\n0x200037be 0x20007290\n0x200037bd 0x00000005\n0x200037bc 0x10004450\n0x200037bb 0x0000ffff\n0x200037ba 0x00000000\n0x200037b9 0x00000000\n0x200037b8 0x00000000\n0x200037b7 0x00000000\n0x200037b6 0x00000001\n0x200037b5 0x10000010\n0x200037b4 0x00000000\n0x200037b3 0x080e50a7\n0x200037b2 0x20025e2c\n0x200037b1 0x20025e46\n0x200037b0 0x20001800\n0x200037af 0x20025b84\n0x200037ae 0x000000f0\n0x200037ad 0x080edf69\n0x200037ac 0x080e5121\n0x200037ab 0x080e5143\n0x200037aa 0x00000000\n0x200037a9 0x00000003\n0x200037a8 0x080e513b\n0x200037a7 0x200037c8\n0x200037a6 0x08038224\n0x200037a5 0x080381f5\n0x200037a4 0x20025c58\n0x200037a3 0x00000000\n0x200037a2 0x00000000\n0x200037a1 0x00000000\n0x200037a0 0x000000f0<-- Interrupt sp\n0x2000379f 0x080e4e8d\n0x2000379e 0x000000ab\n0x2000379d 0x0812d43b\n0x2000379c 0x20002c58\n0x2000379b 0x20003468\n0x2000379a 0x00000000\n0x20003799 0x20003468\n0x20003798 0x000000ab\n0x20003797 0x080fa4bd\n0x20003796 0x20000010\n0x20003795 0x080e5219\n0x20003794 0x20025c58\n0x20003793 0x20024df0\n0x20003792 0x080381f5\n0x20003791 0x00000144\n0x20003790 0x200037a0\n0x2000378f 0x20025b84\n0x2000378e 0x20024df0\n0x2000378d 0x20000000\n0x2000378c 0x20000000\n0x2000378b 0x080e3091\n0x2000378a 0x20003760\n0x20003789 0x20003760\n0x20003788 0x00000000\n0x20003787 0x080e3073\n0x20003786 0x0000011f\n0x20003785 0x200196a0\n0x20003784 0x00000000\n0x20003783 0x0000003d\n0x20003782 0x080f1cd9\n0x20003781 0x080f0aa9\n0x20003780 0x080f1cd9\n0x2000377f 0x080ead1f\n0x2000377e 0x20024df0\n0x2000377d 0x080381f5\n0x2000377c 0x000000ab\n0x2000377b 0x20002b54\n0x2000377a 0x00000000\n0x20003779 0x080e4cb9\n0x20003778 0x20003744\n0x20003777 0x0812d286\n0x20003776 0x00000000\n0x20003775 0x00000000\n0x20003774 0x2000375c\n0x20003773 0x00000004\n0x20003772 0x080f1cd9\n0x20003771 0x080eb8b5\n0x20003770 0x0000000a\n0x2000376f 0x080ed765\n0x2000376e 0x0812db4c\n0x2000376d 0x00000039\n0x2000376c 0x00000004\n0x2000376b 0x080eb45f\n0x2000376a 0x20003724\n0x20003769 0x00000020\n0x20003768 0x080f0aa9\n0x20003767 0x080f0ab5\n0x20003766 0x20003724\n0x20003765 0x0000000a\n0x20003764 0x0000000a\n0x20003763 0x080edef5\n0x20003762 0x0000000a\n0x20003761 0x080ed765\n0x20003760 0x00000010\n0x2000375f 0x080edef5\n0x2000375e 0x00000037\n0x2000375d 0x080ed765\n0x2000375c 0x00000010\n0x2000375b 0x080edef5\n0x2000375a 0x00000036\n0x20003759 0x080ed765\n0x20003758 0x00000010\n0x20003757 0x080edef5\n0x20003756 0x00000031\n0x20003755 0x080ed765\n0x20003754 0x00000010\n0x20003753 0x41a80000\n0x20003752 0xc1400000\n0x20003751 0xffffff40\n0x20003750 0xdeadbeef\n0x2000374f 0x00000000\n0x2000374e 0x00000000\n0x2000374d 0x200036e0\n0x2000374c 0x20003738\n0x2000374b 0x200036d0\n0x2000374a 0x200036b8\n0x20003749 0x0801be95\n0x20003748 0x00000000\n0x20003747 0x200036d0\n0x20003746 0x100099f0\n0x20003745 0x00000000\n0x20003744 0x3cd9d04c\n0x20003743 0x3ba9a1e7\n0x20003742 0x3ba7b44c\n0x20003741 0x080b18c7\n0x20003740 0x00000000\n0x2000373f 0x1000a4b0\n0x2000373e 0x1000a7b0\n0x2000373d 0x080e5219\n0x2000373c 0x00000000\n0x2000373b 0x00000c47\n0x2000373a 0x000000e4\n0x20003739 0xffffffeb\n0x20003738 0x200036a0\n0x20003737 0x00000000\n0x20003736 0x00000002\n0x20003735 0x00000001\n0x20003734 0x1000a700\n0x20003733 0x2c14b502\n0x20003732 0x2ba7a4d3\n0x20003731 0x200035d0\n0x20003730 0x00000001\n0x2000372f 0x00000003\n0x2000372e 0x35294313\n0x2000372d 0x3579fe8a\n0x2000372c 0x362bb1e6\n0x2000372b 0x200035b8\n0x2000372a 0x00000001\n0x20003729 0x00000003\n0x20003728 0x35294313\n0x20003727 0x3579fe8a\n0x20003726 0x362bb1e6\n0x20003725 0x200035a0\n0x20003724 0x00000001\n0x20003723 0x00000003\n0x20003722 0x36882e66\n0x20003721 0x3689bf39\n0x20003720 0x37a640ac\n0x2000371f 0x08009c6f\n0x2000371e 0x011503d4\n0x2000371d 0x200036b8\n0x2000371c 0x10009c48\n0x2000371b 0xdeadbeef\n0x2000371a 0xdeadbeef\n0x20003719 0xdeadbeef\n0x20003718 0xdeadbeef\n0x20003717 0xdeadbeef\n0x20003716 0xdeadbeef\n0x20003715 0xdeadbeef\n0x20003714 0xdeadbeef\n0x20003713 0xdeadbeef\n0x20003712 0xdeadbeef\n0x20003711 0xdeadbeef\n0x20003710 0xdeadbeef\n0x2000370f 0xdeadbeef\n0x2000370e 0xdeadbeef\n0x2000370d 0xdeadbeef\n0x2000370c 0xdeadbeef\n0x2000370b 0xdeadbeef\n0x2000370a 0xdeadbeef\n0x20003709 0xdeadbeef\n0x20003708 0xdeadbeef\n0x20003707 0xdeadbeef\n0x20003706 0xdeadbeef\n0x20003705 0xdeadbeef\n0x20003704 0xdeadbeef\n0x20003703 0xdeadbeef\n0x20003702 0xdeadbeef\n0x20003701 0xdeadbeef\n0x20003700 0xdeadbeef\n0x200036ff 0xdeadbeef\n0x200036fe 0xdeadbeef\n0x200036fd 0xdeadbeef\n0x200036fc 0xdeadbeef\n0x200036fb 0xdeadbeef\n0x200036fa 0xdeadbeef\n0x200036f9 0xdeadbeef\n0x200036f8 0xdeadbeef\n0x200036f7 0x00000000\n0x200036f6 0x00000000\n0x200036f5 0x00000000\n0x200036f4 0x00000000\n0x200036f3 0x00000000\n0x200036f2 0x00000000\n0x200036f1 0x00000000\n0x200036f0 0x00000000\n0x200036ef 0x00000000\n0x200036ee 0x00000000\n0x200036ed 0xc120804d\n0x200036ec 0x3ed41cc3\n0x200036eb 0x3f0a2bd6\n0x200036ea 0x00000000\n0x200036e9 0x3b845996\n0x200036e8 0x3c4e20fc\n0x200036e7 0x3cf4fd72\n0x200036e6 0x3d683d94\n0x200036e5 0x00000000\n0x200036e4 0x0152a14d\n0x200036e3 0x00274400\n0x200036e2 0x4a7fff7f\n0x200036e1 0xff7fff7f\n0x200036e0 0xff7fff7f\n0x200036df 0xff000000\n0x200036de 0x000152a1\n0x200036dd 0x4d002644\n0x200036dc 0x001641df\n0x200036db 0xd70a43a4\n0x200036da 0x9bf9ffff\n0x200036d9 0xb2463edf\n0x200036d8 0x8510bf8a\n0x200036d7 0x99ea3e1d\n0x200036d6 0x1394ffff\n0x200036d5 0xe9013b9d\n0x200036d4 0x4951c121\n0x200036d3 0x68923eed\nUser sp memory region, stack pointer lies within stack\n0x20025d0a 0xc120804d\n0x20025d09 0x3ed41cc3\n0x20025d08 0x3f0a2bd6\n0x20025d07 0x00000000\n0x20025d06 0x3b845996\n0x20025d05 0x3c4e20fc\n0x20025d04 0x3cf4fd72\n0x20025d03 0x3d683d94\n0x20025d02 0x00000000\n0x20025d01 0x0152a14d\n0x20025d00 0x00274400\n0x20025cff 0x4a7fff7f\n0x20025cfe 0xff7fff7f\n0x20025cfd 0xff7fff7f\n0x20025cfc 0xff000000\n0x20025cfb 0x000152a1\n0x20025cfa 0x4d002644\n0x20025cf9 0x001641df\n0x20025cf8 0xd70a43a4\n0x20025cf7 0x9bf9ffff\n0x20025cf6 0xb2463edf\n0x20025cf5 0x8510bf8a\n0x20025cf4 0x99ea3e1d\n0x20025cf3 0x1394ffff\n0x20025cf2 0xe9013b9d\n0x20025cf1 0x4951c121\n0x20025cf0 0x68923eed\n0x20025cef 0x27fd3f06\n0x20025cee 0x433a0000\n0x20025ced 0x00003b9d\n0x20025cec 0x49513bbc\n0x20025ceb 0x365e3c4c\n0x20025cea 0x24093c51\n0x20025ce9 0x5e8d0000\n0x20025ce8 0x00000152\n0x20025ce7 0x91860027\n0x20025ce6 0x44004a7f\n0x20025ce5 0xff7fff7f\n0x20025ce4 0xff7fff7f\n0x20025ce3 0xff7fff00\n0x20025ce2 0x00000001\n0x20025ce1 0x52918600\n0x20025ce0 0x26440016\n0x20025cdf 0x00000000\n0x20025cde 0xc05f66f4\n0x20025cdd 0xbeb2c02c\n0x20025cdc 0x3e551542\n0x20025cdb 0x20003408\n0x20025cda 0x20003418\n0x20025cd9 0x00000810\n0x20025cd8 0x0002a1a0\n0x20025cd7 0x00000001\n0x20025cd6 0xbf800000\n0x20025cd5 0x00000000\n0x20025cd4 0x00746c75\n0x20025cd3 0x61660067\n0x20025cd2 0x6f6c5f74\n0x20025cd1 0x6c756166\n0x20025cd0 0x64726168\n0x20025ccf 0x00000000\n0x20025cce 0x20025e46\n0x20025ccd 0x20025e38\n0x20025ccc 0xdeadbeef\n0x20025ccb 0x00000000\n0x20025cca 0x00000000\n0x20025cc9 0x080e02f9\n0x20025cc8 0x00000000\n0x20025cc7 0x00000000\n0x20025cc6 0x00000000\n0x20025cc5 0x00000000\n0x20025cc4 0x00000000\n0x20025cc3 0x00000000\n0x20025cc2 0x00000101\n0x20025cc1 0x00000000\n0x20025cc0 0x00000000\n0x20025cbf 0x00000000\n0x20025cbe 0x00000000\n0x20025cbd 0x00000000\n0x20025cbc 0x00000000\n0x20025cbb 0x00000000\n0x20025cba 0x00000000\n0x20025cb9 0x01000000\n0x20025cb8 0x080e02d4\n0x20025cb7 0x00000000\n0x20025cb6 0x00000000\n0x20025cb5 0x00000000\n0x20025cb4 0x00000000\n0x20025cb3 0x00000000\n0x20025cb2 0x00000000\n0x20025cb1 0xdeadbeef\n0x20025cb0 0xdeadbeef\n0x20025caf 0xdeadbeef\n0x20025cae 0xdeadbeef\n0x20025cad 0xdeadbeef\n0x20025cac 0xdeadbeef\n0x20025cab 0xdeadbeef\n0x20025caa 0xdeadbeef\n0x20025ca9 0xdeadbeef\n0x20025ca8 0xdeadbeef\n0x20025ca7 0xdeadbeef\n0x20025ca6 0xdeadbeef\n0x20025ca5 0xdeadbeef\n0x20025ca4 0xdeadbeef\n0x20025ca3 0xdeadbeef\n0x20025ca2 0xdeadbeef\n0x20025ca1 0xdeadbeef\n0x20025ca0 0xdeadbeef\n0x20025c9f 0xdeadbeef\n0x20025c9e 0xdeadbeef\n0x20025c9d 0xdeadbeef\n0x20025c9c 0xdeadbeef\n0x20025c9b 0xdeadbeef\n0x20025c9a 0xdeadbeef\n0x20025c99 0xdeadbeef\n0x20025c98 0xdeadbeef\n0x20025c97 0xdeadbeef\n0x20025c96 0xdeadbeef\n0x20025c95 0xdeadbeef\n0x20025c94 0xdeadbeef\n0x20025c93 0xdeadbeef\n0x20025c92 0xdeadbeef\n0x20025c91 0xdeadbeef\n0x20025c90 0xdeadbeef\n0x20025c8f 0xdeadbeef\n0x20025c8e 0xdeadbeef\n0x20025c8d 0xdeadbeef\n0x20025c8c 0xdeadbeef\n0x20025c8b 0xdeadbeef\n0x20025c8a 0xdeadbeef\n0x20025c89 0xdeadbeef\n0x20025c88 0xdeadbeef\n0x20025c87 0xdeadbeef\n0x20025c86 0xdeadbeef\n0x20025c85 0xdeadbeef\n0x20025c84 0xdeadbeef\n0x20025c83 0xdeadbeef\n0x20025c82 0xdeadbeef\n0x20025c81 0xdeadbeef\n0x20025c80 0xdeadbeef\n0x20025c7f 0xdeadbeef\n0x20025c7e 0xdeadbeef\n0x20025c7d 0xdeadbeef\n0x20025c7c 0xdeadbeef\n0x20025c7b 0xdeadbeef\n0x20025c7a 0xdeadbeef\n0x20025c79 0xdeadbeef\n0x20025c78 0xdeadbeef\n0x20025c77 0xdeadbeef\n0x20025c76 0xdeadbeef\n0x20025c75 0xdeadbeef\n0x20025c74 0xdeadbeef\n0x20025c73 0xdeadbeef\n0x20025c72 0xdeadbeef\n0x20025c71 0xdeadbeef\n0x20025c70 0xdeadbeef\n0x20025c6f 0xdeadbeef\n0x20025c6e 0xdeadbeef\n0x20025c6d 0xdeadbeef\n0x20025c6c 0xdeadbeef\n0x20025c6b 0xdeadbeef\n0x20025c6a 0xdeadbeef\n0x20025c69 0xdeadbeef\n0x20025c68 0xdeadbeef\n0x20025c67 0xdeadbeef\n0x20025c66 0xdeadbeef\n0x20025c65 0xdeadbeef\n0x20025c64 0xdeadbeef\n0x20025c63 0xdeadbeef\n0x20025c62 0xdeadbeef\n0x20025c61 0xdeadbeef\n0x20025c60 0xdeadbeef\n0x20025c5f 0xdeadbeef\n0x20025c5e 0xdeadbeef\n0x20025c5d 0xdeadbeef\n0x20025c5c 0xdeadbeef\n0x20025c5b 0xdeadbeef\n0x20025c5a 0xdeadbeef\n0x20025c59 0xdeadbeef\n0x20025c58 0xdeadbeef<-- User sp\n0x20025c57 0xdeadbeef\n0x20025c56 0x00000000\n0x20025c55 0x00000000\n0x20025c54 0x00000000\n0x20025c53 0x00000000\n0x20025c52 0x00000000\n0x20025c51 0x00000000\n0x20025c50 0x00000000\n0x20025c4f 0x00000000\n0x20025c4e 0x00000000\n0x20025c4d 0x00000000\n0x20025c4c 0x00000000\n0x20025c4b 0x00000000\n0x20025c4a 0x00000000\n0x20025c49 0x00000000\n0x20025c48 0x00000000\n0x20025c47 0x00000000\n0x20025c46 0x00000000\n0x20025c45 0x21000000\n0x20025c44 0x08038224\n0x20025c43 0x080381f5\n0x20025c42 0x00000000\n0x20025c41 0xe000ed14\n0x20025c40 0x00000001\n0x20025c3f 0x00000000\n0x20025c3e 0x00000000\n0x20025c3d 0x00000000\n0x20025c3c 0x00000000\n0x20025c3b 0x00000000\n0x20025c3a 0x00000000\n0x20025c39 0x00000000\n0x20025c38 0x00000000\n0x20025c37 0x00000000\n0x20025c36 0x00000000\n0x20025c35 0x00000000\n0x20025c34 0x00000000\n0x20025c33 0x00000000\n0x20025c32 0x00000000\n0x20025c31 0x00000000\n0x20025c30 0x00000000\n0x20025c2f 0x00000000\n0x20025c2e 0x00000000\n0x20025c2d 0xffffffe9\n0x20025c2c 0x00000000\n0x20025c2b 0x00000000\n0x20025c2a 0x00000000\n0x20025c29 0x00000000\n0x20025c28 0x20025e2c\n0x20025c27 0x20025e46\n0x20025c26 0x20001800\n0x20025c25 0x00000000\n0x20025c24 0x000000f0\n0x20025c23 0x20025c58\n0x20025c22 0xdeadbeef\n0x20025c21 0xdeadbeef\n0x20025c20 0xdeadbeef\n0x20025c1f 0xdeadbeef\n0x20025c1e 0xdeadbeef\n0x20025c1d 0xdeadbeef\n0x20025c1c 0xdeadbeef\n0x20025c1b 0xdeadbeef\n0x20025c1a 0xdeadbeef\n0x20025c19 0xdeadbeef\n0x20025c18 0xdeadbeef\n0x20025c17 0xdeadbeef\n0x20025c16 0xdeadbeef\n0x20025c15 0xdeadbeef\n0x20025c14 0xdeadbeef\n0x20025c13 0xdeadbeef\n0x20025c12 0xdeadbeef\n0x20025c11 0xdeadbeef\n0x20025c10 0xdeadbeef\n0x20025c0f 0xdeadbeef\n0x20025c0e 0xdeadbeef\n0x20025c0d 0xdeadbeef\n0x20025c0c 0xdeadbeef\n0x20025c0b 0xdeadbeef\n0x20025c0a 0xdeadbeef\n0x20025c09 0xdeadbeef\n0x20025c08 0xdeadbeef\n0x20025c07 0xdeadbeef\n0x20025c06 0xdeadbeef\n0x20025c05 0xdeadbeef\n0x20025c04 0xdeadbeef\n0x20025c03 0xdeadbeef\n0x20025c02 0xdeadbeef\n0x20025c01 0xdeadbeef\n0x20025c00 0xdeadbeef\n0x20025bff 0xdeadbeef\n0x20025bfe 0xdeadbeef\n0x20025bfd 0xdeadbeef\n0x20025bfc 0xdeadbeef\n0x20025bfb 0xdeadbeef\n0x20025bfa 0xdeadbeef\n0x20025bf9 0xdeadbeef\n0x20025bf8 0xdeadbeef\n0x20025bf7 0xdeadbeef\n0x20025bf6 0xdeadbeef\n0x20025bf5 0xdeadbeef\n0x20025bf4 0xdeadbeef\n0x20025bf3 0xdeadbeef\n0x20025bf2 0xdeadbeef\n0x20025bf1 0xdeadbeef\n0x20025bf0 0xdeadbeef\n0x20025bef 0xdeadbeef\n0x20025bee 0xdeadbeef\n0x20025bed 0xdeadbeef\n0x20025bec 0xdeadbeef\n0x20025beb 0xdeadbeef\n0x20025bea 0xdeadbeef\n0x20025be9 0xdeadbeef\n0x20025be8 0xdeadbeef\n0x20025be7 0xdeadbeef\n0x20025be6 0xdeadbeef\n0x20025be5 0xdeadbeef\n0x20025be4 0xdeadbeef\n0x20025be3 0xdeadbeef\n0x20025be2 0xdeadbeef\n0x20025be1 0xdeadbeef\n0x20025be0 0xdeadbeef\n0x20025bdf 0xdeadbeef\n0x20025bde 0xdeadbeef\n0x20025bdd 0xdeadbeef\n0x20025bdc 0xdeadbeef\n0x20025bdb 0xdeadbeef\n0x20025bda 0xdeadbeef\n0x20025bd9 0xdeadbeef\n0x20025bd8 0xdeadbeef\n0x20025bd7 0xdeadbeef\n0x20025bd6 0xdeadbeef\n0x20025bd5 0xdeadbeef\n0x20025bd4 0xdeadbeef\n0x20025bd3 0xdeadbeef\n0x20025bd2 0xdeadbeef\n0x20025bd1 0xdeadbeef\n0x20025bd0 0xdeadbeef\n0x20025bcf 0xdeadbeef\n0x20025bce 0xdeadbeef\n0x20025bcd 0xdeadbeef\n0x20025bcc 0xdeadbeef\n0x20025bcb 0xdeadbeef\n0x20025bca 0xdeadbeef\n0x20025bc9 0xdeadbeef\n0x20025bc8 0xdeadbeef\n0x20025bc7 0xdeadbeef\n0x20025bc6 0xdeadbeef\n0x20025bc5 0xdeadbeef\n0x20025bc4 0xdeadbeef\n0x20025bc3 0xdeadbeef\n0x20025bc2 0xdeadbeef\n0x20025bc1 0xdeadbeef\n0x20025bc0 0xdeadbeef\n0x20025bbf 0xdeadbeef\n0x20025bbe 0xdeadbeef\n0x20025bbd 0xdeadbeef\n0x20025bbc 0xdeadbeef\n0x20025bbb 0xdeadbeef\n0x20025bba 0xdeadbeef\n0x20025bb9 0xdeadbeef\n0x20025bb8 0xdeadbeef\n0x20025bb7 0xdeadbeef\n0x20025bb6 0xdeadbeef\n0x20025bb5 0xdeadbeef\n0x20025bb4 0xdeadbeef\n0x20025bb3 0xdeadbeef\n0x20025bb2 0xdeadbeef\n0x20025bb1 0xdeadbeef\n0x20025bb0 0xdeadbeef\n0x20025baf 0xdeadbeef\n0x20025bae 0xdeadbeef\n0x20025bad 0xdeadbeef\n0x20025bac 0xdeadbeef\n0x20025bab 0xdeadbeef\n0x20025baa 0xdeadbeef\n0x20025ba9 0xdeadbeef\n0x20025ba8 0xdeadbeef\n0x20025ba7 0xdeadbeef\n0x20025ba6 0xdeadbeef\n[hardfault_log] -- 2000-01-01-00:00:57 END Fault Log --\n'], ['[hardfault_log] -- 2000-01-01-00:01:16 Begin Fault Log --\nSystem fault Occurred on: 2000-01-01-00:01:16\n Type:Hard Fault in file:armv7-m/up_hardfault.c at line: 171 running task: hardfault_log\n FW git-hash: f54a6c2999e1e2fcbf56dd89de06b615b4186a6e\n Build datetime: Jul 3 2017 16:02:41\n Build url: localhost \n Processor registers: from 0x200259e4\n r0:0x00000000 r1:0x00000000 r2:0x00000001 r3:0xe000ed14 r4:0x00000000 r5:0x20001800 r6:0x20025ca6 r7:0x20025c8c\n r8:0x00000000 r9:0x00000000 r10:0x00000000 r11:0x00000000 r12:0x00000000 sp:0x20025ab8 lr:0x080381f5 pc:0x08038224\n xpsr:0x21000000 basepri:0x000000f0 control:0x00000004\n exe return:0xffffffe9\n IRQ stack: \n top: 0x200037f0\n sp: 0x200037a0 Valid\n bottom: 0x20003504\n size: 0x000002ec\n used: 000000c8\n User stack: \n top: 0x20025c88\n sp: 0x20025ab8 Valid\n bottom: 0x200254ac\n size: 0x000007dc\n used: 000007dc\nInterrupt sp memory region, stack pointer lies within stack\n0x20003852 0x00000000\n0x20003851 0x00000000\n0x20003850 0x00000000\n0x2000384f 0x00000000\n0x2000384e 0x00000000\n0x2000384d 0x00000000\n0x2000384c 0x00000000\n0x2000384b 0x00000000\n0x2000384a 0x00000000\n0x20003849 0x00000000\n0x20003848 0x00000000\n0x20003847 0x00000000\n0x20003846 0x00000000\n0x20003845 0x00000000\n0x20003844 0x00000000\n0x20003843 0x00000000\n0x20003842 0x00000000\n0x20003841 0x00000000\n0x20003840 0x00000000\n0x2000383f 0x00000000\n0x2000383e 0x00000000\n0x2000383d 0x00000000\n0x2000383c 0x00000000\n0x2000383b 0x00000000\n0x2000383a 0x00000000\n0x20003839 0x00000000\n0x20003838 0x00000000\n0x20003837 0x00000000\n0x20003836 0x00000000\n0x20003835 0x00000000\n0x20003834 0x00000000\n0x20003833 0x00000000\n0x20003832 0x00000000\n0x20003831 0x00000000\n0x20003830 0x00000000\n0x2000382f 0x00000000\n0x2000382e 0x00000000\n0x2000382d 0x00000000\n0x2000382c 0x00000000\n0x2000382b 0x00000000\n0x2000382a 0x00000000\n0x20003829 0x00000000\n0x20003828 0x00000000\n0x20003827 0x00000603\n0x20003826 0x100061a0\n0x20003825 0x10006050\n0x20003824 0x20003880\n0x20003823 0x10005450\n0x20003822 0x00400003\n0x20003821 0x0812db88\n0x20003820 0x00000002\n0x2000381f 0x00000000\n0x2000381e 0x00000000\n0x2000381d 0x20003880\n0x2000381c 0x00000000\n0x2000381b 0x00400002\n0x2000381a 0x0812db88\n0x20003819 0x00000001\n0x20003818 0x00000000\n0x20003817 0x00000000\n0x20003816 0x20003880\n0x20003815 0x00000000\n0x20003814 0x00400001\n0x20003813 0x0812db88\n0x20003812 0x00000000\n0x20003811 0x00000000\n0x20003810 0x00000000\n0x2000380f 0x20003880\n0x2000380e 0x20003880\n0x2000380d 0x00400000\n0x2000380c 0x0812db88\n0x2000380b 0x00001003\n0x2000380a 0x00000000\n0x20003809 0x00000000\n0x20003808 0x20003880\n0x20003807 0x00000000\n0x20003806 0x00400083\n0x20003805 0x0812db88\n0x20003804 0x00001602\n0x20003803 0x10006210\n0x20003802 0x100062a0\n0x20003801 0x20003880\n0x20003800 0x10005450\n0x200037ff 0x00400082\n0x200037fe 0x0812db88\n0x200037fd 0x00001301\n0x200037fc 0x00000000\n0x200037fb 0x00000000\n0x200037fa 0x20003880\n0x200037f9 0x10005450\n0x200037f8 0x00400081\n0x200037f7 0x0812db88\n0x200037f6 0x00001000\n0x200037f5 0x00000000\n0x200037f4 0x00000000\n0x200037f3 0x20003880\n0x200037f2 0x10005450\n0x200037f1 0x00400080\n0x200037f0 0x0812db88<-- Interrupt sp top\n0x200037ef 0x00000000\n0x200037ee 0x00000000\n0x200037ed 0x00000000\n0x200037ec 0x00000000\n0x200037eb 0x00000000\n0x200037ea 0x00000000\n0x200037e9 0x00000000\n0x200037e8 0x00000000\n0x200037e7 0x00000000\n0x200037e6 0x00000000\n0x200037e5 0x00000000\n0x200037e4 0x00000000\n0x200037e3 0x00000000\n0x200037e2 0x00000000\n0x200037e1 0x00000063\n0x200037e0 0x08000000\n0x200037df 0x00258000\n0x200037de 0x07000000\n0x200037dd 0x0020210e\n0x200037dc 0x0e00020c\n0x200037db 0x10005f94\n0x200037da 0x00000002\n0x200037d9 0x200038e0\n0x200037d8 0x0812db6c\n0x200037d7 0x10000830\n0x200037d6 0x00000000\n0x200037d5 0x10000070\n0x200037d4 0x00000000\n0x200037d3 0x00000002\n0x200037d2 0x0000ffff\n0x200037d1 0x00000000\n0x200037d0 0x00000000\n0x200037cf 0x00000000\n0x200037ce 0x00000000\n0x200037cd 0x00000001\n0x200037cc 0x01a40204\n0x200037cb 0x00000000\n0x200037ca 0x00000000\n0x200037c9 0x100050f0\n0x200037c8 0x00000001\n0x200037c7 0x20007630\n0x200037c6 0x200075c0\n0x200037c5 0x200075a0\n0x200037c4 0x200074b0\n0x200037c3 0x20007268\n0x200037c2 0x20007100\n0x200037c1 0x2000748c\n0x200037c0 0x20007360\n0x200037bf 0x20007338\n0x200037be 0x20007290\n0x200037bd 0x00000005\n0x200037bc 0x10004450\n0x200037bb 0x0000ffff\n0x200037ba 0x00000000\n0x200037b9 0x00000000\n0x200037b8 0x00000000\n0x200037b7 0x00000000\n0x200037b6 0x00000001\n0x200037b5 0x10000010\n0x200037b4 0x00000000\n0x200037b3 0x080e50a7\n0x200037b2 0x20025c8c\n0x200037b1 0x20025ca6\n0x200037b0 0x20001800\n0x200037af 0x200259e4\n0x200037ae 0x000000f0\n0x200037ad 0x080edf69\n0x200037ac 0x080e5121\n0x200037ab 0x080e5143\n0x200037aa 0x00000000\n0x200037a9 0x00000003\n0x200037a8 0x080e513b\n0x200037a7 0x200037c8\n0x200037a6 0x08038224\n0x200037a5 0x080381f5\n0x200037a4 0x20025ab8\n0x200037a3 0x00000000\n0x200037a2 0x00000000\n0x200037a1 0x00000000\n0x200037a0 0x000000f0<-- Interrupt sp\n0x2000379f 0x080e4e8d\n0x2000379e 0x000000ab\n0x2000379d 0x0812d43b\n0x2000379c 0x20002c58\n0x2000379b 0x20003468\n0x2000379a 0x00000000\n0x20003799 0x20003468\n0x20003798 0x000000ab\n0x20003797 0x080fa4bd\n0x20003796 0x20000010\n0x20003795 0x080e5219\n0x20003794 0x20025ab8\n0x20003793 0x200190a0\n0x20003792 0x080381f5\n0x20003791 0x00000144\n0x20003790 0x200037a0\n0x2000378f 0x200259e4\n0x2000378e 0x200190a0\n0x2000378d 0x20000000\n0x2000378c 0x20000000\n0x2000378b 0x080e3091\n0x2000378a 0x20003760\n0x20003789 0x20003760\n0x20003788 0x00000000\n0x20003787 0x080e3073\n0x20003786 0x0000011f\n0x20003785 0x20019680\n0x20003784 0x00000000\n0x20003783 0x0000003d\n0x20003782 0x080f1cd9\n0x20003781 0x080f0aa9\n0x20003780 0x080f1cd9\n0x2000377f 0x080ead1f\n0x2000377e 0x200190a0\n0x2000377d 0x080381f5\n0x2000377c 0x000000ab\n0x2000377b 0x20002b54\n0x2000377a 0x00000000\n0x20003779 0x080e4cb9\n0x20003778 0x20003744\n0x20003777 0x0812d286\n0x20003776 0x00000000\n0x20003775 0x00000000\n0x20003774 0x2000375c\n0x20003773 0x00000004\n0x20003772 0x080f1cd9\n0x20003771 0x080eb8b5\n0x20003770 0x0000000a\n0x2000376f 0x080ed765\n0x2000376e 0x0812db4c\n0x2000376d 0x00000039\n0x2000376c 0x00000004\n0x2000376b 0x080eb45f\n0x2000376a 0x20003724\n0x20003769 0x00000020\n0x20003768 0x080f0aa9\n0x20003767 0x080f0ab5\n0x20003766 0x20003724\n0x20003765 0x0000000a\n0x20003764 0x0000000a\n0x20003763 0x080edef5\n0x20003762 0x0000000a\n0x20003761 0x080ed765\n0x20003760 0x00000010\n0x2000375f 0x080edef5\n0x2000375e 0x00000037\n0x2000375d 0x080ed765\n0x2000375c 0x00000010\n0x2000375b 0x080edef5\n0x2000375a 0x00000036\n0x20003759 0x080ed765\n0x20003758 0x00000010\n0x20003757 0x080edef5\n0x20003756 0x00000031\n0x20003755 0x080ed765\n0x20003754 0x00000010\n0x20003753 0x42a00000\n0x20003752 0x41880000\n0x20003751 0xffffff50\n0x20003750 0xdeadbeef\n0x2000374f 0x00000000\n0x2000374e 0x00000000\n0x2000374d 0x200036e0\n0x2000374c 0x20003738\n0x2000374b 0x200036d0\n0x2000374a 0x200036b8\n0x20003749 0x0801be95\n0x20003748 0x00000000\n0x20003747 0x200036d0\n0x20003746 0x100099f0\n0x20003745 0x00000000\n0x20003744 0x3db70b91\n0x20003743 0x3d0ae5f2\n0x20003742 0x3c70c717\n0x20003741 0x080b18c7\n0x20003740 0x00000000\n0x2000373f 0x1000a4b0\n0x2000373e 0x1000a7b0\n0x2000373d 0x080e5219\n0x2000373c 0x00000000\n0x2000373b 0x00000d60\n0x2000373a 0x0000008b\n0x20003739 0xffffffb0\n0x20003738 0x200036a0\n0x20003737 0x00000000\n0x20003736 0x00000002\n0x20003735 0x00000001\n0x20003734 0x1000a700\n0x20003733 0xaf48d96c\n0x20003732 0xaeb94218\n0x20003731 0x200035d0\n0x20003730 0x00000001\n0x2000372f 0x00000003\n0x2000372e 0x37661e88\n0x2000372d 0x37c938fc\n0x2000372c 0x38d6d158\n0x2000372b 0x200035b8\n0x2000372a 0x00000001\n0x20003729 0x00000003\n0x20003728 0x36037f2a\n0x20003727 0x36896e20\n0x20003726 0x37621ef7\n0x20003725 0x200035a0\n0x20003724 0x00000001\n0x20003723 0x00000003\n0x20003722 0x37453ebe\n0x20003721 0x37dffff9\n0x20003720 0x389c3322\n0x2000371f 0x08009c6f\n0x2000371e 0x00f548e6\n0x2000371d 0x200036b8\n0x2000371c 0x10009c48\n0x2000371b 0xdeadbeef\n0x2000371a 0xdeadbeef\n0x20003719 0xdeadbeef\n0x20003718 0xdeadbeef\n0x20003717 0xdeadbeef\n0x20003716 0xdeadbeef\n0x20003715 0xdeadbeef\n0x20003714 0xdeadbeef\n0x20003713 0xdeadbeef\n0x20003712 0xdeadbeef\n0x20003711 0xdeadbeef\n0x20003710 0xdeadbeef\n0x2000370f 0xdeadbeef\n0x2000370e 0xdeadbeef\n0x2000370d 0xdeadbeef\n0x2000370c 0xdeadbeef\n0x2000370b 0xdeadbeef\n0x2000370a 0xdeadbeef\n0x20003709 0xdeadbeef\n0x20003708 0xdeadbeef\n0x20003707 0xdeadbeef\n0x20003706 0xdeadbeef\n0x20003705 0xdeadbeef\n0x20003704 0xdeadbeef\n0x20003703 0xdeadbeef\n0x20003702 0xdeadbeef\n0x20003701 0xdeadbeef\n0x20003700 0xdeadbeef\n0x200036ff 0xdeadbeef\n0x200036fe 0xdeadbeef\n0x200036fd 0xdeadbeef\n0x200036fc 0xdeadbeef\n0x200036fb 0xdeadbeef\n0x200036fa 0xdeadbeef\n0x200036f9 0xdeadbeef\n0x200036f8 0xdeadbeef\n0x200036f7 0x00000000\n0x200036f6 0x00000000\n0x200036f5 0x00000000\n0x200036f4 0x00000000\n0x200036f3 0x00000000\n0x200036f2 0x00000000\n0x200036f1 0x00000000\n0x200036f0 0x00000000\n0x200036ef 0x00000000\n0x200036ee 0x00000000\n0x200036ed 0xdeadbeef\n0x200036ec 0xdeadbeef\n0x200036eb 0xdeadbeef\n0x200036ea 0xdeadbeef\n0x200036e9 0xdeadbeef\n0x200036e8 0xdeadbeef\n0x200036e7 0xdeadbeef\n0x200036e6 0xdeadbeef\n0x200036e5 0xdeadbeef\n0x200036e4 0xdeadbeef\n0x200036e3 0xdeadbeef\n0x200036e2 0xdeadbeef\n0x200036e1 0xdeadbeef\n0x200036e0 0xdeadbeef\n0x200036df 0xdeadbeef\n0x200036de 0xdeadbeef\n0x200036dd 0xdeadbeef\n0x200036dc 0xdeadbeef\n0x200036db 0xdeadbeef\n0x200036da 0xdeadbeef\n0x200036d9 0xdeadbeef\n0x200036d8 0xdeadbeef\n0x200036d7 0xdeadbeef\n0x200036d6 0xdeadbeef\n0x200036d5 0xdeadbeef\n0x200036d4 0xdeadbeef\n0x200036d3 0xdeadbeef\nUser sp memory region, stack pointer lies within stack\n0x20025b6a 0xdeadbeef\n0x20025b69 0xdeadbeef\n0x20025b68 0xdeadbeef\n0x20025b67 0xdeadbeef\n0x20025b66 0xdeadbeef\n0x20025b65 0xdeadbeef\n0x20025b64 0xdeadbeef\n0x20025b63 0xdeadbeef\n0x20025b62 0xdeadbeef\n0x20025b61 0xdeadbeef\n0x20025b60 0xdeadbeef\n0x20025b5f 0xdeadbeef\n0x20025b5e 0xdeadbeef\n0x20025b5d 0xdeadbeef\n0x20025b5c 0xdeadbeef\n0x20025b5b 0xdeadbeef\n0x20025b5a 0xdeadbeef\n0x20025b59 0xdeadbeef\n0x20025b58 0xdeadbeef\n0x20025b57 0xdeadbeef\n0x20025b56 0xdeadbeef\n0x20025b55 0xdeadbeef\n0x20025b54 0xdeadbeef\n0x20025b53 0xdeadbeef\n0x20025b52 0xdeadbeef\n0x20025b51 0xdeadbeef\n0x20025b50 0xdeadbeef\n0x20025b4f 0xdeadbeef\n0x20025b4e 0xdeadbeef\n0x20025b4d 0xdeadbeef\n0x20025b4c 0xdeadbeef\n0x20025b4b 0xdeadbeef\n0x20025b4a 0xdeadbeef\n0x20025b49 0xdeadbeef\n0x20025b48 0xdeadbeef\n0x20025b47 0xdeadbeef\n0x20025b46 0xdeadbeef\n0x20025b45 0xdeadbeef\n0x20025b44 0xdeadbeef\n0x20025b43 0xdeadbeef\n0x20025b42 0xdeadbeef\n0x20025b41 0xdeadbeef\n0x20025b40 0xdeadbeef\n0x20025b3f 0xdeadbeef\n0x20025b3e 0xdeadbeef\n0x20025b3d 0xdeadbeef\n0x20025b3c 0xdeadbeef\n0x20025b3b 0x20003408\n0x20025b3a 0x20003418\n0x20025b39 0x00000810\n0x20025b38 0x0002a340\n0x20025b37 0xdeadbeef\n0x20025b36 0xdeadbeef\n0x20025b35 0xdeadbeef\n0x20025b34 0x00746c75\n0x20025b33 0x61660067\n0x20025b32 0x6f6c5f74\n0x20025b31 0x6c756166\n0x20025b30 0x64726168\n0x20025b2f 0x00000000\n0x20025b2e 0x20025ca6\n0x20025b2d 0x20025c98\n0x20025b2c 0xdeadbeef\n0x20025b2b 0x00000000\n0x20025b2a 0x00000000\n0x20025b29 0x080e02f9\n0x20025b28 0x00000000\n0x20025b27 0x00000000\n0x20025b26 0x00000000\n0x20025b25 0x00000000\n0x20025b24 0x00000000\n0x20025b23 0x00000000\n0x20025b22 0x00000101\n0x20025b21 0x00000000\n0x20025b20 0x00000000\n0x20025b1f 0x00000000\n0x20025b1e 0x00000000\n0x20025b1d 0x00000000\n0x20025b1c 0x00000000\n0x20025b1b 0x00000000\n0x20025b1a 0x00000000\n0x20025b19 0x01000000\n0x20025b18 0x080e02d4\n0x20025b17 0x00000000\n0x20025b16 0x00000000\n0x20025b15 0x00000000\n0x20025b14 0x00000000\n0x20025b13 0x00000000\n0x20025b12 0x00000000\n0x20025b11 0xdeadbeef\n0x20025b10 0xdeadbeef\n0x20025b0f 0xdeadbeef\n0x20025b0e 0xdeadbeef\n0x20025b0d 0xdeadbeef\n0x20025b0c 0xdeadbeef\n0x20025b0b 0xdeadbeef\n0x20025b0a 0xdeadbeef\n0x20025b09 0xdeadbeef\n0x20025b08 0xdeadbeef\n0x20025b07 0xdeadbeef\n0x20025b06 0xdeadbeef\n0x20025b05 0xdeadbeef\n0x20025b04 0xdeadbeef\n0x20025b03 0xdeadbeef\n0x20025b02 0xdeadbeef\n0x20025b01 0xdeadbeef\n0x20025b00 0xdeadbeef\n0x20025aff 0xdeadbeef\n0x20025afe 0xdeadbeef\n0x20025afd 0xdeadbeef\n0x20025afc 0xdeadbeef\n0x20025afb 0xdeadbeef\n0x20025afa 0xdeadbeef\n0x20025af9 0xdeadbeef\n0x20025af8 0xdeadbeef\n0x20025af7 0xdeadbeef\n0x20025af6 0xdeadbeef\n0x20025af5 0xdeadbeef\n0x20025af4 0xdeadbeef\n0x20025af3 0xdeadbeef\n0x20025af2 0xdeadbeef\n0x20025af1 0xdeadbeef\n0x20025af0 0xdeadbeef\n0x20025aef 0xdeadbeef\n0x20025aee 0xdeadbeef\n0x20025aed 0xdeadbeef\n0x20025aec 0xdeadbeef\n0x20025aeb 0xdeadbeef\n0x20025aea 0xdeadbeef\n0x20025ae9 0xdeadbeef\n0x20025ae8 0xdeadbeef\n0x20025ae7 0xdeadbeef\n0x20025ae6 0xdeadbeef\n0x20025ae5 0xdeadbeef\n0x20025ae4 0xdeadbeef\n0x20025ae3 0xdeadbeef\n0x20025ae2 0xdeadbeef\n0x20025ae1 0xdeadbeef\n0x20025ae0 0xdeadbeef\n0x20025adf 0xdeadbeef\n0x20025ade 0xdeadbeef\n0x20025add 0xdeadbeef\n0x20025adc 0xdeadbeef\n0x20025adb 0xdeadbeef\n0x20025ada 0xdeadbeef\n0x20025ad9 0xdeadbeef\n0x20025ad8 0xdeadbeef\n0x20025ad7 0xdeadbeef\n0x20025ad6 0xdeadbeef\n0x20025ad5 0xdeadbeef\n0x20025ad4 0xdeadbeef\n0x20025ad3 0xdeadbeef\n0x20025ad2 0xdeadbeef\n0x20025ad1 0xdeadbeef\n0x20025ad0 0xdeadbeef\n0x20025acf 0xdeadbeef\n0x20025ace 0xdeadbeef\n0x20025acd 0xdeadbeef\n0x20025acc 0xdeadbeef\n0x20025acb 0xdeadbeef\n0x20025aca 0xdeadbeef\n0x20025ac9 0xdeadbeef\n0x20025ac8 0xdeadbeef\n0x20025ac7 0xdeadbeef\n0x20025ac6 0xdeadbeef\n0x20025ac5 0xdeadbeef\n0x20025ac4 0xdeadbeef\n0x20025ac3 0xdeadbeef\n0x20025ac2 0xdeadbeef\n0x20025ac1 0xdeadbeef\n0x20025ac0 0xdeadbeef\n0x20025abf 0xdeadbeef\n0x20025abe 0xdeadbeef\n0x20025abd 0xdeadbeef\n0x20025abc 0xdeadbeef\n0x20025abb 0xdeadbeef\n0x20025aba 0xdeadbeef\n0x20025ab9 0xdeadbeef\n0x20025ab8 0xdeadbeef<-- User sp\n0x20025ab7 0xdeadbeef\n0x20025ab6 0x00000000\n0x20025ab5 0x00000000\n0x20025ab4 0x00000000\n0x20025ab3 0x00000000\n0x20025ab2 0x00000000\n0x20025ab1 0x00000000\n0x20025ab0 0x00000000\n0x20025aaf 0x00000000\n0x20025aae 0x00000000\n0x20025aad 0x00000000\n0x20025aac 0x00000000\n0x20025aab 0x00000000\n0x20025aaa 0x00000000\n0x20025aa9 0x00000000\n0x20025aa8 0x00000000\n0x20025aa7 0x00000000\n0x20025aa6 0x00000000\n0x20025aa5 0x21000000\n0x20025aa4 0x08038224\n0x20025aa3 0x080381f5\n0x20025aa2 0x00000000\n0x20025aa1 0xe000ed14\n0x20025aa0 0x00000001\n0x20025a9f 0x00000000\n0x20025a9e 0x00000000\n0x20025a9d 0x00000000\n0x20025a9c 0x00000000\n0x20025a9b 0x00000000\n0x20025a9a 0x00000000\n0x20025a99 0x00000000\n0x20025a98 0x00000000\n0x20025a97 0x00000000\n0x20025a96 0x00000000\n0x20025a95 0x00000000\n0x20025a94 0x00000000\n0x20025a93 0x00000000\n0x20025a92 0x00000000\n0x20025a91 0x00000000\n0x20025a90 0x00000000\n0x20025a8f 0x00000000\n0x20025a8e 0x00000000\n0x20025a8d 0xffffffe9\n0x20025a8c 0x00000000\n0x20025a8b 0x00000000\n0x20025a8a 0x00000000\n0x20025a89 0x00000000\n0x20025a88 0x20025c8c\n0x20025a87 0x20025ca6\n0x20025a86 0x20001800\n0x20025a85 0x00000000\n0x20025a84 0x000000f0\n0x20025a83 0x20025ab8\n0x20025a82 0xdeadbeef\n0x20025a81 0xdeadbeef\n0x20025a80 0xdeadbeef\n0x20025a7f 0xdeadbeef\n0x20025a7e 0xdeadbeef\n0x20025a7d 0xdeadbeef\n0x20025a7c 0xdeadbeef\n0x20025a7b 0xdeadbeef\n0x20025a7a 0xdeadbeef\n0x20025a79 0xdeadbeef\n0x20025a78 0xdeadbeef\n0x20025a77 0xdeadbeef\n0x20025a76 0xdeadbeef\n0x20025a75 0xdeadbeef\n0x20025a74 0xdeadbeef\n0x20025a73 0xdeadbeef\n0x20025a72 0xdeadbeef\n0x20025a71 0xdeadbeef\n0x20025a70 0xdeadbeef\n0x20025a6f 0xdeadbeef\n0x20025a6e 0xdeadbeef\n0x20025a6d 0xdeadbeef\n0x20025a6c 0xdeadbeef\n0x20025a6b 0xdeadbeef\n0x20025a6a 0xdeadbeef\n0x20025a69 0xdeadbeef\n0x20025a68 0xdeadbeef\n0x20025a67 0xdeadbeef\n0x20025a66 0xdeadbeef\n0x20025a65 0xdeadbeef\n0x20025a64 0xdeadbeef\n0x20025a63 0xdeadbeef\n0x20025a62 0xdeadbeef\n0x20025a61 0xdeadbeef\n0x20025a60 0xdeadbeef\n0x20025a5f 0xdeadbeef\n0x20025a5e 0xdeadbeef\n0x20025a5d 0xdeadbeef\n0x20025a5c 0xdeadbeef\n0x20025a5b 0xdeadbeef\n0x20025a5a 0xdeadbeef\n0x20025a59 0xdeadbeef\n0x20025a58 0xdeadbeef\n0x20025a57 0xdeadbeef\n0x20025a56 0xdeadbeef\n0x20025a55 0xdeadbeef\n0x20025a54 0xdeadbeef\n0x20025a53 0xdeadbeef\n0x20025a52 0xdeadbeef\n0x20025a51 0xdeadbeef\n0x20025a50 0xdeadbeef\n0x20025a4f 0xdeadbeef\n0x20025a4e 0xdeadbeef\n0x20025a4d 0xdeadbeef\n0x20025a4c 0xdeadbeef\n0x20025a4b 0xdeadbeef\n0x20025a4a 0xdeadbeef\n0x20025a49 0xdeadbeef\n0x20025a48 0xdeadbeef\n0x20025a47 0xdeadbeef\n0x20025a46 0xdeadbeef\n0x20025a45 0xdeadbeef\n0x20025a44 0xdeadbeef\n0x20025a43 0xdeadbeef\n0x20025a42 0xdeadbeef\n0x20025a41 0xdeadbeef\n0x20025a40 0xdeadbeef\n0x20025a3f 0xdeadbeef\n0x20025a3e 0xdeadbeef\n0x20025a3d 0xdeadbeef\n0x20025a3c 0xdeadbeef\n0x20025a3b 0xdeadbeef\n0x20025a3a 0xdeadbeef\n0x20025a39 0xdeadbeef\n0x20025a38 0xdeadbeef\n0x20025a37 0xdeadbeef\n0x20025a36 0xdeadbeef\n0x20025a35 0xdeadbeef\n0x20025a34 0xdeadbeef\n0x20025a33 0xdeadbeef\n0x20025a32 0xdeadbeef\n0x20025a31 0xdeadbeef\n0x20025a30 0xdeadbeef\n0x20025a2f 0xdeadbeef\n0x20025a2e 0xdeadbeef\n0x20025a2d 0xdeadbeef\n0x20025a2c 0xdeadbeef\n0x20025a2b 0xdeadbeef\n0x20025a2a 0xdeadbeef\n0x20025a29 0xdeadbeef\n0x20025a28 0xdeadbeef\n0x20025a27 0xdeadbeef\n0x20025a26 0xdeadbeef\n0x20025a25 0xdeadbeef\n0x20025a24 0xdeadbeef\n0x20025a23 0xdeadbeef\n0x20025a22 0xdeadbeef\n0x20025a21 0xdeadbeef\n0x20025a20 0xdeadbeef\n0x20025a1f 0xdeadbeef\n0x20025a1e 0xdeadbeef\n0x20025a1d 0xdeadbeef\n0x20025a1c 0xdeadbeef\n0x20025a1b 0xdeadbeef\n0x20025a1a 0xdeadbeef\n0x20025a19 0xdeadbeef\n0x20025a18 0xdeadbeef\n0x20025a17 0xdeadbeef\n0x20025a16 0xdeadbeef\n0x20025a15 0xdeadbeef\n0x20025a14 0xdeadbeef\n0x20025a13 0xdeadbeef\n0x20025a12 0xdeadbeef\n0x20025a11 0xdeadbeef\n0x20025a10 0xdeadbeef\n0x20025a0f 0xdeadbeef\n0x20025a0e 0xdeadbeef\n0x20025a0d 0xdeadbeef\n0x20025a0c 0xdeadbeef\n0x20025a0b 0xdeadbeef\n0x20025a0a 0xdeadbeef\n0x20025a09 0xdeadbeef\n0x20025a08 0xdeadbeef\n0x20025a07 0xdeadbeef\n0x20025a06 0xdeadbeef\n[hardfault_log] -- 2000-01-01-00:01:16 END Fault Log --\n']] Name (multi id, message size in bytes) number of data points, total bytes actuator_controls_0 (0, 48) 95 4560 actuator_outputs (0, 76) 95 7220 actuator_outputs (1, 76) 96 7296 commander_state (0, 9) 95 855 control_state (0, 134) 95 12730 cpuload (0, 16) 10 160 ekf2_innovations (0, 140) 184 25760 ekf2_timestamps (0, 20) 2373 47460 estimator_status (0, 267) 48 12816 sensor_combined (0, 72) 2373 170856 sensor_preflight (0, 16) 184 2944 system_power (0, 17) 32 544 task_stack_info (0, 26) 20 520 vehicle_attitude (0, 36) 306 11016 vehicle_attitude_setpoint (0, 55) 306 16830 vehicle_land_detected (0, 15) 1 15 vehicle_local_position (0, 148) 95 14060 vehicle_rates_setpoint (0, 24) 306 7344 vehicle_status (0, 45) 43 1935 wind_estimate (0, 24) 95 2280 ================================================ FILE: test/sample_appended_multiple_messages.txt ================================================ 0:00:11 WARNING: [commander_tests] Not ready to fly: Sensors not set up correctly ================================================ FILE: test/sample_info.txt ================================================ Logging start time: 0:01:52, duration: 0:01:08 Dropouts: count: 4, total duration: 0.1 s, max: 62 ms, mean: 29 ms Info Messages: sys_name: PX4 time_ref_utc: 0 ver_hw: AUAV_X21 ver_sw: fd483321a5cf50ead91164356d15aa474643aa73 Name (multi id, message size in bytes) number of data points, total bytes actuator_controls_0 (0, 48) 3269 156912 actuator_outputs (0, 76) 1311 99636 commander_state (0, 9) 678 6102 control_state (0, 122) 3268 398696 cpuload (0, 16) 69 1104 ekf2_innovations (0, 140) 3271 457940 estimator_status (0, 309) 1311 405099 sensor_combined (0, 72) 17070 1229040 sensor_preflight (0, 16) 17072 273152 telemetry_status (0, 36) 70 2520 vehicle_attitude (0, 36) 6461 232596 vehicle_attitude_setpoint (0, 55) 3272 179960 vehicle_local_position (0, 123) 678 83394 vehicle_rates_setpoint (0, 24) 6448 154752 vehicle_status (0, 45) 294 13230 ================================================ FILE: test/sample_log_small_messages.txt ================================================ 0:00:22 INFO: [commander] Takeoff detected 0:00:23 INFO: [commander] Landing detected 0:00:25 INFO: [commander] Disarmed by landing ================================================ FILE: test/sample_logging_tagged_and_default_params_messages.txt ================================================ 0:00:00 INFO: [px4] Startup script returned successfully 0:00:00 INFO: logging: opening log file 2022-4-29/8_45_27.ulg 0:00:00 INFO: [logger] Start file log (type: full) 0:00:00 INFO: [logger] Opened full log file: ./log/2022-04-29/08_45_27.ulg ================================================ FILE: test/sample_messages.txt ================================================ 0:02:38 ERROR: [sensors] no barometer found on /dev/baro0 (2) 0:02:42 ERROR: [sensors] no barometer found on /dev/baro0 (2) 0:02:51 ERROR: [sensors] no barometer found on /dev/baro0 (2) 0:02:56 ERROR: [sensors] no barometer found on /dev/baro0 (2) ================================================ FILE: test/sample_px4_events_messages.txt ================================================ 475214:49:10 INFO: logging: opening log file 2024-3-18/14_49_10.ulg 475214:49:10 INFO: [px4] Startup script returned successfully 475214:49:10 INFO: [logger] Start file log (type: full) 475214:49:10 INFO: [logger] Opened full log file: ./log/2024-03-18/14_49_10.ulg 475214:49:10 INFO: [mavlink] partner IP: 127.0.0.1 475214:49:11 WARNING: [health_and_arming_checks] Preflight: GPS fix too low 475214:49:18 INFO: [tone_alarm] home set 475214:49:18 WARNING: [health_and_arming_checks] Preflight: GPS fix too low 475214:49:21 INFO: [commander] Ready for takeoff! 475214:49:25 INFO: Armed by internal command 475214:49:25 INFO: Using default takeoff altitude: 2.50 m 475214:49:25 INFO: [tone_alarm] arming warning 475214:49:27 INFO: Takeoff detected 475214:49:32 INFO: RTL: start return at 491 m (3 m above destination) 475214:49:32 INFO: RTL: land at destination 475214:49:38 INFO: Landing detected 475214:49:40 INFO: Disarmed by landing 475214:49:40 INFO: [tone_alarm] notify neutral ================================================ FILE: test/test_cli.py ================================================ ''' Test command line tools ''' import sys import os import inspect import unittest import tempfile from ddt import ddt, data from pyulog import ulog2csv, info, params, messages, extract_gps_dump try: from StringIO import StringIO except ImportError: from io import StringIO TEST_PATH = os.path.dirname(os.path.abspath( inspect.getfile(inspect.currentframe()))) @ddt class TestCommandLineTools(unittest.TestCase): """ Test command line tools. """ def run_against_file(self, expected_output_file, test): """ run a test and compare the output against an expected file """ saved_stdout = sys.stdout with open(expected_output_file, 'r', encoding='utf8') as file_handle: expected_output = file_handle.read().strip() output = None try: out = StringIO() sys.stdout = out test() output = out.getvalue().strip() assert output == expected_output finally: if output is not None: sys.stdout = saved_stdout print("Got output:") print(output) print("\nExpected output:") print(expected_output) @data('sample') def test_ulog2csv(self, test_case): """ Test that 'ulog2csv' runs without error. """ tmpdir = tempfile.gettempdir() print('writing files to ', tmpdir) ulog_file_name = os.path.join(TEST_PATH, test_case+'.ulg') included_messages = [] output=tmpdir delimiter=',' time_s = 0 time_e = 0 ulog2csv.convert_ulog2csv(ulog_file_name, included_messages, output, delimiter, time_s, time_e) @data('sample', 'sample_appended', 'sample_appended_multiple') def test_pyulog_info_cli(self, test_case): """ Test that the output of 'ulog_info' on sample logs match previously generated results. """ sys.argv = [ '', os.path.join(TEST_PATH, test_case+'.ulg'), '-v' ] self.run_against_file( os.path.join(TEST_PATH, test_case+'_info.txt'), info.main) @unittest.skip("no gps data in log file") def test_extract_gps_dump_cli(self): """ Test that the output of 'ulog_extract_gps_dump' on sample logs match previously generated results. """ sys.argv = [ '', os.path.join(TEST_PATH, 'sample.ulg') ] extract_gps_dump.main() @data('sample', 'sample_appended', 'sample_px4_events') def test_messages_cli(self, test_case): """ Test that the output of 'ulog_messages' on sample logs match previously generated results. """ sys.argv = [ '', os.path.join(TEST_PATH, test_case+'.ulg') ] self.run_against_file( os.path.join(TEST_PATH, test_case+'_messages.txt'), messages.main) @data('sample', 'sample_appended') def test_params_cli(self, test_case): """ Test that 'ulog_params' runs without error. """ sys.argv = [ '', os.path.join(TEST_PATH, test_case+'.ulg') ] params.main() # vim: set et fenc=utf-8 ft=python ff=unix sts=4 sw=4 ts=4 ================================================ FILE: test/test_db.py ================================================ ''' Test the DatabaseULog module. ''' import unittest import os import tempfile from unittest.mock import patch import numpy as np from ddt import ddt, data from pyulog import ULog from pyulog.db import DatabaseULog from pyulog.migrate_db import migrate_db TEST_PATH = os.path.dirname(os.path.abspath(__file__)) @ddt class TestDatabaseULog(unittest.TestCase): ''' Test that the DatabaseULog successfully reads a ULog, writes it to database and then is able to read from it without losing any data. ''' def setUp(self): ''' Set up the test database. ''' self.db_path = os.path.join(TEST_PATH, 'pyulog_test.sqlite3') self.db_handle = DatabaseULog.get_db_handle(self.db_path) migrate_db(self.db_path) def tearDown(self): ''' Remove the test database after use. ''' os.remove(self.db_path) @data('sample_log_small', 'sample_appended_multiple', 'sample_appended', 'sample', 'sample_logging_tagged_and_default_params') def test_parsing(self, test_case): ''' Verify that log files written and read from the database are identical to the original ulog file. ''' test_file = os.path.join(TEST_PATH, f'{test_case}.ulg') log_path = os.path.join(TEST_PATH, test_file) ulog = ULog(log_path) dbulog_saved = DatabaseULog(self.db_handle, log_file=test_file) dbulog_saved.save() primary_key = dbulog_saved.primary_key dbulog_loaded = DatabaseULog(self.db_handle, primary_key=primary_key, lazy=False) self.assertEqual(ulog, dbulog_loaded) def test_lazy(self): ''' Verify that when lazy loading is enabled (which is the default behaviour), then the datasets are only retrieved when get_dataset is explicitly called. ''' test_file = os.path.join(TEST_PATH, 'sample_log_small.ulg') log_path = os.path.join(TEST_PATH, test_file) ulog = ULog(log_path) dbulog_saved = DatabaseULog(self.db_handle, log_file=test_file) dbulog_saved.save() primary_key = dbulog_saved.primary_key dbulog_loaded = DatabaseULog(self.db_handle, primary_key=primary_key) for dataset in ulog.data_list: db_dataset = next(ds for ds in dbulog_loaded.data_list if ds.name == dataset.name and ds.multi_id == dataset.multi_id) self.assertEqual(len(db_dataset.data), 0) self.assertNotEqual(len(dataset.data), 0) ulog_dataset = ulog.get_dataset(dataset.name, multi_instance=dataset.multi_id) dbulog_dataset = dbulog_loaded.get_dataset(dataset.name, multi_instance=dataset.multi_id) self.assertEqual(ulog_dataset, dbulog_dataset) def test_data_caching(self): ''' Verify that the caching of dataset data works as expected. ''' test_file = os.path.join(TEST_PATH, 'sample_log_small.ulg') dbulog_saved = DatabaseULog(self.db_handle, log_file=test_file) dbulog_saved.save() primary_key = dbulog_saved.primary_key dbulog_loaded = DatabaseULog(self.db_handle, primary_key=primary_key, lazy=True) for dataset in dbulog_loaded.data_list: cache_miss = dbulog_loaded.get_dataset(dataset.name, multi_instance=dataset.multi_id, caching=True) cache_hit = dbulog_loaded.get_dataset(dataset.name, multi_instance=dataset.multi_id, caching=True) uncached = dbulog_loaded.get_dataset(dataset.name, multi_instance=dataset.multi_id, caching=False) self.assertEqual(cache_miss, cache_hit) self.assertEqual(cache_miss, uncached) self.assertIs(cache_miss, cache_hit) self.assertIsNot(cache_miss, uncached) def test_save(self): ''' Test that save() twice raises an error, since we currently do not support updating the database. ''' log_path = os.path.join(TEST_PATH, 'sample_log_small.ulg') dbulog = DatabaseULog(self.db_handle, log_file=log_path) dbulog.save() with self.assertRaises(KeyError): dbulog.save() def test_load(self): ''' Test that load() on an unknown primary key raises an error.''' with self.assertRaises(KeyError): _ = DatabaseULog(self.db_handle, primary_key=100) def test_unapplied_migrations(self): ''' Test that we get get an error when trying to initialize a DatabaseULog if there are unapplied migrations, i.e. the SCHEMA_VERSION of DatabaseULog is larger than user_version in the database. ''' migrate_db(self.db_path) log_file = os.path.join(TEST_PATH, 'sample_log_small.ulg') _ = DatabaseULog(self.db_handle, log_file=log_file) with self.assertRaises(ValueError): # Increment SCHEMA_VERSION so the database is seemingly out of date with patch.object(DatabaseULog, 'SCHEMA_VERSION', DatabaseULog.SCHEMA_VERSION+1): _ = DatabaseULog(self.db_handle, log_file=log_file) @data('sample_log_small') def test_sha256sum(self, test_case): ''' Verify that the sha256sum set on save can be used to find the same file again, using any of the approved file input methods. ''' test_file = os.path.join(TEST_PATH, f'{test_case}.ulg') dbulog = DatabaseULog(self.db_handle, log_file=test_file) dbulog.save() digest = DatabaseULog.calc_sha256sum(test_file) self.assertEqual(digest, dbulog.sha256sum) test_file_handle = open(test_file, 'rb') # pylint: disable=consider-using-with open_digest = DatabaseULog.calc_sha256sum(test_file_handle) self.assertEqual(digest, open_digest) test_file_handle.close() closed_digest = DatabaseULog.calc_sha256sum(test_file_handle) self.assertEqual(digest, closed_digest) pk_from_digest = DatabaseULog.primary_key_from_sha256sum(self.db_handle, digest) self.assertEqual(pk_from_digest, dbulog.primary_key) dbulog_duplicate = DatabaseULog(self.db_handle, log_file=test_file) with self.assertRaises(KeyError): dbulog_duplicate.save() def test_delete(self): ''' Verify that the delete method completely deletes the relevant ulog from the database, and nothing else, by looking at the size of the database before and after deleting. ''' def db_size(): ''' Get the size in bytes of the database file, after VACUUMING to make sure that deleted rows are cleaned up. ''' with self.db_handle() as con: con.execute('VACUUM') return os.path.getsize(self.db_path) # We pre-populate the database with a log to detect if delete() just # wipes everything test_file1 = os.path.join(TEST_PATH, 'sample.ulg') DatabaseULog(self.db_handle, log_file=test_file1).save() initial_size = db_size() test_file2 = os.path.join(TEST_PATH, 'sample_log_small.ulg') dbulog = DatabaseULog(self.db_handle, log_file=test_file2) dbulog.save() self.assertNotEqual(db_size(), initial_size) dbulog.delete() self.assertEqual(db_size(), initial_size) def test_json(self): ''' Verify that the storage of JSON rows allows for reproduction of the datasets. ''' test_file = os.path.join(TEST_PATH, 'sample_log_small.ulg') log_path = os.path.join(TEST_PATH, test_file) dbulog = DatabaseULog(self.db_handle, log_file=log_path) dbulog.save(append_json=True) with self.db_handle() as con: cur = con.cursor() for dataset in dbulog.data_list: for field_name, values in dataset.data.items(): cur.execute(''' SELECT j.key, j.value FROM ULogField uf, json_each(uf.ValueJson) j JOIN ULogDataset uds ON uf.DatasetId = uds.Id WHERE uds.DatasetName = ? AND uds.MultiId = ? AND uf.TopicName = ? AND uds.ULogId = ? ORDER BY j.key ASC ''', (dataset.name, dataset.multi_id, field_name, dbulog.primary_key)) results = np.array(cur.fetchall(), dtype=float) db_timestamps = results[:,0].flatten() db_values = results[:,1].flatten() # We must filter out None, nan and inf values since JSON # doesn't support nan and inf. db_values_finite = db_values[np.isfinite(db_values)] values_finite = values[np.isfinite(values)] # We test for approximate equality since we are comparing # string-formatted floats. self.assertEqual(len(db_values_finite), len(values_finite)) if len(db_values_finite) > 0: np.testing.assert_allclose(db_values_finite, values_finite) if field_name == 'timestamp': self.assertEqual(len(db_timestamps), len(values)) np.testing.assert_allclose(db_timestamps, values) cur.close() @data('sample', 'sample_appended', 'sample_appended_multiple', 'sample_logging_tagged_and_default_params') def test_write_ulog(self, base_name): ''' Test that the write_ulog method successfully replicates all relevant data. ''' with tempfile.TemporaryDirectory() as tmpdirname: ulog_file_name = os.path.join(TEST_PATH, base_name + '.ulg') written_ulog_file_name = os.path.join(tmpdirname, base_name + '_copy.ulg') dbulog = DatabaseULog(self.db_handle, log_file=ulog_file_name) dbulog.save() lazy_loaded_dbulog = DatabaseULog( self.db_handle, primary_key=dbulog.primary_key, lazy=True ) with self.assertRaises(ValueError): lazy_loaded_dbulog.write_ulog(written_ulog_file_name) loaded_dbulog = DatabaseULog(self.db_handle, primary_key=dbulog.primary_key, lazy=False) loaded_dbulog.write_ulog(written_ulog_file_name) copied = ULog(written_ulog_file_name) # Some fields are not copied but dropped, so we cheat by modifying the original loaded_dbulog._sync_seq_cnt = 0 # pylint: disable=protected-access loaded_dbulog._appended_offsets = [] # pylint: disable=protected-access loaded_dbulog._incompat_flags[0] &= 0xFE # pylint: disable=protected-access assert copied == loaded_dbulog ================================================ FILE: test/test_extract_message.py ================================================ ''' Test extract_message module ''' import os import inspect import unittest from ddt import ddt, data from pyulog.extract_message import extract_message TEST_PATH = os.path.dirname(os.path.abspath( inspect.getfile(inspect.currentframe()))) @ddt class TestExtractMessage(unittest.TestCase): """ Test extract_message module. """ @data('sample') def test_extract_message(self, test_case): """ Test that extract_message module runs without error. """ ulog_file_name = os.path.join(TEST_PATH, test_case+'.ulg') message = "actuator_controls_0" time_s = None time_e = None extract_message(ulog_file_name, message, time_s, time_e) # vim: set et fenc=utf-8 ft=python ff=unix sts=4 sw=4 ts=4 ================================================ FILE: test/test_migration.py ================================================ ''' Test that the migration module works correctly. ''' import unittest import os import re import sqlite3 import subprocess from unittest.mock import patch from ddt import ddt, data from pyulog.db import DatabaseULog from pyulog.migrate_db import migrate_db TEST_PATH = os.path.dirname(os.path.abspath(__file__)) @ddt class TestMigration(unittest.TestCase): ''' Using both fake and real migration files, try various migration sequences and check that the state of the database is as expected from the migrations that were run. ''' def setUp(self): ''' Set up the test database and fake migration script directory for each test. ''' self.db_path = os.path.join(TEST_PATH, 'test_pyulog.sqlite3') self.db_handle = DatabaseULog.get_db_handle(self.db_path) self.sql_dir = os.path.join(TEST_PATH, 'test_sql') os.mkdir(self.sql_dir) def tearDown(self): ''' Remove test database and fake migration script directory after each test. ''' for filename in os.listdir(self.sql_dir): assert re.match(r'pyulog\.\d\.sql', filename), 'Only removing migration files.' filepath = os.path.join(self.sql_dir, filename) os.remove(filepath) os.rmdir(self.sql_dir) if os.path.exists(self.db_path): os.remove(self.db_path) def _make_migration_file(self, sql): ''' Utility function for creating fake migration files. This is necessary because the migration tool reads from disk, so any fake migration scripts we want to test must be written to disk too, with correct file names. The files are cleaned up in tearDown. ''' current_migration_files = os.listdir(self.sql_dir) migration_index = len(current_migration_files) + 1 sql_filename = f'pyulog.{migration_index}.sql' sql_path = os.path.join(self.sql_dir, sql_filename) with open(sql_path, 'w', encoding='utf8') as migration_file: migration_file.write(sql) def _get_db_info(self): ''' Utility function for getting the current database version and column names. This is used to verify the state of the database after running various migration scripts. ''' with self.db_handle() as con: cur = con.cursor() cur.execute('PRAGMA table_info(TestTable)') table_info = cur.fetchall() cur.execute('PRAGMA user_version') (db_version,) = cur.fetchone() cur.close() return db_version, [column_info[1] for column_info in table_info] def test_good_migrations(self): ''' Test that two sequential migrations run successfully and sequentially. ''' self._make_migration_file('BEGIN; CREATE TABLE TestTable ( Id INTEGER ); COMMIT;') with patch.object(DatabaseULog, 'SCHEMA_VERSION', 1): migrate_db(self.db_path, sql_dir=self.sql_dir) db_version, col_names = self._get_db_info() self.assertEqual(col_names[0], 'Id') self.assertEqual(db_version, 1) self._make_migration_file('BEGIN; ALTER TABLE TestTable RENAME Id to IdRenamed; COMMIT;') with patch.object(DatabaseULog, 'SCHEMA_VERSION', 2): migrate_db(self.db_path, sql_dir=self.sql_dir) db_version, col_names = self._get_db_info() self.assertEqual(col_names[0], 'IdRenamed') self.assertEqual(db_version, 2) @data('CREATE TABLE TestTable;', 'BEGIN; CREATE TABLE TestTable;', 'CREATE TABLE TestTable; END;') def test_transactions(self, sql_line): ''' Verify that migration files are rejected if they don't enforce transactions correctly. ''' self._make_migration_file(sql_line) with self.assertRaises(ValueError), \ patch.object(DatabaseULog, 'SCHEMA_VERSION', 1): migrate_db(self.db_path, sql_dir=self.sql_dir) def test_bad_migrations(self): ''' Insert a bug into a line of a migration script, and verify that the script is rolled back. ''' self._make_migration_file(''' BEGIN; CREATE TABLE TestTable ( Id INTEGER, Value TEXT ); COMMIT; ''') self._make_migration_file(''' BEGIN; ALTER TABLE TestTable RENAME COLUMN Id TO IdRenamed; ALTER TABLE TestTable RENAME COLUMN IdRenamed TO IdRenamed2; ALTER TABLE TestTable RENAME COLUMN Value TO ValueRenamed; COMMIT; ''') with self.assertRaises(sqlite3.OperationalError), \ patch.object(DatabaseULog, 'SCHEMA_VERSION', 2): migrate_db(self.db_path, sql_dir=self.sql_dir) db_version, col_names = self._get_db_info() self.assertEqual(col_names[0], 'Id') # Also check the 'Value' column, since the renaming of that field would # not have been impacted by the buggy line. self.assertEqual(col_names[1], 'Value') self.assertEqual(db_version, 1) def test_existing_db(self): ''' Verify that the migration tool will not modify databases that are were created before the migration tool. Then verify that the -f flag correctly forces the execution anyway. ''' db_version, _ = self._get_db_info() # This function implicitly creates the database self.assertEqual(db_version, 0) with self.assertRaises(FileExistsError): migrate_db(self.db_path) migrate_db(self.db_path, force=True) db_version, _ = self._get_db_info() self.assertEqual(db_version, DatabaseULog.SCHEMA_VERSION) def test_missing_migration_file(self): ''' Verify that the migration tool stops after it encounters a non-existent migration file. ''' self._make_migration_file('BEGIN; CREATE TABLE TestTable ( Id INTEGER ); COMMIT;') with self.assertRaises(FileNotFoundError), \ patch.object(DatabaseULog, 'SCHEMA_VERSION', 2): migrate_db(self.db_path, sql_dir=self.sql_dir) db_version, col_names = self._get_db_info() self.assertEqual(col_names[0], 'Id') self.assertEqual(db_version, 1) def test_noop(self): ''' Verify that the noop flag removes any effect on the database. ''' migrate_db(self.db_path, noop=True) db_version, _ = self._get_db_info() self.assertEqual(db_version, 0) def test_real_migrations(self): ''' Verify that the migration scripts in the pyulog/sql directory execute successfully. ''' migrate_db(self.db_path) db_version, _ = self._get_db_info() self.assertEqual(db_version, DatabaseULog.SCHEMA_VERSION) def test_cli(self): ''' Verify that the command line tol ulog_migratedb completes the migrations successfully. ''' result = subprocess.run(['ulog_migratedb', '-d', self.db_path], check=True) self.assertEqual(result.returncode, 0) db_version, _ = self._get_db_info() self.assertEqual(db_version, DatabaseULog.SCHEMA_VERSION) ================================================ FILE: test/test_px4.py ================================================ ''' Tests the PX4ULog class ''' import os import inspect import unittest from ddt import ddt, data from pyulog import ULog from pyulog.px4 import PX4ULog from pyulog.db import DatabaseULog from pyulog.migrate_db import migrate_db TEST_PATH = os.path.dirname(os.path.abspath( inspect.getfile(inspect.currentframe()))) @ddt class TestPX4ULog(unittest.TestCase): ''' Tests the PX4ULog class ''' def setUp(self): ''' Set up the test database. ''' self.db_path = os.path.join(TEST_PATH, 'pyulog_test.sqlite3') self.db_handle = DatabaseULog.get_db_handle(self.db_path) migrate_db(self.db_path) def tearDown(self): ''' Remove the test database after use. ''' os.remove(self.db_path) @data('sample', 'sample_appended', 'sample_appended_multiple', 'sample_logging_tagged_and_default_params') def test_add_roll_pitch_yaw(self, base_name): ''' Test that add_roll_pitch_yaw correctly adds RPY values to 'vehicle_attitude' ''' ulog_file_name = os.path.join(TEST_PATH, base_name + '.ulg') ulog = ULog(ulog_file_name) px4 = PX4ULog(ulog) px4.add_roll_pitch_yaw() dataset = ulog.get_dataset('vehicle_attitude') assert 'roll' in dataset.data assert 'pitch' in dataset.data assert 'yaw' in dataset.data @data('sample', 'sample_appended', 'sample_appended_multiple', 'sample_logging_tagged_and_default_params') def test_add_roll_pitch_yaw_db(self, base_name): ''' Test that add_roll_pitch_yaw correctly adds RPY values to 'vehicle_attitude' on a DatabaseULog object. ''' ulog_file_name = os.path.join(TEST_PATH, base_name + '.ulg') dbulog = DatabaseULog(self.db_handle, log_file=ulog_file_name) dbulog.save() del dbulog digest = DatabaseULog.calc_sha256sum(ulog_file_name) primary_key = DatabaseULog.primary_key_from_sha256sum(self.db_handle, digest) dbulog = DatabaseULog(self.db_handle, primary_key=primary_key, lazy=False) px4 = PX4ULog(dbulog) px4.add_roll_pitch_yaw() dataset = dbulog.get_dataset('vehicle_attitude') assert 'roll' in dataset.data assert 'pitch' in dataset.data assert 'yaw' in dataset.data # vim: set et fenc=utf-8 ft=python ff=unix sts=4 sw=4 ts=4 ================================================ FILE: test/test_px4_events.py ================================================ """ Tests the PX4Events class """ import os import inspect import unittest from ddt import ddt, data import pyulog from pyulog.px4_events import PX4Events TEST_PATH = os.path.dirname(os.path.abspath( inspect.getfile(inspect.currentframe()))) @ddt class TestPX4Events(unittest.TestCase): """ Tests the PX4Events class """ @data('sample_px4_events') def test_px4_events(self, base_name): """ Test that the PX4 events are extracted. """ ulog_file_name = os.path.join(TEST_PATH, base_name + '.ulg') ulog = pyulog.ULog(ulog_file_name) px4_ulog = PX4Events() def default_json_definitions_cb(already_has_default_parser: bool): raise AssertionError('Must use definitions from logs') px4_ulog.set_default_json_definitions_cb(default_json_definitions_cb) messages = px4_ulog.get_logged_events(ulog) expected_messages = [ (1710773350346000, 'INFO', 'logging: opening log file 2024-3-18/14_49_10.ulg'), (1710773365282000, 'INFO', 'Armed by internal command'), (1710773365282000, 'INFO', 'Using default takeoff altitude: 2.50 m'), (1710773367094000, 'INFO', 'Takeoff detected'), (1710773372482000, 'INFO', 'RTL: start return at 491 m (3 m above destination)'), (1710773372694000, 'INFO', 'RTL: land at destination'), (1710773378482000, 'INFO', 'Landing detected'), (1710773380486000, 'INFO', 'Disarmed by landing') ] assert messages == expected_messages ================================================ FILE: test/test_ulog.py ================================================ ''' Tests the ULog class ''' import os import inspect import unittest import tempfile from io import BytesIO from ddt import ddt, data import pyulog TEST_PATH = os.path.dirname(os.path.abspath( inspect.getfile(inspect.currentframe()))) @ddt class TestULog(unittest.TestCase): ''' Tests the ULog class ''' @data('sample') def test_comparison(self, base_name): ''' Test that the custom comparison method works as expected. ''' ulog_file_name = os.path.join(TEST_PATH, base_name + '.ulg') ulog1 = pyulog.ULog(ulog_file_name) ulog2 = pyulog.ULog(ulog_file_name) assert ulog1 == ulog2 assert ulog1 is not ulog2 # make them different in arbitrary field ulog1.data_list[0].data['timestamp'][0] += 1 assert ulog1 != ulog2 @data('sample', 'sample_appended', 'sample_appended_multiple', 'sample_logging_tagged_and_default_params') def test_write_ulog(self, base_name): ''' Test that the write_ulog method successfully replicates all relevant data. ''' with tempfile.TemporaryDirectory() as tmpdirname: ulog_file_name = os.path.join(TEST_PATH, base_name + '.ulg') written_ulog_file_name = os.path.join(tmpdirname, base_name + '_copy.ulg') original = pyulog.ULog(ulog_file_name) original.write_ulog(written_ulog_file_name) copied = pyulog.ULog(written_ulog_file_name) # Some fields are not copied but dropped, so we cheat by modifying the original original._sync_seq_cnt = 0 # pylint: disable=protected-access original._appended_offsets = [] # pylint: disable=protected-access original._incompat_flags[0] &= 0xFE # pylint: disable=protected-access assert copied == original @data('sample') def test_write_ulog_memory(self, base_name): ''' Test that the write_ulog method can write bytes to memory. ''' ulog_file_name = os.path.join(TEST_PATH, base_name + '.ulg') original = pyulog.ULog(ulog_file_name) with BytesIO() as bytes_handle: original.write_ulog(bytes_handle) bytes_handle.seek(0) copied = pyulog.ULog(bytes_handle) for original_key, original_value in original.__dict__.items(): copied_value = getattr(copied, original_key) if original_key == '_sync_seq_cnt': # Sync messages are counted on parse, but otherwise dropped, so # we don't rewrite them assert copied_value == 0 elif original_key == '_appended_offsets': # Abruptly ended messages just before offsets are dropped, so # we don't rewrite appended offsets assert copied_value == [] elif original_key == '_incompat_flags': # Same reasoning on incompat_flags[0] as for '_appended_offsets' assert copied_value[0] == original_value[0] & 0xFE # pylint: disable=unsubscriptable-object assert copied_value[1:] == original_value[1:] # pylint: disable=unsubscriptable-object else: assert copied_value == original_value # vim: set et fenc=utf-8 ft=python ff=unix sts=4 sw=4 ts=4