Premiers pas › Démarrer un projet

Premiers pas

Cette page n’est pas encore traduite : voici la version anglaise.

Starting a project: archec new

A model is easier to change than to start. archec new gives you one that already works — it passes archec check, its test runs in the simulator, and it can generate firmware — so the first thing you do is change names, not stare at an empty file.

archec new my-rover

In a terminal it asks four questions (five with kinds: the kind of project comes first):

  1. What do you build? A template: blank, rover-esp32, can-sensor-node or drone-fc (below), or owner/name, a template of the Arche Store.
  2. Which board? The template's, none, or one of the board store (archec board list).
  3. The main parts, and what talks to what. One a -> b per line: lidar -> planner, planner -> motors. A name alone is a part with no link yet. Parts that are not there are made (a component, a part, a port at each end and a message between them); a part the template has gets a new port.
  4. Generated code? none, c, embassy (Rust) or go. With a board and no deployment yet, a deployment is written: one firmware on the board, one thread running the parts.

It writes main.arche, a README.md with the commands to run next, and a .gitignore. Then:

cd my-rover
archec check main.arche     # no errors
archec test main.arche      # the template's test, in the simulator
code .                      # the VS Code extension draws it

Every step is checked as archec check does. A step that would add an error — a board whose ports the template's links do not find, say — is left out, and the output says so in a note.

Without questions#

The flags answer the questions; --yes takes the defaults for the others (which is also what happens when the input is not a terminal):

archec new my-node --template can-sensor-node --yes
archec new my-robot --board raspberry-pi-4 --parts "lidar -> planner, planner -> motors" --target go --yes
archec new my-drone --template drone-fc --board nucleo-f446re --name "Racer" --dry-run

--name names the project (my-drone gives project MyDrone "My drone"; the default is the folder's name). --dry-run prints the files and writes nothing.

The templates#

Template What it is Board
blank One component that blinks, a requirement, its test. The smallest project that does something. none (--board adds one)
rover-esp32 A wheeled rover: a remote, a distance sensor, a drive controller whose state machine stops before an obstacle, two motors. ESP32-DevKitC
can-sensor-node A temperature reported on CAN every 100 ms, a fault state for a bad reading, a gateway on the bus that resets it. NUCLEO-G474RE (STM32G4, FDCAN)
drone-fc A quadcopter flight controller: RC receiver, IMU, arming and failsafe, a 1 kHz rate loop, four ESCs. NUCLEO-H743ZI2

They are in lib/templates/ beside archec, each a library of kind template; archec store seed publishes them, so a store has them as arche/<id>. A template of your own is a library folder with kind template and a main.arche, published like any package.

Kinds#

--kind says what the project is. Then archec new does not stop at a model that checks: it ends with a repository that already runs.

archec new my-node --kind firmware
First run (firmware):
  ✓ archec check
  ✓ archec test — 3/3 pass
  ✓ archec gen --target c
  ✓ archec gen --target c-tests
  ✓ make (rover_fw)
  ✓ the test plans on the C (make test) — 1/1 pass
  ✓ rover_fw runs 0.5 s on this computer
✓ first run: simulated 500 ms, built (make), 4/4 tests pass, rover_fw runs 0.5 s on this computer

In a terminal, without --kind, the first question is What kind of project?: one of the kinds below, or a template or store package as before. With --describe, the kind is the one the words of the sentence name (firmware, esp32, stm32…), unless --kind says it.

What every kind gets:

  • Its template, its code language, its deployment. The kind starts from its own template (--template picks another), says the language of the behaviour (--handlers another), and writes the deployment its code needs when the project has none. Like every step, one that would add an error is left out with a note.
  • src/ and generated/. The code you fill in is generated into src/ and committed: write between its USER CODE BEGIN and USER CODE END markers, and each archec gen puts it back (regeneration). The rest is generated into generated/, which .gitignore leaves out, with what the build writes.
  • A test for each part. A part no test plan names gets a first case in tests.arche, which archec test runs in the simulator: a message a link brings it moves its state machine, or reaches it; what it sends reaches the part at the other end; or it starts in its first state. Change them to say what the part must do.
  • CI. .github/workflows/archec.yml and .gitlab-ci.yml at the root: the model's job of archec gen --target ci (archec check, archec ci), then a code job that generates the code, builds it and runs its tests, with the tools it needs set up.
  • VS Code. .vscode/extensions.json recommends arche-lang and the extensions for the code; .vscode/tasks.json has build (generate, then build), test (the simulator, then the code's tests) and archec: live. When the folder opens, the extension offers to start archec live.
  • The first run. Once written: archec check, archec test (the test plans in the simulator), archec gen of each target, the kind's build, its tests — each step said, then one line. A tool this computer does not have, or a daemon that is not running (podman, Docker), skips its step with a note. A step that fails is shown with the end of its output, and archec new exits 1: on a project it just made, that is a bug in archec. --no-run skips it; archec new --first-run [dir] runs it again later (the kind is in .archec/kind.json).
Kind What you get Code Built and tested with
firmware Code for a microcontroller board: the model's parts placed on the board, its firmware generated. Template rover-esp32; --board another, --target c (default), embassy or go. src/c/ (or src/embassy/, src/go/), one folder per binary; generated/c-tests/ C: make (the POSIX runtime, on this computer), the binary run for half a second, the test plans as C unit tests (make test). Go: go build, go vet. Rust: cargo check when the board's target is installed (rustup target list --installed); an ESP32's Xtensa target needs Espressif's toolchain, so it is skipped.
service A backend service in Go on a Linux or macOS host, no board. Template web-service: api -> orders -> store, HTTP routes, a store machine. src/go/<project>/ (with http.go, the routes); generated/openapi/ go build, go vet, go test (each route answers); the program serves HTTP.
containers Several programs, each in its own container, talking over the network. Template container-pipeline: ingest -> processor -> dashboard. src/go/<program>/ per container; generated/containerd/, generated/k8s/ go build for the container's Linux, go vet, go test; the compose file and the Kubernetes manifests read offline; podman build of each image when podman (or Docker) is running.
ros2 A robot as ROS 2 nodes, with a simulated world closing the loop. Template ros2-rover: lidar, planner, motors. src/ros2*/ (packages, nodes, launch); generated/c-tests/, generated/gazebo/ make test (the C tests), a static check of the packages; with ROS 2: colcon build, then the nodes launched against the world. CI runs it in ros:jazzy-ros-base.
desktop A program on a PC: parts on threads, an operator screen. Template desktop-app: a source, a limit monitor, a console. src/c/, src/ui-qml/<Ui>/; generated/cmake/, generated/c-tests/, generated/ui-web/ CMake, the C tests, the program run for a second; the Qt screen built and started off screen when Qt 6 is there.
library Components others include: a store package and a C library. Template component-library: a threshold, a latch, an example system. src/c/; CMakeLists.txt (yours); generated/c-tests/ CMake, ctest (a test per case), and archec store publish . --dry-run: the package as published compiles.

A web service (--kind service)#

archec new my-shop --kind service
archec new my-shop --kind service --parts "api -> orders, orders -> store, store -> billing"

A backend service in Go, one process on a Linux or macOS host; no board. It starts from the template web-service: three modules as parts, api -> orders -> store. api answers HTTP (POST /orders, GET /orders/count, DELETE /orders), orders checks each order, store keeps the ones accepted. Four requirements, and a test plan the simulator passes. The behaviour is in the action language (handlers arche): Arche's simulator runs it, and archec translates it to Go.

What archec new adds and makes:

  • the deployment: a Host processor (compute { os linux }) in a Machine platform, and deployment OnHost with one binary, language go, one thread that runs the parts — their messages and HTTP calls one at a time;
  • src/go/<project>/ (archec gen --target go -o src, committed): the Go program. parts.go (each part: its variables, its functions, its state machine), main.go (the parts wired as the model links them, a goroutine per thread), http.go (a route per handler GET "/path" -> fn of the model) and http_test.go (a test of each route), all regenerated. Your code goes in the USER CODE regions (<Part>.init, and the body of a function the model gives none) and in user.go, written once; archec gen keeps both;
  • generated/openapi/<Component>.json (--target openapi): the same API as OpenAPI 3.1, for clients and docs;
  • the first run: go build (the binary into generated/bin/), go vet, go test. Without Go on the PATH these are skipped with a note.

Run it:

cd src/go/my_shop
go run .                                   # listens on :8080; ADDR=:9000 for another
curl -X POST -d '{"item":7,"qty":3}' localhost:8080/orders
curl localhost:8080/orders/count

A route takes its function's parameters from the path (/items/{id}), else from the query (GET, DELETE, HEAD) or a JSON body (POST, PUT, PATCH), and answers the function's return as JSON (a struct with the model's field names), or 204 when it returns nothing; a parameter that does not parse is a 400. The call runs on its part's thread, as a message would, so a part never runs two things at once. The port is the one of the part's attr url (the template: http://localhost:8080), unless ADDR says another.

To add a route, add a handler to a port of a part, and its function:

in http: ethernet "HTTP" {
  handler GET "/orders/{item}" -> stock_of
}
fn stock_of "Stock of an item" (item: uint32) -> uint32 {
  ```arche
  return stock
  ```
}

then archec gen main.arche --target go -o src and --target openapi -o generated.

Limits: the simulator does not run HTTP; the test plan sends the messages the routes lead to (send(api.orders, PLACE(…))). The route test checks that each route answers with its status, not what it answers: write your own *_test.go beside it for that. A function the model declares without a body is a USER CODE region in Go, but Arche's simulator stops on a call to it: give it a body in the model when a test calls it.

Processes in containers (--kind containers)#

Several processes, each in its own container, talking over the network. The template, container-pipeline, is a pipeline of three: ingest takes a reading every second, processor summarises it, dashboard shows the summary and goes to Alarm when a reading is over LIMIT.

archec new my-pipes --kind containers

What you get:

  • A model that simulates. Three components, their messages, three requirements and a test plan. The deployment puts each part in a container on a Linux server (runtime containerd), as a Go program; archec test runs the template's cases and one for the deployment: a reading every second.
  • The code, yours to fill in, in src/go/<part> (--target go). Each part is a Go program: its variables, its ```arche bodies translated, its state machine, a USER CODE region in each constructor and in each function the model gives no body, and user.go, written once. A link between two parts is JSON over HTTP (net.go): each program listens on its own port, from 9001 in the order of the deployment, and reaches the others on 127.0.0.1. ARCHE_LISTEN (:8080) and ARCHE_PEER_<PART> (http://processor:8080) move them. net_test.go tests both ends.
  • The images and the manifests, in generated/ (regenerated, not committed). generated/containerd: a Containerfile per container, which takes the program from bin/<part>, and containers/server/compose.yaml. generated/k8s/k8s: a Kubernetes Deployment per container, for kubectl apply -k. The containers use the host's network, so the same ports work on one machine, under compose, and on a node of a cluster.

The first run builds each program for the server's Linux into generated/containerd/bin, runs go vet and go test on each, checks the compose file (docker-compose config) and the manifests (kubectl kustomize) without a daemon or a cluster, and builds the images with podman when it reaches its machine (else it says podman machine start, and goes on).

Run it on your machine, one terminal per part (cd src/go/ingest && go run .), or in containers:

podman compose -f generated/containerd/containers/server/compose.yaml up

The server's isa is the machine's that made the project (arm64 on an ARM Mac), so the images run there. For an x86 server, write isa x86_64: the programs and the images are then built for linux/amd64. Under podman on an ARM Mac those do not run (Go's runtime crashes under emulation); build them in CI or on the server.

A part you add (--parts "processor -> archive") gets its own container and program. A part with no in port runs its first function with no parameters every second; the others run on the messages they receive. A part with no behaviour yet is in no program: a link to it is left out until it does something.

ROS 2 robot (--kind ros2)#

archec new my-robot --kind ros2

A robot as ROS 2 nodes, from the template ros2-rover: a lidar driver, a planner that drives toward a wall, slower as it gets closer, and stops 0.3 m before it, and a motor driver that turns the speed into wheel speeds. The world is a plant (kind plant): it moves the rover by its wheel speeds and gives the lidar the distance left. The simulator runs it with the rest, so the test plan runs with nothing installed; it is not a node — on the robot, in Gazebo or in a test script, something else is the world.

What you get:

Deployment <Project>Ros2, packaging ros2: one binary <project>_nodes on a Linux computer, a process per part with behaviour that is not hardware (the plant is left out)
src/ (yours, committed) archec gen --target ros2: the message package <project>_msgs (src/ros2), the package of nodes src/ros2_nodes/<project>_nodes (an rclcpp executable per process around the part's C module), <project>_ros2_bringup with the launch file
generated/ (ignored) c-tests (the test plans as C unit tests of the same C modules), gazebo (the world for gz sim: SDF, bridge, adapter), colcon (colcon's build, install and log)
Tests the test plan in the simulator (archec test); make test in generated/c-tests; tests/ros2_check.py --static (each package.xml parses, the Python compiles, each launch file has generate_launch_description, the C modules compile); tests/ros2_check.py with ROS 2: the nodes launched, the script plays the world, the rover must stop between 0.25 and 0.35 m
Build colcon build --base-paths src into generated/colcon
CI the build and test steps in the ros:jazzy-ros-base image (ciContainer)

Your code in the nodes goes in their USER CODE regions, which archec gen keeps: in each src/<exe>.cpp the includes, the end of each node's constructor (<part>.node, a subscription to a real driver's topic, say), the node's members (<part>.members) and main before the threads start; in CMakeLists.txt one for more packages and sources; in each C module the includes and the end of its init. The behaviour itself is in the model.

No ROS 2 on the machine (macOS, say): colcon build and the live run are skipped with a note; the simulator, the C unit tests and the package checks still run. To build anyway:

podman run --rm -v "$PWD:/ws" -w /ws ros:jazzy-ros-base bash -c \
  '. /opt/ros/jazzy/setup.sh && colcon --log-base generated/colcon/log build --base-paths src --build-base generated/colcon/build --install-base generated/colcon/install && . generated/colcon/install/setup.sh && python3 tests/ros2_check.py'

The template row for the table of templates:

| ros2-rover | A rover as ROS 2 nodes: lidar driver, planner, motor driver; a simulated world closes the loop, and the rover stops before the wall. | none (a Linux computer) |

Desktop program (--kind desktop)#

A program on a PC — Linux, macOS or Windows: a controller tool, a test bench, a simulator's front end. The parts are C modules on a few threads of one program; the part with a ui { } is its screen.

archec new my-bench --kind desktop

It starts from the desktop-app template: a signal source that sends a sample every 10 ms, a monitor that goes High above a limit and logs each alarm, an operator console with a gauge and Start/Stop buttons, three requirements and the test plan that verifies them. Its deployment puts one program, app, on the PC: the source on a 10 ms thread, the monitor and the console on another. (A project that comes without a deployment gets one program with a thread per part.)

What you get:

Where What Committed
src/c/ the C of the parts (--target c): your code goes between the USER CODE BEGIN and END markers (Monitor.init, Monitor.includes, app.start in app/wiring.c, …) and archec gen keeps it yes
src/ui-qml/Console/ the screen as a Qt Quick application (--target ui-qml): main.cpp and CMakeLists.txt are yours, the QML is the model's yes
generated/cmake/ the CMake project of the program (--target cmake) no
generated/c-tests/ the test plan as C unit tests (--target c-tests) no
generated/ui-web/ the screen as a web page, the model simulated in it: open index.html no

The first run builds and runs it:

archec test main.arche                                      # the test plan, simulated
cmake -S generated/cmake -B build -DARCHE_GEN=src           # the C is in src/c
cmake --build build
make -C generated/c-tests test                              # 3 passed, 0 failed
./build/app/app 1000                                        # runs for a second, then exits
cmake -S src/ui-qml/Console -B build/ui-Console && cmake --build build/ui-Console
QT_QPA_PLATFORM=offscreen ARCHE_UI_QUIT_AFTER=1000 ./build/ui-Console/console_ui

-DARCHE_GEN=src is given with no type, so CMake makes it absolute from the folder you run it in. ./build/app/app with no number runs until Ctrl-C. The Qt steps need Qt 6 (qmake6 on PATH, and its CMake config); without it they are skipped with a note.

Limits:

  • make test runs the C that --target c-tests writes from the model, not your code in src/c/: what you write in a region is built and run by the program, not unit-tested.
  • The screen and the program are not connected yet: the Qt screen reads its values over a WebSocket (ARCHE_UI_WS, see ui), which the C program does not serve; the web page simulates the model itself.
  • On Windows with a multi-configuration generator (Visual Studio) the program is at build/app/Debug/app.exe, not where the first run looks.

Component library (--kind library)#

archec new my-sensors --kind library

A library of components that other projects include, in two forms: an Arche Store package (the model) and a C library built with CMake (the code). It starts from the component-library template:

File What it is
components.arche the library: its messages, requirements and components (a threshold with hysteresis, a latch)
main.arche the project, and a small system that uses the components (Example), with the test plan
library.arche the store's card: kind library, its name (my-sensors), version, owner, license, export control
CMakeLists.txt the CMake library my_sensors, its ports my_sensors_ports, a ctest test per case; yours, written once
src/c/ the C of each component (archec gen --target c -o src): your code between the USER CODE markers is kept
generated/c-tests/ the test plan as C (--target c-tests), which ctest runs on src/c

The first run checks the model, runs the test plan in the simulator, writes the C, builds it with CMake, runs ctest, then packs the folder as the store would and compiles it as the store's validator does (archec store publish . --dry-run: nothing is sent).

No deployment: a library does not run anywhere by itself. --target c writes the components without one; what each component sends goes through a function in src/c/<component>_ports.c, which the project using the library writes (the file is a start: it encodes the message and leaves the sending to you). The C tests carry the messages between the example's parts themselves.

Using the C in another CMake project:

add_subdirectory(path/to/my-sensors)
target_link_libraries(app PRIVATE my_sensors)   # and my_sensors_ports, or your own send functions

Publishing: set owner, license and export-control in library.arche (the template writes owner your-name, Apache-2.0 and none: they are your statements as publisher), then archec store check . and archec store publish .. A project takes it with archec add main.arche your-name/my-sensors and includes my-sensors/components.arche. What archec builds (generated/, arche-out/) is not part of the package.

Limits: the C goes with the package as you wrote it, but a project that includes the components and runs archec gen itself writes their C again, without the code in your regions; use the library's CMake target for the code.

Remixing a store package#

Any package of the store — a whole architecture, a template, a board — can be the start of a project:

archec new my-car --from arche/autonomous-car
archec new my-node --from acme/can-node@^1.2 --store https://store.example.com

It is a copy, not a dependency: the files are yours to change, and nothing updates them behind your back (archec add is for a library you use). The package is checked as archec add checks it — its hash, its publisher's signature, the files' fingerprint — and what it uses from the store comes along. The project records where it came from:

project MyCar "My car" {
  version "0.1.0"
  derived-from "arche/autonomous-car@0.3.0"
}

When you publish it, its library carries that line on, and the store's package page shows the family: what it was derived from, and what was derived from it.

In VS Code, a package of the Store view has Use as Starting Point (it asks for a folder and opens the new main.arche), and Arche: New Project… in the command palette asks the four questions above.

From a description#

archec new my-robot --describe "a robot with 2 motors, a lidar and a Raspberry Pi"

Claude writes the first model. It gets a short reference of the language, the boards of the board store and a template as an example; archec checks what comes back and sends the errors to Claude, which corrects them — up to four rounds (--rounds). The result is a first draft: read it before you trust it.

When the description has a display — a screen, a dashboard, an HMI, a touch panel, an OLED — Claude also gets the ui { } reference and is asked for the screens: the component that owns the display gets a ui bound to what the model sends it, which archec checks like the rest.

This sends your description, and archec check's messages about what Claude wrote, to the Anthropic API (the model claude-opus-5-5), and only when you run it. --dry-run prints the whole prompt and sends nothing. It needs ANTHROPIC_API_KEY.

Next#

  • The tutorial — what each line of the template means, from a water pump to its firmware.
  • Boards — archec board add and archec device add, to put a board or a sensor in later.
  • archec explain <code> — what a message of archec check means.