Skip to content

Latest commit

 

History

History
739 lines (534 loc) · 27.2 KB

File metadata and controls

739 lines (534 loc) · 27.2 KB

Instructions for developers

How to setup the developer environment

Important: when a new version of Robotics Academy is released it also updates the official RADI. Make sure to upgrade it using the following command: docker pull jderobot/robotics-academy:latest and docker pull jderobot/robotics-database:latest.

Before starting developing, please ensure that you have understood RoboticsAcademy architecture and where the different resources are placed. There are three different ways of developing in RA:

Using automatic script (recommended)

We provide an sh script that configures and runs automatically a developing environment:

  1. Clone RA repo
git clone --recurse-submodules https://github.com/JdeRobot/RoboticsAcademy.git -b <src-branch>
cd RoboticsAcademy/

You can ignore the -b arg if you want to start working from the main branch.

  1. Run the script with your desired config
./scripts/develop_academy.sh -r <link to the RAM repo/fork> -b <branch of the RAM repo> -i <humble>

If you don't provide any arguments, it will prepare a humble environment with the current stable branch of RAM. You may start working from that and then create the branch you need. You may access RA frontend at http://127.0.0.1:7164/academy/


If you need more information about the options available for launching the script, you can use:

./scripts/develop_academy.sh -h

Which will display a help message.

  1. Developing procedure

After running the script, the src folder will be created, which contains all the files of the RoboticsApplicationManager. You can create branches and commit normally to the RAM repo from inside that folder. For the rest of the changes, you can also work normally from the RoboticsAcademy folder, the contents of the src folder are automatically ignored.

Whenever you want to finish developing, you just can close the script with Crtl+C. It will take care of cleaning files so you can restart again without any additional config.

Note: For Apple M1/M2 Chip Users,Docker provides the feature in-built in docker Desktop to use Rosetta

Go to Settings > General : Enable Use Rosetta for x86_64/amd64 emulation on Apple Silicon

  • Use Rosetta for x86_64/amd64 emulation on Apple Silicon

Please look at the attached image for reference.

Screenshot 2024-05-01 at 10 35 55 PM

Some problems that can arise

It is possible that the first time you follow the instructions, a dependency may not be installed correctly, or it may not be added to the path for some reason.

One of the most frequent problems is that the frontend doesn't launch, you can solve it in two ways, the first one is to launch the frontend separately from another terminal:

cd /RoboticsAcademy
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 20
nvm use 20
npm install --global yarn
cd react_frontend/ && yarn install && yarn run dev

Another way to solve it is to try to delete the old RADI image and download it again.

Using Docker compose (Recommended for Windows users)

NOTE: If you are following this tutorial as a Windows users, please follow all these steps but using WSL (Linux kernel for Windows). Please visit the next link in case you don't have WSL installed.

Docker Compose is a tool for defining and running multi-container applications. It is the key to unlocking a streamlined and efficient development and deployment experience. Compose makes easy to manage services, networks, and volumes in a single, comprehensible YAML configuration file. Then, with a single command, you create and start all the services from your configuration file. In this YAML file we provide all the configurations needed for a smooth development experience, mainly ports and volumes. This method works by binding your local folder to the appropiate place inside a RoboticsBackend container, where all the dependencies are installed.

The steps for setting up a development environment using Docker Compose are:

  1. Install Docker Compose
sudo apt install docker-compose
  1. Clone RoboticsAcademy repo (or your fork) and create src folder
git clone --recurse-submodules https://github.com/JdeRobot/RoboticsAcademy.git -b <src-branch>
cd RoboticsAcademy/
  1. Clone RAM repo (or your fork) inside RA
git clone https://github.com/JdeRobot/RoboticsApplicationManager.git -b <src-branch> src

For the moment, the RAM folder MUST be called src, and the previous command takes care of that. You can create branches and commits from that folder without any issues.

  1. Creation commons.zip

In order for the front-end to build, you need to manually create the commons zip, that will be used to pass those files to the Robotics Backend.

# Prepare the commons zip file
cd common
cd console_interfaces
zip -r ../common.zip console_interfaces/
cd ..
cd gui_interfaces
zip -r -u ../common.zip gui_interfaces/
cd ..
cd hal_interfaces
zip -r -u ../common.zip hal_interfaces/
cd ../..
mv common/common.zip react_frontend/src/common.zip
  1. Build the REACT frontend. You must do this each time
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 20
nvm use 20
npm install --global yarn
cd react_frontend/ && yarn install && yarn run dev

Please take into consideration that the yarn run dev script will continously watch for changes in the frontend, so you should execute this commands in a separate terminal.

  1. Copy the desired compose config into the main RA folder
cp compose_cfg/<your desired compose cfg> docker-compose.yaml

Feel free to study the configs, and adapt/create new ones suitable for your needs.

NOTE: As a Windows user, if you are willing to use GPU acceleration, there is a docker-compose file prepared for that BUT ONLY AVAILABLE with Nvidia GPUs (and WSL). Visit the following links WSL + CUDA, WSL + Docker Desktop to set-up Nvidia CUDA on WSL.

  1. Start Docker Compose
docker-compose up

Now you can open the RoboticsAcademy folder in your preferred code editor and test the changes inside the docker without having to regenerate a new image. Please keep in mind that this method works using a given RoboticsBackend version as the base. The only difference for developing between RoboticsBackend versions is the ROS version (humble) and the branch of RoboticsInfrastructure. If you need to make changes in RI, we recommend that you follow this procedure.

After testing the changes, you can simply commit them from the RA repo. Please keep in mind that the changes in RAM inside the src folder won't be commited, as they are not part of RoboticsAcademy. To commit those changes, just get inside the src/ folder and work from there (remember, this is the RAM repo with another name).

  1. Stop docker compose
docker-compose down

When you finish developing, you can close the container with Ctrl+C, but after that, you must clean the environment executing the previous command, otherwise, some things may not work in the next execution.

Using Docker run: Only use this method if the ones before have failed.

If you are launching Robotics Academy this way you need to manually create the commons zip, that will be used to pass those files to the Robotics Backend.

# Prepare the commons zip file
cd common
cd console_interfaces
zip -r ../common.zip console_interfaces/
cd ..
cd gui_interfaces
zip -r -u ../common.zip gui_interfaces/
cd ..
cd hal_interfaces
zip -r -u ../common.zip hal_interfaces/
cd ../..
mv common/common.zip react_frontend/src/common.zip

You must compile the frontend each time using:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 20
nvm use 20
npm install --global yarn
cd react_frontend/ && yarn install && yarn run dev

You also have to download the Robotics Applictaion Manager using:

git clone https://github.com/JdeRobot/RoboticsApplicationManager.git -b humble-devel src

You have 2 ways of launching Robotics Academy with docker run:

  • Creating a new RADI. To see how to do it and why to use it, read how to generate a RADI.
  • Using the docker image: robotics-academy:latest

Then to launch Robotics Academy first you have to launch the database docker container. For example if you want to launch it from where you cloned Robotics Academy you can use the next command:

NOTE: If you are in another folder you may need to change the first part of the paths of the volume bindings (-v) to the correct path.

docker run --hostname my-postgres --name academy_db -d\
    -e POSTGRES_DB=academy_db \
    -e POSTGRES_USER=user-dev \
    -e POSTGRES_PASSWORD=robotics-academy-dev \
    -e POSTGRES_PORT=5432 \
    -d -p 5432:5432 \
    -v ./RoboticsInfrastructure/database/universes.sql:/docker-entrypoint-initdb.d/1.sql \
    -v ./database/exercises/db.sql:/docker-entrypoint-initdb.d/2.sql \
    -v ./database/django_auth.sql:/docker-entrypoint-initdb.d/3.sql \
    jderobot/robotics-database:latest

Now you can launch Robotics Academy using the followings commands:

NOTE: If you are in another folder you may need to change the first part of the paths of the volume bindings (-v) to the correct path.

  • Automatic GPU selection
