summaryrefslogtreecommitdiff
path: root/stacks/tncs/hybbx-ax25-beacon-once.py
diff options
context:
space:
mode:
authorinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
committerinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
commit04d965d67a7264a1c7c211494aebda1953df7603 (patch)
tree0ebd700a6e219f84a26a656f4bee8778bc75da7b /stacks/tncs/hybbx-ax25-beacon-once.py
Initial push
Diffstat (limited to 'stacks/tncs/hybbx-ax25-beacon-once.py')
-rwxr-xr-xstacks/tncs/hybbx-ax25-beacon-once.py105
1 files changed, 105 insertions, 0 deletions
diff --git a/stacks/tncs/hybbx-ax25-beacon-once.py b/stacks/tncs/hybbx-ax25-beacon-once.py
new file mode 100755
index 0000000..192d986
--- /dev/null
+++ b/stacks/tncs/hybbx-ax25-beacon-once.py
@@ -0,0 +1,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