Remote Control and Automation Scripts for Agon Light

What’s It All About?

With the solution outlined here, you can initiate commands and scripts on your physical Agon Light directly from your computer. This feature is particularly useful for automating the transfer and execution of programs during software development, thereby streamlining and enhancing your workflow efficiency.

Agon Light

The Agon Light (or Agon Light 2, or Console 8) is a modern retro-computer based on a Zilog eZ80 CPU. To avoid reliance on old hardware components, many functions such as VGA, keyboard, and sound are handled by an ESP32 microcontroller.

Data Transfer with SD-Card and Hexload

Data and programs are stored on an SD-Card. To transfer something to your Agon, you would normally have to insert the SD Card into your main computer, copy the necessary files onto it, and then place it back into the Agon.

Since this is quite cumbersome, there’s a tool called Hexload. With a USB connection to the ESP32 or via UART (serial port), you can directly transfer data from your computer to the Agon or even execute it directly. To do this, you install Hexload on the Agon and a Python script on your computer. This saves a lot of time and makes it easier to develop on your computer and then transfer the programs.

To use Hexload if you want to transfer via vdp (ESP32):

hexload vdp filename

And on your computer:

python send.py filename /dev/devicename 115200

Note that you may need to adjust DEFAULT_LINE_WAITTIME in the Python script if you encounter any transmission issues.

I work on a Mac. So, many things should be comparable on Linux. On Windows, you would need to make appropriate adjustments (like using COM3 or writing the paths differently). If you’re experiencing issues with invoking python (Python3 installed with brew on a Mac), try using python3 instead.

Automation and Remote Control with agon_automate.py

With my script agon_automate.py , you can directly send commands and ASCII texts to the Agon computer without having to manually enter them via the keyboard. It uses a similar connection as hexload over vdp and works like a remote-control software.

Requirements for agon_automate.py

You’ll need python3, a USB connection from your computer to the Agon at the ESP32-USB port, and PySerial.

pip install pyserial

On your Agon Light: For everything to work, SET CONSOLE 1 must be set on your Agon. I’ve incorporated this into the autoexec.txt file, so it’s always activated:

SET KEYBOARD 1
SET CONSOLE 1
LOAD bbcbasic.bin
RUN

Quick tip: Start SET CONSOLE 1 manually or restart your Agon Light so that the command is executed. Without it, the commands from my script won’t reach the Agon.

On your Mac, Linux or Windows Computer: The script itself can be placed in a directory of your choice. I’ve personally stored the script agon_automate.py under ~/bin and extended .zshrc or .bashrc with export PATH="$HOME/bin:$PATH" so that everything in bin is automatically found.

agon_automate.py

# Title:       agon_automate.py
# Author:      Tom Schimana
# Version:     0.9
# Created:     01/21/2024
# Last update: 01/21/2024

# Description:
# This script automates the process of sending commands and files to devices over a serial connection.
# Designed for versatility, it's suitable for various applications such as transmitting programming code,
# sending text files, or executing general commands.
# Requires 'SET CONSOLE 1' to be enabled on Agon and a USB-to-UART connection to Agon ESP32.
# Optional: hexload (https://github.com/envenomator/agon-hexload) for file transfers.

# Functionalities:
# Direct:
#    Sends individual commands or text directly to the device.
#    Usage: agon_automate.py direct "your text you want to send" [optional: filename]
#    An optional filename can be included and utilized with {FILENAME} as a placeholder.
# Script:
#    Executes a series of predefined commands from a script file.
#    Usage: agon_automate.py script "SCRIPT_PATH" [optional: filename]

# Special Commands and Placeholders:
# - {COMMAND}: Executes an external shell command.
#   Example: {COMMAND python3 ~/bin/send.py {FILENAME} {DEVICE}}
# - {WAIT}: Pauses execution for a specified number of seconds.
#   Example: {WAIT 2}
# - {FILENAME}: Replaced with the optional filename argument provided in the command.
# - {DEVICE}: Placeholder for the device address, replaced with the configured DEVICE variable.
# - \n: In the 'direct' method, this is replaced with a carriage return ('\r')
#   to signify a new line on the device.

# Configuration Variables
DEVICE = "/dev/cu.usbserial-02B1CCDF"  # Serial device address for USB to ESP32 connection
BAUD = 115200                           # Baud rate for serial communication
CHAR_DELAY = 0.03                       # Delay between each character sent via serial
DEBUG = 1                               # Set to 1 to enable debug output, 0 to disable
COMMAND_DELAY = 0.5                     # Delay after sending each command line or after a carriage return

import subprocess
import os
import time
import sys

try:
    import serial
except ImportError:
    print("Missing module: pyserial. Please install it using 'pip install pyserial'.")
    sys.exit(1)

def debug_print(message):
    if DEBUG:
        print(message)

def send_command(command, file_name=None, interpret_escapes=False):
    command = command.replace("{FILENAME}", file_name if file_name else "")
    command = command.replace("{DEVICE}", DEVICE)
    if interpret_escapes:
        command = command.replace('\\n', '\r')

    debug_print(f"Sending command: {command}")
    parts = command.split("{")
    for part in parts:
        if "WAIT" in part:
            wait_time = float(part.split()[1].strip("}"))
            debug_print(f"Waiting for {wait_time} seconds")
            time.sleep(wait_time)
        elif "COMMAND" in part:
            execute_external_command(part, file_name)
        else:
            send_partial_command(part + '\r')