docker run --rm -it $(nvidia-smi >/dev/null 2>&1 && echo "--gpus all" || echo "") --device /dev/dri -p 6080-6090:6080-6090 -p 7163:7163 -p 7164:7164 --link academy_db -v ./:/RoboticsAcademy -v ./src:/RoboticsApplicationManager jderobot/robotics-academy:latest
  • Automatic GPU selection (Without Nvidia)
docker run --rm -it --device /dev/dri -p 6080-6090:6080-6090 -p 7163:7163 -p 7164:7164 --link academy_db -v ./:/RoboticsAcademy -v ./src:/RoboticsApplicationManager jderobot/robotics-academy:latest
  • Only CPU
docker run --rm -it -p 6080-6090:6080-6090 -p 7163:7163 -p 7164:7164 --link academy_db -v ./:/RoboticsAcademy -v ./src:/RoboticsApplicationManager jderobot/robotics-academy:latest

How to use nvidia

When launching the developer script you can use the options -g to use the integrated graphics card or -n to use the nvidia graphics card. Before you start, make sure you have the NVIDIA Container Toolkit installed.

Now we will have to install the nvidia runtime to use it with our docker:

sudo apt-get update
sudo apt-get install -y nvidia-docker2

Now we will check if docker recognises nvidia as a new runtime (restarting the docker service to update the new configuration):

sudo systemctl restart docker
docker info | grep -i runtime

It will most likely not recognise it, so we will have to do it manually by editing or creating the /etc/docker/daemon.json file:

{
  "runtimes": {
    "nvidia": {
      "path": "nvidia-container-runtime",
      "runtimeArgs": []
    }
  }
}

It is also possible that nvidia-runtime is not installed, check and install it if it is not.

dpkg -l | grep nvidia-container-runtime

If it is not installed:

sudo apt-get install -y nvidia-container-runtime

Now everything should be ready to start using nvidia with our dockers, restart the docker service to update the configuration and check that everything works correctly.

sudo systemctl restart docker

How to execute the tests

In order to execute the Robotics Academy test you must have launched RoboticsAcademy beforehand and then follow the next steps:

  1. In another terminal enter inside the react_frontend directory and execute npm run test.
  2. In the new open window select "E2E Testing".
  3. Select your prefered browser. RoboticsAcademy officially supports Chrome and Firefox.
  4. Click on the green button "Start E2E Testing in ...".
  5. It will open a new window in the selected browser. Now select one of the available test and let it execute.

You must mantain the window were the tests are executing always visible, because some tests fail if it is not.

How to add a new exercise

To create a new exercise you must complete this 2 sections:

Create the Exercise Folder with the source code and frontend

Create a folder with the folder name as "exercise_id" at the location from repository root : "exercises".

Inside that folder create 2 or more new ones with the following names:

  • <language>_template: the available languages are python and cpp. One for each supported language.
  • cpp_lib: used for precompilation of additional C++ libaries like WebGUI or HAL.
  • frontend

You may add a teaser to the exercise by creating a file called teaser.png using a 9/10 aspect ratio.

Source code: inside python_template

An exercise must contain this 3 files:

  • WebGUI.py: used for the interactions between exercise and frontend.
  • HAL.py: Hardware Abstraction Layer for accesing the robot data.
  • Frequency.py: needed for frequency control of iterative code.

There are a three python packages to help the development of a new exercise:

For knowing how to use each package, please follow the links in the list above.

Source code: inside cpp_template

An exercise must contain this 3 files and a folder:

  • main.cpp: used as the entrypoint for launching the WebGUI, HAL, console control and the user code. Must contain the next code:
#include "HAL.hpp"
#include "WebGUI.hpp"
#include "academy.cpp"
#include "rclcpp/rclcpp.hpp"
#include <bits/stdc++.h>
#include <filesystem>
#include <string>
#include <thread>

