autorope / donkeycar

Open source hardware and software platform to build a small scale self driving car.
http://www.donkeycar.com
MIT License
3.13k stars 1.29k forks source link

Make RC controller 'pigpio_rc' button(s) programmable #1023

Open Ezward opened 2 years ago

Ezward commented 2 years ago

I did an analysis of the RC controller code. It was super confusing because I think we have some abandoned code.

The method controller.py:get_js_controller() returns the chosen game controller class IF it is one based on controller.py:JoystickController class.

def get_js_controller(cfg):
    cont_class = None
    if cfg.CONTROLLER_TYPE == "ps3":
        cont_class = PS3JoystickController
    elif cfg.CONTROLLER_TYPE == "ps3sixad":
        cont_class = PS3JoystickSixAdController
    elif cfg.CONTROLLER_TYPE == "ps4":
        cont_class = PS4JoystickController
    elif cfg.CONTROLLER_TYPE == "nimbus":
        cont_class = NimbusController
    elif cfg.CONTROLLER_TYPE == "xbox":
        cont_class = XboxOneJoystickController
    elif cfg.CONTROLLER_TYPE == "xboxswapped":
        cont_class = XboxOneSwappedJoystickController
    elif cfg.CONTROLLER_TYPE == "wiiu":
        cont_class = WiiUController
    elif cfg.CONTROLLER_TYPE == "F710":
        cont_class = LogitechJoystickController
    elif cfg.CONTROLLER_TYPE == "rc3":
        cont_class = RC3ChanJoystickController
    elif cfg.CONTROLLER_TYPE == "pygame":
        cont_class = PyGamePS4JoystickController
    else:
        raise( Exception("Unknown controller type: " + cfg.CONTROLLER_TYPE))

Notice https://github.com/autorope/donkeycar/blob/050009fbf09675e5034bbe04763187ca234528db/donkeycar/parts/controller.py#L1725

    elif cfg.CONTROLLER_TYPE == "rc3":
        cont_class = RC3ChanJoystickController

This is an RC controller with 3 channels. It is based on JoystickController, which means it is mapped to a Linux device at /dev/input/js0.

Now look at complete.py:add_user_controller(), the method that actually adds the controller(s) to the vehicle pipeline based on the configuration.

def add_user_controller(V, cfg, use_joystick, input_image='cam/image_array'):
    """
    Add the web controller and any other
    configured user input controller.
    :param V: the vehicle pipeline.
              On output this will be modified.
    :param cfg: the configuration (from myconfig.py)
    :return: the controller
    """

    #
    # This web controller will create a web server that is capable
    # of managing steering, throttle, and modes, and more.
    #
    ctr = LocalWebController(port=cfg.WEB_CONTROL_PORT, mode=cfg.WEB_INIT_MODE)
    V.add(ctr,
          inputs=[input_image, 'tub/num_records', 'user/mode', 'recording'],
          outputs=['user/angle', 'user/throttle', 'user/mode', 'recording'],
          threaded=True)

    #
    # also add a physical controller if one is configured
    #
    if use_joystick or cfg.USE_JOYSTICK_AS_DEFAULT:
        #
        # RC controller
        #
        if cfg.CONTROLLER_TYPE == "pigpio_rc":  # an RC controllers read by GPIO pins. They typically don't have buttons
            from donkeycar.parts.controller import RCReceiver
            ctr = RCReceiver(cfg)
            V.add(
                ctr,
                inputs=['user/mode', 'recording'],
                outputs=['user/angle', 'user/throttle',
                         'user/mode', 'recording'],
                threaded=False)
        else:
            #
            # custom game controller mapping created with
            # `donkey createjs` command
            #
            if cfg.CONTROLLER_TYPE == "custom":  # custom controller created with `donkey createjs` command
                from my_joystick import MyJoystickController
                ctr = MyJoystickController(
                    throttle_dir=cfg.JOYSTICK_THROTTLE_DIR,
                    throttle_scale=cfg.JOYSTICK_MAX_THROTTLE,
                    steering_scale=cfg.JOYSTICK_STEERING_SCALE,
                    auto_record_on_throttle=cfg.AUTO_RECORD_ON_THROTTLE)
                ctr.set_deadzone(cfg.JOYSTICK_DEADZONE)
            elif cfg.CONTROLLER_TYPE == "MM1":
                from donkeycar.parts.robohat import RoboHATController
                ctr = RoboHATController(cfg)
            elif cfg.CONTROLLER_TYPE == "mock":
                from donkeycar.parts.controller import MockController
                ctr = MockController(steering=cfg.MOCK_JOYSTICK_STEERING,
                                     throttle=cfg.MOCK_JOYSTICK_THROTTLE)
            else:
                #
                # game controller
                #
                from donkeycar.parts.controller import get_js_controller
                ctr = get_js_controller(cfg)
                if cfg.USE_NETWORKED_JS:
                    from donkeycar.parts.controller import JoyStickSub
                    netwkJs = JoyStickSub(cfg.NETWORK_JS_SERVER_IP)
                    V.add(netwkJs, threaded=True)
                    ctr.js = netwkJs
            V.add(
                ctr,
                inputs=[input_image, 'user/mode', 'recording'],
                outputs=['user/angle', 'user/throttle',
                         'user/mode', 'recording'],
                threaded=True)
    return ctr

