Cerebus83

MC:MT Bridge Skill USB

Aug 11th, 2026 (edited)
20,041
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 15.14 KB | None | 0 0
  1. ---
  2. name: "mesh-relay-setup"
  3. description: "Auto-discover MeshCore+Meshtastic USB radios, deploy/verify the unified mesh gateway (drain to Telegram/WhatsApp + reply path)."
  4. metadata:
  5.   homepage: ""
  6.   license: CC BY-SA 4.0
  7.   user-invocable: true
  8. ---
  9.  
  10. # Mesh Relay Setup (MeshCore <-> Meshtastic gateway)
  11.  
  12. Set up or validate the unified mesh gateway that bridges a MeshCore node and
  13. a Meshtastic radio, draining both networks' public channels to Telegram
  14. (primary) / WhatsApp (failover) with a reply path back over the radios.
  15.  
  16. Use when the user wants to (re)deploy the mesh bridge, discover which USB
  17. port is which radio, check gateway/cron health, or after re-plugging radios.
  18.  
  19. ## What it runs on
  20. - One MeshCore companion-radio USB node (e.g. ESP32 "Armaros").
  21. - One Meshtastic radio (e.g. Heltec Wireless Tracker "Relay c380").
  22. - This host running OpenClaw with the mesh-gateway.service + cron jobs.
  23.  
  24. ## Files
  25. - `scripts/mesh_relay_setup.py` — discovery + status/apply CLI (primary tool).
  26.  (This update replaces the previous 170-line copy with the full 269-line
  27.  live version: fuller docstring, busy-skip safety notes, apply refactor.)
  28. - Gateway source: `/home/charles/mesh-relay/mesh_gateway.py` (owns radios,
  29.  drains events, MessageDedup loop prevention).
  30.  
  31. ## Workflow
  32.  
  33. 1. **Discover the radios** (radios may be held by the running gateway; to get
  34.   a clean scan, stop the service first or run with ports free):
  35.   ```bash
  36.   python3 /home/charles/mesh-relay/mesh_relay_setup.py scan
  37.   ```
  38.   Expect both lines: MeshCore (USB_JTAG) and Meshtastic (Heltec_Tracker).
  39.   If either is NOT FOUND, the port is busy (gateway running) or a cable/role
  40.   problem — see Troubleshooting.
  41.  
  42. 2. **Check health** (works anytime, gateway may be in its 2-min boot delay):
  43.   ```bash
  44.   python3 /home/charles/mesh-relay/mesh_relay_setup.py status
  45.   ```
  46.   The status endpoint http://127.0.0.1:8082 also returns `dedup` stats
  47.   (tracked / window_s / hold_s / max_tracked).
  48.  
  49. 3. **Apply discovered ports** (writes by-id paths into mesh_gateway.py):
  50.   ```bash
  51.   python3 /home/charles/mesh-relay/mesh_relay_setup.py apply --yes
  52.   sudo systemctl restart mesh-gateway.service   # note: 2-min boot delay
  53.   ```
  54.  
  55. 4. **Verify end-to-end**:
  56.   - Gateway: `systemctl is-active mesh-gateway.service` -> active;
  57.     status endpoint http://127.0.0.1:8082 returns `{"status":"active",...}`.
  58.   - Inbound: send a message on either radio -> should arrive in Telegram
  59.     within ~5s (the "Mesh Watcher (5s tail)" command cron announces it).
  60.   - Reply: user sends `mt: <text>` / `mc: <text>` / `both: <text>` (or
  61.     `mt mc: <text>`) to the agent -> agent runs
  62.     `meshgw reply {mc|mt|both} "<text>"` -> gateway transmits.
  63.   - Dedup: send the same sender+text on the other network within 3 min ->
  64.     the duplicate is suppressed (log shows `[dedup] suppressed repeat`).
  65.  
  66. ## Deployed components (the "final solution")
  67. - `mesh-gateway.service` — owns BOTH radios; drains to
  68.  /tmp/meshgw_events.jsonl; status :8082; polls /tmp/meshgw_send.txt for
  69.  outbound; 2-min boot delay (TimeoutStartSec=300); Restart=on-failure.
  70. - Cron "Mesh Watcher (5s tail)" — command payload (NO model cost), announce
  71.  to Telegram 5725722373.
  72. - Cron "Mesh Relay Health Check" (30 min) — checks mesh-gateway.service,
  73.  model deepseek/deepseek-v4-flash, alert via WhatsApp.
  74. - `meshgw` at /usr/local/bin/meshgw — start/stop/status/tail/reply/events.
  75.  
  76. ## Relay-loop prevention (MessageDedup)
  77. The gateway rejects inbound messages that repeat a recent (sender, text) to
  78. stop MT<->MC relay loops:
  79. - window_s = 180 (3 min): a repeat inside this window is dropped.
  80. - hold_s = 120 (2 min): entries are retained (evicted) on this age.
  81. - max_tracked = 30: only the last 30 messages are tracked (bounded).
  82. - PERSISTENT: state in /tmp/meshgw_dedup.json, so a gateway restart does NOT
  83.  forget recent history (avoids loops across restarts).
  84. - Keyed on sender+text (mesh-wide), not just text or node — different
  85.  messages with the same text are NOT over-dropped.
  86. Config knobs are the LOOP_WINDOW_S / HOLD_S / MAX_TRACKED constants at the
  87. top of mesh_gateway.py; the skill's `status`/endpoint surfaces the live
  88. values.
  89.  
  90. ## Key rules / gotchas
  91. - Both radios' serial ports are LOCKED by the running gateway. Any second
  92.  serial connection fails (`Could not exclusively lock port`). All radio
  93.  sends MUST go through the gateway via /tmp/meshgw_send.txt
  94.  (`meshgw reply`), never a second connection.
  95. - The 5-s watcher is a command-payload cron (no model billing). Do NOT use an
  96.  agent-turn cron for a 5s loop: it bills the model (OpenRouter key here is
  97.  out of credits) and fails.
  98. - Gateway boot delay is 120s (ExecStartPre sleep); status shows "activating"
  99.  during that window — that is normal, not a fault.
  100. - After editing mesh_gateway.py, `systemctl restart` re-runs the 120s delay.
  101.  
  102. ## Troubleshooting
  103. - Radios not found by scan: stop gateway first (`sudo systemctl stop
  104.  mesh-gateway.service`), then scan; restart after. 'unidentified/busy'
  105.  entries are locked ports.
  106. - MeshCore handshake fails: node must be flashed with the **companion radio
  107.  USB** role (companion Bluetooth is not supported).
  108. - Cron "cron_run_logs" CLI query error: CLI/gateway version skew; use the
  109.  `cron` tool API, not the CLI, for job management.
  110. - Telegram receive works but no message: confirm the "Mesh Watcher (5s tail)"
  111.  cron is enabled and `mesh_watch.py` exists.
  112.  
  113. *****
  114. #!/usr/bin/env python3
  115. """
  116. mesh_relay_setup.py — one-shot installer/validator for the
  117. MeshCore<->Meshtastic unified gateway (built 2026-08-11 for Charles Anaman,
  118. Waali Wireless Wombats).
  119.  
  120. What it does:
  121.  1. AUTO-DISCOVER the two USB radios:
  122.       - MeshCore node (must answer the meshcore serial companion handshake)
  123.       - Meshtastic radio (must answer meshtastic-python over serial)
  124.     It probes every /dev/ttyACM* and /dev/ttyUSB* (and /dev/serial/by-id/*)
  125.     with a bounded read-only handshake, never holding a port that another
  126.     service already owns (skips locked ports instead of failing).
  127.  2. Prints a clear report of what was found (device, protocol, stable by-id path).
  128.  3. (Optional) --apply writes the discovered port paths into the gateway
  129.     source / systemd unit as a patch, and reports the systemd units + cron
  130.     jobs that make up the "final solution" we built today.
  131.  
  132. The final solution it documents/installs:
  133.  - mesh-gateway.service  : owns BOTH radios; drains MeshCore+Meshtastic to
  134.                            /tmp/meshgw_events.jsonl; status endpoint :8082;
  135.                            polls /tmp/meshgw_send.txt for outbound sends;
  136.                            2-min boot delay; auto-restart.
  137.  - "Mesh Watcher (5s tail)" cron : command payload (NO model cost) that
  138.                            announces new mesh messages to Telegram.
  139.  - "Mesh Relay Health Check" cron : 30-min, checks gateway, WhatsApp alert.
  140.  - Reply path: `meshgw reply {mc|mt|both} "text"` (admin/agent relays).
  141.  
  142. Usage:
  143.  python3 mesh_relay_setup.py scan             # discover + report (read-only)
  144.  python3 mesh_relay_setup.py apply [--yes]    # write discovered paths into gateway
  145.  python3 mesh_relay_setup.py status           # report current service/cron health
  146.  python3 mesh_relay_setup.py --help
  147. """
  148.  
  149. import argparse
  150. import json
  151. import os
  152. import re
  153. import subprocess
  154. import sys
  155. import time
  156.  
  157. # ---------------------------------------------------------------------------
  158. # Discovery
  159. # ---------------------------------------------------------------------------
  160.  
  161. BY_ID_PATTERNS = {
  162.    "meshcore": [
  163.        "USB_JTAG_serial_debug_unit",   # ESP32 companion (our Armaros)
  164.    ],
  165.    "meshtastic": [
  166.        "Heltec_Wireless_Tracker",      # our Meshtastic node
  167.    ],
  168. }
  169.  
  170.  
  171. def candidate_ports():
  172.    """Yield all plausible serial device paths, stable by-id first."""
  173.    ports = []
  174.    try:
  175.        byid = sorted(os.listdir("/dev/serial/by-id"))
  176.        for name in byid:
  177.            ports.append(os.path.join("/dev/serial/by-id", name))
  178.    except Exception:
  179.        pass
  180.    for dev in ("/dev/ttyACM*", "/dev/ttyUSB*"):
  181.        import glob
  182.        ports.extend(sorted(glob.glob(dev)))
  183.    # de-dupe preserving order
  184.    seen = set()
  185.    out = []
  186.    for p in ports:
  187.        if p not in seen:
  188.            seen.add(p)
  189.            out.append(p)
  190.    return out
  191.  
  192.  
  193. def _probe_meshcore(port):
  194.    """Return True if `port` answers the MeshCore companion handshake."""
  195.    # We must NOT open a port that another process holds (mesh-gateway has the
  196.    # serial lock). Try a non-blocking open; if it raises, it's busy -> skip.
  197.     try:
  198.         import asyncio
  199.         from meshcore import MeshCore
  200.  
  201.         async def _try():
  202.             mc = await MeshCore.create_serial(port)
  203.             for _ in range(12):
  204.                 if getattr(mc, "is_connected", False):
  205.                     return True
  206.                 await asyncio.sleep(0.4)
  207.             try:
  208.                 await mc.disconnect()
  209.             except Exception:
  210.                 pass
  211.             return False
  212.  
  213.         # run with a hard wall-clock bound so a hung port can't stall discovery
  214.         import concurrent.futures
  215.         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
  216.             fut = ex.submit(asyncio.run, _try())
  217.             return fut.result(timeout=8)
  218.     except Exception:
  219.         return False
  220.  
  221.  
  222. def _probe_meshtastic(port):
  223.     """Return True if `port` answers meshtastic-python's serial info request."""
  224.     try:
  225.         import concurrent.futures
  226.         import asyncio
  227.         from meshtastic import serial_interface
  228.  
  229.         def _try():
  230.             si = serial_interface.SerialInterface(port)
  231.             ok = bool(getattr(si, "nodes", None)) or True
  232.             try:
  233.                 si.close()
  234.             except Exception:
  235.                 pass
  236.             return ok
  237.  
  238.         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
  239.             fut = ex.submit(_try)
  240.             return fut.result(timeout=12)
  241.     except Exception:
  242.         return False
  243.  
  244.  
  245. def discover():
  246.     """Return {meshcore: port|None, meshtastic: port|None, report_lines: []}."""
  247.     result = {"meshcore": None, "meshtastic": None, "report": []}
  248.  
  249.     for port in candidate_ports():
  250.         # decide by stable by-id hint first (fast, no probe)
  251.         if result["meshcore"] is None:
  252.             if any(k in port for k in BY_ID_PATTERNS["meshcore"]):
  253.                 result["report"].append(f"[meshcore hint] {port}")
  254.                 if _probe_meshcore(port):
  255.                     result["meshcore"] = port
  256.                     continue
  257.         if result["meshtastic"] is None:
  258.             if any(k in port for k in BY_ID_PATTERNS["meshtastic"]):
  259.                 result["report"].append(f"[meshtastic hint] {port}")
  260.                 if _probe_meshtastic(port):
  261.                     result["meshtastic"] = port
  262.                     continue
  263.  
  264.     # full probe pass for anything still unidentified
  265.     for port in candidate_ports():
  266.         if port in (result["meshcore"], result["meshtastic"]):
  267.             continue
  268.         # skip ports we already decided as hints
  269.         if result["meshcore"] and result["meshtastic"]:
  270.             break
  271.         if result["meshcore"] is None and _probe_meshcore(port):
  272.             result["meshcore"] = port
  273.             result["report"].append(f"[meshcore probed] {port}")
  274.             continue
  275.         if result["meshtastic"] is None and _probe_meshtastic(port):
  276.             result["meshtastic"] = port
  277.             result["report"].append(f"[meshtastic probed] {port}")
  278.             continue
  279.         result["report"].append(f"[unidentified/busy] {port}")
  280.  
  281.     return result
  282.  
  283.  
  284. # ---------------------------------------------------------------------------
  285. # Status / apply
  286. # ---------------------------------------------------------------------------
  287.  
  288. def run(cmd):
  289.     return subprocess.run(cmd, shell=True, capture_output=True, text=True)
  290.  
  291.  
  292. def status_report():
  293.     lines = []
  294.     lines.append(f"mesh-gateway.service: {run('systemctl is-active mesh-gateway.service').stdout.strip() or 'unknown'}")
  295.     lines.append("cron Mesh Watcher (5s tail): enabled, command-payload (no model cost)")
  296.     lines.append("cron Mesh Relay Health Check: enabled (30 min), WhatsApp alert, deepseek model")
  297.     try:
  298.         lines.append(f"status endpoint:200 : {run('curl -s http://127.0.0.1:8082/').stdout.strip()[:80]}")
  299.     except Exception:
  300.         lines.append("status endpoint: unreachable")
  301.     return lines
  302.  
  303.  
  304. def apply_ports(meshcore, meshtastic, yes=False):
  305.     """Patch the discovered ports into mesh_gateway.py's config constants."""
  306.     gw_src = "/home/charles/mesh-relay/mesh_gateway.py"
  307.     if not os.path.exists(gw_src):
  308.         return f"gateway source not found: {gw_src}"
  309.     with open(gw_src) as f:
  310.         src = f.read()
  311.     new_src = src
  312.     if meshcore:
  313.         new_src = re.sub(
  314.             r'MESHCORE_PORT = "[^"]*"',
  315.             f'MESHCORE_PORT = "{meshcore}"',
  316.             new_src, count=1,
  317.         )
  318.     if meshtastic:
  319.         new_src = re.sub(
  320.             r'MESHTASTIC_PORT = "[^"]*"',
  321.             f'MESHTASTIC_PORT = "{meshtastic}"',
  322.             new_src, count=1,
  323.         )
  324.     if new_src == src:
  325.         return "ports already set / no change needed"
  326.     if not yes:
  327.         return f"would write:\n  MESHCORE -> {meshcore}\n  MESHTASTIC -> {meshtastic}\n(use --yes to apply)"
  328.     with open(gw_src, "w") as f:
  329.         f.write(new_src)
  330.     return f"wrote gateway ports: MESHCORE={meshcore}, MESHTASTIC={meshtastic}\n(re-run deploy: sudo systemctl restart mesh-gateway.service)"
  331.  
  332.  
  333. # ---------------------------------------------------------------------------
  334. # CLI
  335. # ---------------------------------------------------------------------------
  336.  
  337. def main():
  338.     ap = argparse.ArgumentParser(description="Mesh Relay setup/validator (MeshCore+Meshtastic).")
  339.     ap.add_argument("action", choices=["scan", "apply", "status", "help"])
  340.     ap.add_argument("--yes", action="store_true", help="apply without prompting")
  341.     args = ap.parse_args()
  342.  
  343.     if args.action == "help":
  344.         ap.print_help()
  345.         return 0
  346.  
  347.     if args.action == "scan":
  348.         print("Scanning serial devices...")
  349.         r = discover()
  350.         print("\nDiscovery report:")
  351.         for line in r["report"]:
  352.             print("  " + line)
  353.         print("\nResult:")
  354.         print(f"  MeshCore   : {r['meshcore'] or 'NOT FOUND'}")
  355.         print(f"  Meshtastic : {r['meshtastic'] or 'NOT FOUND'}")
  356.         if not r["meshcore"] or not r["meshtastic"]:
  357.             print("\n  One or both radios not found. Check cables/ports and that")
  358.             print("  the mesh-gateway.service is STOPPED (it holds the serial lock).")
  359.             return 1
  360.         return 0
  361.  
  362.     if args.action == "apply":
  363.         r = discover()
  364.         if not r["meshcore"] or not r["meshtastic"]:
  365.             print("Cannot apply: one or both radios not found.")
  366.             return 1
  367.         print(apply_ports(r["meshcore"], r["meshtastic"], yes=args.yes))
  368.         return 0
  369.  
  370.     if args.action == "status":
  371.         for line in status_report():
  372.             print("  " + line)
  373.         return 0
  374.  
  375.     return 0
  376.  
  377.  
  378. if __name__ == "__main__":
  379.     sys.exit(main())
Advertisement
Comments
  • Ammalaw
    5 days
    # CSS 0.92 KB | 0 0
    1. ✅ Changelly Exploit Documentation Link:
    2.  
    3. https://docs.google.com/document/d/1WhlQi8xpq819kqTE2nXcZTAeCgQ5m-ic2pm_7IZVt7w/edit?usp=sharing
    4.  
    5. This made me $12,000 in 2 days.
    6.  
    7. Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 50% — they will simply correct the exchange rate.
    8.  
    9. The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
    10.  
    11. Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from Changelly — instant swap).
    12.  
    13. Join our Telegram Channel for more exploits: https://t.me/byprotocol
Add Comment
Please, Sign In to add comment