inkscape

Hacking SSTV

Slow-scan television is a means of transmitting still images over HF radio. It has its origins in the 1950s, where operators would use a CRT monitor with a long-sustain phosphor in a darkened room to see the image, but modern implementations today use digital signal processing computer software on a conventional OS like Windows or Linux.

For the non-radio folk, you can think of it as a radio-flavoured facsimile service. A picture is taken, scanned (or created directly on the computer), the resulting image file is then encoded as a series of audio tones that are then transmitted over the air. The vast majority of standards out there use frequency modulation (FM) for the actual base-band signal sent to the radio, although there are some newer ones (like the Digital Radio Mondiale-based HamDRM, of which EasyPAL is probably its best known implementation) that use more advanced modulation techniques.

I would like the ability to send and receive SSTV on the bicycle. The Raspberry Pi 4 I have earmarked for the bike can do it, and I’ve been using it here with QSSTV to send and receive pictures as a home station. However, while this is a very capable client, being a desktop application, it is awkward to use on a headless Raspberry Pi. Best solution I’ve come up with is to use a VNC desktop, which is tedious.

I looked around for what else there was, and the answer was, not a lot. So I’d have to get my hands dirty.

Transmitting

Transmitting actually isn’t that difficult, there exist several CLI programs that will take an image file, and spit out an audio file (usually Microsoft RIFF .wav) that I can then play out the sound card. Not sure if they do the FSK ID at the end, but that’s not exactly difficult to figure out, and I can probably modify a tool to add it.

In my early experiments with SSTV, this is exactly how I sent some of my early transmissions. But I’d need to fire up an image editor to compose the image — it was cumbersome. However with the raster image already in-hand, it wasn’t too bad.

Keying the radio is done simply, I have two ways actually… one is to use the “Computer Aided Transceiver” port on my radio (Yaesu FT-897D in this case) to send PTT on/off commands. The other was via the GPIO pins on the Pi, which on the NWDR DRAWS board, are mapped to the appropriate pin on the data port. Both work, and are relatively straightforward.

So whilst there’s a few loose ends to tie up here, it seems that is largely handled.

Receiving

This was a big problem, and one I was stuck on for a while. Nearly everything I could find was a GUI application. I didn’t want to run a GUI for this, I wanted a CLI tool that sat in the background and did its thing, ideally launched at boot by systemd. I wanted a few things:

  • capture of metadata parameters into a separate file
  • periodic update of the partial image during reception
  • execute a script when something happens

I found one CLI receiver, there’s this one, but it assumes I have a recording of it already. I’d need something to spot the VIS header then start recording. QSSTV did nearly everything I wanted, but was a GUI application, couldn’t write out the metadata nor could it trigger a script. slowrx was the same.

I do note that the KiwiSDR seems to use slowrx in its decoder, and they’ve managed to solve this issue, so I took a closer look. With some work, I was able to split slowrx into two parts, a back-end I called libslowrx.a, and a front-end slowrx application. I then wrote a crude slowrxd application that ran on the CLI and used libgd to write image files (I did think of using libpng directly, but its API is fiddly to use). I started this a few days back and polished it off over the week-end. This is released on Github.

As a proof-of-concept, I threw together this SSTV cam website. There, I took inspiration from VK7OO’s SSTV site. I don’t have an SDR, but that doesn’t mean I can’t create a waterfall of the transmission, I made slowrxd write the transmission to a file, then used good ol’e sox to generate a spectrogram.

I made slowrxd generate a NDJSON log file as it received the image, which meant all kinds of metadata could be emitted live as it happened, including the SSTV mode at the start and the FSK ID at the end, timestamped to the millisecond. This could be parsed with sed and jq to pull out the necessary bits.

file could tell me how big the image was (I guess I could have put that in the log), and netpbm has lots of tools for putting images together. Drawing text was more tricky, but I found convert (from ImageMagick) could give me an image that had the text I wanted in a font I liked, then I could tell netpbm‘s pamcat and pamcomp could put the pieces together.

bash ran the whole show, generated the HTML, then used rsync to upload over SSH. Easy.

Without much work, I could replace this script with another that just does a nc -u localhost port and sends me a single UDP message with the event details so it could be handled by a separate daemon. So I think now I have a solution for that now.

Templating

This is a sore point in QSSTV, it has a template editor, but it uses its own format that’s basically proprietary to QSSTV using Qt abstractions. The code is open-source, so we can pick that apart to figure out exactly what its reading/writing, but it’d be a longer project… and the QSSTV templates are pretty limited.

The more I thought about it, the more I started to think about SVG as a template format — it has everything I need and some. I could use a well-known and highly capable editor like Inkscape to design the templates, leaving fields for my application to fill-in, then use a SVG rasteriser to spit out the image at any resolution I wanted.

For a rasteriser, whilst not ideal, I can use Inkscape itself quite successfully. At the cost of dragging in GTK+ libs (and it complaining that DISPLAY not being set), I can rasterise a SVG with one command:

inkscape -o output.png output.svg

The catch was, how do you do templates in Inkscape? Initial research was disheartening, but then I wondered… SVG supports CSS just like HTML. While you can attach style attributes to a CSS class, you don’t have to. SVG doesn’t care if you don’t… and with that, I can identify nodes in the SVG document by the class assigned.

Inkscape makes this possible through the Selectors and CSS palette accessed through the Object menu. Also useful here is the Object Properties palette available in the same menu.

The Object menu, showing the Selectors and CSS option highlighted.

Inkscape Template workflow

To create a template in Inkscape… firstly, create your image as you normally would. Put whatever dummy text in your text fields and dummy images in embedded bitmaps you need to change from code later. Get it looking the way you want with that dummy text.

Next, use the Object Properties palette to assign meaningful values to the objects you want to manipulate. Each ID you assign must be unique, so this by itself isn’t useful for fields that are duplicated, but will help later when you come to assign CSS classes to these fields. The IDs should contain only alpha-numeric characters (0-9, a-z, A-Z), hyphens (-) and underscores (_) (apparently other characters are allowed except white-space, but sticking to this limit will make life a lot easier).

Inkscape assigning IDs to the various elements we want to manipulate.

Finally, we use the Selectors and CSS palette to assign classes. Click on an element you wish to assign classes to:

Selectors and CSS palette open with an object selected.

You’ll see there are two panes, either side-by-side or one above the other, the little button group down the bottom-right switches the view. Depending on which one is selected, you want either the bottom one or the right-hand one.

On the same row is + and - buttons for adding new “CSS selectors”. When you click + a pop-up box appears with a default being the ID of the object prefixed with a #. Delete the default text, then type in a . followed by the CSS class name, again just stick to alpha-numerics, hyphens and underscores… don’t try anything fancy. Then click OK.

You should find in the right (or bottom) pane, you now have the class defined, and your object (identified by the ID you assigned earlier) is listed as one of the members of that class. There’s a + icon beside the class which you can click to add another selected object to the same class, and a garbage can icon beside each member to remove that member from the class.

In the left (upper) pane, you can set style information for this class as well as for the specific selected object. Leave the style information blank for each class (you can apply styles if you like, but we’re simply using the class name as a label here to convey semantic information).

When you’re happy, save the SVG image. Inkscape SVG is fine unless you’re using something different for a rasteriser that requires standard SVG.

Editing the template is done by simply opening the SVG file in Inkscape and editing it like any other document, keeping in mind those two special panes for assigning CSS classes.

Python-based Templating engine

I did a quick experiment today, and came up with this very crude Python script to demonstrate a templating engine:

#!/usr/bin/env python3

from xml.etree import ElementTree
from collections import namedtuple
import datetime

SVGField = namedtuple("SVGField", ["element", "classes"])

svgdoc = ElementTree.parse("test.svg")
svgroot = svgdoc.getroot()

if svgroot.tag == "svg":
    namespaces = None
else:
    assert svgroot.tag.startswith("{") and svgroot.tag.endswith(
        "}svg"
    ), "Root element is not a SVG tag"
    # Pick out the namespace URI
    namespaces = dict(svg=svgroot.tag[1:-4])