Note

        if cfg.CONTROLLER_TYPE == "pigpio_rc":  # an RC controllers read by GPIO pins. They typically don't have buttons
            from donkeycar.parts.controller import RCReceiver
            ctr = RCReceiver(cfg)
            V.add(
                ctr,
                inputs=['user/mode', 'recording'],
                outputs=['user/angle', 'user/throttle',
                         'user/mode', 'recording'],
                threaded=False)

This adds a controller that is not based on controllers.py:JoystickController at all. It is documented as having no buttons.

So it seems there are two configurations for RC controllers as joysticks; "pigpio_rc" and "rc3". They are implemented in two very different ways. The 'pigpio_rc' is a special controller that uses the pigpio library to read the channels of an RC controller. It does read 3 channels; it uses the third channel to cycle the user modes.

The 'rc3' seems to be a more traditional Linux driver that exposes the RC controller as a Linux device at /dev/input/js0 just like other joysticks. I suspect this magic is done using a Teensy or other microcontroller that can act as an HID (Human Interface Device) using some emulation and a USB connection. However, we don't have that sketch anywhere, so I suspect this is abandoned code. In anycase, with the RC hat we want read the joystick with the gpio pins. So I don't think we are using 'rc3' anymore.

'rc2' that has button code that is erasing the records etc. based on the button down or button up state (not just a simple button push)

The 'pigpio_rc' controller does not do this; it is hard coded to just cycle through the user and pilot modes. So if we changed that what would we do. What would the cycle be in path_follow and what would the cycle be in complete.py and what would the cycle be in the cv template? So there is no simple hack to make this work; if I touch it and make it work for path follow it will break the complete template. So we have to take a more considered approach.

For pigpio_rc we probably need to be able to define a cycle of states or calls that will be made as the button is pushed.

We probably want to handle several buttons; better controllers have more buttons available.

Or we could focus on making the phone's web ui have programmable buttons and stop it from sleeping, so users would be expected ot use their phones to get the game-controller functions while using an RC controller. See https://github.com/autorope/donkeycar/issues/928 and https://github.com/autorope/donkeycar/issues/1022

All of took me a couple of hours to figure out.

TCIII commented 2 years ago

@Ezward,

The whole reason for using an RC controller is to be able to drive a DC robot chassis without using a webui over WiFi. I believe that a phone webui would require having the SBC be the WiFi Hotspot or have to have a local WiFi Hotspot. When I drive my DC robot chassis I use the screen app to keep DC running while it is out of WiFi range and reestablish an SSH connection over WiFi when it is back in range. Since I can't use a gamepad at the same time I am using an RC controller with the path_follow.py template, it would be nice to be able to be able to save the recorded path either with the RC controller or from the SSH CLI.

Regards, TCIII