diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..76580b8 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[pre-commit] Running ruff (fix), pytest, and mypy..." + +# Only run if Python files changed; otherwise skip quickly +changed_py=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.py$' || true) +if [[ -z "$changed_py" ]]; then + echo "[pre-commit] No Python changes detected. Skipping checks." + exit 0 +fi + +if ! command -v uv >/dev/null 2>&1; then + echo "[pre-commit] 'uv' not found. Install from https://github.com/astral-sh/uv" + exit 1 +fi + +# Lint and auto-fix Python code +echo "[pre-commit] Ruff: check & fix" +uvx ruff check --fix + +# Re-stage any auto-fixed files so the commit includes updates +git add -A + +# Run tests +echo "[pre-commit] Pytest" +uv run pytest -q python/ + +# Type checking +echo "[pre-commit] Mypy" +uv run mypy + +echo "[pre-commit] All checks passed. Proceeding with commit." +exit 0 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..6df129d --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,45 @@ +name: Publish Docs to GitHub Pages + +on: + push: + branches: [main] + paths: + - 'documentation/**' + - '_config.yml' + - 'Gemfile' + - 'README.md' + + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.4' # Not needed with a .ruby-version, .tool-versions or mise.toml + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: Build with Jekyll + run: bundle exec jekyll build + env: + JEKYLL_ENV: production + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + deploy: + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} \ No newline at end of file diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 0000000..7fcfee2 --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,37 @@ +name: Publish to PyPI + +on: + release: + types: [published] + +jobs: + test-lint-typecheck: + uses: ./.github/workflows/python-ci.yml + build-and-publish: + needs: test-lint-typecheck + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Setup uv + uses: astral-sh/setup-uv@v3 + + - name: Build package (sdist and wheel) + run: uv build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_TOKEN }} + # Uncomment to be verbose or skip existing versions + # verbose: true + # skip-existing: true diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml new file mode 100644 index 0000000..c0e5bad --- /dev/null +++ b/.github/workflows/python-ci.yml @@ -0,0 +1,41 @@ +name: Python CI + +on: + workflow_call: + push: + branches: ["**"] + paths: + - '**/*.py' + - 'pyproject.toml' + pull_request: + branches: ["**"] + paths: + - '**/*.py' + - 'pyproject.toml' + +jobs: + test-lint-typecheck: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Setup uv + uses: astral-sh/setup-uv@v3 + + - name: Sync dependencies (including dev) + run: uv sync --all-extras --dev + + - name: Run tests (pytest) + run: uv run pytest + + - name: Lint (ruff) + run: uvx ruff check + + - name: Type check (mypy) + run: uv run mypy \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0057c27..0de93e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ __pycache__/ .* !.gitignore +!.githooks +!.github +__about__.py +_site +*.lock \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..6e4c7a1 --- /dev/null +++ b/Gemfile @@ -0,0 +1,33 @@ +source "https://rubygems.org" +# Hello! This is where you manage which Jekyll version is used to run. +# When you want to use a different version, change it below, save the +# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: +# +# bundle exec jekyll serve +# +# This will help ensure the proper Jekyll version is running. +# Happy Jekylling! +gem "jekyll", "~> 4.4.1" +# This is the default theme for new Jekyll sites. You may change this to anything you like. +gem "just-the-docs" +# If you have any plugins, put them here! +group :jekyll_plugins do + gem "jekyll-feed", "~> 0.12" + gem "jekyll-readme-index" + gem "jekyll-gfm-admonitions" + gem "jekyll-relative-links" +end + +# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem +# and associated library. +platforms :mingw, :x64_mingw, :mswin, :jruby do + gem "tzinfo", ">= 1", "< 3" + gem "tzinfo-data" +end + +# Performance-booster for watching directories on Windows +gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin] + +# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem +# do not have a Java counterpart. +gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] diff --git a/README.md b/README.md index a49906e..1e35621 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,38 @@ +--- +layout: home +title: Open Echo +nav_exclude: true +--- + + Open Echo Cover ## Universal Open-Source SONAR Controller and Development Stack An ongoing open-source hardware and software project for building sonar systems for testing, boating, bathymetry, and research. -The most commonly used hardware is the [TUSS4470 Arduino Shield](TUSS4470_shield_002/), which stacks on top of an Arduino Uno to drive the TUSS4470 ultrasonic driver. -The board can run the [RAW Data Firmware](TUSS4470_shield_002/getting_started_TUSS4470_firmware.md) to operate a wide variety of ultrasonic transducers, covering frequencies from 40 kHz up to 1000 kHz in different media such as air or water. +The most commonly used hardware is the [TUSS4470 Arduino Shield](documentation/getting_started/TUSS4470_hardware.md), which stacks on top of an Arduino Uno to drive the TUSS4470 ultrasonic driver. +The board can run the [RAW Data Firmware](documentation/getting_started/desktop_interface.md) to operate a wide variety of ultrasonic transducers, covering frequencies from 40 kHz up to 1000 kHz in different media such as air or water. -The [NMEA Output Firmware](TUSS4470_shield_002/arduino/NMEA_DBT_OUT/NMEA_DBT_OUT.ino) can read depth data from commercially available in-water ultrasonic transducers (e.g., on boats) and output NMEA0183-compatible data to a computer or a UART-connected device such as a Pixhawk or other controllers. +The [NMEA Output Firmware](https://github.com/neumi/open_echo/tree/main/TUSS4470_shield_002/arduino/NMEA_DBT_OUT/NMEA_DBT_OUT.ino) can read depth data from commercially available in-water ultrasonic transducers (e.g., on boats) and output NMEA0183-compatible data to a computer or a UART-connected device such as a Pixhawk or other controllers. Open Echo has been tested on multiple ultrasonic transducers and is compatible with all of them—from car parking sensors to Lowrance Tripleshot side-scan transducers. -The [Python Interface Software](TUSS4470_shield_002/getting_started_interface.md) connects to Open Echo boards running the [RAW Data Firmware](TUSS4470_shield_002/getting_started_TUSS4470_firmware.md). It can display raw echo data, change configurations, output a TCP depth data stream, and more. +The [Python Interface Software](documentation/getting_started/desktop_interface.md) connects to Open Echo boards running the [RAW Data Firmware](documentation/getting_started/TUSS4470_firmware.md). It can display raw echo data, change configurations, output a TCP depth data stream, and more. -Check the [Getting Started Guide](TUSS4470_shield_002/README.md)! +Check the [Getting Started Guide](documentation/getting_started/index.md)! If something is unclear or you find a bug, please open an issue. Raw Data Waterfall chart in the Python Desktop software: -Open Echo Interface Software +Open Echo Interface Software ## Getting the Hardware -If you need the hardware, you can order it using the [Hardware Files](TUSS4470_shield_002/TUSS4470_shield_hardware/TUSS4470_shield) from a board + SMT house ([JLC recommended](https://jlcpcb.com/?from=Neumi)). +If you need the hardware, you can order it using the [Hardware Files](https://github.com/neumi/open_echo/tree/main/TUSS4470_shield_002/TUSS4470_shield_hardware/TUSS4470_shield) from a board + SMT house ([JLC recommended](https://jlcpcb.com/?from=Neumi)). -They can also be bought as a complete and tested set direclty from Elecrow: https://www.elecrow.com/open-echo-tuss4470-development-shield.html +They can also be bought as a complete and tested set directly from Elecrow: https://www.elecrow.com/open-echo-tuss4470-development-shield.html If they’re out of stock, or if you’d prefer to order them within Germany to reduce shipping costs, please send me an email at: openechoes@gmail.com @@ -36,12 +43,12 @@ All profits go directly toward supporting and advancing the Open Echo project! [TUSS4470 Arduino Shield](TUSS4470_shield_002/): PCB overview TUSS4470 -### This project is currently in development. The [TUSS4470 Development Shield](TUSS4470_shield_002/) is ready for external use! +### This project is currently in development. The [TUSS4470 Development Shield](documentation/getting_started/TUSS4470_hardware.md) is ready for external use! Development is ongoing! Check the documentation and Discord channel for the latest updates. Want to stay updated or participate? Join the [Discord](https://discord.com/invite/rerCyqAcrw)! -Check the [Getting Started Guide](TUSS4470_shield_002/README.md). +Check the [Getting Started Guide](documentation/getting_started/). ## Vision An accessible Open Source SONAR stack for development, research and real use: diff --git a/TUSS4470_shield_002/echo_interface.py b/TUSS4470_shield_002/echo_interface.py deleted file mode 100644 index a9321b9..0000000 --- a/TUSS4470_shield_002/echo_interface.py +++ /dev/null @@ -1,893 +0,0 @@ -import sys -import numpy as np -import serial -import serial.tools.list_ports -import struct -import time -import socket -from PyQt5.QtWidgets import ( - QApplication, - QMainWindow, - QVBoxLayout, - QWidget, - QComboBox, - QPushButton, - QLabel, - QLineEdit, -) -from PyQt5.QtCore import QThread, pyqtSignal -import pyqtgraph as pg -import qdarktheme -from PyQt5.QtWidgets import ( - QHBoxLayout, -) -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPalette, QColor -from PyQt5.QtWidgets import QVBoxLayout, QLabel, QCheckBox, QLineEdit -from PyQt5.QtWidgets import QApplication - -# Serial Configuration -BAUD_RATE = 250000 -NUM_SAMPLES = 1800 # (X-axis) - -MAX_ROWS = 300 # Number of time steps (Y-axis) -Y_LABEL_DISTANCE = 50 # distance between labels in cm - -SPEED_OF_SOUND = 1440 # default sound speed meters/second in water -# SPEED_OF_SOUND = 343 # default sound speed meters/second in water - -# SAMPLE_TIME = 52.226e-6 # 13.2 microseconds on Atmega328 max sample speed plus 50 microseconds delay in sampling loop -# SAMPLE_TIME = 47.0e-6 -# SAMPLE_TIME = 41.666e-6 # 13.2 microseconds on Atmega328 max sample speed plus 40 microseconds delay in sampling loop -# SAMPLE_TIME = 22.22e-6 # 13.2 microseconds on Atmega328 max sample speed plus 20 microseconds delay in sampling loop -SAMPLE_TIME = 13.2e-6 # 13.2 microseconds on Atmega328 max sample speed without additional delay -# SAMPLE_TIME = 11.0e-6 # 13.2 microseconds on RP2040 max sample speed with 10 microseconds additional delay per sample -# SAMPLE_TIME = 7.682e-6 # 7.682 microseconds on STM32F103 max sample speed -# SAMPLE_TIME = 6.0e-6 # 6 microseconds on RP2040 max sample speed with 5 microseconds additional delay per sample -# SAMPLE_TIME = 1.290e-6 # 13.2 microseconds on RP2040 max sample speed without additional delay - -DEFAULT_LEVELS = (0, 256) # Expected data range - -SAMPLE_RESOLUTION = (SPEED_OF_SOUND * SAMPLE_TIME * 100) / 2 # cm per row (0.99 cm per row) -PACKET_SIZE = 1 + 6 + NUM_SAMPLES + 1 # header + payload + checksum -MAX_DEPTH = NUM_SAMPLES * SAMPLE_RESOLUTION # Total depth in cm -depth_labels = {int(i / SAMPLE_RESOLUTION): f"{i / 100}" for i in range(0, int(MAX_DEPTH), Y_LABEL_DISTANCE)} - - -def read_packet(ser): - while True: - header = ser.read(1) - if header != b"\xaa": - continue # Wait for the start byte - - payload = ser.read(6 + NUM_SAMPLES) - checksum = ser.read(1) - - if len(payload) != 6 + NUM_SAMPLES or len(checksum) != 1: - continue # Incomplete packet - - # Verify checksum - calc_checksum = 0 - for byte in payload: - calc_checksum ^= byte - if calc_checksum != checksum[0]: - print("⚠️ Checksum mismatch: {} != {}".format(calc_checksum, checksum[0])) - continue - - # Unpack payload (firmware sends little-endian raw struct bytes) - depth, temp_scaled, vDrv_scaled = struct.unpack("