├── .github └── workflows │ └── build-publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── Makefile ├── contributing.md ├── paper.bib ├── paper.md └── requirements.txt ├── freertos_visualizer ├── __init__.py └── visualize.py ├── pyproject.toml ├── requirements.txt └── tests └── test_serial.py /.github/workflows/build-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' # This will run the workflow only when you push a new tag, e.g., v1.0.0 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up Python 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: '3.x' 19 | 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install poetry twine 24 | 25 | - name: Build package 26 | run: | 27 | poetry install 28 | poetry build 29 | 30 | - name: Publish to PyPI 31 | env: 32 | TWINE_USERNAME: ${{secrets.PYPI_USERNAME}} 33 | TWINE_PASSWORD: ${{secrets.PYPI_PASSWORD}} 34 | run: | 35 | twine upload dist/* 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 hariharanragothaman 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 | # freeRTOS-visualizer 2 | Python Tool to visualize RTOS tasks in real-time 3 | 4 | ## Introduction 5 | A Python-based tool that provides real-time visualization of task states in a FreeRTOS environment. It connects to a running FreeRTOS instance (emulated via QEMU) and displays task states dynamically using an intuitive GUI. 6 | 7 | ## Features 8 | - **Real-Time Visualization:** Monitor task states as they change in real-time. 9 | - **Dynamic Bar Charts:** Visual representation of each task's current state. 10 | - **Data Export:** Export task state histories as CSV files. 11 | - **Cross-Platform Support:** Compatible with macOS, Linux, and Windows. 12 | - **Customizable Interface:** Easily modify the visualization parameters. 13 | 14 | ## Installation 15 | 16 | ### Prerequisites 17 | - `python3.x` 18 | - `pip3` 19 | 20 | ### Steps 21 | 1. **Clone the Repository:** 22 | ```bash 23 | git clone https://github.com/your-repo/freeRTOS-visualization-tool.git 24 | cd freeRTOS-visualization-tool 25 | ``` 26 | 27 | 2. **Install Dependencies:** 28 | ```bash 29 | pip install -r requirements.txt 30 | ``` 31 | 32 | ## Usage 33 | 34 | 1. **Start QEMU with Serial Redirection:** 35 | ```bash 36 | qemu-system-arm -M mps2-an385 -kernel RTOSDemo.axf -nographic -serial tcp::12345,server,nowait 37 | ``` 38 | 39 | 2. **Run the Visualization Tool:** 40 | ```bash 41 | python visualize.py 42 | ``` 43 | 44 | The GUI will launch, displaying the current states of tasks in your FreeRTOS environment. 45 | 46 | ## Contributing 47 | 48 | Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. 49 | 50 | ## License 51 | 52 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 53 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hariharanragothaman/freeRTOS-visualizer/7c3a4ffc88a64adfc18eae169ee883f850727cdd/docs/Makefile -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hariharanragothaman/freeRTOS-visualizer/7c3a4ffc88a64adfc18eae169ee883f850727cdd/docs/contributing.md -------------------------------------------------------------------------------- /docs/paper.bib: -------------------------------------------------------------------------------- 1 | @manual{freertos2020, 2 | author = {Richard Barry}, 3 | title = {FreeRTOS: Real-Time Operating System for Embedded Devices}, 4 | year = {2020}, 5 | url = {https://www.freertos.org/}, 6 | note = {Accessed: 2024-09-03} 7 | } 8 | 9 | @manual{qemu2024, 10 | title = {QEMU Documentation}, 11 | year = {2024}, 12 | url = {https://www.qemu.org/documentation/}, 13 | note = {Accessed: 2024-09-03} 14 | } 15 | 16 | @manual{pyqt5docs, 17 | title = {PyQt5 Documentation}, 18 | url = {https://www.riverbankcomputing.com/static/Docs/PyQt5/}, 19 | note = {Accessed: 2024-09-03} 20 | } 21 | 22 | @manual{matplotlibdocs, 23 | title = {Matplotlib Documentation}, 24 | url = {https://matplotlib.org/stable/contents.html}, 25 | note = {Accessed: 2024-09-03} 26 | } 27 | -------------------------------------------------------------------------------- /docs/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "`A Python-Based, Open-Source Visualization Tool for Real-Time Monitoring of FreeRTOS Task States" 3 | tags: 4 | - python 5 | - freeRTOS 6 | - embeddedSystems 7 | authors: 8 | - name: Hariharan Ragothaman 9 | orcid: 0009-0005-1164-384X 10 | affiliation: "1" 11 | affiliations: 12 | - name: athenahealth Inc 13 | index: 1 14 | date: 7 September 2024 15 | bibliography: paper.bib 16 | --- 17 | 18 | # Summary 19 | The Python-Based Visualization Tool for FreeRTOS Task State Monitoring offers a lightweight, accessible, and real-time interface for visualizing task states within a FreeRTOS environment. Unlike commercial solutions that are often complex or costly, this tool is open-source, cross-platform, and easy to integrate into existing projects. By establishing a live communication channel between FreeRTOS (emulated via QEMU) and a Python GUI, the tool provides developers with dynamic graphical representations of task states, aiding in debugging and performance optimization processes. 20 | 21 | 22 | # Statement of Need 23 | Effective monitoring of task states is crucial for debugging, performance optimization, and understanding system behavior in embedded systems. Existing tools for FreeRTOS task visualization, such as Tracealyzer, are powerful but often come with complexity and cost that may not be justified for all projects. This tool fills the gap by providing an open-source, real-time visualization solution that is easy to set up, cross-platform, and customizable. It caters to developers who need a practical and straightforward tool for real-time task monitoring without the overhead of more complex systems. 24 | 25 | 26 | # Key Features 27 | - **Real-Time Visualization**: Continuously monitors and displays task states as they change within the FreeRTOS environment. 28 | - **Dynamic Bar Charts**: Graphically represents each task's current state, updating in real-time for easy interpretation and analysis. 29 | - **Accessibility and Simplicity**: The tool is open-source, cross-platform (macOS, Linux, Windows), and can be set up quickly with minimal dependencies, making it accessible to a wide range of users. 30 | - **Customizable Interface**: Offers users the ability to modify visualization parameters, including color schemes, chart types, and data export options, to suit their specific needs. 31 | - **Open-Source and Community-Friendly**: Released under the MIT License, the tool encourages contributions, extensions, and collaboration from the community. 32 | - **Ease of Integration**: The tool is designed to integrate seamlessly into existing FreeRTOS projects, providing a lightweight yet effective solution for real-time task state monitoring. 33 | 34 | ## Usage and Installation Instructions 35 | To install the Python-Based Visualization Tool for FreeRTOS Task State Monitoring, clone the repository from GitHub and install the required dependencies using pip: 36 | 37 | ```bash 38 | git clone https://github.com/your-repo/freeRTOS-visualization-tool.git 39 | cd freeRTOS-visualization-tool 40 | pip install -r requirements.txt 41 | ``` 42 | Once installed, use the following command to start QEMU with serial redirection and run the visualization tool: 43 | 44 | ```bash 45 | qemu-system-arm -M mps2-an385 -kernel RTOSDemo.axf -nographic -serial tcp::12345,server,nowait 46 | python visualize.py 47 | ``` 48 | 49 | 50 | # Acknowledgements 51 | This project was inspired by the need for accessible, real-time task visualization tools in embedded systems development. Special thanks to the FreeRTOS community, QEMU developers, and contributors to the Python, PyQt5, and Matplotlib libraries. Their ongoing efforts in providing robust tools and libraries have greatly facilitated the development of this visualization tool. 52 | 53 | 54 | # References 55 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hariharanragothaman/freeRTOS-visualizer/7c3a4ffc88a64adfc18eae169ee883f850727cdd/docs/requirements.txt -------------------------------------------------------------------------------- /freertos_visualizer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hariharanragothaman/freeRTOS-visualizer/7c3a4ffc88a64adfc18eae169ee883f850727cdd/freertos_visualizer/__init__.py -------------------------------------------------------------------------------- /freertos_visualizer/visualize.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | import serial 4 | from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget 5 | from PyQt5.QtCore import QTimer 6 | from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 7 | from matplotlib.figure import Figure 8 | 9 | # Task state dictionary 10 | state_dict = { 11 | '0': 'Running', 12 | '1': 'Ready', 13 | '2': 'Blocked', 14 | '3': 'Suspended' 15 | } 16 | 17 | 18 | class MplCanvas(FigureCanvas): 19 | def __init__(self, parent=None, width=5, height=4, dpi=100): 20 | fig = Figure(figsize=(width, height), dpi=dpi) 21 | self.axes = fig.add_subplot(111) 22 | super(MplCanvas, self).__init__(fig) 23 | 24 | 25 | class TaskVisualization(QMainWindow): 26 | def __init__(self, serial_port): 27 | super().__init__() 28 | self.serial_port = serial_port 29 | self.task_states = {} 30 | self.current_time = 0 31 | 32 | self.initUI() 33 | 34 | def initUI(self): 35 | self.setWindowTitle("FreeRTOS Task State Visualization") 36 | self.setGeometry(100, 100, 800, 600) 37 | 38 | # Set up layout 39 | layout = QVBoxLayout() 40 | 41 | # Initialize Matplotlib Canvas 42 | self.canvas = MplCanvas(self, width=5, height=4, dpi=100) 43 | layout.addWidget(self.canvas) 44 | 45 | # Set up widget 46 | container = QWidget() 47 | container.setLayout(layout) 48 | self.setCentralWidget(container) 49 | 50 | # Timer to update task states 51 | self.timer = QTimer(self) 52 | self.timer.timeout.connect(self.update_task_states) 53 | self.timer.start(1000) # Update every second 54 | 55 | def update_task_states(self): 56 | try: 57 | # Read a line from the serial port 58 | line = self.serial_port.readline().decode('utf-8').strip() 59 | if line: 60 | # Parse the line using regex 61 | match = re.search(r"Task:(\S+),State:(\d+)", line) 62 | if match: 63 | task_name = match.group(1) 64 | task_state = state_dict.get(match.group(2), 'Unknown') 65 | 66 | # Initialize task history if new 67 | if task_name not in self.task_states: 68 | self.task_states[task_name] = [] 69 | 70 | # Append current state 71 | self.task_states[task_name].append(task_state) 72 | 73 | except Exception as e: 74 | print(f"Error reading serial data: {e}") 75 | 76 | # Update Matplotlib plot 77 | self.plot_task_states() 78 | 79 | def plot_task_states(self): 80 | self.canvas.axes.clear() 81 | 82 | # Prepare data 83 | tasks = list(self.task_states.keys()) 84 | states = [self.task_states[task][-1] for task in tasks] 85 | state_values = [list(state_dict.values()).index(state) + 1 for state in 86 | states] # Assign numerical values for plotting 87 | 88 | # Create bar chart 89 | bars = self.canvas.axes.bar(tasks, state_values, color='skyblue') 90 | 91 | # Set labels and title 92 | self.canvas.axes.set_ylim(0, len(state_dict) + 1) 93 | self.canvas.axes.set_ylabel('Task State') 94 | self.canvas.axes.set_title('Current Task States') 95 | 96 | # Set y-ticks to state names 97 | self.canvas.axes.set_yticks(range(1, len(state_dict) + 1)) 98 | self.canvas.axes.set_yticklabels(state_dict.values()) 99 | 100 | # Add text labels on bars 101 | for bar, state in zip(bars, states): 102 | height = bar.get_height() 103 | self.canvas.axes.text(bar.get_x() + bar.get_width() / 2., height + 0.1, state, ha='center', va='bottom') 104 | 105 | self.canvas.draw() 106 | 107 | 108 | def main(): 109 | # Connect to QEMU's serial port over TCP 110 | try: 111 | ser = serial.serial_for_url('socket://localhost:12345', baudrate=115200, timeout=1) 112 | except Exception as e: 113 | print(f"Failed to connect to serial port: {e}") 114 | sys.exit(1) 115 | 116 | app = QApplication(sys.argv) 117 | vis = TaskVisualization(ser) 118 | vis.show() 119 | sys.exit(app.exec_()) 120 | 121 | 122 | if __name__ == "__main__": 123 | main() 124 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.poetry] 6 | name = "freertos-visualizer" # Keep the package name with dashes, but not for the package directory 7 | version = "0.1.4" 8 | description = "A Python tool for real-time FreeRTOS task state visualization" 9 | authors = ["Hariharan Ragothaman "] 10 | license = "MIT" 11 | readme = "README.md" 12 | homepage = "https://github.com/hariharanragothaman/freeRTOS-visualizer" 13 | repository = "https://github.com/hariharanragothaman/freeRTOS-visualizer" 14 | keywords = ["FreeRTOS", "visualizer", "real-time", "Python"] 15 | classifiers = [ 16 | "Programming Language :: Python :: 3", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | ] 20 | 21 | [tool.poetry.dependencies] 22 | python = "^3.9" 23 | pyserial = "^3.5" 24 | PyQt5 = "^5.15" 25 | matplotlib = "^3.4" 26 | 27 | [tool.poetry.scripts] 28 | rtos-visualize = "freertos_visualizer.visualize:main" # Updated to match the correct module 29 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyserial 2 | PyQt5 3 | matplotlib 4 | -------------------------------------------------------------------------------- /tests/test_serial.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from unittest.mock import MagicMock 3 | 4 | import re 5 | from visualize import parse_serial_line, state_dict 6 | 7 | 8 | def parse_serial_line(line): 9 | match = re.search(r"Task:(\S+),State:(\d+)", line) 10 | if match: 11 | task_name = match.group(1) 12 | task_state = state_dict.get(match.group(2), 'Unknown') 13 | return task_name, task_state 14 | return None 15 | 16 | 17 | def test_parse_serial_line(): 18 | line = "Task:Task1,State:0" 19 | task_name, task_state = parse_serial_line(line) 20 | assert task_name == "Task1" 21 | assert task_state == "Running" 22 | 23 | line_invalid = "Invalid Line" 24 | result = parse_serial_line(line_invalid) 25 | assert result is None 26 | --------------------------------------------------------------------------------