Skip to content

RadioController.send_tnc_data with VR-N76 #15

@amirdahal

Description

@amirdahal

I have observed a behaviour while sending an APRS message using RadioController.send_tnc_data. This method works well while working with the BTECH UV-PRO, but I am facing trouble when working with the VR-N76. I saw in the README that the package has not been tested with the VR-N76, which might be the issue.

The behaviour isn't consistent, as there are times when I can send one packet once in a while, but 95% of the time, the transmission fails. I will include the code for encoding and decoding the packet.

Encoder

def ax25_callsign(call, ssid=0, last=False):
    call = call.upper().ljust(6)  # Ensure call is exactly 6 characters
    encoded = bytes([(ord(c) << 1) for c in call[:6]])  # Shift ASCII values
    encoded += bytes([0x60 | (ssid << 1) | (0x01 if last else 0x00)])  # SSID + last bit
    return encoded

def build_aprs_message(dest_call, src_call, digipeaters, recipient, message):
    # Encode AX.25 callsigns
    dest = ax25_callsign(dest_call, 0, last=False)
    src_ssid = int(src_call.split('-')[1]) if '-' in src_call else 0
    src = ax25_callsign(src_call.split('-')[0], src_ssid, last=(len(digipeaters) == 0))

    # Encode digipeaters
    digis = b""
    for i, digi in enumerate(digipeaters):
        call, ssid = digi.split('-') if '-' in digi else (digi, 0)
        last = (i == len(digipeaters) - 1)  # Only last digipeater has last bit set
        digis += ax25_callsign(call, int(ssid), last=last)

    # Construct APRS payload correctly (ensure exactly 9 characters for recipient)
    recipient = recipient.ljust(9)[:9]  # Left-align and enforce 9-char limit
    aprs_payload = f":{recipient}:{message}"  # Ensure only two colons

    control_pid = b'\x03\xf0'  # UI-frame, no layer 3 protocol
    return dest + src + digis + control_pid + aprs_payload.encode()

Decoder

def ax25_decode(addr_bytes):
        callsign = ''
        for i in range(6):
            char = (addr_bytes[i] >> 1) & 0x7F
            if char != 0x20:
                callsign += chr(char)
        ssid = (addr_bytes[6] >> 1) & 0x0F
        return f"{callsign}-{ssid}" if ssid else callsign

def parse_aprs(data):
        source_addr = ax25_decode(data[7:14])
        dest_addr = ax25_decode(data[:7])

        digipeaters = []
        offset = 14
        while offset + 7 < len(data) and not (data[offset] & 1):
            digipeaters.append(ax25_decode(data[offset:offset+7]))
            offset += 7

        payload_index = data.index(b'\x03\xf0') + 2
        aprs_payload = data[payload_index:].decode(
            'ascii', errors='ignore').strip()
        aprs_packet = f"{source_addr}>{dest_addr}" + \
            (f",{','.join(digipeaters)}" if digipeaters else "") + \
            f":{aprs_payload}"
       return aprs_packet

Usage

frame = build_aprs_message("APN000", "NOCALL-1", ["WIDE1-1", "WIDE2-2"], "DESCAL-1", "Hello there")
print(frame)
parsed = parse_aprs(frame)
print(parsed)

The above algorithm is implemented to work for both UV-PRO and VR-N76, but its only working for one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions