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
38 changes: 21 additions & 17 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ ARG DEBIAN_FRONTEND=noninteractive
# Update package list and install necessary tools and dependencies
RUN apt-get update && \
apt-get install -y \
sudo \
git \
curl \
gnupg \
Expand All @@ -22,21 +23,24 @@ RUN apt-get update && \
libsqlite3-dev \
libreadline-dev \
libffi-dev \
libbz2-dev \
&& rm -rf /var/lib/apt/lists/*

# Install Python 3.12.1 from source
RUN curl -fsSL "https://www.python.org/ftp/python/3.12.1/Python-3.12.1.tgz" -o "Python-3.12.1.tgz" && \
tar -xzf "Python-3.12.1.tgz" --strip-components=1 -C /usr/local/src && \
cd /usr/local/src && \
./configure --enable-optimizations && \
make -j$(nproc) && \
make altinstall && \
ln -s /usr/local/bin/python3.12 /usr/local/bin/python3 && \
ln -s /usr/local/bin/pip3.12 /usr/local/bin/pip3 && \
cd / && \
rm -rf /usr/local/src "Python-3.12.1.tgz"

# Verify installation
RUN python3 --version && pip3 --version
libbz2-dev



# Make a 'dev' user
RUN echo 'root:dev' | chpasswd
RUN useradd -ms /bin/bash dev && echo 'dev:dev' | chpasswd && adduser dev sudo
RUN echo 'dev ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

COPY --chown=dev:dev .devcontainer/scripts/gu /usr/local/bin/gu
COPY --chown=dev:dev .devcontainer/scripts/.bashrc /home/dev/.bashrc

USER dev

# Install Python 3 using apt
RUN sudo apt-get install -y python3 python3-pip

# Install uv
RUN curl -LsSf https://astral.sh/uv/install.sh | sh


34 changes: 18 additions & 16 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
{
"name": "ASE-roverlib-python",
"name": "ASE-roverlib-python-dev",
"build": {
"dockerfile": "Dockerfile"
"dockerfile": "./Dockerfile",
"context": "..",
"options": [
"--network=host"
]
},
"customizations": {
"vscode": {
"extensions": [
"streetsidesoftware.code-spell-checker",
"ms-vscode.makefile-tools", // Makefile Tools extension
"dbankier.vscode-quick-select" // Quick select with cmd/ctrl+k "
]
}
"vscode": {
"extensions": [
"tamasfe.even-better-toml", // TOML syntax support
"dbankier.vscode-quick-select", // Quick select with cmd/ctrl+k "
"charliermarsh.ruff"
]
}
},
"capAdd": [
"SYS_PTRACE"
"runArgs": [
"--network=host"
],
"securityOpt": [
"seccomp=unconfined"
],
"forwardPorts": []
}
"remoteUser": "dev"
}

61 changes: 61 additions & 0 deletions .devcontainer/scripts/.bashrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# personalized PS1 prompt
PS1="\W \e[01;31m$\e[m "

# If not running interactively, don't do anything
case $- in
*i*) ;;
*) return;;
esac

# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000

# custom
alias python="python3"
alias l="ls -lah"
alias ..="cd .."

# git aliases
alias gs="git status"
alias gc="git commit -m "
alias ga="git add "
alias gp="git push"
alias gpl="git pull"
alias gl="git log --pretty=oneline"

# check the window size after each command and, if necessary
# update the values of LINES and COLUMNS.
shopt -s checkwinsize

# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"


# Alias definitions in bash_aliases
if [ -f ~/work/scripts/.bash_aliases ]; then
. ~/work/scripts/.bash_aliases
fi

# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
if ! shopt -oq posix; then
if [ -f /usr/share/bash-completion/bash_completion ]; then
. /usr/share/bash-completion/bash_completion
elif [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi
fi

export PATH=$PATH:/home/dev/.local/bin

if [ -f "$HOME/.cargo/env" ]; then . "$HOME/.cargo/env"; fi

21 changes: 21 additions & 0 deletions .devcontainer/scripts/gu
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash

STATUS=$(git status)

TEST=$(echo $STATUS | awk '{print $NF}')

if [ "$TEST" != "clean" ]; then
git add .
git status
echo -n "Sure you want to commit and push? (y/n) "
read INPUT
if [ "$INPUT" = "y" ]; then
echo -n "Commit message: "
read INPUT
git commit -m "$INPUT"
git push
else
echo "cancelled"
fi
fi

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,6 @@ cython_debug/
.pypirc
h
.vscode

.ruff_cache

1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
61 changes: 57 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
test:
python3 src/sample.py --debug

testnd:
python3 src/sample.py


# Since we are using uv to manage our dependencies, we can utilize the "uv run python ..."
# command which makes sure to launch the python script with the dependencies installed as
# defined by the pyproject.toml file. That makes it the single source of truth and we
# can generate a requirements.txt file from there.



# We always want to run lint checks before we build or test
lint:
@echo "Linting src and tests"
@uv run ruff check src tests

# This will generate a python package that can be published and installs it locally for
# testing. Note, that it installs it with the -e (editable) option which means that when
# changing any library files, you can immediately test them and don't have to re-install
# with 'uv build'. However if we want to publish, we must always build.
build: lint
@rm -rf dist/
@uv build
@uv pip compile pyproject.toml -o requirements.txt

# Add all test files into /tests they will all get run when this target is invoked
test: lint
@uv run pytest

# Open a python repl for quick debugging
repl:
uv run python


clean:
uv cache clean
rm -r .pytest_cache .ruff_cache .venv dist


check-publish-token:
@if [ -z "$(UV_PUBLISH_TOKEN)" ]; then \
echo "Error: UV_PUBLISH_TOKEN environment variable is not set"; \
exit 1; \
else \
echo "UV_PUBLISH_TOKEN is set"; \
fi



publish-test: check-publish-token build
@echo "Publishing to test.pypi.org"
@uv publish dist/* --index testpypi


publish: check-publish-token build
@echo "Publishing to pypi.org"
@uv publish dist/* --index pypi


78 changes: 7 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,9 @@
# roverlib-python
Building a service that runs on the rover? Then you'll need the roverlib. This is the variant for Python.
<h1 align="center"><code>roverlib-python</code> library</h1>
<div align="center">

<a href="https://ase.vu.nl/docs/category/roverlib-python">Documentation</a>
<br />
</div>
<br/>

# Requirements

In order to use roverlib, you will need the following 3 requirements:

- pyzmq
- loguru
- betterproto

these can be installed by running:

```bash
pip install pyzmq loguru betterproto
```

# Installation

To install roverlib-python, simply run:

```bash
pip install roverlib
```

# Usage

After installation, you can use roverlib as follows:

```python
import roverlib
import signal
import time

def run(service : Service, configuration : ServiceConfiguration):
speed, err = configuration.GetFloatSafe("speed")
if err is not None:
logger.error(err)

name, err = configuration.GetStringSafe("name")
if err is not None:
logger.error(err)

write_stream = service.GetWriteStream("motor_movement")
if write_stream is None:
return ValueError("WriteStream motor_movement not found")

err = write_stream.Write(
rovercom.SensorOutput(
sensor_id=2,
timestamp=int(time.time() * 1000),
controller_output=rovercom.ControllerOutput(
steering_angle=float(1),
left_throttle=float(speed),
right_throttle=float(speed),
front_lights=False
),
)
)

if err is not None:
logger.error(err)

return None


def on_terminate(sig : signal):
logger.info("Terminating")
return None


roverlib.Run(run, on_terminate)
```
50 changes: 50 additions & 0 deletions docs/00-usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Usage

TODO: add more roverlib-python specific documentation for end-users

After installation, you can use roverlib as follows:

```python
import roverlib
import signal
import time

def run(service : Service, configuration : ServiceConfiguration):
speed, err = configuration.GetFloatSafe("speed")
if err is not None:
logger.error(err)

name, err = configuration.GetStringSafe("name")
if err is not None:
logger.error(err)

write_stream = service.GetWriteStream("motor_movement")
if write_stream is None:
return ValueError("WriteStream motor_movement not found")

err = write_stream.Write(
rovercom.SensorOutput(
sensor_id=2,
timestamp=int(time.time() * 1000),
controller_output=rovercom.ControllerOutput(
steering_angle=float(1),
left_throttle=float(speed),
right_throttle=float(speed),
front_lights=False
),
)
)

if err is not None:
logger.error(err)

return None


def on_terminate(sig : signal):
logger.info("Terminating")
return None


roverlib.Run(run, on_terminate)
```
Loading