You don’t need node hardware to use MeshFlow — a phone with a hotspot is a gateway on its own (the alpha). A node is what you add when you want range (Wi-Fi HaLow reaches kilometres, not metres) and a media server in the field that phones can hand their feeds to. The mesh itself is OpenMANET: an open-source, B.A.T.M.A.N.-adv tactical mesh on 802.11ah HaLow radios. MeshFlow is the video layer that runs on top of it.

The recipe is short: flash OpenMANET, copy in two binaries and the dashboard, enable a service. Everything mesh-side (radios, routing, gateways) is OpenMANET’s job and is documented by them; this page only covers what MeshFlow adds.

What a node gives you
A dashboard at http://<node>.local listing every stream on the mesh by node name · a MediaMTX media server on every node that phones, GoPros and RTSP/RTMP cameras publish to · pull-based routing across hops chosen by B.A.T.M.A.N. link quality · a cloud bridge (through a gateway node with internet) so a mesh that's split by terrain, or another mesh on another continent, joins the same network.

0. Pick your setup

Every mesh has exactly one gateway — the node with the internet uplink (a Starlink or router cabled in, or a Wi-Fi network it joins). Every other node is a mesh point. Start with the simplest setup that does the job. The Tested column says what we have built and run end to end on real hardware with this release.

Setup Nodes Pick it when Tested
1. One Pi gateway one Raspberry Pi 4, with the uplink You want the smallest thing that works: phones join the Pi’s Wi-Fi, see and watch each other’s video, and teammates anywhere can watch through the cloud. ✅
2. Pi gateway + Pi mesh points the gateway Pi, plus one or more Pis You need range. Each extra Pi carries video over HaLow from its own Wi-Fi to everyone else, and the gateway publishes the whole mesh to the cloud. Our recommended setup. ✅
3. GL.iNet gateway running MeshFlow a GL.iNet HaLowLink 2 with the uplink and MeshFlow, plus Pis You already have a HaLowLink 2 at the uplink site and want it to publish to the cloud itself. It works, with caveats: it runs MeshFlow from RAM, so after every reboot you copy the files back (step 4) and its identity on the network changes; it has little memory to spare, and cloud viewers of feeds from other nodes can see a slightly lower frame rate while it relays. ✅ with the caveats here
4. GL.iNet gateway, MeshFlow on the Pis a GL.iNet HaLowLink 2 with the uplink (plain OpenMANET), plus Pis running MeshFlow You have a HaLowLink 2 at the uplink site and want nothing to reinstall after a reboot. The HaLowLink stays a plain OpenMANET gateway; a Pi behind it does the cloud publishing. The recommended way to use a HaLowLink. ✅

What changes between them is only which node is the Mesh Gate in OpenMANET’s setup wizard (step 2) and which node gets cloud egress turned on (step 6): the gateway in setups 1–3, and one designated Pi behind the gateway in setup 4 (step 6). Turn it on for exactly one node per mesh. The rest of the steps are the same on every node that runs MeshFlow.

A few things that hold in all of them:

  • Mixed OpenMANET versions mesh fine. A 1.6 node and a 1.8 node on the same mesh ID, channel and passphrase find each other — you can upgrade one node at a time. MeshFlow 0.1.18 is the last release that supports OpenMANET 1.6; later releases target 1.8. If a node stays on 1.6, keep it on MeshFlow 0.1.18 — its release and these docs stay available from the version menu at the top of the page.
  • Node addresses aren’t fixed. OpenMANET hands out addresses across the mesh and moves a node to a free one when another joins. Reach nodes by name, http://<hostname>.local, rather than by writing down an IP.
  • Phones can join any node’s Wi-Fi. A phone on a mesh point gets its address from that node and sees every stream on the mesh; its internet goes through the mesh to the gateway.

1. Pick hardware

MeshFlow is built for the two OpenMANET targets we run ourselves:

Node CPU Role we use it in Storage
Raspberry Pi 4 + Morse Micro / Seeed HaLow HAT ARM64 (aarch64) field node — cameras, phones, the dashboard SD card: plenty. Binaries live in /root.
GL.iNet HaLowLink 2 MIPS little-endian (mipsel_24kc), 248 MB RAM backbone / gateway (Starlink, cell uplink) 2.4 MB of writable flash. Binaries live in RAM at /tmp (see step 4).

OpenMANET’s own hardware pages are the reference for what to buy and how to assemble it: Raspberry Pi · HaLowLink 2 · all supported hardware. Other OpenMANET targets (Heltec HT-HD01, Gateworks Venice) are ARM64 too and should run the ARM64 binary, but we haven’t tested them.

If a Raspberry Pi will be your gateway, plan its internet uplink now: Ethernet (a USB Ethernet adapter keeps the Pi’s own port for the mesh side), or a USB Wi-Fi adapter. The Pi 4’s built-in Wi-Fi can’t be an access point and a Wi-Fi client at the same time — make it the uplink and its access point disappears.

2. Flash OpenMANET and join the mesh

Follow OpenMANET’s setup guide for your device — it’s the authoritative source and we don’t duplicate it:

A freshly flashed node comes up at http://10.41.254.1 (user root, no password) and opens OpenMANET’s setup wizard. It asks for:

  1. Country and a root password — set one. MeshFlow’s dashboard is served without a login, on the assumption that the mesh itself is the trust boundary; the password is what protects the node’s SSH and admin pages.
  2. Mesh Point or Mesh Gate. A mesh gate is the node with the internet uplink (Starlink, a router, a hotspot) — it becomes the mesh’s gateway. Every other node is a mesh point (step 0); on a mesh point, choose the Bridge traffic mode, which puts phones on that node’s Wi-Fi in the same network as the rest of the mesh.
  3. The HaLow mesh — mesh ID, passphrase, channel and bandwidth. Every radio on one mesh must share all four.
  4. Upstream network (mesh gate only) — Ethernet, or a Wi-Fi network to join. On a Raspberry Pi 4, don’t pick the Pi’s built-in Wi-Fi for this (see step 1).
  5. The Wi-Fi access points phones will join — note the SSID and passphrase you set; the default passphrase is random.
The node's address changes when the wizard finishes. It leaves 10.41.254.1 — a mesh gate typically ends up at 10.41.0.1. Reconnect to the node's Wi-Fi (or cable) and reach it by name, <hostname>.local, or look for it in your DHCP leases. Examples below use NODE for whatever it is.

Check the 5 GHz access point’s channel. OpenMANET 1.8 leaves the 5 GHz radio on channel 34, which isn’t permitted in the US — the access point quietly stays down. It’s also the band a GoPro joins, and cameras only stream reliably on channel 36 and up. Set it to 36:

ssh root@$NODE "uci show wireless | grep \"band='5g'\""   # find the 5 GHz radio (radio1 on a Pi 4)
ssh root@$NODE "uci set wireless.radio1.channel='36'; uci commit wireless; wifi reload"

When batctl n on a node lists its neighbours, the mesh is up and you’re ready for MeshFlow. (A single node has no neighbours — batctl if showing the HaLow interface as active is the one-node check.)

3. Download the MeshFlow node release

MeshFlow 0.1.19 · released 27 September 2026 · release files
ATAK across your network: with cot_bridge on, every ATAK on the mesh — MeshFlow installed or not — shares positions, chat and markers with your whole network, both ways. Video (cloud_egress) and ATAK sharing (cot_bridge) are separate switches, both off by default. For OpenMANET 1.8 — use 0.1.18 on 1.6.

HardwareOpenMANETRoleStatus
Raspberry Pi 41.8.0gateway (video + ATAK sharing, with a 1.6 mesh point behind it)✅ verified
Raspberry Pi 41.8.0mesh point— not tested
Raspberry Pi 41.6.xuse 0.1.18— not tested
GL.iNet HaLowLink 21.6.5plain gateway, MeshFlow on a Pi behind it (setup 4)— not tested

verified: we built and ran it end to end on that hardware with this release · supported: written and built for it, not re-verified since · being verified: targeted, testing not finished · not tested: may work, we haven't run it.

Current node release: 0.1.19 (matches the alpha app). Free for non-commercial use under the MeshFlow EULA; the node contains no telemetry and never phones home on its own.

FileForWhat it is
meshflow-linux-arm64Raspberry Pi 4 and other ARM64 nodesThe MeshFlow node agent (discovery, routing, dashboard, cloud bridge). 25 MB, static, no dependencies.
meshflow-linux-mipsleGL.iNet HaLowLink 2The same agent built for MIPS (`mipsle`, soft-float). 29 MB.
meshflow-ui.tar.gzevery nodeThe dashboard — the same web console you get at /app/, served by the node itself. Unpack to ui/ next to the agent.
mediamtx.ymlevery nodeOur MediaMTX configuration: RTSP :8554, HLS :8888, WebRTC :8889, control API :9997, publisher-sourced paths.
meshflow.initevery nodeOpenWrt procd service that keeps the agent and MediaMTX alive across crashes and reboots.
mediamtx (ARM64)Raspberry Pi 4MediaMTX v1.11.0, the media server, straight from its upstream release. Extract the mediamtx binary only — keep our mediamtx.yml.
mediamtx-linux-mipsleGL.iNet HaLowLink 2MediaMTX v1.11.0 built by us for MIPS (upstream publishes no MIPS build). 23 MB.
SHA256SUMSChecksums for everything above that we host. sha256sum -c SHA256SUMS

OpenMANET's Raspberry Pi image already runs its own MediaMTX for the Pi camera on RTSP port 554. MeshFlow runs a second, separate instance on its own ports, so the two don't collide and the camera feed keeps working.

4. Install on the node

The examples below use scp/ssh from your laptop with the node reachable at NODE (its mesh IP, or <name>.local). Pass -O to scp: the HaLowLink 2’s SSH server (dropbear) has no SFTP support, and modern OpenSSH’s scp speaks SFTP by default and fails there with “EOF during negotiation”. -O uses the legacy protocol, which works on every OpenMANET node.

Raspberry Pi (ARM64) — installs to /root, survives reboot

NODE=10.41.0.1       # your node
# from the directory where you downloaded the release:
tar xzf mediamtx_v1.11.0_linux_arm64v8.tar.gz mediamtx
scp -O meshflow-linux-arm64 mediamtx mediamtx.yml meshflow-ui.tar.gz meshflow.init root@$NODE:/root/
ssh root@$NODE '
  cd /root && mv meshflow-linux-arm64 meshflow && chmod +x meshflow mediamtx &&
  mkdir -p ui && tar xzf meshflow-ui.tar.gz -C ui && rm meshflow-ui.tar.gz &&
  mv meshflow.init /etc/init.d/meshflow && chmod +x /etc/init.d/meshflow
'

The HaLowLink 2 has 2.4 MB of writable flash; the agent alone is 29 MB, so it runs from /tmp (a 121 MB RAM disk). That means a reboot wipes it — re-run the copy afterwards. The service script looks in /root first and falls back to /tmp automatically.

NODE=10.41.1.1       # your backbone node
scp -O meshflow-linux-mipsle mediamtx-linux-mipsle mediamtx.yml meshflow-ui.tar.gz meshflow.init root@$NODE:/tmp/
ssh root@$NODE '
  cd /tmp && mv meshflow-linux-mipsle meshflow && mv mediamtx-linux-mipsle mediamtx && chmod +x meshflow mediamtx &&
  mkdir -p ui && tar xzf meshflow-ui.tar.gz -C ui && rm meshflow-ui.tar.gz &&
  mv meshflow.init /etc/init.d/meshflow && chmod +x /etc/init.d/meshflow
'
If the flash ever fills the overlay silently mounts read-only at boot (dmesg | grep overlayfs shows failed to create directory /overlay/work/work (errno: 28)) and nothing will install or keep its exec bit. Free space by deleting through /overlay/upper/… (the underlying JFFS2 is still writable), then reboot. Never put the binaries in /root on this device.

5. Free port 80 and fix the MTU

Port 80. The MeshFlow dashboard listens on 80 (HTTP) and 443 (HTTPS) so a phone can reach it as plain http://<node>.local — browsers won’t accept a self-signed certificate on a .local name without that plain-HTTP front door. OpenMANET’s own web UI lives on :8080/:8081, but if LuCI’s uhttpd is still on 80/443, move it to 8000/4443 first:

ssh root@$NODE '
  uci set uhttpd.main.listen_http="0.0.0.0:8000"
  uci set uhttpd.main.listen_https="0.0.0.0:4443"
  uci commit uhttpd && /etc/init.d/uhttpd restart
'

(Or run the agent elsewhere with -port 8088 in the service command line — but then phones need the port typed in.) If 80 is taken, the agent logs [API] HTTP Server Error and keeps running on 443 only, which looks like “the dashboard is dead” from a phone. Check logread before anything else.

