phyelds-webots 8.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Run phyelds programs on the robots of a [Webots](https://cyberbotics.com) simulation.
|
|
3
|
+
|
|
4
|
+
Each robot runs its own controller, so each robot is a device: its messages travel as bytes
|
|
5
|
+
from its ``Emitter`` to the ``Receiver`` of the robots in range.
|
|
6
|
+
"""
|
|
7
|
+
import json
|
|
8
|
+
from collections.abc import Callable, Mapping
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from phyelds.vm.device import Device
|
|
12
|
+
|
|
13
|
+
__all__ = ["AggregateController"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AggregateController:
|
|
17
|
+
"""
|
|
18
|
+
Runs an aggregate program on a Webots robot, one round per simulation step.
|
|
19
|
+
|
|
20
|
+
The device id is the name of the robot, so robots must have unique names. The neighbors
|
|
21
|
+
of a robot are the robots its ``Emitter`` reaches; Webots delivers a message in the step
|
|
22
|
+
after it was sent, so with a round at every step of every robot the rounds are
|
|
23
|
+
synchronous.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
robot: The ``controller.Robot`` of this controller.
|
|
27
|
+
program: The ``@aggregate`` function to run.
|
|
28
|
+
emitter: The name of the ``Emitter`` device of the robot.
|
|
29
|
+
receiver: The name of the ``Receiver`` device of the robot.
|
|
30
|
+
gps: The name of a ``GPS`` device, whose values become the ``position`` sensor.
|
|
31
|
+
sensors: Extra sensors, read at each round, added to ``time`` (the simulation time)
|
|
32
|
+
and ``position``.
|
|
33
|
+
paths: The path mode of the engine, ``"debug"`` or ``"compact"``: the same on
|
|
34
|
+
every robot.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ValueError: If the robot has no device with one of the given names.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__( # pylint: disable=too-many-arguments
|
|
41
|
+
self,
|
|
42
|
+
robot: Any,
|
|
43
|
+
program: Callable[[], Any],
|
|
44
|
+
*,
|
|
45
|
+
emitter: str = "emitter",
|
|
46
|
+
receiver: str = "receiver",
|
|
47
|
+
gps: str | None = None,
|
|
48
|
+
sensors: Callable[[], Mapping[str, Any]] | None = None,
|
|
49
|
+
paths: str = "debug",
|
|
50
|
+
):
|
|
51
|
+
self.robot = robot
|
|
52
|
+
self.sensors = sensors
|
|
53
|
+
self._device = Device(robot.getName(), program, paths=paths)
|
|
54
|
+
self._emitter = _get_device(robot, emitter)
|
|
55
|
+
self._receiver = _get_device(robot, receiver)
|
|
56
|
+
self._gps = None if gps is None else _get_device(robot, gps)
|
|
57
|
+
time_step = int(robot.getBasicTimeStep())
|
|
58
|
+
self._receiver.enable(time_step)
|
|
59
|
+
if self._gps is not None:
|
|
60
|
+
self._gps.enable(time_step)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def result(self) -> Any:
|
|
64
|
+
"""The value the program computed in the last round."""
|
|
65
|
+
return self._device.result
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def outputs(self) -> dict[str, Any]:
|
|
69
|
+
"""What the program stored with ``store`` in the last round."""
|
|
70
|
+
return self._device.outputs
|
|
71
|
+
|
|
72
|
+
def run_round(self) -> Any:
|
|
73
|
+
"""
|
|
74
|
+
Run the program once: read the messages received, compute, and send.
|
|
75
|
+
|
|
76
|
+
Call it after each ``robot.step``.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The value the program computed.
|
|
80
|
+
"""
|
|
81
|
+
while self._receiver.getQueueLength() > 0:
|
|
82
|
+
sender, _, payload = self._receiver.getBytes().partition(b"\n")
|
|
83
|
+
self._device.receive(json.loads(sender), payload)
|
|
84
|
+
self._receiver.nextPacket()
|
|
85
|
+
payload = self._device.run_round(self._sense())
|
|
86
|
+
self._emitter.send(json.dumps(self._device.device_id).encode() + b"\n" + payload)
|
|
87
|
+
return self.result
|
|
88
|
+
|
|
89
|
+
def _sense(self) -> dict[str, Any]:
|
|
90
|
+
"""The sensors of this round."""
|
|
91
|
+
position = None if self._gps is None else tuple(self._gps.getValues())
|
|
92
|
+
sensors = {"position": position, "time": self.robot.getTime()}
|
|
93
|
+
if self.sensors is not None:
|
|
94
|
+
sensors.update(self.sensors())
|
|
95
|
+
return sensors
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _get_device(robot: Any, name: str) -> Any:
|
|
99
|
+
"""The device ``name`` of ``robot``."""
|
|
100
|
+
device = robot.getDevice(name)
|
|
101
|
+
if device is None:
|
|
102
|
+
raise ValueError(f"robot {robot.getName()!r} has no device named {name!r}")
|
|
103
|
+
return device
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: phyelds-webots
|
|
3
|
+
Version: 8.2.0
|
|
4
|
+
Summary: Webots integration for phyelds: aggregate programs on the robots of a Webots simulation
|
|
5
|
+
Home-page: https://github.com/phyelds/phyelds
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Author: Davide Domini
|
|
8
|
+
Author-email: davide.domini@unibo.it
|
|
9
|
+
Requires-Python: >=3.12,<3.14
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Dist: phyelds (>=8.2.0,<9.0.0)
|
|
16
|
+
Project-URL: Repository, https://github.com/phyelds/phyelds
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# phyelds-webots
|
|
20
|
+
|
|
21
|
+
[Webots](https://cyberbotics.com) integration for [phyelds](https://pypi.org/project/phyelds/):
|
|
22
|
+
run aggregate programs on the robots of a Webots simulation.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Install it in the Python that Webots runs the controllers with (Python 3.12 or later):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install phyelds-webots
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The `controller` module comes with Webots.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Give each robot an `Emitter`, a `Receiver` and, for the `position` sensor, a `GPS`; the range
|
|
37
|
+
of the emitter decides who the neighbors are. Then, in the controller:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from controller import Robot
|
|
41
|
+
|
|
42
|
+
from phyelds.calculus import aggregate
|
|
43
|
+
from phyelds.libraries.device import sense
|
|
44
|
+
from phyelds.libraries.distances import hops_distance
|
|
45
|
+
from phyelds.libraries.spreading import distance_to
|
|
46
|
+
from phyelds.webots import AggregateController
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@aggregate
|
|
50
|
+
def hops_to_source():
|
|
51
|
+
return distance_to(sense("source"), hops_distance())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
robot = Robot()
|
|
55
|
+
aggregate_controller = AggregateController(
|
|
56
|
+
robot, hops_to_source, gps="gps", sensors=lambda: {"source": robot.getName() == "source"}
|
|
57
|
+
)
|
|
58
|
+
while robot.step(int(robot.getBasicTimeStep())) != -1:
|
|
59
|
+
hops = aggregate_controller.run_round() # one round per step
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
See the [documentation](https://phyelds.github.io/phyelds/integration/webots/).
|
|
63
|
+
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
phyelds/webots/__init__.py,sha256=Ad69bK4E_8gUeWSXxR1zir8TenX9jA1Xs7_6Fh-_iG8,3830
|
|
2
|
+
phyelds_webots-8.2.0.dist-info/METADATA,sha256=YCrz9NPe1kLmxR9SshNBa0RIx3VrnMQeiPczXp3sT-c,2007
|
|
3
|
+
phyelds_webots-8.2.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
4
|
+
phyelds_webots-8.2.0.dist-info/RECORD,,
|