Skip to content

Webots

Webots is an open-source robot simulator. The phyelds-webots package runs phyelds programs on its robots: Webots moves the robots and simulates their radios, and phyelds computes fields on top of them.

pip install phyelds-webots

Install it in the Python that Webots runs the controllers with, which must be Python 3.12 or later. By default Webots runs python3 from the PATH; you can pick another one in the preferences of Webots, or in the runtime.ini of a controller. The controller module comes with Webots.


How it works

In Webots each robot runs its own controller, in its own process: nobody sees the whole swarm. So each robot is a device that talks over a radio, as a board does (see MicroPython and the ESP32). phyelds.webots.AggregateController runs the rounds of a robot on phyelds.vm.device.Device:

  • Devices: each robot is a device, identified by its name. Robot names must be unique.
  • Rounds: run_round() runs the program once, and returns its result. Call it after each robot.step.
  • Neighbors: the robots whose Receiver the Emitter of the robot reaches. Its range field sets the communication range; an infra-red emitter also needs a line of sight.
  • Synchronous rounds: Webots delivers a message in the step after it was sent. With a round at every step of every robot, a robot receives in each round what its neighbors sent in the previous one, as in the phyelds simulator. A neighbor that stops sending is forgotten at the next round.
  • Sensors: every robot senses time (the simulation time) and, if you pass the name of a GPS device with gps=, its position. sensors= adds more, from a function called at each round.
  • Outputs: what the program stores is in outputs; the controller applies it to the robot (motors, LEDs).

The robot needs an Emitter and a Receiver, by default named emitter and receiver (emitter= and receiver= choose others).


Example

Five robots stand in a line, 10 cm apart, and their radio reaches 15 cm. Each robot computes how many hops it is from the robot named source, and lights its LED when it is close to it:

@aggregate
def hops_to_source():
    """Hops to the source; the LED shows the area around it."""
    distance = distance_to(sense("source"), hops_distance())
    store("led", distance < 2)
    return distance


def main() -> None:
    """Run a round at every step, and apply the outputs to the robot."""
    robot = Robot()
    is_source = robot.getName() == "source"
    aggregate_controller = AggregateController(
        robot, hops_to_source, sensors=lambda: {"source": is_source}
    )
    led = robot.getDevice("led")
    last = None
    while robot.step(int(robot.getBasicTimeStep())) != -1:
        hops = aggregate_controller.run_round()
        led.set(int(aggregate_controller.outputs["led"]))
        if hops != last:
            print("phyelds", robot.getName(), hops, flush=True)
            last = hops

The world and the controller are in docs/examples/webots: open worlds/line.wbt in Webots to run it. The tests run the same world in Webots, headless.


Notes

  • Messages are bytes: they are encoded with the wire format, so what travels must be something the codec knows. Values of your own dataclasses need register.
  • Compact paths: paths="compact" makes the messages smaller (see compact paths). All the robots must use the same mode.
  • Headless runs: webots --batch --mode=fast --no-rendering --minimize --stdout --stderr world.wbt runs a world without a window, for instance in CI under xvfb-run.