void start_console()
{
  int virtual_terminal = 0;
  for (const auto &entry : std::filesystem::directory_iterator("/dev/pts/"))
  {
    std::filesystem::path outfilename = entry.path();
    std::string filename = outfilename.filename().string();
    if (filename != "ptmx" && std::stoi(filename) > virtual_terminal)
    {
      virtual_terminal = std::stoi(filename);
    }
  }

  const std::string v_terminal_str = "/dev/pts/" + std::to_string(virtual_terminal);

  if (freopen(v_terminal_str.c_str(), "w", stdout) == NULL)
  {
    std::cerr << "Error redirecting stdout!" << std::endl;
  }

  if (freopen(v_terminal_str.c_str(), "w", stderr) == NULL)
  {
    std::cerr << "Error redirecting stderr!" << std::endl;
  }

  if (freopen(v_terminal_str.c_str(), "w", stdin) == NULL)
  {
    std::cerr << "Error redirecting stdin!" << std::endl;
  }
};

int main(int argc, char *argv[])
{
  rclcpp::init(argc, argv);
  start_console();

  rclcpp::executors::MultiThreadedExecutor executor(rclcpp::ExecutorOptions(), 2);

  auto HAL_node = std::make_shared<HAL>();
  executor.add_node(HAL_node);

  auto WebGUI_node = std::make_shared<WebGUINode>();
  executor.add_node(WebGUI_node);

#ifdef USER_NODE
  auto user_node = std::make_shared<UserNode>();
  executor.add_node(user_node);
#else
  std::thread user(exercise);
#endif
  std::thread ros([&executor]{executor.spin();});
  WebGUI();

#ifndef USER_NODE
  user.join();
#endif
  ros.join();

  rclcpp::shutdown();
  return 0;
}
  • package.xml: Package description of the ROS package.
  • CMakeLists.txt: needed for compilation of the ROS package.
  • libs/: needed for storing the libraries for user access.

Source code: inside cpp_lib

WORK IN PROGRESS

This directory contains the source code for the C++ libraries WebGUI, HAL, Frequency and others.

It must contain:

  • src/: contains the source code for at least HAL, WebGUI and Frequency.
  • include/: contains the headers for the source code.
  • CMakeLists.txt: needed for compilation of the libraries.

This directory will not be accesible to the user and will only be used to compile the libraries.

To do that compilation you must launch the RADI using Robotics Academy as a volume (it is recommende to use the developer script) using the next command (in Linux):

docker exec -it cf17d87822efd8f7596d8c5dd274ef84789e7be57eaa6a9a78ad7d1e16dd0807 bash

Then inside the RADI navigate to the desired exercise like:

cd RoboticsAcademy/exercises/vacuum_cleaner

After being inside the exercise directory you must compile the libary inside the RADI using;

cd cpp_lib
mkdir build
cd build/
cmake ..
make
chmod 777 *.so

After compiling the libraries you must move them to the libs folder created in the cpp_template section.

mv *.so ../../cpp_template/libs/
cd ..
rm -r build/
# Copy the headers to the cpp_template directory
cp -r include/ ../cpp_template/libs/

Frontend: inside frontend

An exercise must contain the following files:

  • tsconfig.json: used for the tsconfig. Must contain the next code:
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "types": [
      "../../../react_frontend/svg.d.ts",
      "../../../react_frontend/png.d.ts",
      "../../../react_frontend/jpg.d.ts",
      "../../../react_frontend/zip.d.ts"
    ],
    "paths": {
      "Assets/*": ["../../../react_frontend/src/assets/*"],
      "Components/*": ["../../../react_frontend/src/components/*"],
      "Utils/*": ["../../../react_frontend/src/utils/*"],
      "Contexts/*": ["../../../react_frontend/src/contexts/*"],
      "Helpers/*": ["../../../react_frontend/src/helpers/*"],
      "Icons/*": ["../../../react_frontend/src/icons/*"],
      "Styles/*": ["../../../react_frontend/src/styles/*"],
      "Types/*": ["../../../react_frontend/src/types/*"],
      "Constants/*": ["../../../react_frontend/src/constants/*"],
      "Api": ["../../../react_frontend/src/api/index.ts"],
      "Routes": ["../../../react_frontend/src/routes/index.ts"],
      "*": ["../../../react_frontend/node_modules/*"]
    },
    "baseUrl": "./",
    "rootDirs": ["./", "../../../react_frontend/src"],
    "typeRoots": ["../../../react_frontend/node_modules/@types"],
    "declaration": true,
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "noImplicitAny": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "./",
    "eslint.config.mts",
    "../../../react_frontend/src",
    "../../../react_frontend/png.d.ts",
    "../../../react_frontend/jpg.d.ts",
    "../../../react_frontend/svg.d.ts",
    "../../../react_frontend/zip.d.ts"
  ]
}
  • eslint.config.mts: used for the eslint (Currently does not work). Must contain the next code:
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
import { defineConfig } from "eslint/config";

export default defineConfig([
  {
    files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
    plugins: { js },
    extends: ["js/recommended"],
    languageOptions: {
      globals: globals.browser,
      parserOptions: {
        project: "./tsconfig.json",
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  tseslint.configs.recommended,
  pluginReact.configs.flat.recommended,
  {
    settings: {
      react: {
        version: "detect",
      },
    },
  },
]);
  • WebGUI.tsx: used for the exercise frontend.

And should have the following:

  • updateCallback: will be called when the data is sent from the application. In here you must update the shown data. Obligatory
  • stateCallback: will be called when the state of the Robotics Backend is changed. In here you may unset shown images or reset variables when the application is reset.
  • canvasRef and resizeObserver: should be used when information displayed is dependant on the screen size. resizeObserver will be called when the size changes.
import React, { useState, useEffect } from "react";
import WebGUIImage from "Components/exercise/WebGUIImage";
import WebGUIContainer, {
  connectApplication,
} from "Components/exercise/WebGUIContainer";
import { useExercise } from "Contexts/ExerciseContext";
import { states } from "jderobot-commsmanager";

function WebGUI() {
  const exerciseContext = useExercise();
  const [image, setImage] = useState<string | undefined>(undefined);
  const [manager, setManager] = useState(exerciseContext.manager);

  useEffect(() => {
    setManager(exerciseContext.manager);
  }, [exerciseContext]);

  const updateCallback = (updateData: unknown) => {
    const data = updateData as any;
    const update = data.update;

    if (update.image) {
      const image = JSON.parse(update.image);
      setImage(`data:image/png;base64,${image.image}`);
    }
  };

  const stateCallback = (state: string) => {
    if (state === states.TOOLS_READY) {
      setImage(undefined);
    }
  };

  connectApplication(manager, updateCallback, stateCallback);

  return (
    <WebGUIContainer>
      <WebGUIImage id="gui_canvas" src={image} style={{ width: "100%" }} />
    </WebGUIContainer>
  );
}

export default WebGUI;

You may add as many TypeScript helper files needed inside a helper directory, and as many CSS files inside a css folder.

If there is need for additional resources such as images, you may add them inside a resources folder.

Add the exercise to the database

To add a exercise to the database you must use Django Web Admin:

  1. Launch the docker as normal.
  2. Access http://127.0.0.1:7164/admin/ on a browser and log in with "user" and "pass".
  3. Click on "add exercise" and fill the required fields specified below.
  4. Save the exercise clicking on the "Local Save" button.

An exercise entry in the database must include the following data:

  • exercise id: unique exercise identifier, must match the folder name
  • name: name to display on the exercise list
  • description: description to display on the exercise list
  • tags: an exercise must include at least one ROS tag ("ROS2"). The exercise will only be shown on the exercise list when the RoboticsBackend ROS version installed is listed in the tags. Tags are also used by the search bar.
  • status: changes the state indicator (ACTIVE = green; PROTOTYPE = yellow; INACTIVE = red)
  • url: url of the exercise documentation

Steps to change models from CustomRobots in RoboticsAcademy exercises.