Adding Perl script to analyze run results, adding feature of message file and determining if Announce needs to be repeated
This commit is contained in:
parent
cd7c41f898
commit
48e9aac047
2 changed files with 284 additions and 6 deletions
|
|
@ -44,6 +44,9 @@ running = True
|
|||
active_links = {}
|
||||
active_links_lock = threading.Lock()
|
||||
temporary_config_dir = None
|
||||
message_file_text = None
|
||||
message_file_path = None
|
||||
message_chunk_size = 900
|
||||
|
||||
|
||||
|
||||
|
|
@ -54,6 +57,23 @@ def log(msg):
|
|||
print(f"[{timestamp}.{milliseconds:03d}] {msg}", flush=True)
|
||||
|
||||
|
||||
def normalise_argv(argv):
|
||||
normalised = [argv[0]]
|
||||
key_value_args = {
|
||||
"message_file": "--message-file",
|
||||
"message-file": "--message-file",
|
||||
}
|
||||
|
||||
for arg in argv[1:]:
|
||||
key, separator, value = arg.partition("=")
|
||||
if separator and key in key_value_args:
|
||||
normalised.extend([key_value_args[key], value])
|
||||
else:
|
||||
normalised.append(arg)
|
||||
|
||||
return normalised
|
||||
|
||||
|
||||
def stop(_signum=None, _frame=None):
|
||||
global running
|
||||
running = False
|
||||
|
|
@ -248,6 +268,63 @@ def send_link_packet(link, text):
|
|||
RNS.Packet(link, payload, create_receipt=False).send()
|
||||
|
||||
|
||||
def active_link_count():
|
||||
with active_links_lock:
|
||||
links = list(active_links.values())
|
||||
|
||||
return sum(1 for link in links if link.status == RNS.Link.ACTIVE)
|
||||
|
||||
|
||||
def load_message_file(path):
|
||||
expanded_path = os.path.abspath(os.path.expanduser(path))
|
||||
with open(expanded_path, "r", encoding="utf-8") as file_handle:
|
||||
return expanded_path, file_handle.read()
|
||||
|
||||
|
||||
def utf8_chunks(text, max_bytes):
|
||||
chunk = ""
|
||||
chunk_bytes = 0
|
||||
|
||||
for character in text:
|
||||
character_bytes = len(character.encode("utf-8"))
|
||||
if chunk and chunk_bytes + character_bytes > max_bytes:
|
||||
yield chunk
|
||||
chunk = character
|
||||
chunk_bytes = character_bytes
|
||||
else:
|
||||
chunk += character
|
||||
chunk_bytes += character_bytes
|
||||
|
||||
if chunk:
|
||||
yield chunk
|
||||
|
||||
|
||||
def send_message_file(link):
|
||||
if message_file_text is None:
|
||||
return
|
||||
|
||||
chunks = list(utf8_chunks(message_file_text, message_chunk_size))
|
||||
total = max(1, len(chunks))
|
||||
total_bytes = len(message_file_text.encode("utf-8"))
|
||||
log(f"Sending file {message_file_path} as {total} chunk(s), {total_bytes} bytes")
|
||||
|
||||
for index, chunk_text in enumerate(chunks):
|
||||
if not running or link.status != RNS.Link.ACTIVE:
|
||||
return
|
||||
|
||||
send_link_packet(
|
||||
link,
|
||||
f"file_chunk {index + 1}/{total} from {NODE_NAME} "
|
||||
f"bytes={len(chunk_text.encode('utf-8'))} file={os.path.basename(message_file_path)} data={chunk_text}",
|
||||
)
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
def start_message_file_sender(link):
|
||||
if message_file_text is not None:
|
||||
threading.Thread(target=send_message_file, args=(link,), daemon=True).start()
|
||||
|
||||
|
||||
def link_packet_received(message, packet):
|
||||
try:
|
||||
text = message.decode("utf-8", errors="replace")
|
||||
|
|
@ -276,6 +353,7 @@ def outbound_link_established(link):
|
|||
|
||||
log(f"Outbound link established: {key}")
|
||||
send_link_packet(link, f"hello from {NODE_NAME}")
|
||||
start_message_file_sender(link)
|
||||
|
||||
|
||||
def inbound_link_established(link):
|
||||
|
|
@ -288,6 +366,7 @@ def inbound_link_established(link):
|
|||
|
||||
log(f"Inbound link established: {key}")
|
||||
send_link_packet(link, f"hello back from {NODE_NAME}")
|
||||
start_message_file_sender(link)
|
||||
|
||||
|
||||
def direct_packet_received(data, packet):
|
||||
|
|
@ -295,10 +374,13 @@ def direct_packet_received(data, packet):
|
|||
log(f"RX direct packet: {text}")
|
||||
|
||||
|
||||
def announce_loop(destination, interval):
|
||||
def announce_loop(destination, interval, only_when_disconnected):
|
||||
while running:
|
||||
destination.announce(app_data=NODE_NAME.encode("utf-8"))
|
||||
log(f"Announced {RNS.prettyhexrep(destination.hash)} as {NODE_NAME}")
|
||||
if only_when_disconnected and active_link_count() > 0:
|
||||
log("Skipped announce because an active Reticulum link exists")
|
||||
else:
|
||||
destination.announce(app_data=NODE_NAME.encode("utf-8"))
|
||||
log(f"Announced {RNS.prettyhexrep(destination.hash)} as {NODE_NAME}")
|
||||
|
||||
for _ in range(interval):
|
||||
if not running:
|
||||
|
|
@ -375,6 +457,22 @@ def parse_args():
|
|||
parser.add_argument("--announce-interval", type=int, default=30)
|
||||
parser.add_argument("--send-interval", type=int, default=10)
|
||||
parser.add_argument("--path-timeout", type=int, default=120)
|
||||
parser.add_argument(
|
||||
"--message-file",
|
||||
default=None,
|
||||
help="Send this UTF-8 text file once per established link instead of periodic heartbeats",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--message-chunk-size",
|
||||
type=int,
|
||||
default=900,
|
||||
help="Maximum UTF-8 bytes per file chunk packet",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--announce-only-when-disconnected",
|
||||
action="store_true",
|
||||
help="Skip periodic announces while at least one Reticulum link is active",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--ble-role",
|
||||
|
|
@ -402,7 +500,10 @@ def parse_args():
|
|||
help="Reticulum log verbosity for this run",
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
args = parser.parse_args(normalise_argv(sys.argv)[1:])
|
||||
if args.message_chunk_size < 1:
|
||||
parser.error("--message-chunk-size must be at least 1")
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
@ -417,6 +518,9 @@ if __name__ == "__main__":
|
|||
NODE_NAME = args.name or socket.gethostname().split(".")[0]
|
||||
identity_path = args.identity or default_identity_path(args.config, NODE_NAME)
|
||||
reticulum_config = runtime_config_dir(args, NODE_NAME)
|
||||
message_chunk_size = args.message_chunk_size
|
||||
if args.message_file:
|
||||
message_file_path, message_file_text = load_message_file(args.message_file)
|
||||
|
||||
log(f"Starting node {NODE_NAME}")
|
||||
log(f"Reticulum config: {args.config or '~/.reticulum'}")
|
||||
|
|
@ -441,9 +545,17 @@ if __name__ == "__main__":
|
|||
|
||||
log(f"Destination hash: {RNS.prettyhexrep(destination.hash)}")
|
||||
log("Use this hash as --peer on the other node.")
|
||||
if message_file_text is not None:
|
||||
log(f"Message file mode: {message_file_path} ({len(message_file_text.encode('utf-8'))} bytes)")
|
||||
log("Periodic heartbeats are disabled in message file mode.")
|
||||
|
||||
threading.Thread(target=announce_loop, args=(destination, args.announce_interval), daemon=True).start()
|
||||
threading.Thread(target=heartbeat_loop, args=(args.send_interval,), daemon=True).start()
|
||||
threading.Thread(
|
||||
target=announce_loop,
|
||||
args=(destination, args.announce_interval, args.announce_only_when_disconnected),
|
||||
daemon=True,
|
||||
).start()
|
||||
if message_file_text is None:
|
||||
threading.Thread(target=heartbeat_loop, args=(args.send_interval,), daemon=True).start()
|
||||
|
||||
if args.peer:
|
||||
threading.Thread(target=connect_to_peer, args=(args.peer, args.path_timeout), daemon=True).start()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue