Refactor python code, add publish workflows, add docs website#17
Refactor python code, add publish workflows, add docs website#17JohnCHarrington wants to merge 6 commits intomainfrom
Conversation
bd51fcf to
ef958ae
Compare
ef958ae to
51d3012
Compare
There was a problem hiding this comment.
Pull request overview
This PR restructures the repository around a publishable open_echo Python package (shared by desktop + web UIs), adds CI/release automation, and migrates documentation to a Jekyll/Just-the-Docs site for GitHub Pages.
Changes:
- Introduces a packaged Python implementation (
python/src/open_echo) with shared readers/settings/output logic plus a CLI entrypoint. - Adds Python CI (tests/lint/typecheck) and PyPI publish-on-release workflows.
- Migrates docs into
/documentationand adds a GitHub Pages publishing workflow + Jekyll config.
Reviewed changes
Copilot reviewed 30 out of 43 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| reverse_engineering/live_waterfall.py | Minor import reordering. |
| reverse_engineering/images/.DS_Store | Adds a macOS metadata file (should not be versioned). |
| python/tests/test_settings.py | Adds unit tests for Settings parsing/validation and save/load roundtrip. |
| python/tests/test_echo.py | Adds tests for packet parsing, checksum handling, and reader behaviors. |
| python/tests/test_depth_output.py | Adds tests for SignalK/NMEA outputs and OutputManager lifecycle. |
| python/src/open_echo/web.py | Adds FastAPI web server + websocket streaming using shared readers/settings. |
| python/src/open_echo/settings.py | Moves/updates Settings model and validators into package. |
| python/src/open_echo/py.typed | Marks package as typed for type checkers. |
| python/src/open_echo/echo.py | Adds shared Echo packet parsing + Serial/UDP async readers. |
| python/src/open_echo/desktop.py | Refactors desktop UI to use shared readers/settings and qasync loop integration. |
| python/src/open_echo/depth_output.py | Refactors depth output (SignalK + NMEA0183) into the package. |
| python/src/open_echo/cli.py | Adds openecho CLI to run desktop or web. |
| python/src/open_echo/assets/templates/spectrogram.js | Adds client-side rendering for live spectrogram with cursor/zoom UI. |
| python/src/open_echo/assets/templates/frontend.html | Adds web frontend template for the spectrogram view. |
| python/src/open_echo/assets/templates/config.html | Adds web config form for connection/output/display settings. |
| python/src/open_echo/assets/static/style.css | Adds styling for the web spectrogram UI. |
| pyproject.toml | Defines packaged project metadata, dependencies, scripts, and tooling (ruff/mypy/pytest). |
| documentation/lucky_fishfinder/lucky_fishfinder.md | Adds Jekyll frontmatter and updates image paths. |
| documentation/getting_started/web_interface.md | Updates install/run instructions for the new CLI-based web flow. |
| documentation/getting_started/index.md | Adds Getting Started landing page/navigation. |
| documentation/getting_started/desktop_interface.md | Updates desktop install/run instructions and references new code location. |
| documentation/getting_started/TUSS4470_hardware.md | Adds frontmatter and updates ordering + image references. |
| documentation/getting_started/TUSS4470_firmware.md | Adds frontmatter and updates firmware references/links. |
| documentation/contributing.md | Adds contribution guide including uv workflow and git hooks. |
| _config.yml | Adds Jekyll configuration for GitHub Pages + just-the-docs. |
| README.md | Adds Jekyll frontmatter and updates documentation links/media. |
| Gemfile | Adds Ruby dependencies for building the docs site. |
| .gitignore | Updates ignore rules (including lockfiles and Jekyll build output). |
| .github/workflows/python-ci.yml | Adds CI for pytest + ruff + mypy using uv. |
| .github/workflows/pypi-publish.yml | Adds release workflow to build and publish to PyPI. |
| .github/workflows/docs.yml | Adds workflow to build and deploy docs to GitHub Pages. |
| .githooks/pre-commit | Adds optional local pre-commit hook running ruff/pytest/mypy via uv. |
| TUSS4470_shield_002/web/uv.lock | Removes legacy web interface lockfile. |
| TUSS4470_shield_002/web/requirements.txt | Removes legacy web interface requirements export. |
| TUSS4470_shield_002/web/pyproject.toml | Removes legacy web interface project definition. |
| TUSS4470_shield_002/web/echo.py | Removes legacy web echo reader implementation (replaced by package). |
| TUSS4470_shield_002/web/app.py | Removes legacy FastAPI app (replaced by open_echo.web). |
| TUSS4470_shield_002/requirements.txt | Removes legacy top-level Python requirements file. |
Comments suppressed due to low confidence (7)
python/src/open_echo/depth_output.py:49
- Output scheduling logic is incorrect here: (1) the interval comparison multiplies
output_interval(seconds) by 1000 even thoughasyncio.get_event_loop().time()is already in seconds, which makes outputs run ~1000x less frequently than configured; (2) theor last_output_time is Nonebranch can calloutput()even whencurrent_valueis None, causing outputs to send null depth. Use a seconds-based comparison (no *1000) and gate outputs oncurrent_value is not None(or explicitly handle an initial/heartbeat output separately).
documentation/lucky_fishfinder/lucky_fishfinder.md:20 - These image
srcvalues includedocumentation/lucky_fishfinder/...even though the page itself already lives under that path, so they will resolve to a duplicated path at render time. Use a path relative to the markdown file (e.g.,images/...) or Jekyll’srelative_urlfilter so the images render correctly on the docs site.
python/src/open_echo/depth_output.py:59 _run()currently loops without any sleep/backoff whensettingsis set, which will busy-spin and consume 100% CPU. Re-introduce a sleep (or await a condition/event) to control the output frequency (e.g.,await asyncio.sleep(...)based on the smallest configured output interval).
python/src/open_echo/desktop.py:780- Qt override must be named
closeEventto run on window close. Withclose_event, the async reader task may not be cancelled/cleaned up when the window closes. Rename this back tocloseEvent(and consider awaiting reader shutdown if needed).
documentation/getting_started/desktop_interface.md:42 - This image path is likely broken when rendered under
/documentation/getting_started/...because it’s a relative URL includingdocumentation/again. Prefer using Jekyll’srelative_url/{{ site.baseurl }}helpers (or a path relative to the page, e.g.../images/...) so it resolves correctly on GitHub Pages.
documentation/getting_started/TUSS4470_hardware.md:34 - The
src="documentation/images/..."paths are relative to the current page URL, so on pages under/documentation/getting_started/...this will typically resolve to a duplicated path and break images. Use Jekyll helpers like{{ '/documentation/images/shield_pinout.png' | relative_url }}(or../images/...) for stable links under the configuredbaseurl.
python/src/open_echo/desktop.py:663 - Qt overrides must be named
keyPressEvent(camel-case) to be invoked by Qt. Renaming it tokey_press_eventmeans keyboard shortcuts (Q/C) will no longer work. Restore the method name tokeyPressEvent(keeping the body) or explicitly install an event filter to handle key presses.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@JohnCHarrington I've opened a new pull request, #21, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@JohnCHarrington I've opened a new pull request, #22, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@JohnCHarrington I've opened a new pull request, #23, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 43 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (3)
python/src/open_echo/desktop.py:772
- Method name
close_eventdoesn't follow PyQt5 naming convention. PyQt5 usescloseEvent(camelCase) as the method name that gets called when the window closes. This snake_case version won't be triggered. Rename tocloseEventto properly override the parent method.
python/src/open_echo/depth_output.py:46 - The time comparison logic is incorrect.
output_intervalis in seconds (as indicated by line 74 comment), but it's being multiplied by 1000 and compared against time in seconds fromget_event_loop().time(). This will cause output to happen every millisecond instead of every second. Remove the* 1000multiplication.
python/src/open_echo/desktop.py:653 - Method name
key_press_eventdoesn't follow PyQt5 naming convention. PyQt5 useskeyPressEvent(camelCase) as the method name that gets called by the framework. This snake_case version won't be triggered by keyboard events. Rename tokeyPressEventto properly override the parent method.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
fb11178 to
eee1e5d
Compare
Move all files to new folder structure feat: combine echo readers chore: stylefix feat: Add CLI commands for desktop/web chore: Typing and ruff Readd deleted assets feat: Add tests chore: stylefixes chore: stylefixes again feat: Add github actions for python CICD feat: Add bump-my-version Bump version: 0.1.0 → 0.1.1 feat: Use hatch-vcs Bump version: 0.1.1 → 0.1.2 chore: Reset version docs: Adjust for using install package fix: bump-my-version command syntax chore: use @patch in tests fix(tests): mock asyncio.open_connection temp: Publish from this branch to test feat: Output methods can set their own interval fix: add id-token write permission to pypi-publish temp: Trigger on workflow change chore: Separate workflows to use version update temp: Trigger python CI feat: simplify workflow setup Bump version: 0.1.0 → 0.1.1 fix: Don't double-fetch tags feat: Simplify, publish on release fix: Rename openecho package to open_echo feat: Run test/lint/typecheck before publishing release feat: Set up jekyll, add contributor docs fix: Remove double publish step feat: Adjust num_samples and sample_time in app settings docs: Update for new settings docs: Tidy up, exclude unnecessary files from build feat: Add workflow to publish docs temp: publish pages from this branch fix: Only publish docs when doc files change chore: Remove unused minima gem docs: Adjust wording fix: Run bundle install on docs docs: Allow manual deploy docs: Adjust wording fix: Install ruby chore: Remove lockfiles chore: ignore lockfiles docs: Adjust wording fix: Plain jekyll build not pages build docs: Adjust baseurl and use relative links docs: Adjust wording docs: Update links docs: Remove relative links fix: Build docs with right baseurl feat: Trigger docs update on release docs: Update paths docs: publish on commit to main docs: Remove build folder Update .github/workflows/docs.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Update documentation/getting_started/index.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Use relative paths for images Initial plan Fix broadcast_json to handle per-connection send failures Co-authored-by: JohnCHarrington <1857365+JohnCHarrington@users.noreply.github.com> Initial plan Fix type hints for depth_callback and data_callback in EchoReader Co-authored-by: JohnCHarrington <1857365+JohnCHarrington@users.noreply.github.com> Initial plan Fix EchoReader to retry with backoff on exceptions instead of blocking Co-authored-by: JohnCHarrington <1857365+JohnCHarrington@users.noreply.github.com> Update .github/workflows/pypi-publish.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
cfae7a9 to
685f21f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 44 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
python/src/open_echo/depth_output.py:48
- The conditional logic for determining whether to output is incorrect. The condition
output_class.current_value is not None or (...)will always trigger output if current_value is not None, ignoring the time interval check. Additionally, the time comparison uses milliseconds incorrectly -output_intervalis in seconds but is multiplied by 1000 when compared to a difference in seconds. This will cause output to occur much less frequently than intended (e.g., 1 second interval becomes 1000 seconds).
python/src/open_echo/depth_output.py:58 - The loop removed the
await asyncio.sleep(1.0)which was throttling the output rate. Now the loop runs as fast as possible, constantly callingawait self.output(). This will cause excessive CPU usage and network traffic. The sleep should be restored or the loop should wait on an event/signal.
python/src/open_echo/desktop.py:653 - The method name
key_press_eventfollows snake_case but should bekeyPressEvent(camelCase) to properly override the Qt event handler. The current name will not receive keyboard events from Qt. This appears to be a renaming error - the old code hadkeyPressEventwhich was correct.
python/src/open_echo/desktop.py:772 - The method name
close_eventfollows snake_case but should becloseEvent(camelCase) to properly override the Qt event handler. The current name will not be called when the window is closed. This appears to be a renaming error - the old code hadcloseEventwhich was correct.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 48 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (8)
python/src/open_echo/UART_UDP_relay.py:88
- The condition
if not header or header[0] != START_BYTEis incorrect.ser.read(1)returns an empty bytes object (b'') when no data is available, not None. An empty bytes object is falsy, sonot headerworks, but thenheader[0]will raise IndexError if header is empty. The check should beif len(header) == 0 or header[0] != START_BYTEor split into two conditions to avoid the IndexError.
python/src/open_echo/settings.py:96 - The mode parameter in open() was removed on line 96. While Python defaults to "r" mode, it's better to be explicit when reading files, especially since the write mode is explicitly specified in save(). Add mode="r" for clarity and consistency.
documentation/getting_started/TUSS4470_hardware.md:25 - Typo on line 25: "direclty" should be "directly".
python/src/open_echo/depth_output.py:50 - The output timing logic is flawed. Line 42 checks if
current_value is not NoneOR if enough time has passed, but then always updateslast_output_timeand calls output(). This means output() will be called even when current_value is None if enough time has passed. The logic should be: only output if current_value is not None AND (last_output_time is None OR enough time has passed).
python/src/open_echo/depth_output.py:58 - The _run() loop no longer has a sleep, so it will spin at maximum CPU usage. The old code had
await asyncio.sleep(1.0)at line 58 which was removed. Without any delay, this tight loop will consume unnecessary CPU resources. Add back a small sleep (e.g.,await asyncio.sleep(0.01)) to prevent CPU spinning.
documentation/lucky_fishfinder/lucky_fishfinder.md:21 - Typo in line 21: "brigter" should be "brighter".
python/src/open_echo/depth_output.py:35 - All logging has been replaced with print() statements. This is a regression from proper logging practices. The code should use Python's logging module (as it did before) for proper log level control, formatting, and integration with application logging frameworks. Replace print() calls with appropriate log.info(), log.debug(), log.error(), etc.
python/src/open_echo/depth_output.py:135 - The description field was changed from "OpenEcho Depth Sounder" to "open_echo Depth Sounder". This creates an inconsistency - the project is called "Open Echo" (with capital letters and space) everywhere else in the codebase. Use "Open Echo Depth Sounder" for consistency with the project branding.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - name: Publish to PyPI | ||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| password: ${{ secrets.PYPI_TOKEN }} |
There was a problem hiding this comment.
The password field references a secret that may not exist. Ensure that PYPI_TOKEN is configured in the repository secrets before this workflow runs, otherwise the publish will fail. Consider adding a note in the PR description or documentation about required secrets.
README.md
Outdated
| 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 |
There was a problem hiding this comment.
Typo on line 35: "direclty" should be "directly".
| 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 |
| "pyqtdarktheme>=2.1.0", | ||
| "pyqtgraph>=0.14.0", | ||
| "qasync>=0.24.0", | ||
| "pyserial<3.5", |
There was a problem hiding this comment.
The dependency pyserial<3.5 pins to versions older than 3.5, which may be unnecessarily restrictive. Unless there's a specific incompatibility with 3.5+, consider using pyserial>=3.4,<4.0 to allow bug fixes and minor updates while preventing breaking changes.
| "pyserial<3.5", | |
| "pyserial>=3.4,<4.0", |
c5cebb5 to
e1ae819
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 48 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
python/src/open_echo/depth_output.py:61
- The
_run()loop removed theawait asyncio.sleep(1.0)call after callingoutput(), which means this tight loop will continuously calloutput()at maximum speed without yielding control. This can cause high CPU usage and starve other async tasks. The rate limiting should happen in the loop itself, not just within the output interval check. Consider addingawait asyncio.sleep(0.01)or similar small delay afterawait self.output()to yield control to the event loop.
python/src/open_echo/depth_output.py:154 - The
get_token()method has an infinite polling loop without timeout (line 151-154). If the SignalK server never transitions from PENDING state or becomes unreachable during polling, this will hang indefinitely. Add a timeout mechanism or maximum retry count to prevent hanging the application.
documentation/getting_started/TUSS4470_firmware.md:34 - The link to
documentation/getting_started/desktop_interface.mdappears to be a relative path from the repository root, but in a Jekyll site with baseurl, this may not resolve correctly. Since this file is already indocumentation/getting_started/, consider using a relative link likedesktop_interface.mdor an absolute Jekyll link. Additionally, verify this link works when the site is deployed to GitHub Pages with the/open_echobaseurl.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
e1ae819 to
2106da1
Compare
2106da1 to
2819d15
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 48 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
python/src/open_echo/depth_output.py:61
- The
_run()method runs an infinite loop that callsawait self.output()with no sleep between iterations. This will cause a busy loop consuming 100% CPU. Add a small sleep (e.g.,await asyncio.sleep(0.1)) afterawait self.output()to prevent CPU spinning.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Apologies, lots going on here ;)
pythonfolderuvfor managing python dependencies and build/documentationfolderopen_echopython package on release