MTU. B.A.T.M.A.N.-adv adds ~28–32 bytes to every frame it carries. If the mesh interface (bat0) is left at 1500, its frames no longer fit the radio: the mesh forms (neighbours appear, batctl n looks fine) but unicast data is silently dropped — 100% packet loss on ping, dead video. The fix is to keep bat0 and the bridge at 1460. OpenMANET 1.8’s wizard already does this — check:

ssh root@$NODE 'for i in bat0 br-ahwlan; do echo "$i $(cat /sys/class/net/$i/mtu)"; done; batctl if'

If either reads 1500 (older OpenMANET images), set them:

ssh root@$NODE '
  uci set network.bat0.mtu="1460"; uci set network.ahwlan.mtu="1460"; uci commit network
  ip link set dev bat0 mtu 1460; ip link set dev br-ahwlan mtu 1460
'

batctl if names the HaLow interface batman runs on — wlh0 on OpenMANET 1.8, wlan0 on 1.6. It can stay at its default 1500 once bat0 is 1460. The MeshFlow service re-applies these MTUs every time it starts.

Gateways. On the node with the internet uplink, batctl gw_mode server (OpenMANET’s mesh gate) and turn on bridge-loop avoidance if it bridges the mesh to a shared backbone like a Starlink — otherwise you get broadcast storms. Field nodes run batctl gw_mode client. OpenMANET’s wizard sets this; MeshFlow just reads it.

6. Start it

ssh root@$NODE '/etc/init.d/meshflow enable && /etc/init.d/meshflow start'

The service runs the agent as meshflow -debug -ui-path <base>/ui -name <hostname> and MediaMTX with our mediamtx.yml, respawning either if it dies. The node’s name on the dashboard is its hostname — set it with uci set system.@system[0].hostname='SCOUT-01'; uci commit system and reboot, and it’s SCOUT-01 everywhere instead of a MAC address.

Join your team’s network. If your team uses its own network in the app (a name and password), give the node the same one:

ssh root@$NODE "
  touch /etc/config/meshflow
  uci set meshflow.main=meshflow
  uci set meshflow.main.network_name='<name>'
  uci set meshflow.main.network_pass='<password>'
  uci commit meshflow && /etc/init.d/meshflow restart
"

Bridge a gateway to the cloud. On the mesh gate only, also run uci set meshflow.main.cloud_egress='1'; uci commit meshflow && /etc/init.d/meshflow restart — the restart is what applies it; uci commit alone does not. The node then publishes every stream on its mesh to your network in the cloud, so teammates anywhere — on cellular, or on another mesh — can find and watch them. It only runs while the node really is a gateway (OpenMANET’s gateway mode plus working internet), and it needs the node’s clock to be right, which it gets over that same uplink.

If your gateway doesn’t run MeshFlow (setup 4). When a HaLowLink is a plain OpenMANET gateway, pick one Pi behind it to do the cloud bridging and tell it so:

ssh root@$PI "
  uci set meshflow.main.cloud_egress='1'
  uci set meshflow.main.egress_designated='1'
  uci commit meshflow && /etc/init.d/meshflow restart
"

A designated node bridges whenever it has internet (through the gateway), whatever its role in the mesh. Designate exactly one node per mesh — two would both publish every stream. Its log says which rule it’s running: logread | grep CloudEgress shows DESIGNATED egress on the designated Pi and advertises only while this node is the WAN gateway everywhere else.

Share ATAK across your network. With uci set meshflow.main.cot_bridge='1' (then uci commit meshflow && /etc/init.d/meshflow restart), the gateway joins every ATAK on its mesh to everyone on your network: positions, chat, markers and routes go out to your teammates anywhere, and theirs come back onto the mesh. The phones on the mesh don’t need MeshFlow installed, only ATAK. They already see each other over the mesh, and the gateway is the one place that needs to bridge. It’s separate from cloud_egress: turn on either, or both. Both are off until you set them, and like cloud bridging it only runs on the node doing the cloud bridging (the gateway, or the one egress_designated Pi). It shares with your network only, never with anyone else’s.

7. Verify

