├── tests ├── __init__.py └── test_read_write.py ├── examples ├── __init__.py ├── file_read_write.py ├── arduino_serial.py ├── arduino_threads.py ├── bluetooth_example.py └── socket_example.py ├── .coverage ├── docs ├── api.rst ├── utils.rst ├── install.rst ├── _static │ └── css │ │ └── custom_theme.css ├── Makefile ├── make.bat ├── index.rst ├── examples.rst └── conf.py ├── .gitignore ├── robust_serial ├── __init__.py ├── utils.py ├── robust_serial.py └── threads.py ├── LICENSE ├── pyproject.toml ├── Makefile ├── .github └── workflows │ └── ci.yml ├── setup.py └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/araffin/python-arduino-serial/HEAD/.coverage -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | 4 | Available Functions 5 | =================== 6 | 7 | .. automodule:: robust_serial.robust_serial 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/utils.rst: -------------------------------------------------------------------------------- 1 | .. _utils: 2 | 3 | Utils 4 | ===== 5 | 6 | .. automodule:: robust_serial.utils 7 | :members: 8 | 9 | 10 | Threads 11 | ======= 12 | 13 | .. automodule:: robust_serial.threads 14 | :members: 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | __pycache__/ 3 | .cache/ 4 | *.x 5 | *.pyc 6 | _build/ 7 | .pytest_cache/ 8 | 9 | # Setuptools distribution folder. 10 | dist/ 11 | 12 | # Python egg metadata, regenerated from source files by setuptools. 13 | *.egg-info 14 | dist/ 15 | build/ 16 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Using pip: 5 | 6 | .. code-block:: bash 7 | 8 | pip install robust_serial 9 | 10 | From Source: 11 | 12 | .. code-block:: bash 13 | 14 | git clone https://github.com/araffin/python-arduino-serial.git 15 | pip install -e . 16 | -------------------------------------------------------------------------------- /docs/_static/css/custom_theme.css: -------------------------------------------------------------------------------- 1 | /* Header fonts y */ 2 | h1, h2, .rst-content .toctree-wrapper p.caption, h3, h4, h5, h6, legend, p.caption { 3 | font-family: "Lato","proxima-nova","Helvetica Neue",Arial,sans-serif; 4 | } 5 | 6 | 7 | /* Make code blocks have a background */ 8 | .codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight'] { 9 | background: #f8f8f8;; 10 | } 11 | -------------------------------------------------------------------------------- /robust_serial/__init__.py: -------------------------------------------------------------------------------- 1 | from robust_serial.robust_serial import ( 2 | Order, 3 | decode_order, 4 | read_i8, 5 | read_i16, 6 | read_i32, 7 | read_order, 8 | write_i8, 9 | write_i16, 10 | write_i32, 11 | write_order, 12 | ) 13 | 14 | __version__ = "0.2" 15 | 16 | __all__ = [ 17 | "Order", 18 | "read_order", 19 | "read_i8", 20 | "read_i16", 21 | "read_i32", 22 | "write_i8", 23 | "write_order", 24 | "write_i16", 25 | "write_i32", 26 | "decode_order", 27 | ] 28 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = RobustArduinoSerialProtocol 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /examples/file_read_write.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | from robust_serial import Order, read_i16, read_i32, read_order, write_i8, write_i16, write_i32, write_order 5 | 6 | if __name__ == "__main__": 7 | parser = argparse.ArgumentParser(description="Reading / Writing a file") 8 | parser.add_argument("-f", "--test_file", help="Test file name", default="test.txt", type=str) 9 | args = parser.parse_args() 10 | 11 | with open(args.test_file, "wb") as f: 12 | write_order(f, Order.HELLO) 13 | 14 | write_i8(f, Order.MOTOR.value) 15 | write_i16(f, -56) 16 | write_i32(f, 131072) 17 | 18 | with open(args.test_file, "rb") as f: 19 | # Equivalent to Order(read_i8(f)) 20 | order = read_order(f) 21 | print(order) 22 | 23 | motor_order = read_order(f) 24 | print(motor_order) 25 | print(read_i16(f)) 26 | print(read_i32(f)) 27 | 28 | os.remove(args.test_file) 29 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=RobustArduinoSerialProtocol 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Antonin RAFFIN 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 | -------------------------------------------------------------------------------- /examples/arduino_serial.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from robust_serial import Order, read_order, write_i8, write_i16, write_order 4 | from robust_serial.utils import open_serial_port 5 | 6 | if __name__ == "__main__": 7 | try: 8 | serial_file = open_serial_port(baudrate=115200, timeout=None) 9 | except Exception as e: 10 | raise e 11 | 12 | is_connected = False 13 | # Initialize communication with Arduino 14 | while not is_connected: 15 | print("Waiting for arduino...") 16 | write_order(serial_file, Order.HELLO) 17 | bytes_array = bytearray(serial_file.read(1)) 18 | if not bytes_array: 19 | time.sleep(2) 20 | continue 21 | byte = bytes_array[0] 22 | if byte in [Order.HELLO.value, Order.ALREADY_CONNECTED.value]: 23 | is_connected = True 24 | 25 | print("Connected to Arduino") 26 | 27 | motor_speed = -56 28 | 29 | # Equivalent to write_i8(serial_file, Order.MOTOR.value) 30 | write_order(serial_file, Order.MOTOR) 31 | write_i8(serial_file, motor_speed) 32 | 33 | write_order(serial_file, Order.SERVO) 34 | write_i16(serial_file, 120) 35 | 36 | for _ in range(10): 37 | order = read_order(serial_file) 38 | print("Ordered received: {:?}", order) 39 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | # Same as Black. 3 | line-length = 127 4 | # Assume Python 3.8 5 | target-version = "py38" 6 | # See https://beta.ruff.rs/docs/rules/ 7 | select = ["E", "F", "B", "UP", "C90", "RUF"] 8 | # Ignore explicit stacklevel` 9 | ignore = ["B028"] 10 | 11 | 12 | [tool.ruff.mccabe] 13 | # Unlike Flake8, default to a complexity level of 10. 14 | max-complexity = 15 15 | 16 | [tool.black] 17 | line-length = 127 18 | 19 | [tool.isort] 20 | profile = "black" 21 | line_length = 127 22 | src_paths = ["robust_serial"] 23 | 24 | [tool.pytype] 25 | inputs = ["robust_serial"] 26 | disable = ["pyi-error"] 27 | 28 | [tool.mypy] 29 | ignore_missing_imports = true 30 | follow_imports = "silent" 31 | show_error_codes = true 32 | exclude = """(?x)( 33 | stable_baselines3/a2c/a2c.py$ 34 | )""" 35 | 36 | [tool.pytest.ini_options] 37 | # Deterministic ordering for tests; useful for pytest-xdist. 38 | env = [ 39 | "PYTHONHASHSEED=0" 40 | ] 41 | 42 | filterwarnings = [ 43 | ] 44 | 45 | 46 | [tool.coverage.run] 47 | disable_warnings = ["couldnt-parse"] 48 | branch = false 49 | omit = [ 50 | "tests/*", 51 | "setup.py", 52 | # Require device 53 | "examples/*", 54 | ] 55 | 56 | [tool.coverage.report] 57 | exclude_lines = [ "pragma: no cover", "raise NotImplementedError()", "if typing.TYPE_CHECKING:"] 58 | -------------------------------------------------------------------------------- /tests/test_read_write.py: -------------------------------------------------------------------------------- 1 | from tempfile import TemporaryFile 2 | 3 | from robust_serial import Order, read_i8, read_i16, read_i32, read_order, write_i8, write_i16, write_i32, write_order 4 | 5 | 6 | def assert_eq(left, right): 7 | assert left == right, f"{left} != {right}" 8 | 9 | 10 | def test_read_write_orders(): 11 | motor_speed = -57 12 | servo_angle = 512 # 2^9 13 | big_number = -32768 # -2^15 14 | 15 | f = TemporaryFile() 16 | 17 | # Equivalent to write_i8(f, Order.MOTOR.value) 18 | write_order(f, Order.MOTOR) 19 | write_i8(f, motor_speed) 20 | 21 | write_order(f, Order.SERVO) 22 | write_i16(f, servo_angle) 23 | 24 | write_order(f, Order.ERROR) 25 | write_i32(f, big_number) 26 | 27 | f.seek(0, 0) 28 | # Equivalent to Order(read_i8(f)) 29 | read_1st_order = read_order(f) 30 | read_motor_speed = read_i8(f) 31 | 32 | read_2nd_order = read_order(f) 33 | read_servo_angle = read_i16(f) 34 | 35 | read_3rd_order = read_order(f) 36 | read_big_number = read_i32(f) 37 | 38 | assert_eq(read_1st_order, Order.MOTOR) 39 | assert_eq(read_motor_speed, motor_speed) 40 | 41 | assert_eq(read_2nd_order, Order.SERVO) 42 | assert_eq(read_servo_angle, servo_angle) 43 | 44 | assert_eq(read_3rd_order, Order.ERROR) 45 | assert_eq(read_big_number, big_number) 46 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash 2 | LINT_PATHS=robust_serial/ tests/ docs/conf.py setup.py examples/ 3 | 4 | pytest: 5 | python3 -m pytest --cov-config .coveragerc --cov-report html --cov-report term --cov=. -v --color=yes 6 | 7 | pytype: 8 | pytype -j auto 9 | 10 | mypy: 11 | mypy ${LINT_PATHS} 12 | 13 | #type: pytype mypy 14 | type: mypy 15 | 16 | 17 | lint: 18 | # stop the build if there are Python syntax errors or undefined names 19 | # see https://www.flake8rules.com/ 20 | ruff ${LINT_PATHS} --select=E9,F63,F7,F82 --show-source 21 | # exit-zero treats all errors as warnings. 22 | ruff ${LINT_PATHS} --exit-zero 23 | 24 | format: 25 | # Sort imports 26 | isort ${LINT_PATHS} 27 | # Reformat using black 28 | black ${LINT_PATHS} 29 | 30 | check-codestyle: 31 | # Sort imports 32 | isort --check ${LINT_PATHS} 33 | # Reformat using black 34 | black --check ${LINT_PATHS} 35 | 36 | commit-checks: format type lint 37 | 38 | doc: 39 | cd docs && make html 40 | 41 | spelling: 42 | cd docs && make spelling 43 | 44 | clean: 45 | cd docs && make clean 46 | 47 | # PyPi package release 48 | release: 49 | python setup.py sdist 50 | python setup.py bdist_wheel 51 | twine upload dist/* 52 | 53 | # Test PyPi package release 54 | test-release: 55 | python setup.py sdist 56 | python setup.py bdist_wheel 57 | twine upload --repository-url https://test.pypi.org/legacy/ dist/* 58 | 59 | .PHONY: clean spelling doc lint format check-codestyle commit-checks 60 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | env: 15 | TERM: xterm-256color 16 | FORCE_COLOR: 1 17 | 18 | # Skip CI if [ci skip] in the commit message 19 | if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" 20 | runs-on: ubuntu-latest 21 | strategy: 22 | matrix: 23 | python-version: ["3.8", "3.9", "3.10"] 24 | 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Set up Python ${{ matrix.python-version }} 28 | uses: actions/setup-python@v4 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | - name: Install dependencies 32 | run: | 33 | python -m pip install --upgrade pip 34 | pip install .[tests,docs] 35 | - name: Lint with ruff 36 | run: | 37 | make lint 38 | - name: Build the doc 39 | run: | 40 | make doc 41 | - name: Check codestyle 42 | run: | 43 | make check-codestyle 44 | - name: Type check 45 | run: | 46 | make type 47 | - name: Test with pytest 48 | run: | 49 | make pytest 50 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | long_description = """ 4 | Robust Arduino Serial is a simple and robust serial communication protocol. 5 | It was designed to make two arduinos communicate, 6 | but can also be useful when you want a computer (e.g. a Raspberry Pi) to communicate with an Arduino. 7 | https://medium.com/@araffin/simple-and-robust-computer-arduino-serial-communication-f91b95596788 8 | """ 9 | 10 | setup( 11 | name="robust_serial", 12 | packages=[package for package in find_packages() if package.startswith("robust_serial")], 13 | install_requires=[ 14 | "pyserial", 15 | ], 16 | extras_require={ 17 | "tests": ["pytest", "pytest-cov", "mypy", "ruff", "black", "isort"], 18 | "docs": ["sphinx", "sphinx_rtd_theme", "sphinx-autodoc-typehints"], 19 | }, 20 | author="Antonin RAFFIN", 21 | author_email="antonin.raffin@ensta.org", 22 | url="https://github.com/araffin/arduino-robust-serial", 23 | description="Simple and Robust Serial Communication Protocol", 24 | long_description=long_description, 25 | keywords="serial hardware arduino RS232 communication protocol raspberry", 26 | license="MIT", 27 | version="0.2", 28 | python_requires=">=3.8", 29 | classifiers=[ 30 | "Programming Language :: Python :: 3", 31 | "Programming Language :: Python :: 3.8", 32 | "Programming Language :: Python :: 3.9", 33 | "Programming Language :: Python :: 3.10", 34 | ], 35 | zip_safe=False, 36 | ) 37 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Robust Arduino Serial Protocol documentation master file, created by 2 | sphinx-quickstart on Sun Sep 2 15:21:19 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Robust Arduino Serial Protocol's documentation! 7 | ========================================================== 8 | 9 | **Robust Arduino Serial** is a simple and robust serial communication 10 | protocol. It was designed to make two arduinos communicate, but can also 11 | be useful when you want a computer (e.g. a Raspberry Pi) to communicate 12 | with an Arduino. 13 | 14 | It supports both Python 3.8+. 15 | 16 | This repository is part of the Robust Arduino Serial project, main 17 | repository: `https://github.com/araffin/arduino-robust-serial`_ 18 | 19 | .. warning:: 20 | 21 | Please read the `Medium Article`_ to have an overview of this 22 | protocol. 23 | 24 | Implementations are available in various programming languages: 25 | 26 | - `Arduino`_ 27 | - `Python`_ 28 | - `C++`_ 29 | - `Rust`_ 30 | 31 | .. _`https://github.com/araffin/arduino-robust-serial`: https://github.com/araffin/arduino-robust-serial 32 | .. _Medium Article: https://medium.com/@araffin/simple-and-robust-computer-arduino-serial-communication-f91b95596788 33 | .. _Arduino: https://github.com/araffin/arduino-robust-serial 34 | .. _Python: https://github.com/araffin/python-arduino-serial 35 | .. _C++: https://github.com/araffin/cpp-arduino-serial 36 | .. _Rust: https://github.com/araffin/rust-arduino-serial 37 | 38 | .. toctree:: 39 | :maxdepth: 1 40 | :caption: User Guide 41 | 42 | install 43 | 44 | 45 | .. toctree:: 46 | :maxdepth: 2 47 | :caption: Reference 48 | 49 | api 50 | utils 51 | examples 52 | 53 | 54 | 55 | Indices and tables 56 | ================== 57 | 58 | * :ref:`genindex` 59 | * :ref:`modindex` 60 | * :ref:`search` 61 | -------------------------------------------------------------------------------- /examples/arduino_threads.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | from robust_serial import Order, write_order 5 | from robust_serial.threads import CommandThread, ListenerThread 6 | from robust_serial.utils import CustomQueue, open_serial_port 7 | 8 | 9 | def reset_command_queue(): 10 | """ 11 | Clear the command queue 12 | """ 13 | command_queue.clear() 14 | 15 | 16 | if __name__ == "__main__": 17 | try: 18 | serial_file = open_serial_port(baudrate=115200) 19 | except Exception as e: 20 | raise e 21 | 22 | is_connected = False 23 | # Initialize communication with Arduino 24 | while not is_connected: 25 | print("Waiting for arduino...") 26 | write_order(serial_file, Order.HELLO) 27 | bytes_array = bytearray(serial_file.read(1)) 28 | if not bytes_array: 29 | time.sleep(2) 30 | continue 31 | byte = bytes_array[0] 32 | if byte in [Order.HELLO.value, Order.ALREADY_CONNECTED.value]: 33 | is_connected = True 34 | 35 | print("Connected to Arduino") 36 | 37 | # Create Command queue for sending orders 38 | command_queue = CustomQueue(2) 39 | # Number of messages we can send to the Arduino without receiving an acknowledgment 40 | n_messages_allowed = 3 41 | n_received_semaphore = threading.Semaphore(n_messages_allowed) 42 | # Lock for accessing serial file (to avoid reading and writing at the same time) 43 | serial_lock = threading.Lock() 44 | 45 | # Event to notify threads that they should terminate 46 | exit_event = threading.Event() 47 | 48 | print("Starting Communication Threads") 49 | # Threads for arduino communication 50 | threads = [ 51 | CommandThread(serial_file, command_queue, exit_event, n_received_semaphore, serial_lock), 52 | ListenerThread(serial_file, exit_event, n_received_semaphore, serial_lock), 53 | ] 54 | for t in threads: 55 | t.start() 56 | 57 | # Send 3 orders to the arduino 58 | command_queue.put((Order.MOTOR, -56)) 59 | command_queue.put((Order.SERVO, 120)) 60 | time.sleep(2) 61 | # 62 | command_queue.put((Order.MOTOR, 0)) 63 | 64 | # End the threads 65 | exit_event.set() 66 | n_received_semaphore.release() 67 | 68 | print("Exiting...") 69 | 70 | for t in threads: 71 | t.join() 72 | -------------------------------------------------------------------------------- /examples/bluetooth_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import bluetooth 4 | 5 | from robust_serial import read_i8, read_i32, write_i8, write_i32 6 | 7 | CHANNEL = 1 8 | # show mac address: hciconfig 9 | SERVER_ADDR = "B8:27:EB:F1:E4:5F" 10 | 11 | 12 | def receive_messages(): 13 | """ 14 | Receive messages (server side) 15 | """ 16 | server_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) 17 | server_sock.bind(("", CHANNEL)) 18 | print("Waiting for client...") 19 | # Wait for client 20 | server_sock.listen(1) 21 | 22 | client_sock, client_address = server_sock.accept() 23 | print(f"Accepted connection from {client_address}") 24 | # Rename function to work with the lib 25 | client_sock.read = client_sock.recv 26 | 27 | for _ in range(10): 28 | print(f"Received (i8): {read_i8(client_sock)}") 29 | big_number = read_i32(client_sock) 30 | 31 | print(f"Received (i32): {big_number}") 32 | 33 | client_sock.close() 34 | server_sock.close() 35 | 36 | 37 | def send_messages(mac_address): 38 | """ 39 | Send messages (client side) 40 | :param mac_address: (str) 41 | """ 42 | socket = bluetooth.BluetoothSocket(bluetooth.RFCOMM) 43 | socket.connect((mac_address, CHANNEL)) 44 | 45 | print(f"Connected to {mac_address}") 46 | # Rename function to work with the lib 47 | socket.write = socket.send 48 | for i in range(10): 49 | write_i8(socket, i) 50 | write_i32(socket, 32768) 51 | socket.close() 52 | 53 | 54 | def discover_devices(): 55 | """ 56 | Print nearby bluetooth devices 57 | """ 58 | nearby_devices = bluetooth.discover_devices() 59 | for bdaddr in nearby_devices: 60 | print(f"{bluetooth.lookup_name(bdaddr)} + [{bdaddr}]") 61 | 62 | 63 | if __name__ == "__main__": 64 | parser = argparse.ArgumentParser(description="Test bluetooth server/client connection") 65 | arg_group = parser.add_mutually_exclusive_group(required=True) 66 | arg_group.add_argument("-s", "--server", dest="server", action="store_true", default=False, help="Create a server") 67 | arg_group.add_argument("-c", "--client", dest="client", action="store_true", default=False, help="Create a client") 68 | args = parser.parse_args() 69 | if args.server: 70 | receive_messages() 71 | else: 72 | send_messages(SERVER_ADDR) 73 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | Examples 4 | ======== 5 | 6 | Examples provided here are also in the ``examples/`` folder of the repo. 7 | 8 | Arduino Serial Communication 9 | ---------------------------- 10 | 11 | Serial communication with an Arduino: `Arduino Source Code`_ 12 | 13 | .. _Arduino Source Code: https://github.com/araffin/arduino-robust-serial/tree/master/arduino-board/ 14 | 15 | 16 | .. code-block:: python 17 | 18 | from __future__ import print_function, division, absolute_import 19 | 20 | import time 21 | 22 | from robust_serial import write_order, Order, write_i8, write_i16, read_i8, read_order 23 | from robust_serial.utils import open_serial_port 24 | 25 | 26 | try: 27 | serial_file = open_serial_port(baudrate=115200, timeout=None) 28 | except Exception as e: 29 | raise e 30 | 31 | is_connected = False 32 | # Initialize communication with Arduino 33 | while not is_connected: 34 | print("Waiting for arduino...") 35 | write_order(serial_file, Order.HELLO) 36 | bytes_array = bytearray(serial_file.read(1)) 37 | if not bytes_array: 38 | time.sleep(2) 39 | continue 40 | byte = bytes_array[0] 41 | if byte in [Order.HELLO.value, Order.ALREADY_CONNECTED.value]: 42 | is_connected = True 43 | 44 | print("Connected to Arduino") 45 | 46 | motor_speed = -56 47 | 48 | # Equivalent to write_i8(serial_file, Order.MOTOR.value) 49 | write_order(serial_file, Order.MOTOR) 50 | write_i8(serial_file, motor_speed) 51 | 52 | write_order(serial_file, Order.SERVO) 53 | write_i16(serial_file, 120) 54 | 55 | for _ in range(10): 56 | order = read_order(serial_file) 57 | print("Ordered received: {:?}", order) 58 | 59 | 60 | 61 | Reading / Writing in a file 62 | --------------------------- 63 | 64 | Read write in a file (WARNING: the file will be deleted when the script exits) 65 | 66 | 67 | .. code-block:: python 68 | 69 | from __future__ import print_function, division, absolute_import 70 | import os 71 | 72 | from robust_serial import Order, write_order, write_i8, write_i16, write_i32, read_i8, read_i16, read_i32, read_order 73 | 74 | test_file = "test.txt" 75 | 76 | with open(test_file, 'wb') as f: 77 | write_order(f, Order.HELLO) 78 | 79 | write_i8(f, Order.MOTOR.value) 80 | write_i16(f, -56) 81 | write_i32(f, 131072) 82 | 83 | with open(test_file, 'rb') as f: 84 | # Equivalent to Order(read_i8(f)) 85 | order = read_order(f) 86 | print(order) 87 | 88 | motor_order = read_order(f) 89 | print(motor_order) 90 | print(read_i16(f)) 91 | print(read_i32(f)) 92 | 93 | # Delete file 94 | os.remove(test_file) 95 | -------------------------------------------------------------------------------- /examples/socket_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import socket 3 | 4 | from robust_serial import read_i8, read_i32, write_i8, write_i32 5 | 6 | PORT = 4444 7 | SERVER_ADDR = "localhost" 8 | 9 | 10 | class SocketAdapter: 11 | """ 12 | Wrapper around socket object to use the robust_serial lib 13 | It just renames recv() to read() and send() to write() 14 | """ 15 | 16 | def __init__(self, client_socket): 17 | super().__init__() 18 | self.client_socket = client_socket 19 | 20 | def read(self, num_bytes): 21 | return self.client_socket.recv(num_bytes) 22 | 23 | def write(self, num_bytes): 24 | return self.client_socket.send(num_bytes) 25 | 26 | def close(self): 27 | return self.client_socket.close() 28 | 29 | 30 | def receive_messages(): 31 | """ 32 | Receive messages (server side) 33 | """ 34 | server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 35 | server_socket.bind(("", PORT)) 36 | print("Waiting for client...") 37 | # Wait for client 38 | server_socket.listen(1) 39 | 40 | client_sock, client_address = server_socket.accept() 41 | print(f"Accepted connection from {client_address}") 42 | # Wrap socket to work with the lib 43 | client_sock = SocketAdapter(client_sock) 44 | 45 | for _ in range(10): 46 | print(f"Received (i8): {read_i8(client_sock)}") 47 | big_number = read_i32(client_sock) 48 | 49 | print(f"Received (i32): {big_number}") 50 | 51 | print("Server exiting...") 52 | client_sock.close() 53 | server_socket.close() 54 | 55 | 56 | def send_messages(server_address): 57 | """ 58 | Send messages (client side) 59 | :param server_address: (str) 60 | """ 61 | client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 62 | client_socket.connect((server_address, PORT)) 63 | 64 | # Wrap socket to work with the lib 65 | client_socket = SocketAdapter(client_socket) 66 | 67 | print(f"Connected to {server_address}") 68 | for i in range(10): 69 | write_i8(client_socket, i) 70 | write_i32(client_socket, 32768) 71 | print("Client exiting...") 72 | client_socket.close() 73 | 74 | 75 | if __name__ == "__main__": 76 | parser = argparse.ArgumentParser(description="Test socket server/client connection") 77 | arg_group = parser.add_mutually_exclusive_group(required=True) 78 | arg_group.add_argument("-s", "--server", dest="server", action="store_true", default=False, help="Create a server") 79 | arg_group.add_argument("-c", "--client", dest="client", action="store_true", default=False, help="Create a client") 80 | args = parser.parse_args() 81 | if args.server: 82 | receive_messages() 83 | else: 84 | send_messages(SERVER_ADDR) 85 | -------------------------------------------------------------------------------- /robust_serial/utils.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import queue 3 | import sys 4 | from typing import List, Optional 5 | 6 | import serial 7 | 8 | 9 | # From https://stackoverflow.com/questions/6517953/clear-all-items-from-the-queue 10 | class CustomQueue(queue.Queue): 11 | """ 12 | A custom queue subclass that provides a :meth:`clear` method. 13 | """ 14 | 15 | def clear(self) -> None: 16 | """ 17 | Clears all items from the queue. 18 | """ 19 | 20 | with self.mutex: 21 | unfinished = self.unfinished_tasks - len(self.queue) 22 | if unfinished <= 0: 23 | if unfinished < 0: 24 | raise ValueError("task_done() called too many times") 25 | self.all_tasks_done.notify_all() 26 | self.unfinished_tasks = unfinished 27 | self.queue.clear() 28 | self.not_full.notify_all() 29 | 30 | 31 | # From https://stackoverflow.com/questions/12090503/listing-available-com-ports-with-python 32 | def get_serial_ports() -> List[str]: 33 | """ 34 | Lists serial ports. 35 | 36 | 37 | :return: A list of available serial ports 38 | """ 39 | if sys.platform.startswith("win"): 40 | ports = ["COM%s" % (i + 1) for i in range(256)] 41 | elif sys.platform.startswith("linux") or sys.platform.startswith("cygwin"): 42 | # this excludes your current terminal "/dev/tty" 43 | ports = glob.glob("/dev/tty[A-Za-z]*") 44 | elif sys.platform.startswith("darwin"): 45 | ports = glob.glob("/dev/tty.*") 46 | else: 47 | raise OSError("Unsupported platform") 48 | 49 | results = [] 50 | for port in ports: 51 | try: 52 | s = serial.Serial(port) 53 | s.close() 54 | results.append(port) 55 | except (OSError, serial.SerialException): 56 | pass 57 | return results 58 | 59 | 60 | def open_serial_port( 61 | serial_port: Optional[str] = None, 62 | baudrate: int = 115200, 63 | timeout: Optional[int] = 0, 64 | write_timeout: int = 0, 65 | ) -> serial.Serial: 66 | """ 67 | Try to open serial port with Arduino 68 | If not port is specified, it will be automatically detected 69 | 70 | :param serial_port: 71 | :param baudrate: 72 | :param timeout: None -> blocking mode 73 | :param write_timeout: 74 | :return: (Serial Object) 75 | """ 76 | # Open serial port (for communication with Arduino) 77 | if serial_port is None: 78 | serial_port = get_serial_ports()[0] 79 | # timeout=0 non-blocking mode, return immediately in any case, returning zero or more, 80 | # up to the requested number of bytes 81 | return serial.Serial(port=serial_port, baudrate=baudrate, timeout=timeout, writeTimeout=write_timeout) 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Robust Arduino Serial Protocol in Python 2 | 3 | ![CI](https://github.com/araffin/python-arduino-serial/workflows/CI/badge.svg) [![Documentation Status](https://readthedocs.org/projects/python-arduino-serial/badge/?version=latest)](https://python-arduino-serial.readthedocs.io/en/latest/?badge=latest) 4 | 5 | **Robust Arduino Serial** is a simple and robust serial communication protocol. It was designed to make two arduinos communicate, but can also be useful when you want a computer (e.g. a Raspberry Pi) to communicate with an Arduino. 6 | 7 | It supports Python 3.8+. 8 | 9 | This repository is part of the Robust Arduino Serial project, main repository: [https://github.com/araffin/arduino-robust-serial](https://github.com/araffin/arduino-robust-serial) 10 | 11 | **Please read the [Medium Article](https://medium.com/@araffin/simple-and-robust-computer-arduino-serial-communication-f91b95596788) to have an overview of this protocol.** 12 | 13 | Documentation: [https://python-arduino-serial.readthedocs.io](https://python-arduino-serial.readthedocs.io) 14 | 15 | Implementations are available in various programming languages: 16 | 17 | - [Arduino](https://github.com/araffin/arduino-robust-serial) 18 | - [Python](https://github.com/araffin/python-arduino-serial) 19 | - [C++](https://github.com/araffin/cpp-arduino-serial) 20 | - [Rust](https://github.com/araffin/rust-arduino-serial) 21 | 22 | ## Installation 23 | 24 | Using pip: 25 | ``` 26 | pip install robust_serial 27 | ``` 28 | 29 | From Source: 30 | ``` 31 | git clone https://github.com/araffin/python-arduino-serial.git 32 | pip install -e . 33 | ``` 34 | 35 | ## Tests 36 | Run the tests (require pytest): 37 | ``` 38 | make pytest 39 | ``` 40 | 41 | ## Examples 42 | 43 | Read write in a file (WARNING: the file will be deleted when the script exits) 44 | ``` 45 | python -m examples.file_read_write -f test.txt 46 | ``` 47 | 48 | Serial communication with an Arduino: [Arduino Source Code](https://github.com/araffin/arduino-robust-serial/tree/master/arduino-board/) 49 | ``` 50 | python -m examples.arduino_serial 51 | ``` 52 | 53 | ### Example: Communication with Sockets 54 | 55 | It can be useful when you want two programs to communicate over a network (e.g. using wifi) or even locally on the same computer (e.g. when you want a python2 script that communicates with a python3 script). 56 | 57 | 1. Start the server: 58 | ``` 59 | python -m examples.socket_example --server 60 | ``` 61 | 62 | 2. Run the client: 63 | ``` 64 | python -m examples.socket_example --client 65 | ``` 66 | 67 | ### Bluetooth Example with Two Computers 68 | 69 | Dependencies: 70 | ``` 71 | sudo apt-get install libbluetooth-dev bluez 72 | pip install pybluez 73 | ``` 74 | 75 | You need to change the server mac address `SERVER_ADDR`, you can use `hciconfig` to know the mac address of your computer. 76 | 77 | Server: 78 | ``` 79 | python -m examples.bluetooth_example --server 80 | ``` 81 | 82 | Client: 83 | ``` 84 | python -m examples.bluetooth_example --client 85 | ``` 86 | -------------------------------------------------------------------------------- /robust_serial/robust_serial.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from enum import Enum 3 | from typing import BinaryIO 4 | 5 | 6 | class Order(Enum): 7 | """ 8 | Pre-defined orders 9 | """ 10 | 11 | HELLO = 0 12 | SERVO = 1 13 | MOTOR = 2 14 | ALREADY_CONNECTED = 3 15 | ERROR = 4 16 | RECEIVED = 5 17 | STOP = 6 18 | 19 | 20 | def read_order(f: BinaryIO) -> Order: 21 | """ 22 | :param f: file handler or serial file 23 | :return: (Order Enum Object) 24 | """ 25 | return Order(read_i8(f)) 26 | 27 | 28 | def read_i8(f: BinaryIO) -> Order: 29 | """ 30 | :param f: file handler or serial file 31 | :return: (int8_t) 32 | """ 33 | return struct.unpack(" Order: 37 | """ 38 | :param f: file handler or serial file 39 | :return: (int16_t) 40 | """ 41 | return struct.unpack(" None: 53 | """ 54 | :param f: file handler or serial file 55 | :param value: (int8_t) 56 | """ 57 | if -128 <= value <= 127: 58 | f.write(struct.pack(" None: 64 | """ 65 | :param f: file handler or serial file 66 | :param order: (Order Enum Object) 67 | """ 68 | write_i8(f, order.value) 69 | 70 | 71 | def write_i16(f: BinaryIO, value: int) -> None: 72 | """ 73 | :param f: file handler or serial file 74 | :param value: (int16_t) 75 | """ 76 | f.write(struct.pack(" None: 80 | """ 81 | :param f: file handler or serial file 82 | :param value: (int32_t) 83 | """ 84 | f.write(struct.pack(" None: 88 | """ 89 | :param f: file handler or serial file 90 | :param byte: (int8_t) 91 | :param debug: (bool) whether to print or not received messages 92 | """ 93 | try: 94 | order = Order(byte) 95 | if order == Order.HELLO: 96 | msg = "HELLO" 97 | elif order == Order.SERVO: 98 | angle = read_i16(f) 99 | # Bit representation 100 | # print('{0:016b}'.format(angle)) 101 | msg = f"SERVO {angle}" 102 | elif order == Order.MOTOR: 103 | speed = read_i8(f) 104 | msg = f"motor {speed}" 105 | elif order == Order.ALREADY_CONNECTED: 106 | msg = "ALREADY_CONNECTED" 107 | elif order == Order.ERROR: 108 | error_code = read_i16(f) 109 | msg = f"Error {error_code}" 110 | elif order == Order.RECEIVED: 111 | msg = "RECEIVED" 112 | elif order == Order.STOP: 113 | msg = "STOP" 114 | else: 115 | msg = "" 116 | print("Unknown Order", byte) 117 | 118 | if debug: 119 | print(msg) 120 | except Exception as e: 121 | print(f"Error decoding order {byte}: {e}") 122 | print(f"byte={byte:08b}") 123 | -------------------------------------------------------------------------------- /robust_serial/threads.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | import serial 5 | 6 | from .robust_serial import Order, decode_order, write_i8, write_i16, write_order 7 | from .utils import queue 8 | 9 | rate = 1 / 2000 # 2000 Hz (limit the rate of communication with the arduino) 10 | 11 | 12 | class CommandThread(threading.Thread): 13 | """ 14 | Thread that send orders to the arduino 15 | it blocks if there no more send_token left (here it is the n_received_semaphore). 16 | 17 | :param serial_file: (Serial object) 18 | :param command_queue: (Queue) 19 | :param exit_event: (Threading.Event object) 20 | :param n_received_semaphore: (threading.Semaphore) 21 | :param serial_lock: (threading.Lock) 22 | """ 23 | 24 | def __init__(self, serial_file, command_queue, exit_event, n_received_semaphore, serial_lock): 25 | threading.Thread.__init__(self) 26 | self.deamon = True 27 | self.serial_file = serial_file 28 | self.command_queue = command_queue 29 | self.exit_event = exit_event 30 | self.n_received_semaphore = n_received_semaphore 31 | self.serial_lock = serial_lock 32 | 33 | def run(self): 34 | while not self.exit_event.is_set(): 35 | self.n_received_semaphore.acquire() 36 | if self.exit_event.is_set(): 37 | break 38 | try: 39 | order, param = self.command_queue.get_nowait() 40 | except queue.Empty: 41 | time.sleep(rate) 42 | self.n_received_semaphore.release() 43 | continue 44 | 45 | with self.serial_lock: 46 | write_order(self.serial_file, order) 47 | # print("Sent {}".format(order)) 48 | if order == Order.MOTOR: 49 | write_i8(self.serial_file, param) 50 | elif order == Order.SERVO: 51 | write_i16(self.serial_file, param) 52 | time.sleep(rate) 53 | print("Command Thread Exited") 54 | 55 | 56 | class ListenerThread(threading.Thread): 57 | """ 58 | Thread that listen to the Arduino 59 | It is used to add send_tokens to the n_received_semaphore 60 | 61 | :param serial_file: (Serial object) 62 | :param exit_event: (threading.Event object) 63 | :param n_received_semaphore: (threading.Semaphore) 64 | :param serial_lock: (threading.Lock) 65 | """ 66 | 67 | def __init__(self, serial_file, exit_event, n_received_semaphore, serial_lock): 68 | threading.Thread.__init__(self) 69 | self.deamon = True 70 | self.serial_file = serial_file 71 | self.exit_event = exit_event 72 | self.n_received_semaphore = n_received_semaphore 73 | self.serial_lock = serial_lock 74 | 75 | def run(self): 76 | while not self.exit_event.is_set(): 77 | try: 78 | bytes_array = bytearray(self.serial_file.read(1)) 79 | except serial.SerialException: 80 | time.sleep(rate) 81 | continue 82 | if not bytes_array: 83 | time.sleep(rate) 84 | continue 85 | byte = bytes_array[0] 86 | with self.serial_lock: 87 | try: 88 | order = Order(byte) 89 | except ValueError: 90 | continue 91 | if order == Order.RECEIVED: 92 | self.n_received_semaphore.release() 93 | decode_order(self.serial_file, byte) 94 | time.sleep(rate) 95 | print("Listener Thread Exited") 96 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration file for the Sphinx documentation builder. 3 | # 4 | # This file does only contain a selection of the most common options. For a 5 | # full list see the documentation: 6 | # http://www.sphinx-doc.org/en/master/config 7 | 8 | # -- Path setup -------------------------------------------------------------- 9 | 10 | # If extensions (or modules to document with autodoc) are in another directory, 11 | # add these directories to sys.path here. If the directory is relative to the 12 | # documentation root, use os.path.abspath to make it absolute, like shown here. 13 | # 14 | import os 15 | import sys 16 | from typing import Dict 17 | 18 | sys.path.insert(0, os.path.abspath("..")) 19 | 20 | import robust_serial # noqa: E402 21 | 22 | # -- Project information ----------------------------------------------------- 23 | 24 | project = "Robust Arduino Serial Protocol" 25 | copyright = "2018-2023, Antonin Raffin" 26 | author = "Antonin Raffin" 27 | 28 | # The short X.Y version 29 | version = "" 30 | # The full version, including alpha/beta/rc tags 31 | release = robust_serial.__version__ 32 | 33 | # -- General configuration --------------------------------------------------- 34 | 35 | # If your documentation needs a minimal Sphinx version, state it here. 36 | # 37 | # needs_sphinx = '1.0' 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | "sphinx.ext.autodoc", 44 | "sphinx.ext.autosummary", 45 | "sphinx.ext.doctest", 46 | "sphinx.ext.viewcode", 47 | ] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ["_templates"] 51 | 52 | # The suffix(es) of source filenames. 53 | # You can specify multiple suffix as a list of string: 54 | # 55 | # source_suffix = ['.rst', '.md'] 56 | source_suffix = ".rst" 57 | 58 | # The master toctree document. 59 | master_doc = "index" 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = "en" 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This pattern also affects html_static_path and html_extra_path . 71 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = "sphinx" 75 | 76 | 77 | # -- Options for HTML output ------------------------------------------------- 78 | 79 | # The theme to use for HTML and HTML Help pages. See the documentation for 80 | # a list of builtin themes. 81 | # 82 | # Fix for read the docs 83 | on_rtd = os.environ.get("READTHEDOCS") == "True" 84 | if on_rtd: 85 | html_theme = "default" 86 | else: 87 | html_theme = "sphinx_rtd_theme" 88 | 89 | 90 | def setup(app): 91 | app.add_css_file("css/custom_theme.css") 92 | 93 | 94 | # Theme options are theme-specific and customize the look and feel of a theme 95 | # further. For a list of options available for each theme, see the 96 | # documentation. 97 | # 98 | # html_theme_options = {} 99 | 100 | # Add any paths that contain custom static files (such as style sheets) here, 101 | # relative to this directory. They are copied after the builtin static files, 102 | # so a file named "default.css" will overwrite the builtin "default.css". 103 | html_static_path = ["_static"] 104 | 105 | # Custom sidebar templates, must be a dictionary that maps document names 106 | # to template names. 107 | # 108 | # The default sidebars (for documents that don't match any pattern) are 109 | # defined by theme itself. Builtin themes are using these templates by 110 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 111 | # 'searchbox.html']``. 112 | # 113 | # html_sidebars = {} 114 | 115 | 116 | # -- Options for HTMLHelp output --------------------------------------------- 117 | 118 | # Output file base name for HTML help builder. 119 | htmlhelp_basename = "RobustArduinoSerialProtocoldoc" 120 | 121 | 122 | # -- Options for LaTeX output ------------------------------------------------ 123 | 124 | latex_elements: Dict[str, str] = { 125 | # The paper size ('letterpaper' or 'a4paper'). 126 | # 127 | # 'papersize': 'letterpaper', 128 | # The font size ('10pt', '11pt' or '12pt'). 129 | # 130 | # 'pointsize': '10pt', 131 | # Additional stuff for the LaTeX preamble. 132 | # 133 | # 'preamble': '', 134 | # Latex figure (float) alignment 135 | # 136 | # 'figure_align': 'htbp', 137 | } 138 | 139 | # Grouping the document tree into LaTeX files. List of tuples 140 | # (source start file, target name, title, 141 | # author, documentclass [howto, manual, or own class]). 142 | latex_documents = [ 143 | ( 144 | master_doc, 145 | "RobustArduinoSerialProtocol.tex", 146 | "Robust Arduino Serial Protocol Documentation", 147 | "Antonin Raffin", 148 | "manual", 149 | ), 150 | ] 151 | 152 | 153 | # -- Options for manual page output ------------------------------------------ 154 | 155 | # One entry per manual page. List of tuples 156 | # (source start file, name, description, authors, manual section). 157 | man_pages = [(master_doc, "robustarduinoserialprotocol", "Robust Arduino Serial Protocol Documentation", [author], 1)] 158 | 159 | 160 | # -- Options for Texinfo output ---------------------------------------------- 161 | 162 | # Grouping the document tree into Texinfo files. List of tuples 163 | # (source start file, target name, title, author, 164 | # dir menu entry, description, category) 165 | texinfo_documents = [ 166 | ( 167 | master_doc, 168 | "RobustArduinoSerialProtocol", 169 | "Robust Arduino Serial Protocol Documentation", 170 | author, 171 | "RobustArduinoSerialProtocol", 172 | "One line description of project.", 173 | "Miscellaneous", 174 | ), 175 | ] 176 | 177 | 178 | # -- Extension configuration ------------------------------------------------- 179 | --------------------------------------------------------------------------------