Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement examples & improved type interfaces & motion interface #4

Merged
merged 19 commits into from
Dec 6, 2024
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
4 changes: 2 additions & 2 deletions .github/workflows/ci-dev.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: ci-dev
name: Test, Lint and Format

on:
pull_request:
Expand Down Expand Up @@ -40,4 +40,4 @@ jobs:
- name: Run tests
run: |
cp envs/.env.tests envs/test_.env.tests
# poetry run pytest -rs -v
PYTHONPATH=. poetry run pytest -rs -v
37 changes: 37 additions & 0 deletions .github/workflows/pr-title-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
name: 'Validate PR Title'

on:
pull_request:
types: [opened, edited, reopened]

jobs:
validate-title:
runs-on: ubuntu-latest
steps:
- name: 'Validate PR Title'
shell: bash
run: |
PR_TITLE="${{ github.event.pull_request.title }}"
echo "PR Title: $PR_TITLE"

# Define the allowed pattern
PATTERN='^(chore|feat|fix)(!?)((\([A-Za-z0-9-]+\)))?: .+'

if [[ "$PR_TITLE" =~ $PATTERN ]]; then
echo "✅ PR title is valid."
else
echo "❌ PR title is invalid."
echo ""
echo "Allowed formats:"
echo "- chore: Description"
echo "- feat: Description"
echo "- fix: Description"
echo "- chore(scope): Description"
echo "- feat(scope): Description"
echo "- fix(scope): Description"
echo ""
echo "Scope can include letters, numbers, and hyphens."
echo "An optional '!' can be included before the ':' to indicate breaking changes."
exit 1
fi
51 changes: 51 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Release

on:
push:
branches:
- main

jobs:
release:
runs-on: ubuntu-latest
concurrency: release

permissions:
id-token: write
contents: write

steps:
# Note: we need to checkout the repository at the workflow sha in case during the workflow
# the branch was updated. To keep PSR working with the configured release branches,
# we force a checkout of the desired release branch but at the workflow sha HEAD.
- name: Setup | Checkout Repository at workflow sha
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.sha }}

- name: Setup | Force correct release branch on workflow sha
run: |
git checkout -B ${{ github.ref_name }} ${{ github.sha }}

- name: Action | Semantic Version Release
id: release
uses: python-semantic-release/python-semantic-release@v9.11.1
with:
build: true
github_token: ${{ secrets.GITHUB_TOKEN }}
git_committer_name: "github-actions"
git_committer_email: "actions@users.noreply.github.com"

- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_TOKEN }}
if: steps.release.outputs.released == 'true'

- name: Publish | Upload to GitHub Release Assets
uses: python-semantic-release/publish-action@v9.8.9
if: steps.release.outputs.released == 'true'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ steps.release.outputs.tag }}
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
# nova-python-sdk
# nova-py

This library provides an SDK for the Wandelbots NOVA API.

The SDK will help you to build your own apps and services on top of NOVA and makes programming a robot as easy as possible.

## Requirements

This library requires
* Python >=3.10

## Installation

To use the library, first install it using the following command

```bash
pip install nova-py
```

Then import the library in your code

```python
from nova import Nova, MotionGroup
```

## Development

To install the development dependencies, run the following command

```bash
poetry install
```

## Usage

Have a look at the [examples](/examples) directory to see how to use the library.
12 changes: 6 additions & 6 deletions examples/01_basic.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from wandelbots import use_nova_access_token, Controller
from decouple import config
import asyncio

from nova import Nova


async def main():
nova = use_nova_access_token()
controller = Controller(nova, cell=config("CELL_ID"), controller_host=config("CONTROLLER_HOST"))
nova = Nova()
cell = nova.cell()
controller = await cell.controller("ur")

# Connect to the controller and activate motion groups
async with controller:
motion_group = controller[0]
motion_group = controller.get_motion_group()

# Current motion group state
state = await motion_group.get_state("Flange")
Expand Down
36 changes: 8 additions & 28 deletions examples/02_plan_and_execute.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,31 @@
from wandelbots import use_nova_access_token, Controller
from wandelbots.types.trajectory import MotionTrajectory
from wandelbots.types.pose import Pose
from wandelbots.types.motion import ptp, jnt
from decouple import config
from nova import Nova, ptp, jnt, Pose
import asyncio
import numpy as np


async def run_wandelscript(file: str, variables: dict[str, str | int | float]) -> str:
pass


async def main():
nova = use_nova_access_token()
controller = Controller(nova, cell=config("CELL_ID"), controller_host=config("CONTROLLER_HOST"))
nova = Nova()
cell = nova.cell()
controller = await cell.controller("ur")

# Define a home position
home_joints = (0, -np.pi / 2, -np.pi / 2, -np.pi / 2, np.pi / 2, 0)

# Connect to the controller and activate motion groups
async with controller:
motion_group = controller[0]
motion_group = controller.get_motion_group()

# Get current TCP pose and offset it slightly along the x-axis
current_pose = await motion_group.tcp_pose("Flange")
# TODO: fix pose concatenation: target_pose = current_pose @ Vector3d(x=100, y=0, z=0)
target_pose = Pose(**current_pose.model_dump()).to_tuple()
target_pose = current_pose @ Pose((100, 0, 0, 0, 0, 0))

trajectory = MotionTrajectory(items=[jnt(home_joints), ptp(target_pose), jnt(home_joints)])
actions = [jnt(home_joints), ptp(target_pose), jnt(home_joints)]

# plan_response = await motion_group.plan(trajectory, tcp="Flange")
# print(plan_response)

motion_iter = motion_group.stream_move(trajectory, tcp="Flange")
async for motion_state in motion_iter:
print(motion_state)

result = await run_wandelscript("ws/move.ws", {"box_size": 20})
print(result)

await motion_group.run(actions, tcp="Flange")

"""
move via p2p() to home
...
python_function()
...
"""

if __name__ == "__main__":
asyncio.run(main())
42 changes: 22 additions & 20 deletions examples/03_plan_and_execute_ios.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,46 @@
from wandelbots import use_nova_access_token, Controller
from wandelbots.types.trajectory import MotionTrajectory
from wandelbots.types.pose import Pose
from wandelbots.types.motion import ptp, jnt
from decouple import config
from nova import Nova, Pose, ptp, jnt

# TODO: public interface
from nova.types.action import WriteAction
import asyncio
import numpy as np


async def main():
nova = use_nova_access_token()
controller = Controller(nova, cell=config("CELL_ID"), controller_host=config("CONTROLLER_HOST"))
nova = Nova()
cell = nova.cell()
controller = await cell.controller("ur")

# Define a home position
home_joints = (0, -np.pi / 2, -np.pi / 2, -np.pi / 2, np.pi / 2, 0)

# Connect to the controller and activate motion groups
async with controller:
motion_group = controller[0]
motion_group = controller.get_motion_group()

# Get current TCP pose and offset it slightly along the x-axis
current_pose = await motion_group.tcp_pose("Flange")
# TODO: fix pose concatenation: target_pose = current_pose @ Vector3d(x=100, y=0, z=0)
target_pose = Pose(**current_pose.model_dump()).to_tuple()

trajectory = MotionTrajectory(
items=[
jnt(home_joints),
# Add action to path
ptp(target_pose),
jnt(home_joints),
]
)
target_pose = current_pose @ Pose.from_tuple((100, 0, 0, 0, 0, 0))
actions = [
jnt(home_joints),
# controller.write_on_path("digital_out[0]", value=False),
WriteAction(device_id="ur", key="digital_out[0]", value=False),
ptp(target_pose),
jnt(home_joints),
]

# io_value = await controller.read_io("digital_out[0]")

# plan_response = await motion_group.plan(trajectory, tcp="Flange")
# print(plan_response)

motion_iter = motion_group.stream_move(trajectory, tcp="Flange")
motion_iter = motion_group.stream_run(actions, tcp="Flange")
async for motion_state in motion_iter:
print(motion_state)

await motion_group.run(actions, tcp="Flange")
await motion_group.run(ptp(target_pose), tcp="Flange")


if __name__ == "__main__":
asyncio.run(main())
29 changes: 29 additions & 0 deletions examples/04_plan_and_execute_multiple_robots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from nova import Nova, ptp, jnt, Pose, Controller
import asyncio
import numpy as np


async def main():
nova = Nova()
cell = nova.cell()
ur = await cell.controller("ur")
kuka = await cell.controller("kuka")

await asyncio.gather(move_robot(ur), move_robot(kuka))


async def move_robot(controller: Controller):
home_joints = (0, -np.pi / 2, -np.pi / 2, -np.pi / 2, np.pi / 2, 0)

async with controller:
motion_group = controller.get_motion_group()

current_pose = await motion_group.tcp_pose("Flange")
target_pose = current_pose @ Pose((100, 0, 0, 0, 0, 0))
actions = [jnt(home_joints), ptp(target_pose), jnt(home_joints)]

await motion_group.run(actions, tcp="Flange")


if __name__ == "__main__":
asyncio.run(main())
15 changes: 15 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# nova-python-sdk examples

## Usage

Run an example by executing the following command:

```bash
PYTHONPATH=. poetry run python examples/<file>.py
```

For example:

```bash
PYTHONPATH=. poetry run python examples/01_basic.py
```
18 changes: 18 additions & 0 deletions nova/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from nova.core.nova import Nova, Cell
from nova.core.motion_group import MotionGroup
from nova.core.controller import Controller
from nova.types.pose import Pose
from nova.types.action import Action, lin, ptp, jnt, cir

__all__ = [
"Nova",
"Cell",
"MotionGroup",
"Controller",
"lin",
"ptp",
"jnt",
"cir",
"Action",
"Pose",
]
File renamed without changes.
Loading
Loading