cyberbotics / webots

Webots Robot Simulator
https://cyberbotics.com
Apache License 2.0
3.2k stars 1.68k forks source link

The Python controller again assumes robots will not have new devices imported dynamically #6378

Open Justin-Fisher opened 1 year ago

Justin-Fisher commented 1 year ago

The (newish) Python controller creates a dictionary robot.devices of devices by name when the robot is initialized, and will not add extra devices to that dictionary, even when those devices are dynamically added to the robot later, via supervisor import commands. The underlying wb_commands work fine to interface with these newly added devices directly via the C interface, but the python controller does not repopulate its dictionary, so cannot provide any way to control them. This is especially notable in that you can use robot.getNumberOfDevices() to see the increased device count, but cannot use robot.getDeviceByIndex(i) since the python controller implements this by looking up the device's name (which works fine) and then looking for that name in its dictionary (which fails).

It'd be better to either trigger wholesale repopulation of the dictionary when it is noted that the number of devices has increased, or to incrementally populate the dictionary with additional devices as they are initially accessed.

Justin-Fisher commented 1 year ago

Here's a partial workaround (written as an overwritten method in a subclass of Robot as that was easiest for me to test). This is unfortunate in that (a) it duplicates a lot of code from Robot.__init__ that ideally should be refactored so that it can be used in all the contexts where Python Device objects are created for newly found devices, (b) the controller module's __all__ definition is missing Pen so this still doesn't work for Pen devices, and (c) a separate solution is still needed to fix Robot.getDeviceByIndex which would again share a lot of the same functionality. Still this points you in the right direction.

    def getDevice(self, name: str):
        if name in self.devices:
            return self.devices[name]

        # Otherwise check if this might have been dynamically added later
        tag = wb.wb_robot_get_device(name.encode())
        if not tag:
            print(f'Device "{name}" was not found on robot "{self.name}"', file=sys.stderr)
            return None

        # Otherwise this must have been a device that was dynamically added after our initial dictionary
        name = wb.wb_device_get_name(tag).decode()
        type = wb.wb_device_get_node_type(tag)
        Node = controller.Node
        if type == Node.ACCELEROMETER:
            d = controller.Accelerometer(tag)
        elif type == Node.ALTIMETER:
            d = controller.Altimeter(name)
        elif type == Node.BRAKE:
            d = controller.Brake(tag)
        elif type == Node.CAMERA:
            d = controller.Camera(tag)
        elif type == Node.COMPASS:
            d = controller.Compass(tag)
        elif type == Node.CONNECTOR:
            d = controller.Connector(tag)
        elif type == Node.DISPLAY:
            d = controller.Display(tag)
        elif type == Node.DISTANCE_SENSOR:
            d = controller.DistanceSensor(tag)
        elif type == Node.EMITTER:
            d = controller.Emitter(tag)
        elif type == Node.GPS:
            d = controller.GPS(tag)
        elif type == Node.GYRO:
            d = controller.Gyro(tag)
        elif type == Node.INERTIAL_UNIT:
            d = controller.InertialUnit(tag)
        elif type == Node.LED:
            d = controller.LED(tag)
        elif type == Node.LIDAR:
            d = controller.Lidar(tag)
        elif type == Node.LIGHT_SENSOR:
            d = controller.LightSensor(tag)
        elif type == Node.LINEAR_MOTOR or type == Node.ROTATIONAL_MOTOR:
            d = controller.Motor(tag)
        elif type == Node.PEN:
            d = controller.Pen(tag)
        elif type == Node.POSITION_SENSOR:
            d = controller.PositionSensor(tag)
        elif type == Node.RADAR:
            d = controller.Radar(tag)
        elif type == Node.RANGE_FINDER:
            d = controller.RangeFinder(tag)
        elif type == Node.RECEIVER:
            d = controller.Receiver(tag)
        elif type == Node.SKIN:
            d = controller.Skin(tag)
        elif type == Node.SPEAKER:
            d = controller.Speaker(tag)
        elif type == Node.TOUCH_SENSOR:
            d = controller.TouchSensor(tag)
        elif type == Node.VACUUM_GRIPPER:
            d = controller.VacuumGripper(tag)
        else:
            print('Unsupported device type: ' + str(type) + ' for device named "' + name + '"', file=sys.stderr)
            return None
        self.devices[name]=d
        return d