def execute_external_command(part, file_name):
    cmd = part.split('COMMAND')[1].strip("}").strip()
    cmd = cmd.replace("{FILENAME}", file_name if file_name else "").replace("{DEVICE}", DEVICE)
    cmd_parts = [os.path.expanduser(p) for p in cmd.split()]
    try:
        subprocess.run(cmd_parts, check=True)
        time.sleep(COMMAND_DELAY)
    except (subprocess.CalledProcessError, FileNotFoundError) as e:
        debug_print(f"Error executing command: {e}")

def send_partial_command(part):
    with serial.Serial(DEVICE, BAUD, timeout=1) as ser:
        for char in part:
            ser.write(char.encode())
            time.sleep(CHAR_DELAY)
        time.sleep(COMMAND_DELAY)

def process_command_line(command_line, file_name=None):
    send_command(command_line, file_name, interpret_escapes=True)

def process_script_file(script_path, file_name=None):
    with open(script_path, 'r') as file:
        for line in file:
            line = line.replace('\n', '\r\n')
            process_command_line(line.strip(), file_name)

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: {} [direct|script] argument [filename]".format(sys.argv[0]))
        sys.exit(1)

    method, argument = sys.argv[1], sys.argv[2]
    file_name = sys.argv[3] if len(sys.argv) > 3 else None

    if method == "direct":
        send_command(argument, file_name, interpret_escapes=True)
    elif method == "script":
        process_script_file(argument, file_name)
    else:
        print("Invalid method. Use 'direct' or 'script'.")
        sys.exit(1)

Configuration Parameters

You need to adjust the Python code so it works in your environment.

Necessary changes.:

  • DEVICE: Path to the port for communication with Agon Light. For example, on Windows, it could be com3 or on Linux or Mac /dev/ttyxxxx. I assume you might already be using hexload. Hence, it’s the same device as you use there for the vdp connection.

Optional changes:

  • BAUD: Baud rate for data transmission. I would leave it as it is.
  • CHAR_DELAY: Only change if there are issues. This is the time waited between each character for processing. If it misses characters, then increase this.
  • COMMAND_DELAY: This is the time waited after each line. Mainly because, for example, a command needs computation time and otherwise the coming characters will be missed. I would leave it as it is and possibly work with the WAIT command (see below) if needed.
  • DEBUG: If active (1), then what is transmitted to Agon will also be displayed at your end.

Using agon_automate.py

Direct Mode

For quick, single commands that you want to send to the Agon Light. Useful for ad-hoc changes. An example, if you’re in Basic and just want to change a line in the code:

python agon_automate.py direct "10 PRINT \"HELLO WORLD\""

The issue with Direct Mode is the limitation of the command line. That’s why the basic example above includes additional special characters; otherwise, the shell would interpret the characters incorrectly.

Most of the time, you’ll use direct mode for simple tasks like starting a program. To start hexload on the other side:

python agon_automate.py direct "hexload vdp helloword.bas"

Essentially, you can send even more than one line separated by \n:

python agon_automate.py direct "10 PRINT \"HELLO WORLD\"\n20 GOTO 10"


The direct mode is accordingly limited, as there is a limitation on the command prompt for certain special characters or text length.

Script Mode

For this reason, there’s also the ‘script’ mode for more complex sequences with multiple commands. It’s ideal for recurring tasks or longer automations.

My standard automation script, agonbasic.script, is what I use to end BBC Basic on the Agon LIght, start Hexload, run send.py on my local Computer, and after the transfer, reload Basic and load the transferred Basic program to run it. The script assumes that you are in BBC Basic. Of course, you’ll need hexload for this. The reason I close BBC Basic is that hexload tends to cause issues within Basic, at least for me.

agonbasic.script (you can, of course, give it another name):

*BYE
hexload vdp {FILENAME}
{COMMAND python ~/bin/send.py {FILENAME} {DEVICE}}{WAIT 2}
load /bbcbasic.bin
run
LOAD "{FILENAME}"
{WAIT 2}
RUN

I call it like this:

python agon_automate.py script agonbasic.script helloagon.bas

The first parameter ist script for the mode, then the name of the script containing the commands for what should happen, and the last parameter is the name of the file for {FILENAME}.

Now, you might be wondering about the commands in the curly brackets. These are the magic for automation:

  • {COMMAND}: This allows you to start a local program on your computer. As you can see, it can even contain additional parameters. What happens in the script above is that we start send.py and pass it the filename (helloagon.bas), the device (which is set in agon_automate.py), and a 2-second pause.
  • {WAIT}: This is ideal if you start a command on the Agon that takes some time, like loading a longer program. Without a parameter, it’s one second (which isn’t necessary, as this is the default wait time after every line in the script) or for example, {WAIT 2} for two seconds.
  • {FILENAME}: Here, the filename is passed, which in the call to agon_automate is the last parameter after ‘script’ (so in our example, helloagon.bas).
  • {DEVICE}: Placeholder for the device address, replaced with the configured DEVICE variable.

This way, you can quickly and automatically send commands and perform short automations on the Agon. Of course, this isn’t limited to Basic. The only limit is that you can only transmit things that can be input via a keyboard. It’s not meant as a transmission tool, as it lacks error checking or similar features. For that purpose, hexload is more suitable. But the combination of my script with hexload allows you to do a lot.


Using neovim or VIM? In the next post, I’ll explain how you can use neovim or vim as an small IDE for your Agon Light.

This will enable you to write or edit BBC Basic programs on your computer using vim or neovim, for instance. You’ll be able to transfer them with a single command, launch them on the Agon, and even modify individual lines of code and transfer just those.

Similar Posts

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *