Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .flake8

This file was deleted.

13 changes: 6 additions & 7 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
python-version-file: "pyproject.toml"

- name: Install Poetry
uses: abatilo/actions-poetry@v3.0.2
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
poetry-version: "2.0.1"

version: "0.7.11"
- name: Build package with poetry
run: poetry build
run: uv build protocol

- name: Publish to PyPi
run: poetry publish --username __token__ --password ${{ secrets.PYPI_TOKEN }}
run: uv publish --username __token__ --password ${{ secrets.PYPI_TOKEN }} dist/blueye_protocol-*
12 changes: 6 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Install Poetry
uses: abatilo/actions-poetry@v3.0.2
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
poetry-version: "2.0.1"
version: "0.7.11"
- name: Install dependencies
run: poetry install
run: uv sync --all-packages
- name: Run tests
run: poetry run invoke test
run: uv run invoke test
- name: Run style checker
run: poetry run flake8
run: uv run ruff check
22 changes: 10 additions & 12 deletions .github/workflows/update-protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,28 @@ jobs:
with:
submodules: true
token: ${{ secrets.GIT_TOKEN }}
- name: Set up Python 3.13
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install Poetry
uses: abatilo/actions-poetry@v3.0.2
python-version-file: "pyproject.toml"
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
poetry-version: "2.0.1"
version: "0.7.11"
- name: Install dependencies
run: poetry install
run: uv sync --all-packages
- name: Update ProtocolDefinitions
working-directory: ProtocolDefinitions
run: git fetch && git checkout ${{ github.event.client_payload.sha }}
- name: Generate UDP protocol
run: poetry run invoke generate-udp
run: uv run invoke generate-udp
- name: Generate TCP protocol
run: poetry run invoke generate-tcp
run: uv run invoke generate-tcp
- name: Generate Protobuf protocol
run: poetry run invoke generate-proto
- name: Generate setup.py
run: poetry run invoke generate-setup-py
run: uv run invoke generate-proto
- name: Add & Commit
uses: EndBug/add-and-commit@v9
with:
add: "blueye ProtocolDefinitions setup.py"
add: "protocol legacyprotocol ProtocolDefinitions"
message: "Update ProtocolDefinitions"
default_author: github_actions
27 changes: 1 addition & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# blueye.protocol
[![Tests](https://github.com/BluEye-Robotics/blueye.protocol/workflows/Tests/badge.svg)](https://github.com/BluEye-Robotics/blueye.protocol/actions)

**Deprecation notice**

Blunux 3.0 introduces a new protocol based on [Protobuf](https://developers.google.com/protocol-buffers/) messages passed over a [ZeroMQ](https://zeromq.org/) layer. Starting with Blunux 3.1 the old TCP/UDP based protocol will no longer be supported/compatible.


## About
This repository contains a python library that defines how to communicate with the underwater drones made by [Blueye Robotics](https://blueyerobotics.com).
Expand All @@ -23,7 +19,7 @@ pip install blueye.protocol
## Development

### Dependency/Package management
We use Poetry for dependency/package management, see the [Poetry docs](https://python-poetry.org/docs/) for installation instructions.
We use `uv` for dependency/package management, see the [uv docs](https://docs.astral.sh/uv/getting-started/installation/) for installation instructions.


### Code generators
Expand All @@ -49,24 +45,3 @@ or directly using pytest (if you don't want to generate the definitions)
``` shell
pytest
```

### `setup.py`
Since bitbake doesn't have support for pyproject.toml files yet, we need to include a
`setup.py` file to specify the dependencies needed. There's an invoke task for
generating the file that can be run with
``` shell
invoke generate-setup-py
```

If you are running MacOS, you need to install gnu-tar
``` shell
brew install gnu-tar
```

and then run the follwing line before the invoke command:
``` shell
PATH="/opt/homebrew/opt/gnu-tar/libexec/gnubin:$PATH"
```

**Be sure to run this script and commit the `setup.py` file when the dependencies have
changed.**
2 changes: 0 additions & 2 deletions blueye/__init__.py

This file was deleted.

9 changes: 0 additions & 9 deletions blueye/protocol/__init__.py

This file was deleted.

70 changes: 42 additions & 28 deletions generators/generate_tcp_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,33 @@ def get_protocol(protocol_path):
class Context:
def __init__(self, path=os.path.dirname(os.path.abspath(__file__))):
self.path = path
self.module_path = os.path.join(path, "../blueye/protocol/v2")
self.module_path = os.path.join(path, "../legacyprotocol/blueye/legacyprotocol")
template_path = os.path.join(path, "templates")
self.template_environment = Environment(autoescape=False,
trim_blocks=True,
lstrip_blocks=True,
loader=FileSystemLoader(template_path))
self.template_environment = Environment(
autoescape=False,
trim_blocks=True,
lstrip_blocks=True,
loader=FileSystemLoader(template_path),
)
self.output_file_path = os.path.join(self.module_path, "tcp_protocol_class.py")
json_path = os.path.join(path, "..", "ProtocolDefinitions", "tcp_protocol.json")
self.tcp_protocol = get_protocol(json_path)


def dtype_to_format_char(dtype):
STRUCT_CONVERSION = {"<u1": "B", "<i1": "b", "<u2": "H", "<i2": "h",
"<u4": "I", "<i4": "i", "<u8": "Q", "<i8": "q",
"<f4": "f", "<f8": "d", "<u1[64]": "64s"}
STRUCT_CONVERSION = {
"<u1": "B",
"<i1": "b",
"<u2": "H",
"<i2": "h",
"<u4": "I",
"<i4": "i",
"<u8": "Q",
"<i8": "q",
"<f4": "f",
"<f8": "d",
"<u1[64]": "64s",
}
return STRUCT_CONVERSION[dtype]


Expand All @@ -38,48 +50,50 @@ def write_tcp_send_functions(context):
f.write("# This file is autogenerated. Please do not edit\n")
f.write("import struct\n")
f.write("from .tcp_client import TcpClientBase\n")
protocol_selector_context = {'protocol_versions_list': []}
protocol_selector_context = {"protocol_versions_list": []}
for protocol_version in context.tcp_protocol:
# Store protocol versions and latest version for the class that selects versions
protocol_name = 'TcpClientV' + protocol_version['version']
protocol_selector_context['protocol_versions_list'].append(protocol_name)
protocol_selector_context['latest_protocol_version'] = protocol_name
protocol_name = "TcpClientV" + protocol_version["version"]
protocol_selector_context["protocol_versions_list"].append(protocol_name)
protocol_selector_context["latest_protocol_version"] = protocol_name

# Write the protocol version class
class_template_context = protocol_version
class_template_context['protocol_name'] = protocol_name
class_template_context["protocol_name"] = protocol_name
protocol_class = context.template_environment.get_template(
"protocol_class.template").render(class_template_context)
"protocol_class.template"
).render(class_template_context)
f.write(protocol_class)

# Write the protocol commands of the version class
for command in protocol_version["commands"]:
template_context = command
if 'fields' in command:
if "fields" in command:
format_string = ""
for field in command['fields']:
format_string += dtype_to_format_char(field['dtype'])
template_context['format_string'] = format_string
if 'returned_fields' in command:
for field in command["fields"]:
format_string += dtype_to_format_char(field["dtype"])
template_context["format_string"] = format_string
if "returned_fields" in command:
return_format_string = "<"
read_size = 0
for field in command['returned_fields']:
return_format_string += dtype_to_format_char(field['dtype'])
for field in command["returned_fields"]:
return_format_string += dtype_to_format_char(field["dtype"])
if field["dtype"] == "<u1[64]":
read_size += 64
else:
# get byte count from dtype of field
read_size += int(field['dtype'][2])
template_context['return_format_string'] = return_format_string
template_context['read_size'] = read_size
python_command = context.template_environment\
.get_template("send_command_function.template")\
.render(template_context)
read_size += int(field["dtype"][2])
template_context["return_format_string"] = return_format_string
template_context["read_size"] = read_size
python_command = context.template_environment.get_template(
"send_command_function.template"
).render(template_context)
f.write(python_command)

# Write the class that selects protocol version
protocol_selector_class = context.template_environment.get_template(
"protocol_selector_class.template").render(protocol_selector_context)
"protocol_selector_class.template"
).render(protocol_selector_context)
f.write(protocol_selector_class)


Expand Down
27 changes: 19 additions & 8 deletions generators/generate_udp_protocol.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pickle
import hashlib
from collections import OrderedDict


Expand All @@ -15,10 +13,21 @@ def md5(fname):


def generate():
data = json.loads(open("ProtocolDefinitions/udp_protocol.json").read(),
object_pairs_hook=OrderedDict)
protocol_md5 = md5("ProtocolDefinitions/udp_protocol.json")
generator_script_md5 = md5("generators/generate_udp_protocol.py")
# Get the directory of the current script
script_dir = os.path.dirname(os.path.abspath(__file__))

# Paths relative to the script directory
protocol_definitions_path = os.path.join(script_dir, "../ProtocolDefinitions/udp_protocol.json")
output_path = os.path.join(script_dir, "../legacyprotocol/blueye/legacyprotocol/udp_protocol_dict.py")

# Read and process the protocol definitions
with open(protocol_definitions_path, "r") as f:
data = json.loads(f.read(), object_pairs_hook=OrderedDict)

protocol_md5 = md5(protocol_definitions_path)
Copy link

Copilot AI Jun 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You compute protocol_md5 and generator_script_md5 but never inject them into data_file. Update the template to embed these checksums in the generated header so _json_hash and _generator_hash stay in sync.

Copilot uses AI. Check for mistakes.
generator_script_md5 = md5(os.path.abspath(__file__))

# Generate the output file content
data_file = f"""# -*- coding: utf-8 -*-
# This file is autogenerated. Please do not edit
from collections import OrderedDict
Expand All @@ -27,5 +36,7 @@ def generate():
protocol_data = {str(data)}

"""
with open(os.path.join("blueye", "protocol", "v2", "udp_protocol_dict.py"), "w") as f:

# Write the output file
with open(output_path, "w") as f:
f.write(data_file)
Loading