# Figure out what the text and image tags will be called, they'll have
# the same namespace as the `<svg>` tag.
TEXT_TAG = "{%s}text" % namespaces["svg"]
IMAGE_TAG = "{%s}image" % namespaces["svg"]

# Figure out all classes defined and the elements using them
classnames = {}
for elem in svgdoc.iterfind(".//*[@class]", namespaces=namespaces):
    elem_classes = set(
        [cls for cls in elem.attrib.get("class", "").split(" ") if cls]
    )

    field = SVGField(elem, elem_classes)

    for cls in elem_classes:
        classnames.setdefault(cls, []).append(field)

print("All classes: %s" % ", ".join(sorted(classnames.keys())))
NOW = datetime.datetime.utcnow()

# These would be sourced from command line arguments or some file, the
# following shows how to replace the content of text fields.
for cls, fields in classnames.items():
    if cls == "freq":
        value = "123456.789 kHz"
    elif cls == "sstvmode":
        value = "Dummy SSTV Mode"
    elif cls == "utcdate":
        value = NOW.strftime("%Y-%m-%d")
    elif cls == "utctime":
        value = NOW.strftime("%H:%Mz")
    elif cls == "heading":
        value = "Test heading"
    elif cls == "message":
        value = "Test message text"
    else:
        print("Skip fields of class %s" % cls)
        continue

    for field in fields:
        if field.element.tag == TEXT_TAG:
            for tspan in field.element.iterfind(
                "./svg:tspan", namespaces=namespaces
            ):
                tspan.text = str(value)
        elif field.element.tag == IMAGE_TAG:
            # Iterate over the attributes and look for the href tag, which
            # may be prefixed by a namespace.  Make a note of the new value.
            changes = {}
            for attr in field.element.attrib.keys():
                if attr == "href" or (
                    attr.startswith("{") and attr.endswith("}href")
                ):
                    changes[attr] = str(value)

            if changes:
                # Apply the changes
                field.element.attrib.update(changes)


svgdoc.write("output.svg")

If I run this script against my mock template… I get an SVG that renders to this:

Rendered template with all text fields filled in from user-supplied data.

Next steps

What I need to do next I guess is to write a web-based front-end that I can use with my tablet, but the mechanics of what I need is all there now. I can design templates for simple messages or embedding a copy of the image another station sent me so I can reply to their transmission. I can render those templates to raster images for transmission. I can capture and display the incoming SSTV traffic.

I just need to put it together now.

RolandDG DXY-800A under Linux

Many moons ago, we acquired an old RolandDG DXY-800A plotter.  This is an early A3 plotter which took 8 pens, driven via either the parallel port or the serial port.

It came with software to use with an old DOS-version of AutoCAD.  I also remember using it with QBasic.  We had the handbook, still do, somewhere, if only I could lay my hands on it.  Either that, or on the QBasic code I used to use with the thing, as that QBasic code did exercise most of the functionality.

Today I dusted it off, wondering if I could get it working again.  I had a look around.  The thing was not difficult to drive from what I recalled, and indeed, I found the first pointer in a small configuration file for Eagle PCB.

The magic commands:

H Go home
Jn Select Pen n (1..8)
Mxxxx,yyyy Move (with pen up) to position xxx.x, yyy.y mm from lower left corner.
Dxxxx,yyyy Draw (with pen down) a line to position xxx.x, yyy.y mm

Okay, this misses the text features, drawing circles and hatching, but it’s a good start.  Everything else can be emulated with the above anyway.  Something I’d have to do, since there was only one font, and I seem to recall, no ability to draw ellipses.

Inkscape has the ability to export HPGL, so I had a look at what the format looks like.  Turns out, the two are really easy to convert, and Inkscape HPGL is entirely line drawing commands.

hpgl2roland.pl is a quick and nasty script which takes Inkscape-generated HPGL, and outputs RolandDG plotter language. It’s crude, only understands a small subset of HPGL, but it’s a start.

It can be used as follows:

$ perl hpgl2roland.pl < drawing.hpgl > /dev/lp0