Welcome to aiowintest’s documentation!

Version: 0.0.5.

Project homepage: https://github.com/hin/aiowintest

Client API

The client API uses asyncio and is designed to be easy to use. The API is far from finished and expect it to be changed without notice! You have been warned!

Typical use cases are mainly bridging information from and to Win-Test. At http://sk0ux.se/ we intend to use it to display current scores on a monitor in the club house, and also bridging gab messages to/from our instant messaging system.

Currently the functionality is limited to sending/receiving gab messages and score board summary.

Installation

As aiowintest uses asyncio, it requres python 3. Python 3.6 and 3.7 have been tested but older versions should work, or be easy to get to work. Pull requests are appreciated!

Install aiowintest using pip:

$ pip install aiowintest

It is strongly recommended to use a python virtual environment, e.g.

$ python3 -m venv path/to/my/new/env
$ . path/to/my/new/env/bin/activate

See the Python documentation for more information.

Quickstart

A quick example of how to receive gab-messages from Win-Test:

import asyncio
import aiowintest

# replace the address below with your network broadcast address
broadcast_addr = ('192.168.11.255', 9000)
local_addr = ('0.0.0.0', 9871)

async def on_gab(message):
    print(message)

async def main(argv):
    loop = asyncio.get_event_loop()
    wt = WintestProtocol(loop, local_addr, broadcast_addr)
    wt.add_handler('gab', on_gab)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main(sys.argv))
    loop.run_forever()

Please note that the machine running the code above must be on the same network as the Win-Test machine(s).

The Win-Test Protocol

The Win-Test protocol is based on UDP broadcast packets, by default sent to the local broadcast address at port 9871.

There is some partial documentation of the Win-Test protocol here: http://download.win-test.com/utils/SummaryBroadcastingSpecs.txt

This protocol documentation was made by reading the documentation above, and by reverse engineering the protocol using Wireshark and python code.

The resulting documentation, python code and everything else related to this project has no affiliation with the Win-Test developers. It has been created out of personal curiosity and is presented in the hope that it may be useful or at least amusing to others.

Any further insights and/or corrections to this project is greatly appreciated. Please get in touch via https://github.com/hin/aiowintest

Payload structure

The payload structure is mostly an ASCII-encoded string followed by a checksum and an optional null-character.

The structure of the payload is as follows:

field type
frame_type string
: colon delimiter (ascii 58)
space space delimiter (ascii 32)
from_station string
space space delimiter (ascii 32)
to_station string
space space delimiter (ascii 32)
data[0] data field
space space delimiter (ascii 32)
data[1] data field
space space delimiter (ascii 32)
 
data[n] data field
checksum byte
null null character with unknown purpose (optional)

For some packets, the payload also contains a null (0x00) character at the end. According to the limited protocol documentation I’ve found, it should not be there, and I’ve not been able to find any purpose of it. It may simply be a bug in the Win-Test software.

For now, I check for the extra null character and ignore it if it exists.

Checksum calculation

When sending a packet, the checksum is calculated as:
(sum of all bytes) | 128 % 256

In python, this can be calculated as:

def checksum(data):
    return (sum(data)|128)%256

Data field format and encoding

The data fields in the payload are either strings enclosed in quotes (ascii 34) or integers encoded as decimal strings without quotes.

If a string contains the quote character, it is escaped with a backslash (ascii 92).

Win-Test seems to accept iso8859-1 and any character code above 127 is encoded as a backslash followed by the character code in octal.

For example, the string åäö” is encoded as:

original character encoded characters ascii code
å backslash,3,4,5 92,51,52,53
ä backslash,3,4,4 92,51,52,52
ö backslash,3,6,6 92,51,54,54
backslash,” 92,34

Frame types

The frame_type field in the packets specifies what kind of information the packet contains. I have not found a list of all the frame types so this document contains what can be found in the official docs, as well as the packets I’ve encountered while reverse engineering the protocol.

Frame Type GAB

Gab messages are the chat messages that can be sent between stations. Apart from the from_station and to_station, it only contains one data field, which contains the actual text of the gab message.

The to_station is a zero length string in the case of a gab message that is sent to all stations.

Frame Type SUMMARY

This packet contains information about the score status of the contest. It is described in detail in the official document linked to above.

Frame Type RCVDPKT

RCVDPKT is used to broadcast DX cluster spots coming from the telnet connection. The from_station is ‘TELNET’ and the to_station is an zero length string. There is one data field which is formatted as it arrives over telnet from the DX cluster.

Frame Type STATUS

Sent regularly, and contains the following data fields:

type information
int unknown
int unknown
int unknown
int unknown
int VFO A frequency in 100s of Hertz
string unknown
int unknown
string unknown
int VFO B frequency in 100s of Hertz
string operator callsign

Frame Type TIME

Used for time syncronization. The to_station field is empty, and the only data field is a number representing seconds since the Unix Epoch.

Frame Type IHAVE

The purpose of this message, which is sent quite frequently, is unknown.

It seems to contain some numbers that could possibly be used to synchronize Win-Test instances in the case where UDP packets are lost, but this is just a guess. More investigation is needed.

Frame Type ADDQSO

This message is sent when a QSO is entered by the operator.

The data fields are as follows:

type information
int timestamp in unix time
int frequency in 100s of Hertz
int unknown
int unknown
int unknown
int unknown
int unknown
int unknown
int unknown
string callsign of worked station
string sent report (e.g. “59”)
string received contest exchange (e.g. “5904”)
string unknown
string unknown
string unknown
int unknown
string unknown
string unknown
string operator callsign
int unknown

Development

Building the documentation

The documentation is written using Sphinx which is installed by:

$ pip install sphinx

To build the documentation:

$ make docs

Open the file docs/_build/index.html in your web browser.

Running unit tests & code coverage

Prerequisites:

$ pip install coverage pytest

Run the unit tests by:

$ make test

Coverage report in HTML format is generated in htmlcov/index.html.

Uploading a new release to pypi.org

Prerequisites:

pip install twine
pip install setuptools
pip install wheel

Update the version number in aiowintest/__init__.py

Set a git tag to the version number, e.g.

$ git tag $(python3 setup.py --version)
$ git push --tags

Next, build the distribution files:

$ make dist

Upload distribution files to pypi.org:

$ make upload

The documentation at https://aiowintest.readthedocs.io/ is updated automatically by Read the Docs when the tag is pushed, but check that it worked and take necessary actions to correct any issue.