From anything on the mesh (a phone on the node’s AP counts):

  • Dashboard: http://<node-ip>/ or http://<hostname>.local/. Your node is listed by name; other MeshFlow nodes on the mesh appear as they’re discovered (UDP broadcast on 9998, a few seconds).
  • API: curl http://<node-ip>/api/info returns the node’s identity (name, addresses); curl http://<node-ip>/api/streams lists every stream it can see.
  • Media server: curl http://127.0.0.1:9997/v3/paths/list on the node itself. Publish a test feed at it — ffmpeg -re -f lavfi -i testsrc=size=1280x720:rate=30 -c:v libx264 -f rtsp rtsp://<node-ip>:8554/test — and it shows up on the dashboard as test on your node, and on every other node’s dashboard too.
  • Logs: logread -e meshflow (or logread -f). A healthy start logs Starting HTTP Server on port 80 and Starting HTTPS Server on port 443, and no [AUDIT-ERROR] MediaMTX line. curl http://<node-ip>/api/info also reports the node’s version.
  • The app: open the MeshFlow app on a phone joined to the node’s Wi-Fi. Node presence goes bridged, the node’s streams appear in the Streams tab, and Share to ATAK puts them on the map.
  • The cloud (gateways with cloud_egress or cot_bridge): logread -e meshflow | grep CloudEgress shows connected as … to network "<name>" (video egress true, CoT bridge …) — which of the two are on.
  • ATAK sharing (cot_bridge): logread -e meshflow | grep CotBridge shows hearing ATAK CoT on 239.2.3.1:6969, then mesh -> backbone and backbone -> mesh lines as ATAK users on either side report in. From anywhere off the mesh, open https://totaloverwatchproductions.com/app/#<name>:<password> — the node’s streams are listed and play.

Ports a node uses

Port What
80 / 443 MeshFlow dashboard and API (HTTP / HTTPS, self-signed)
9998 / udp MeshFlow stream discovery between nodes
8554 RTSP publish + play (MediaMTX)
1935 RTMP publish — what a GoPro livestreams to (MediaMTX)
8888 HLS (MediaMTX)
8889 / 8189 udp WebRTC signalling and ICE (MediaMTX)
9997 MediaMTX control API — used by the agent and the dashboard, open to the mesh without a password (the mesh is the trust boundary)
8000 / 4443 Where we park LuCI (see step 5)

Troubleshooting

  • Mesh forms but nothing flows (neighbours listed, ping 100% loss): MTU. Step 5.
  • Phones can’t see the node’s 5 GHz network: the 5 GHz radio is on a channel your country doesn’t allow (OpenMANET 1.8 defaults to 34). Step 2.
  • Can’t find the node after the setup wizard, or after adding another node: its address changed — the wizard moves it, and OpenMANET moves nodes to a free address as others join. Use http://<hostname>.local. Step 0.
  • A GL.iNet gateway went quiet after a power cut: MeshFlow was in its RAM. Copy the files back and start it (step 4, step 6); the service and its settings survive.
  • Dashboard unreachable from a phone but curl works on the node: the OpenWrt firewall. The node’s AP zone (ahwlan) accepts input on OpenMANET’s image; if you changed zones, allow input on the interface phones arrive from.
  • A node that was working “went dark” after a reboot (HaLowLink 2): the binaries were in /tmp. Re-copy (step 4) and start. The service is still enabled.
  • HaLowLink 2 refuses to install anything / lost exec bits: read-only overlay. See the note in step 4.
  • Powered on, batctl o empty, radio silent: dmesg | grep -i morse. SPI transfer timed out means the HaLow radio’s bus hung — physically power-cycle; software resets don’t recover it.
  • Two gateways, both on Starlink: give the second dish a different LAN subnet (e.g. 192.168.2.1) in the Starlink app or the backbone IPs collide.
  • Camera works on OpenMANET’s rtsp://…:554/rpicamera but not in MeshFlow: that stream belongs to OpenMANET’s MediaMTX. Point the camera (or an ffmpeg re-publish) at MeshFlow’s rtsp://<node>:8554/<name> and it’s on the dashboard.

Stuck? info@totaloverwatchproductions.com — send logread output and batctl n.

License

The node binaries are free for personal, hobbyist and educational use. Business, government and other revenue-generating deployments need a commercial license — an annual flat rate per organisation with no seat or node limits. The full End User License Agreement is in the release. The software contains no telemetry, no auto-update pings and no remote license checks; it is built to work air-gapped.

⬇ Node release 0.1.19 ← Back to MeshFlow