summaryrefslogtreecommitdiff
path: root/stacks/tncs/hybbx-ax25-beacon-once.py
blob: 192d98612f70f2721082b933ceddf3b656aa8357 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/usr/bin/env python3
"""One-shot AX.25 UI beacon matching HyBBX [broadcast] INI (HyBBX must stay running).

Connects to the internal circuit hub and sends an HBX AX25_UI frame with TX flag
the same way hybbx_broadcast_ax25() does. Requires packet_radio1 linked with
link_password on the Main.
"""
import argparse
import socket
import struct
import sys
import time

MAGIC = b"HBX"
VERSION = 0x01
HEADER_SIZE = 12
PROTO_LINK_AUTH = 0x03
PROTO_LINK_AUTH_ACK = 0x04
PROTO_AX25_UI = 0x02
FLAG_TX = 0x0002
FLAG_PATH = 0x0004
ADDR_LEN = 7


def be16(v):
    return struct.pack(">H", v)


def be32(v):
    return struct.pack(">I", v)


def encode_frame(proto, flags, payload):
    return bytes([ord("H"), ord("B"), ord("X"), VERSION, proto]) + be16(flags) + be32(len(payload)) + payload


def encode_addr(call, ssid=0, last=False):
    call = call.upper().ljust(6)[:6]
    raw = bytes((ord(c) << 1) for c in call)
    ssid_b = ((ssid & 0x0F) << 1) | (0x60 if not last else 0x61)
    return raw + bytes([ssid_b])


def pack_ax25_ui(dest, src, message):
    body = bytes([0])  # digi count
    body += encode_addr(dest, last=False)
    body += encode_addr(src, last=True)
    body += message.encode("ascii", errors="replace")
    return encode_frame(PROTO_AX25_UI, FLAG_TX | FLAG_PATH, body)


def link_auth_payload(password, link_id, role, baud=2400, duplex="half", bandwidth="low", mhz="27.235"):
    lines = [
        f"password={password}",
        f"role={role}",
        f"id={link_id}",
        f"baud={baud}",
        f"duplex={duplex}",
        f"bandwidth={bandwidth}",
        f"frequency_mhz={mhz}",
    ]
    return "\n".join(lines).encode("ascii")


def recv_frames(sock, timeout=5.0):
    sock.settimeout(timeout)
    buf = b""
    try:
        while True:
            chunk = sock.recv(4096)
            if not chunk:
                break
            buf += chunk
    except socket.timeout:
        pass
    return buf


def main():
    ap = argparse.ArgumentParser(description="HyBBX one-shot AX.25 RF beacon via circuit hub")
    ap.add_argument("--host", default="127.0.0.1")
    ap.add_argument("--port", type=int, default=7323)
    ap.add_argument("--password", default="changeme")
    ap.add_argument("--link-id", default="beacon-once")
    ap.add_argument("--mycall", default="CB-0")
    ap.add_argument("--dest", default="*")
    ap.add_argument("--message", default="Broadcast: CB-0 online")
    args = ap.parse_args()

    sock = socket.create_connection((args.host, args.port), timeout=5)
    auth = link_auth_payload(args.password, args.link_id, "gateway")
    sock.sendall(encode_frame(PROTO_LINK_AUTH, 0, auth))
    ack = recv_frames(sock, 3.0)
    if b"ok=yes" not in ack:
        print("LINK_AUTH failed:", ack[:200], file=sys.stderr)
        sys.exit(1)
    print(f"LINK_AUTH ok — TX {args.mycall} -> {args.dest}: {args.message!r}")
    sock.sendall(pack_ax25_ui(args.dest, args.mycall, args.message))
    time.sleep(2)
    sock.close()
    print("Beacon frame sent to circuit hub.")


if __name__ == "__main__":
    main()
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com