Table of Contents

Creating a simple BPQ Application

This tutorial shows you how to set up a simple python script connected to linbpq as a telnet application.

In order to do this we'll do the following:

  1. Create a location for the script, data, and a python venv
  2. Write the script
  3. Add the application to BPQ32.cfg
  4. Setup a systemd service to keep it running.

Setup

To organize this project, I created a python virtual environment called bpq-apps in my .local directory, and installed the telnetlib3 module that it depends on. Within the bpq-apps virtual environment directory, I also created a folder called scripts and a folder called data. I'm going to install various small BPQ applications here.

I'm using the python virtual environment framework to help manage the various dependencies I might want to use for these scripts.

$ cd ~/.local
$ python -m venv bpq-apps
$ cd ./bpq-apps
$ mkdir scripts data
$ source ./bin/activate
(bpq-apps)$ pip install telnetlib3

Writing the program

I chose to write this in Python to be a little more clear than the Perl application provided in the link above.

The BPQ Application, called ROLL uses a file called messages.json to provide it a welcome message and the blocks of text. The script itself was placed under the scripts directory created above, saved as rick.py

Program Code

This program was adapted from the example for the ''telnetlib3'' Python module

import asyncio, telnetlib3, json, sys
from optparse import OptionParser

# this sets up the command line options, primarily the file to read and the port to open for BPQ
parser = OptionParser()
parser.add_option("-f", "--file", dest="filename", help="which json file with message and welcome", metavar="FILE")
parser.add_option("-p", "--port", dest="port", help="on which port?", metavar="PORT")
(options, args) = parser.parse_args()

# read file with messages
messages = json.load(open(options.filename,'r'))

# logging method to write to stderr for journalctl to pick up
def print_log(*a):
    print(*a, file=sys.stderr)

# this is the main function that processes input from user and writes outputs
async def shell(reader, writer):
    # first thing provided is user's callsign
    incall = await reader.readline()
    print_log("%s connected" % incall)
    writer.write("\r\nWelcome %s" % incall)
    writer.write("\r\n%s" % messages["welcome"])
    # make copy of blocks so we can pop 'em
    textblocks = messages["text"].copy()
    # don't let them leave until they've been rolled
    leave = False 
    rolled = False
    # take first input from user after welcome message
    inp = await reader.readline()
    text_count = 0
    incall = incall.rstrip()
    # loop on inputs until they leave
    while inp and not leave and len(textblocks) > 0:
        writer.write(textblocks.pop(0))
        await writer.drain()
        inp = await reader.readline()
        text_count += 1
        if not rolled: 
            # log that rolling has happened
            print_log("%s rickrolled" % incall)
            rolled = True
        # if they type something starting with Q or B (quit, bye, bye-bye, etc.) let them leave
        leave = inp[0].upper() in ["Q", "B"] and rolled
        # log their input to journalctl via stderr
        print_log("%s (%d): \"%s\"" % (incall, text_count, inp.rstrip()))
    writer.close()

# sets up the async process to take inputs
loop = asyncio.get_event_loop()
coro = telnetlib3.create_server(port=options.port, shell=shell)
server = loop.run_until_complete(coro)
loop.run_until_complete(server.wait_closed())

''messages.json'' file

{
	"welcome": "We're no strangers to love\r\nYou know the rules and so do I (Do I)\r\nA full commitment's what I'm thinking of\r\nYou wouldn't get this from any other guy\r\nI just wanna tell you how I'm feeling\r\nGotta make you understand\r\n",
	"text": ["Never gonna give you up\r\nNever gonna let you down\r\nNever gonna run around and desert you\r\nNever gonna make you cry\r\nNever gonna say goodbye\r\nNever gonna tell a lie and hurt you\r\n",
		 "\r\nWe've known each other for so long\r\nYour heart's been aching, but you're too shy to say it (To say it)\r\nInside, we both know what's been going on (Going on)\r\nWe know the game, and we're gonna play it\r\n\r\nAnd if you ask me how I'm feeling\r\nDon't tell me you're too blind to see\r\n",
		 "\r\nNever gonna give you up\r\nNever gonna let you down\r\nNever gonna run around and desert you\r\nNever gonna make you cry\r\nNever gonna say goodbye\r\nNever gonna tell a lie and hurt you\r\n",
		 "\r\nNever gonna give you up\r\nNever gonna let you down\r\nNever gonna run around and desert you\r\nNever gonna make you cry\r\nNever gonna say goodbye\r\nNever gonna tell a lie and hurt you\r\n",
		 "\r\nOoh (Give you up)\r\nOoh-ooh (Give you up)\r\nOoh-ooh\r\nNever gonna give, never gonna give (Give you up)\r\nOoh-ooh\r\nNever gonna give, never gonna give (Give you up)\r\n\r\nWe've known each other for so long\r\nYour heart's been aching, but you're too shy to say it (To say it)\r\n",
		 "\r\nInside, we both know what's been going on (Going on)\r\nWe know the game, and we're gonna play it\r\n\r\nI just wanna tell you how I'm feeling\r\nGotta make you understand\r\n",
		 "\r\nNever gonna give you up\r\nNever gonna let you down\r\nNever gonna run around and desert you\r\nNever gonna make you cry\r\nNever gonna say goodbye\r\nNever gonna tell a lie and hurt you\r\n",
		 "\r\nNever gonna give you up\r\nNever gonna let you down\r\nNever gonna run around and desert you\r\nNever gonna make you cry\r\nNever gonna say goodbye\r\nNever gonna tell a lie and hurt you\r\n",
		 "\r\nNever gonna give you up\r\nNever gonna let you down\r\nNever gonna run around and desert you\r\nNever gonna make you cry\r\nNever gonna say goodbye\r\nNever gonna tell a lie and hurt you\r\n"
	]
}

BPQ configuration

In my bpq32.cfg file, I added the port number to the telnet port, where the fourth entry, 10001 is the port for my new application:

CMDPORT=8005 63001 10001

and in the application list, I added a new entry, where HOST 2 is due to being the 0th-indexed 3rd entry above.

APPLICATION 3,ROLL,C 1 HOST 2 S

Configuring this as a service

Next we'll configure a systemd service to make sure this script is always running and ready to accept connections.

Put this file in /etc/systemd/system/rick.service:

# service for RRoAX25 server
# replace USER with the username under which you created the bpq-apps venv

[Unit]
Description="RRoAX25 service"
After=network.target
# this might be backwards, but I do need linbpq for this to work.
Requires=linbpq.service

[Service]
Type=simple
User=evan
Group=evan
WorkingDirectory=/home/USER/.local/bpq-apps/
ExecStart=/home/USER/.local/bpq-apps/bin/python /home/USER/.local/bpq-apps/scripts/rick.py -p 10001 -f /home/USER/.local/bpq-apps/data/messages.json
Restart=always
RestartSec=3

[Install]
WantedBy=default.target

Activate it by running:

sudo systemctl enable rick.service
sudo systemctl start rick.service

Then check the status to confirm it is working:

systemctl status rick.service

If you need to edit the service file, reload it with:

systemctl daemon-reload

Finally, to see who has used it:

journalctl -u rick.service -f