7. Extending the controller

The controller is designed to make modifying the functionality as easy as possible, while still allowing the core code to be updated without requiring users to modify the controller with their custom functionality every time.

7.1. Adding New Hardware Support

Adding new hardware to the controller is fairly simple. Create a new .py file in the hardware directory. Ths name you give this file becomes the name for this hardware type in the configuration file. This file needs to contain two functions, as shown in this example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def setup(robot_config):
    # your hardware setup code goes here
    return

def move(args):
    command = args['button']['command']

    if command == 'F':
        # your hardware movement code for forward goes here
        return
    elif command == 'B':
        # your hardware movement code for backwards goes here
        return
    elif command == 'L':
        # your hardware movement code for left goes here
        return
    elif command == 'R':
        # your hardware movement code for right goes here
        return
    return

7.1.1. Hardware setup() Function

The setup() function is passed the ConfigParser object for the controller.conf file. You can create a new section for your hardware in the controller.conf file, and store your configuration variables there. Variables in the configuration file can be accessed with robot_config.get(section, option), robot_config.getint(section, option), and robot_config.getboolean(section, option).

Any initial hardware setup should happen when the setup() function is called. This function is only called once.

7.1.2. move() Function

The move() function is passed the object containing the command for the robot received from the server.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
"e": "BUTTON_COMMAND",
"d": {
    "user": {
        "username": "brooke",
        "id": "user-328a312d-0ac0-4290-b482-720f0dc2166a",
        "status": { "timeout": false, "expireTimeout": 1561673063070 }
    },
    "button": {
            "id": "bttn-75a8e2b5-5bfc-4fa0-8d2d-8a2365a16be1",
            "label": "forward",
            "command": "f",
            "hot_key": "w"
        },
        "controls_id": "cont-ea0d8dd9-c7be-4659-b41a-2ccaa67486bb",
        "channel": "chan-63a18243-9424-4594-b496-e7d80a06a6f1"
    }
}

It should be mostly self explanatory, the most important part is the command. The move() function should use the provided command to determine how to move the robot. If your hardware needs specific start and stop instructions to move, with a pause between, you should import the time module and use its sleep() function.

GPIO.output(StepPinForward, GPIO.HIGH)
time.sleep(sleepTime)
GPIO.output(StepPinForward, GPIO.LOW)

The controller will block, and ignore any other commands from the server until the move() function finishes. If you want to implement your own blocking, or implement proper asynchronous movement commands, you can disable the blocking by setting enable_async=True in the [misc] section of controller.conf.

7.2. Adding new TTS Support

This is almost exactly the same as adding new hardware. Create a new .py file in the tts directory. The name you give this file becomes the name for this tts type in the configuration file. This needs to contain two functions, as shown in this example.

1
2
3
4
5
6
def setup(robot_config):
    return

def say(*args):
    message = args[0]
    return

7.2.1. TTS setup() Function

The setup() function is passed the ConfigParser object for the controller.conf file. You can create a new section for your tts in the conf file, and store configuration variables there. Variables in the configuration can be accessed with robot_config.get(section, option), robot_config.getint(section, option), and robot_config.getboolean(section, option).

Any initial TTS setup should happen when the setup() function is called. This function is only called once.

7.2.2. say() Function

The first argument will always be the plain text message. The second argument, if it exists, will be the chat message object that was sent to the robot from the server. It looks like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
    "e": "MESSAGE_RECIEVED",
    "d": {
        "message": "beep beep ",
        "sender": "brooke",
        "sender_id": "user-328a312d-0ac0-4290-b482-720f0dc2166a",
        "chat_id": "chat-07aba28b-42b8-4b16-8b69-bdd6997807ae",
        "server_id": "serv-be71b50b-209f-4d76-82e9-c25ce2f547f5",
        "id": "mesg-54fa88df-3b64-4e05-91a4-3cd6175558e4",
        "time_stamp": 1563061665625,
        "broadcast": "",
        "displayMessage": true,
        "badges": ["global_moderator"],
        "type": ""
    }
}

The actual code required to take the text message, and play it as audio should reside in this function.

7.3. Extending Existing Hardware

When the custom_hardware option in the misc section of controller.conf is set to true, the controller will look for a file named hardware_custom.py in the hardware directory. If the file exists, it will load that instead of the file relating to the hardware type specified in controller.conf.

In this way, existing hardware functions can be modified or extended, or entirely replaced. Though if you are replacing the functions entirely, you may be better off creating a new hardware type instead.

The hardware_custom.py file needs to have the same two functions, as outlined for new hardware above, but in order to extend functionality, there is a slight difference.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import mod_utils
module = None

def setup(robot_config):
    # Your custom setup code goes here

    # This code calls the default setup function for your hardware.
    # global module
    module = mod_utils.import_module('hardware', robot_config.get('robot', 'type'))
    module.setup(robot_config)

def move(args):
    command = args['button']['command']
    # Your custom command interpreter goes here

    # This code calls the default command interpreter function for your hardware.
    module.move(command)

The setup() function includes code to import the hardware controller you are extending into module. It also calls the setup function for that module.

The move() function also has a command interpreter similar to the one in the hardware module you are extending, except this one only needs to contain the commands that aren’t handled in the hardware module. After the custom command interpreter has run, it should call the move() command from the hardware module, to handle the non-custom commands.

7.4. Extending existing TTS

This is very similar to extending existing hardware as outlined above. Except that where extending hardware involves adding new functions, extending TTS is more about modifying the way messages are handled.

When the custom_tts option in the misc section of controller.conf is set to true, the controller will look for a file named tts_custom.py in the TTS directory. If that file exists, it will load that instead of the file relating to the tts type specified in the controller.conf.

In this way, existing TTS functions can be modified and extended, or entirely replaced. Though if you are replacing functions entirely, you may be better off creating a new TTS type instead.

The tts_custom.py file needs to have the same two functions, as outlined for new tts above, but in order to extend functionality there is a slight difference.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import mod_utils
module = None

def setup(robot_config):
    # Your custom setup code goes here

    # This code calls the default setup function for your tts.
    # global module
    module = mod_utils.import_module('tts', robot_config.get('tts', 'type'))
    module.setup(robot_config)

def say(*args):
    message = args[0]
    # Your custom tts interpreter code goes here

    module.say(message, args[1])

The setup() function includes code to import the hardware controller you are extending into module. It also calls the setup function for that module.

The say() function should modify the message or TTS function before calling the say() command from the TTS module, to handle the actual text conversion to sound.