├── .editorconfig ├── .github └── workflows │ └── lint.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── pyrtt-viewer ├── requirements.txt ├── setup.py └── tox.ini /.editorconfig: -------------------------------------------------------------------------------- 1 | # See https://EditorConfig.org for more information. 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | 10 | [*.py] 11 | # PEP8-recommended indent 12 | indent_size = 4 13 | # This should match configuration in setup.cfg 14 | max_line_length = 100 15 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ################################# 3 | ################################# 4 | ## Super Linter GitHub Actions ## 5 | ################################# 6 | ################################# 7 | name: Lint 8 | 9 | # 10 | # Documentation: 11 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions 12 | # 13 | 14 | ############################# 15 | # Start the job on all push # 16 | ############################# 17 | on: 18 | push: 19 | branches-ignore: [master, main] 20 | # Remove the line above to run when pushing to master 21 | pull_request: 22 | branches: [master, main] 23 | 24 | ############### 25 | # Set the Job # 26 | ############### 27 | jobs: 28 | build: 29 | # Name the Job 30 | name: Lint 31 | # Set the agent to run on 32 | runs-on: ubuntu-latest 33 | 34 | ################## 35 | # Load all steps # 36 | ################## 37 | steps: 38 | ########################## 39 | # Checkout the code base # 40 | ########################## 41 | - name: Checkout Code 42 | uses: actions/checkout@v2 43 | with: 44 | # Full git history is needed to get a proper list of changed files within `super-linter` 45 | fetch-depth: 0 46 | 47 | ################################ 48 | # Run Linter against code base # 49 | ################################ 50 | - name: Lint 51 | uses: github/super-linter/slim@v4 52 | env: 53 | VALIDATE_ALL_CODEBASE: false 54 | VALIDATE_PYTHON: true 55 | DEFAULT_BRANCH: master 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .tox/ 3 | dist/ 4 | *.egg-info/ 5 | *.pyc 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas Stenersen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyRTT Viewer 2 | 3 | [![Lint status](https://github.com/thomasstenersen/pyrtt-viewer/workflows/Lint/badge.svg)](https://github.com/thomasstenersen/pyrtt-viewer) 4 | 5 | This is a simple Python 3 script for simple RTT interaction with an nRF5x device. 6 | 7 | If you just want to use it directly, you can install using `pip`: 8 | 9 | pip install pyrtt-viewer 10 | 11 | Then you can run the script as a normal executable: 12 | 13 | pyrtt-viewer -h 14 | 15 | ## Usage 16 | 17 | pyrtt-viewer [-s ] [-c ] 18 | 19 | 20 | If you think you'd like to modify the script a bit, install it as a local package: 21 | 22 | pip install -e . 23 | 24 | ## Requirements 25 | 26 | - Python 3.x 27 | - `pynrfjprog` -- `pip install pynrfjprog` 28 | -------------------------------------------------------------------------------- /pyrtt-viewer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # MIT License 3 | # 4 | # Copyright (c) 2018 Thomas Stenersen 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import sys 25 | import time 26 | import threading 27 | import logging 28 | import argparse 29 | 30 | try: 31 | from pynrfjprog.API import API 32 | from pynrfjprog.API import DeviceFamily as NrfDeviceFamily 33 | except ImportError: 34 | print("Error: Could not find pynrfjprog.") 35 | print("Did you run `pip install pynrfjprog`?") 36 | sys.exit(1) 37 | 38 | 39 | def get_snr(nrf): 40 | devices = nrf.enum_emu_snr() 41 | if devices and len(devices) > 0: 42 | device_range = list(range(len(devices))) 43 | print("Connected devices:") 44 | print("".join(["%d: %d\n" % (i, devices[i]) for i in device_range])) 45 | 46 | number = None 47 | while number is None: 48 | try: 49 | number = input("Select a device number or quit (q): ") 50 | if number == "q": 51 | return None 52 | elif int(number) in device_range: 53 | return devices[int(number)] 54 | except ValueError: 55 | pass 56 | 57 | print("Invalid input \"%s\"" % (number)) 58 | number = None 59 | else: 60 | print("No devices connected.") 61 | 62 | 63 | def connect(snr=None, jlink_khz=50000): 64 | nrf = API(NrfDeviceFamily.NRF52) 65 | nrf.open() 66 | if not snr: 67 | snr = get_snr(nrf) 68 | 69 | if not snr: 70 | nrf.close() 71 | return None 72 | 73 | nrf.connect_to_emu_with_snr(snr, jlink_khz) 74 | try: 75 | _version = nrf.read_device_version() # noqa F81: unused variable 76 | except API.APIError as e: 77 | if e.err_code == API.NrfjprogdllErr.WRONG_FAMILY_FOR_DEVICE: 78 | nrf.close() 79 | nrf = API(NrfDeviceFamily.NRF51) 80 | nrf.open() 81 | if snr: 82 | nrf.connect_to_emu_with_snr(snr, jlink_khz) 83 | else: 84 | nrf.connect_to_emu_without_snr(jlink_khz) 85 | else: 86 | raise e 87 | return nrf 88 | 89 | 90 | def list_devices(): 91 | nrf = API(NrfDeviceFamily.NRF51) 92 | nrf.open() 93 | devices = nrf.enum_emu_snr() 94 | if devices: 95 | print("\n".join(list(map(str, devices)))) 96 | nrf.close() 97 | 98 | 99 | class RTT: 100 | """RTT commication class""" 101 | def __init__(self, nrf, args): 102 | self._args = args 103 | self._nrf = nrf 104 | self._close_event = None 105 | self._writer_thread = None 106 | self._reader_thread = None 107 | 108 | def _writer(self): 109 | while not self._close_event.is_set(): 110 | data = sys.stdin.readline().strip("\n") 111 | if len(data) > 0: 112 | self._nrf.rtt_write(self._args.channel, data) 113 | # Yield 114 | time.sleep(0.1) 115 | 116 | def _reader(self): 117 | BLOCK_SIZE = 512 118 | rtt_data = "" 119 | while not self._close_event.is_set(): 120 | rtt_data = self._nrf.rtt_read(self._args.channel, BLOCK_SIZE) 121 | 122 | if rtt_data == "" or type(rtt_data) == int: 123 | time.sleep(0.1) 124 | continue 125 | rtt_data = rtt_data.rstrip("\r\n") 126 | for s in rtt_data.splitlines(): 127 | if s.strip() == "": 128 | continue 129 | try: 130 | sys.stdout.buffer.write(bytes(s, "ascii")) 131 | except TypeError: 132 | continue 133 | 134 | sys.stdout.buffer.write(b'\n') 135 | sys.stdout.buffer.flush() 136 | 137 | def run(self): 138 | self._nrf.rtt_start() 139 | 140 | # Wait for RTT to find control block etc. 141 | time.sleep(0.5) 142 | while not self._nrf.rtt_is_control_block_found(): 143 | logging.info("Looking for RTT control block...") 144 | self._nrf.rtt_stop() 145 | time.sleep(0.5) 146 | self._nrf.rtt_start() 147 | time.sleep(0.5) 148 | 149 | self._close_event = threading.Event() 150 | self._close_event.clear() 151 | self._reader_thread = threading.Thread(target=self._reader) 152 | self._reader_thread.start() 153 | self._writer_thread = threading.Thread(target=self._writer) 154 | self._writer_thread.start() 155 | 156 | try: 157 | while self._reader_thread.is_alive() or \ 158 | self._writer_thread.is_alive(): 159 | time.sleep(0.1) 160 | except KeyboardInterrupt: 161 | self._close_event.set() 162 | self._reader_thread.join() 163 | self._writer_thread.join() 164 | 165 | 166 | def main(): 167 | parser = argparse.ArgumentParser("pyrtt-viewer") 168 | parser.add_argument("-s", "--segger-id", help="SEGGER ID of the nRF device", type=int) 169 | parser.add_argument("-c", "--channel", help="RTT channel", type=int, default=0) 170 | args = parser.parse_args() 171 | nrf = connect(args.segger_id) 172 | if not nrf: 173 | exit(1) 174 | 175 | rtt = RTT(nrf, args) 176 | try: 177 | rtt.run() 178 | except KeyboardInterrupt: 179 | print("\nExiting...") 180 | sys.exit(0) 181 | rtt.run() 182 | 183 | 184 | if __name__ == "__main__": 185 | main() 186 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pynrfjprog 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup( 5 | name="pyrtt-viewer", 6 | author="Thomas Stenersen", 7 | author_email="stenersen.thomas@gmail.com", 8 | url="https://github.com/thomasstenersen/pyrtt-viewer", 9 | version="0.1.1", 10 | description="A simple script for RTT I/O", 11 | install_requires=["pynrfjprog"], 12 | python_requires=">=3", 13 | scripts=["pyrtt-viewer"] 14 | ) 15 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py3 3 | 4 | [flake8] 5 | # Don't lint setup.py, the .tox virtualenv directory, or the build directory 6 | exclude = setup.py,.tox,build 7 | max-line-length = 100 8 | 9 | [testenv] 10 | deps = 11 | setuptools-scm 12 | flake8 13 | setenv = 14 | TOXTEMPDIR={envtmpdir} 15 | commands = 16 | python -m flake8 --config={toxinidir}/tox.ini pyrtt-viewer 17 | --------------------------------------------------------------------------------