diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..82542ba --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,73 @@ +name: Build snap +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - ros_distro: humble + # - ros_distro: jazzy + + # outputs: + # snap-file: ${{ steps.build-snap.outputs.snap }} + + steps: + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-tags: true + + - name: Render snapcraft.yaml + run: | + pip install jinja2 + export ROS_DISTRO=${{ matrix.ros_distro }} + ./render_template.py ./snapcraft_template.yaml.jinja2 snap/snapcraft.yaml + + - name: Build snap + uses: snapcore/action-build@v1 + with: + snapcraft-channel: latest/edge + id: build-snap + env: + SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: 1 + + - name: Make sure the snap is installable + run: | + sudo snap install --dangerous ${{ steps.build-snap.outputs.snap }} + + # # Save snap for subsequent job(s) + # - uses: actions/upload-artifact@v3 + # with: + # name: husarion-camera-snap + # path: ${{ steps.build-snap.outputs.snap }} + + # publish: + # if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') + # needs: build + # runs-on: ubuntu-latest + + # steps: + + # # Retrieve the snap + # - uses: actions/download-artifact@v3 + # with: + # name: husarion-camera-snap + # path: . + + # # Publish the snap on the store + # # by default on 'edge' but on 'candidate' for tags + # - uses: snapcore/action-publish@v1 + # env: + # SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }} + # with: + # snap: ${{needs.build.outputs.snap-file}} + # release: ${{ startsWith(github.ref, 'refs/tags/') && '${{ matrix.ros_distro }}/candidate' || '${{ matrix.ros_distro }}/edge'}} \ No newline at end of file diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..8bcc69e --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,61 @@ +name: Build and publish snap +on: + push: + tags: + - '*' + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.runner }} + + strategy: + fail-fast: false + matrix: + include: + - ros_distro: humble + runner: ubuntu-latest + # - ros_distro: jazzy + # runner: ubuntu-latest + - ros_distro: humble + runner: ubuntu-24.04-arm64 + # - ros_distro: jazzy + # runner: ubuntu-24.04-arm64 + + steps: + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-tags: true + + - name: Render snapcraft.yaml + run: | + sudo apt update + sudo apt install python3-jinja2 + export ROS_DISTRO=${{ matrix.ros_distro }} + ./render_template.py ./snapcraft_template.yaml.jinja2 snap/snapcraft.yaml + + - name: Build snap + uses: snapcore/action-build@v1 + with: + snapcraft-channel: latest/edge + id: build-snap + env: + SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: 1 + + - name: Make sure the snap is installable + run: | + sudo snap install --dangerous ${{ steps.build-snap.outputs.snap }} + + # Publish the snap on the store + # by default on 'edge' but on 'candidate' for tags + - name: Publish snap + uses: snapcore/action-publish@v1 + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }} + with: + snap: ${{ steps.build-snap.outputs.snap }} + release: ${{ matrix.ros_distro }}/${{ startsWith(github.ref, 'refs/tags/') && 'candidate' || 'edge' }} diff --git a/.github/workflows/snap.yaml b/.github/workflows/snap.yaml deleted file mode 100644 index 87a333c..0000000 --- a/.github/workflows/snap.yaml +++ /dev/null @@ -1,61 +0,0 @@ -name: snap -on: - push: - tags: - - '*' - branches: - - main - pull_request: - branches: - - main - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - outputs: - snap-file: ${{ steps.build-snap.outputs.snap }} - steps: - - - uses: actions/checkout@v3 - with: - fetch-tags: true - - # Build the snap - - uses: snapcore/action-build@v1 - with: - snapcraft-channel: latest/edge - id: build-snap - env: - SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: 1 - - # Make sure the snap is installable - - run: | - sudo snap install --dangerous ${{ steps.build-snap.outputs.snap }} - - # Save snap for subsequent job(s) - - uses: actions/upload-artifact@v3 - with: - name: rosbot-xl-snap - path: ${{ steps.build-snap.outputs.snap }} - - publish: - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') - needs: build - runs-on: ubuntu-latest - steps: - - # Retrieve the snap - - uses: actions/download-artifact@v3 - with: - name: rosbot-xl-snap - path: . - - # Publish the snap on the store - # by default on 'edge' but on 'candidate' for tags - - uses: snapcore/action-publish@v1 - env: - SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }} - with: - snap: ${{needs.build.outputs.snap-file}} - release: ${{ startsWith(github.ref, 'refs/tags/') && 'candidate' || 'edge'}} diff --git a/render_template.py b/render_template.py new file mode 100644 index 0000000..0d6780f --- /dev/null +++ b/render_template.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +import sys +import os +from jinja2 import Environment, FileSystemLoader + +def render_template(template_path, output_path, context): + env = Environment(loader=FileSystemLoader(os.path.dirname(template_path))) + template = env.get_template(os.path.basename(template_path)) + + with open(output_path, 'w') as f: + f.write(template.render(context)) + +if __name__ == "__main__": + template_path = sys.argv[1] + output_path = sys.argv[2] + context = { + 'ros_distro': os.getenv('ROS_DISTRO'), + # 'core_version': os.getenv('CORE_VERSION') + } + + render_template(template_path, output_path, context) \ No newline at end of file diff --git a/snapcraft_template.yaml.jinja2 b/snapcraft_template.yaml.jinja2 new file mode 100644 index 0000000..f22912b --- /dev/null +++ b/snapcraft_template.yaml.jinja2 @@ -0,0 +1,416 @@ +name: rosbot-xl +adopt-info: rosbot-xl +license: Apache-2.0 +summary: A driver for ROSbot XL mobile robot +icon: snap/gui/rosbot-xl.png +description: | + The **rosbot-xl** snap includes all the essential software required to operate the ROSbot XL, including the controller, robot state publisher, and more. + + **Installation** + + To install this snap on the Single Board Computer (SBC) within the ROSbot XL chassis, follow these steps: + + 1. Connect the SBC to the Digital Board inside ROSbot XL using an Ethernet cable. + 2. Configure the Ethernet Interface with the static IP address `192.168.77.2` (port `8888` on this IP is used by the firmware). + 3. Run the following command to install the snap: + + snap install rosbot-xl + + **Parameters** + + The snap provides the following configurable parameters (`param name`: `default value`): + + * `configuration`: `basic` - presets for ROSbot XL + * `driver`: `{...}` + * `ros`: `{...}` + * `webui`: `{...}` + + The `ros` contains the following keys: + + * `ros.domain-id`: `0` - Sets the `ROS_DOMAIN_ID` environment variable for the ROS driver. + * `ros.localhost-only`: `0` - Sets the `ROS_LOCALHOST_ONLY` environment variable for the ROS driver. + * `ros.transport`: `udp` - Configures DDS transport. Options are `udp`, `shm`, `builtin` (or `rmw_fastrtps_cpp`), `rmw_cyclonedds_cpp`. Corresponding DDS XML files can be found in the `/var/snap/rosbot-xl/common` directory (custom FastDDS setups can also be created here). + * `ros.namespace`: `(unset)` - Namespace for all topics and transforms. + + The `driver` contains the following keys: + + * `driver.mecanum`: `True` - Enables the mecanum drive controller; otherwise, uses the differential drive controller. + * `driver.include-camera-mount`: `True` - Includes the camera mount in the robot URDF. + * `driver.camera-model`: `None` - Adds the camera model to the robot URDF. + * `driver.lidar-model`: `None` - Adds the LIDAR model to the robot URDF. + * `driver.db-serial-port`: `auto` - Serial port for firmware (e.g., `/dev/ttyUSB0`), or set it to `auto`. + * `driver.manipulator-serial-port`: `auto` - Serial port for OpenManipulator-X (e.g., `/dev/ttyUSB0`), or set it to `auto`. + + The `webui` contains the following keys: + + * `webui.layout`: `default` - Specifies the layout for the Web UI. Available `*.json` layout files can be found in the `/var/snap/rosbot-xl/common` directory (custom layouts can also be created here). + * `webui.port`: `8080` - Specifies the port for the built-in web server hosting the Web UI. + + To set parameters, use the snap set command, e.g., + + snap set rosbot-xl driver.mecanum=True + + **Available Apps (Main)** + + * `rosbot-xl.flash` - Flash firmware for the STM32F4 microcontroller. + * `rosbot-xl.start` - Start the daemon running the ROSbot XL ROS 2 driver. + * `rosbot-xl.stop` - Stop the daemon running the ROSbot XL ROS 2 driver. + * `rosbot-xl.start-web-ui` - Start the daemon running the Web UI (available at `http://:8080/ui` by default). + * `rosbot-xl.stop-web-ui` - Stop the daemon running the Web UI. + * `rosbot-xl.teleop` - Run the `teleop_twist_keyboard` node to control the robot from a terminal. + + **Auxiliary Apps** + + * `rosbot-xl.config-ftdi` - Set up the CBUS pins in the FTDI chip (used with RST and BOOT0 pins in STM32); needs to be done only once. + * `rosbot-xl.print-serial-number` - Print the CPU ID and serial number of ROSbot XL. + * `rosbot-xl.reset-stm32` - Reset the STM32F4 microcontroller. + + **Working with ROS Parameters** + + For multiple snaps running ROS 2 or host-snap-Docker communication, useful files and scripts are available in `/var/snap/rosbot-xl/common/`. + + Example usage: + + # Set parameters in the rosbot-xl snap + sudo snap set rosbot-xl transport=udp ros-domain-id=123 ros.namespace=abc + + # Mirror the setup for other ROS 2 snaps + sudo snap set husarion-depthai $(cat /var/snap/rosbot-xl/common/ros_snap_args) + sudo snap set husarion-rplidar $(cat /var/snap/rosbot-xl/common/ros_snap_args) + + # Set up the current shell with the same configurations + source /var/snap/rosbot-xl/common/ros.env + + # Install configurations with + /var/snap/rosbot-xl/common/manage_ros_env.sh + source ~/.bashrc + + ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args --remap __ns:=/${ROS_NAMESPACE} + +# Additional info: +# **Notes** + +# * `rosbot-xl.daemon` runs as the `root` user. To enable shared-memory communication (`ros-localhost-only=1` or `transport` set to `shm` or `builtin`), ensure the user on the host (or in Docker) is also `root`. +# * To run the ROS 2 driver in the current terminal session (and enable shared memory communication with a non-root user): + +# sudo rosbot-xl.stop +# rosbot-xl + +# * Display logs from the ROS 2 node with: + +# sudo snap logs rosbot-xl.daemon + +# * Display logs from the snap logger with: + +# journalctl -t rosbot-xl + +# * `rosbot-xl` snap by default uses the following ports: `3000` by the safe-shutdown service, `8888` by microROS Agent, `8080` by a Web UI. + +grade: stable +confinement: strict +base: {{ 'core22' if ros_distro == 'humble' else 'core24' }} + +contact: https://github.com/husarion/rosbot-xl-snap/issues +issues: https://github.com/husarion/rosbot-xl-snap/issues +website: https://husarion.com/manuals/rosbot-xl/overview/ + +architectures: + - build-on: amd64 + build-for: amd64 + # - build-on: amd64 + # build-for: arm64 + - build-on: arm64 + build-for: arm64 + +slots: + shm-slot: + interface: shared-memory + write: ['*'] # paths are relative to /dev/shm + +plugs: + shm-plug: + interface: shared-memory + shared-memory: shm-slot + private: false + +apps: + + daemon: + command: usr/bin/launcher.sh + command-chain: [usr/bin/ros_setup.sh] + daemon: simple + install-mode: enable + plugs: [network, network-bind, shm-plug, raw-usb] + slots: [shm-slot] + extensions: [ros2-{{ ros_distro }}-ros-base] + + db-server: + command: usr/bin/db_server_launcher.sh + daemon: simple + install-mode: enable + plugs: [network, network-bind, shutdown] + + rosbot-xl: + command: usr/bin/launcher.sh + command-chain: [usr/bin/check_daemon_running.sh, usr/bin/ros_setup.sh] + plugs: [network, network-bind, shm-plug, raw-usb] + slots: [shm-slot] + extensions: [ros2-{{ ros_distro }}-ros-base] + + flash: + command: usr/bin/flash_launcher.sh + plugs: [raw-usb, network-bind] + extensions: [ros2-{{ ros_distro }}-ros-base] + + teleop: + command: usr/bin/teleop_launcher.sh + command-chain: [usr/bin/ros_setup.sh] + plugs: [network, network-bind, shm-plug] + slots: [shm-slot] + extensions: [ros2-{{ ros_distro }}-ros-base] + + joy: + command: usr/bin/joy_launcher.sh + command-chain: [usr/bin/ros_setup.sh] + daemon: simple + install-mode: disable + environment: + LD_LIBRARY_PATH: "$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pulseaudio:$LD_LIBRARY_PATH" + plugs: [hardware-observe, joystick, network, network-bind, shm-plug] + slots: [shm-slot] + extensions: [ros2-{{ ros_distro }}-ros-base] + + teleop-twist-joy: + command: usr/bin/teleop_twist_joy_launcher.sh + command-chain: [usr/bin/ros_setup.sh] + daemon: simple + install-mode: enable + plugs: [network, network-bind, shm-plug] + slots: [shm-slot] + extensions: [ros2-{{ ros_distro }}-ros-base] + + arm-activate: + command: usr/bin/arm_activate_launcher.sh active + command-chain: [usr/bin/ros_setup.sh] + plugs: [network, network-bind, shm-plug] + slots: [shm-slot] + extensions: [ros2-{{ ros_distro }}-ros-base] + + arm-disactivate: + command: usr/bin/arm_activate_launcher.sh inactive + command-chain: [usr/bin/ros_setup.sh] + plugs: [network, network-bind, shm-plug] + slots: [shm-slot] + extensions: [ros2-{{ ros_distro }}-ros-base] + + print-serial-number: + command: usr/bin/serial_number_launcher.sh + command-chain: [usr/bin/ros_setup.sh] + plugs: [network, network-bind, shm-plug] + slots: [shm-slot] + extensions: [ros2-{{ ros_distro }}-ros-base] + + start: + command: usr/bin/start_launcher.sh + + stop: + command: usr/bin/stop_launcher.sh + + reset-stm32: + command: usr/bin/reset_stm32_launcher.sh + plugs: [raw-usb, network-bind] + + config-ftdi: + command: usr/bin/ftdi_eeprom_conf_launcher.sh + plugs: [raw-usb, network-bind] + + web-ui: + command: usr/bin/caddy_launcher.sh + daemon: simple + install-mode: disable + plugs: [network, network-bind] + + web-ws: + command: usr/bin/bridge_launcher.sh + command-chain: [usr/bin/ros_setup.sh] + daemon: simple + install-mode: disable + plugs: [network, network-bind, shm-plug] + slots: [shm-slot] + extensions: [ros2-{{ ros_distro }}-ros-base] + + start-web-ui: + command: usr/bin/start_web_ui_launcher.sh + + stop-web-ui: + command: usr/bin/stop_web_ui_launcher.sh + +parts: + # rosbot-xl: + # plugin: colcon + # source: https://github.com/husarion/rosbot_xl_ros.git + # source-branch: "0.11.5" + # build-packages: + # - python3-vcstool + # stage-packages: + # - stm32flash + # - libusb-1.0-0 + # - usbutils + # - ros-{{ ros_distro }}-rmw-cyclonedds-cpp + # override-pull: | + # craftctl default + + # vcs import $CRAFT_PART_SRC < $CRAFT_PART_SRC/rosbot_xl/rosbot_xl_hardware.repos + # cp -r $CRAFT_PART_SRC/ros2_controllers/diff_drive_controller $CRAFT_PART_SRC/ + # cp -r $CRAFT_PART_SRC/ros2_controllers/imu_sensor_broadcaster $CRAFT_PART_SRC/ + # rm -rf $CRAFT_PART_SRC/ros2_controllers + # rm -r $CRAFT_PART_SRC/rosbot_xl_gazebo + # # Ignore so that rosdep doesn't pull deps + # # and colcon doesn't build + # # touch $CRAFT_PART_SRC/rosbot_xl_gazebo/COLCON_IGNORE + + # # Set the snap version from the git tag + # # The grade is set to 'stable' if the latest entry in the git history + # # is the tag itself, otherwise set to devel + # version="$(git describe --always --tags| sed -e 's/^v//;s/-/+git/;y/-/./')" + # [ -n "$(echo $version | grep "+git")" ] && grade=devel || grade=stable + # craftctl set version="$version" + # craftctl set grade="$grade" + + rosbot-xl: + plugin: colcon + source: https://github.com/husarion/rosbot_xl_manipulation_ros.git + source-branch: "1.1.2" + build-packages: + - python3-vcstool + stage-packages: + - stm32flash + - libusb-1.0-0 + - usbutils + - ros-{{ ros_distro }}-rmw-cyclonedds-cpp + override-pull: | + craftctl default + + vcs import $CRAFT_PART_SRC < $CRAFT_PART_SRC/rosbot_xl_manipulation/rosbot_xl_manipulation.repos + vcs import $CRAFT_PART_SRC < $CRAFT_PART_SRC/rosbot_xl_ros/rosbot_xl/rosbot_xl_hardware.repos + vcs import $CRAFT_PART_SRC < $CRAFT_PART_SRC/open_manipulator_x/open_manipulator_x.repos + + cp -r $CRAFT_PART_SRC/ros2_controllers/diff_drive_controller $CRAFT_PART_SRC/ + cp -r $CRAFT_PART_SRC/ros2_controllers/imu_sensor_broadcaster $CRAFT_PART_SRC/ + rm -rf $CRAFT_PART_SRC/ros2_controllers + rm -r $CRAFT_PART_SRC/rosbot_xl_ros/rosbot_xl_gazebo + rm -r $CRAFT_PART_SRC/rosbot_xl_manipulation_gazebo + # Ignore so that rosdep doesn't pull deps + # and colcon doesn't build + # touch $CRAFT_PART_SRC/rosbot_xl_gazebo/COLCON_IGNORE + + # Set the snap version from the git tag + # The grade is set to 'stable' if the latest entry in the git history + # is the tag itself, otherwise set to devel + version="$(git describe --always --tags| sed -e 's/^v//;s/-/+git/;y/-/./')" + [ -n "$(echo $version | grep "+git")" ] && grade=devel || grade=stable + craftctl set version="$version" + craftctl set grade="$grade" + + teleop: + plugin: nil + stage-packages: + - ros-{{ ros_distro }}-teleop-twist-keyboard + - ros-{{ ros_distro }}-teleop-twist-joy + + # web-ui: + # plugin: nil + # build-packages: + # - curl + # build-environment: + # - CADDY_RELEASE: "2.7.4" + # override-build: | + # set -x + # craftctl default + # curl -L https://github.com/caddyserver/caddy/releases/download/v${CADDY_RELEASE}/caddy_${CADDY_RELEASE}_linux_${CRAFT_ARCH_BUILD_FOR}.deb -o caddy.deb + # dpkg -i caddy.deb + + web-server: + plugin: dump + source: + - on amd64: https://github.com/caddyserver/caddy/releases/download/v2.7.4/caddy_2.7.4_linux_amd64.deb + - on arm64: https://github.com/caddyserver/caddy/releases/download/v2.7.4/caddy_2.7.4_linux_arm64.deb + source-type: deb + + web-ui: + plugin: nil + build-packages: + - curl + override-build: | + # Use the Snapcraft build directory which is writable + cd $SNAPCRAFT_PART_BUILD + curl -L https://github.com/husarion/foxglove-studio/releases/download/v1.0.1/web-build.tar.gz -o foxglove.tar.gz + mkdir -p www/foxglove + tar -xzf foxglove.tar.gz -C www/foxglove + rm foxglove.tar.gz + mkdir -p $SNAPCRAFT_PART_INSTALL/usr/local + cp -r www $SNAPCRAFT_PART_INSTALL/usr/local + + web-ws: + plugin: nil + stage-packages: + - ros-{{ ros_distro }}-foxglove-bridge + - ros-{{ ros_distro }}-rosbridge-server + - ros-{{ ros_distro }}-bond + - ros-{{ ros_distro }}-control-msgs + - ros-{{ ros_distro }}-controller-manager-msgs + - ros-{{ ros_distro }}-image-transport-plugins + - ros-{{ ros_distro }}-map-msgs + - ros-{{ ros_distro }}-nav2-msgs + - ros-{{ ros_distro }}-tf2-msgs + - ros-{{ ros_distro }}-robot-localization + + yq: + plugin: nil + override-build: | + craftctl default + + YQ_VERSION="v4.35.1" + TARGETARCH=$CRAFT_ARCH_BUILD_FOR + curl -L "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_${TARGETARCH}" -o $CRAFT_PART_BUILD/yq + override-prime: | + craftctl default + + cp $CRAFT_PART_BUILD/yq $CRAFT_PRIME/usr/bin/yq + chmod +x $CRAFT_PRIME/usr/bin/yq + build-packages: + - curl + + db-server: + plugin: nil + stage-packages: + - dbus + - curl + - netcat + + pip3-dependencies: + plugin: python + source: . + build-packages: + - python3-pip + python-packages: + - pyftdi + + # copy local scripts to the snap usr/bin + local-files-ros: + plugin: dump + source: snap/local/local-ros/ + organize: + '*.sh': usr/bin/ + '*.xml': usr/share/rosbot-xl/config/ + + local-files: + plugin: dump + source: snap/local/ + organize: + '*.sh': usr/bin/ + '*.py': usr/bin/ + '*.yaml': usr/share/rosbot-xl/config/ + '*.xml': usr/share/rosbot-xl/config/ + '*.json': usr/share/rosbot-xl/config/ + 'Caddyfile': usr/share/rosbot-xl/config/