{"id":27210734,"url":"https://github.com/openipc/adaptive-link","last_synced_at":"2025-10-24T11:49:51.318Z","repository":{"id":258020645,"uuid":"867947375","full_name":"OpenIPC/adaptive-link","owner":"OpenIPC","description":"Greg's Adaptive-Link - Files for OpenIPC camera and Radxa Zero 3w/e ground station","archived":false,"fork":false,"pushed_at":"2025-04-01T05:57:10.000Z","size":513,"stargazers_count":12,"open_issues_count":10,"forks_count":5,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-01T06:31:18.325Z","etag":null,"topics":["adaptive-link","fpv","openipc"],"latest_commit_sha":null,"homepage":"https://openipc.org","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/OpenIPC.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"open_collective":"openipc"}},"created_at":"2024-10-05T04:37:26.000Z","updated_at":"2025-04-01T05:57:13.000Z","dependencies_parsed_at":"2024-12-17T07:33:29.252Z","dependency_job_id":"4a9881f1-9889-476e-99d3-7ae34e1298a0","html_url":"https://github.com/OpenIPC/adaptive-link","commit_stats":null,"previous_names":["sickgreg/openipc-adaptive-link","openipc/adaptive-link"],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenIPC%2Fadaptive-link","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenIPC%2Fadaptive-link/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenIPC%2Fadaptive-link/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenIPC%2Fadaptive-link/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/OpenIPC","download_url":"https://codeload.github.com/OpenIPC/adaptive-link/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248138209,"owners_count":21053833,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adaptive-link","fpv","openipc"],"created_at":"2025-04-10T01:20:59.983Z","updated_at":"2025-10-24T11:49:51.311Z","avatar_url":"https://github.com/OpenIPC.png","language":"C","funding_links":["https://opencollective.com/openipc"],"categories":[],"sub_categories":[],"readme":"## OpenIPC-Adaptive-Link\n### Greg's Adaptive-Link - For OpenIPC Camera and Radxa Zero 3w/e or Android PixelPilot Ground Station\n\nWarning: Mindfully set power levels appropriately in /etc/txprofiles.conf.  Default is an attempt at MarioAIO-safe, max 30 (Which is quite low for a lot of cards). \n\n### How Adaptive-Link works\n\n  A wireless link can handle higher data-rates over short distances with strong signal than long distances and weak signal conditions.  OpenIPC on it's own lets us choose a single link-speed and a single video bitrate that we think can fit in said link.  Default at the time of writing is 4mbps video bitrate over a 20Mhz channel @ MCS1 long gi with 8/12 forward error correction.  Without needing to understand all those parameters, we can simply summarize them as \"a medium to long range link speed with low quality video\" profile.  A compromise of both range and image quality.  This is great if you don't need to fly really far away or don't want to fly really close with nice looking high bitrates.  For ease of operation... consider leaving it on default and fly!  Have fun.\n\nAdaptive-Link is a (relatively) simple link profile selector.  We define a set of profiles; from longest range/worst quality, medium range/medium quality to short range/high quality link and video settings and alink continually selects one based on received signal quality at the ground station.  So ideally, if alink is tuned correctly, strong signal will initiate selection of highest quality video and weak signal will initiate selection of longest range and whatever we define in between.\n\nalink_gs (on ground station) monitors RSSI and SNR and generates link quality scores.  Link scores are sent to drone and alink_drone chooses a profile based on the link scores.  If tuned correctly, high score = high quality, medium score = medium quality, low score = low quality.  Scores are values from 1000 to 2000.\n\nThe most fundamental settings are in /config/alink_gs.conf on ground station and /etc/alink.conf on drone.  The all important profiles are defined in /etc/txprofiles.conf on drone.\n\nWe need to define what is a good/bad score:\n\nIn /config/alink_gs.conf we find min and max ranges\nFor example if we set:\n\n(1000) rssi_min = -80\n\n(2000) rssi_max = -40\n\n\n(1000) snr_min = 12\n\n(2000) snr_max = 36\n\n\nAs an example, with these ranges, if we have ground station rx stats of:\n\nrssi = -60, the normalized rssi_score will be 1500\nsnr = 20, the normalized snr_score will be 1333\n\nThese scores are calculated and sent from ground station to drone every 100ms (by default).  when alink_drone receives one of these messages it will consider if it needs to change txprofile or not.  There must be, and is, contraints so we don't get continuous unnessesary link changes.  A lot of options are in /etc/alink.conf regarding these contraints.\n\nIf we have our rssi/snr weights set to 0.5 and 0.5 the scores in the example above will each contribute equally and the overall score considered will be 1416.  alink will, based on the other constraints, effectively choose the profile whose range 1416 falls into.\n\nexample /etc/txprofiles.conf (from alink v0.56.0)\n\n```\n# \u003cra - nge\u003e \u003cgi\u003e \u003cmcs\u003e \u003cfecK\u003e \u003cfecN\u003e \u003cbitrate\u003e \u003cgop\u003e \u003cPwr\u003e \u003croiQP\u003e \u003cbandwidth\u003e \u003cqpDelta\u003e\n999 - 999 long 0 8 12 1999 10 30 0,0,0,0 20 -12\n1000 - 1050 long 0 8 12 2000 10 30 0,0,0,0 20 -12\n1051 - 1500 long 1 8 12 4000 10 25 0,0,0,0 20 -12\n1501 - 1950 long 2 8 12 8000 10 20 12,6,6,12 20 -12\n1951 - 2001 short 2 8 12 9000 10 20 12,6,6,12 20 -12\n```\n\n\nIn our example above of total score of 1416, so long as alink_drone considers it appropriate to do so based on contraints, it will select the \u003c 1051 - 1500 \u003e profile.\n\n\nMCS 1, long guard interval, 8/12 fec, 25 power, 20MHz bandwidth link settings will be applied.\n\n\nbitrate 4000kbps, gopSize 10, 0,0,0,0 focus mode qP definition and qpDelta of -12 video settings will be applied.\n\n\nAdaptive-Link is only as good as the information we give it.  Garbage in, garbage out.  It is important to have it tuned for our specific setup.\n\n\nIf folks cannot achieve 10mbps with their setup, then having alink select 20mbps is not going to work.\n\n\n### Installation\n\n## Drone\n\nMost recent ssc338q and ssc30kq OpenIPC firmware should already include adaptive-link, thuogh it may not be enabled by default.  You can have it run at startup by adding the following line to `/etc/rc.local`\n\n```\nalink_drone \u0026\n```  \n\n## Manual install method for drone\n1. A recent OpenIPC firmware for Sigmastar including wfb tunnel is required. It is recommended to upgrade camera to latest OpenIPC (Warning: All files will be overwritten)\n\n`sysupgrade -k -r -n`\n\n2. For manual file download; check out releases ---------\u003e\n\nOR Auto-install on drone --\u003e run this (fetches and installs latest pre-release, for now)\n\n```\ncd /etc\ncurl -L -o alink_install.sh https://raw.githubusercontent.com/OpenIPC/adaptive-link/refs/heads/main/alink_install.sh\nchmod +x alink_install.sh\n./alink_install.sh drone install\nreboot\n```\nConfig files are `/etc/txprofiles.conf` and `/etc/alink.conf`\n\n## Ground Station\n\nMost recent radxa image from Johhn or CC should include alink, though it may not be enabled by default\n\n## Install on Radxa ground station\n\n```\nsudo curl -L -o alink_install.sh https://raw.githubusercontent.com/OpenIPC/adaptive-link/refs/heads/main/alink_install.sh\nsudo chmod +x alink_install.sh\nsudo ./alink_install.sh gs remove\nsudo ./alink_install.sh gs install\n```\n\nService will be added to systemd. Use stop | start | disable | enable | status with `sudo systemctl stop alink_gs`\n\n`alink_gs.conf` config file will be created in `/config/` if it exists or `/home/radxa` if it exists or finally `/etc` if neither of those exist\n\n2.  Set power level on your ground station wifi card(s)\n\nalink_drone listens for an informative_heartbeat from the ground station to decide on link speed and range and therefor video quality.  If it doesn't hear anything for a preset period of time, it will fall back to profile 999, which is set to MCS 0 for the furthest possible range.  If you're not getting out of this mode, the drone isn't hearing the ground station.\n\nWhenever an informative_heartbeat is heard, information about the link imbedded in the hearbeat is used by alink_drone to set link speed appropriatly.  We need to set ground station power levels high enough to be heard by the drone.\n\nI have found 20 to 40 for AU cards to be appropriate\n\n\nHere's how i have been setting the cards power. Works for 8812AU cards\n\n```\n#/bin/bash\n\n# usage ./setpower.sh \u003cpwrlvl\u003e\necho Stopping wifibroadcast\nsudo systemctl stop wifibroadcast\necho Unloading 88XXau_wfb  module\nsudo modprobe -r 88XXau_wfb \u0026\u0026\necho Loading 88XXau_wfb module with power level $1\nsudo modprobe 88XXau_wfb rtw_tx_pwr_idx_override=$1\necho Restarting wifibroadcast\nsudo systemctl start wifibroadcast\necho Finished\n```\n\nGet this gs script by running this:\n\n```\ncurl -L -o setpower.sh https://raw.githubusercontent.com/sickgreg/OpenIPC-Adaptive-Link/refs/heads/main/more/setpower.sh\nchmod +x setpower.sh\n```\n\nThen run, eg, `sudo ./setpower.sh 30`\n\n**--- Changing the rate at which wfb-ng talks to the gs script ---**\n\nNote: Installer sets this to latest release's default automatically.  If it finds /home/radxa/gs/wfb.sh then it will update it there (taking effect after restart) otherwise it will directly update `/etc/wifibroadcast.cfg`\n\nDefault is only 1Hz (1000ms).  200ms gives the script 5 rssi/snr/etc/etc updates per second\n```\n[common]\nlog_interval = 200\n```\n\n**Some of the information below MAY BE OUTDATED -- it is there for reference**\n\n**--- Config files on drone/camera ---**\n**--- txprofiles.conf ---**\n\nLives on camera: `/etc/txprofiles.conf`\nStores video / mcs settings for all the different modes used in adaptive link\n\nNOTE: Be careful with PWR settings.  These examples are for pushing the BL-8812EU2 (square-blue) fairly hard.  They may blow up other adapters. power settings are the 56 to 61 values towards the end of each line in this example\n\nExample contents of `/etc/txprofiles.conf` for use with BL-8812EU2 (sqauare blue)\n\n\n```\n999 - 999 long 0 12 15 3332 5.0 61 0,0,0,0\n1000 - 1150 long 0 12 15 3333 5.0 60 0,0,0,0\n1151 - 1300 long 1 12 15 6667 5.0 59 12,6,6,12\n1301 - 1700 long 2 12 15 10000 5.0 58 12,6,6,12\n1701 - 1950 long 3 12 15 12500 5.0 56 8,4,4,8\n1951 - 2001 short 3 12 15 14000 5.0 56 4,0,0,4\n```\n\nThe values are: `rangestart - rangeend guard_interval FECN FECK Bitrate GOP PWR ROI-QP-definition`\n\n\n**--- alink.conf ---**\n\nLives on camera: `/etc/alink.conf`\n\n```\n\nrssi_weight=0.3\nsnr_weight=0.7\nfallback_ms=1000\nhold_fallback_mode_s=3\nmin_between_changes_ms=150\nhold_modes_down_s=4\nidr_every_change=false\nroi_focus_mode=false\nrequest_keyframe_interval_ms=100\nhysteresis_percent=15\n\n\n# Command templates - Don't change these unless you know what you are doing\npowerCommand=\"iw dev wlan0 set txpower fixed %d\"\nmcsCommand=\"wfb_tx_cmd 8000 set_radio -B 20 -G %s -S 1 -L 1 -M %d\"\nbitrateCommand=\"curl -s 'http://localhost/api/v1/set?video0.bitrate=%d'\"\ngopCommand=\"curl -s 'http://localhost/api/v1/set?video0.gopSize=%f'\"\nfecCommand=\"wfb_tx_cmd 8000 set_fec -k %d -n %d\"\nroiCommand=\"curl -s 'http://localhost/api/v1/set?fpv.roiQp=%s'\"\nidrCommand=\"echo 'IDR 0' | nc localhost 4000\"\n#idrCommand=\"curl localhost/request/idr\"\nmsposdCommand=\"echo '%ld s %d M:%d %s F:%d/%d P:%d G:%.1f\u0026L30\u0026F28 CPU:\u0026C \u0026Tc' \u003e/tmp/MSPOSD.msg\"\n#timeElapsed, profile-\u003esetBitrate, profile-\u003esetMCS, profile-\u003esetGI, profile-\u003esetFecK, profile-\u003esetFecN, profile-\u003ewfbPower, profile-\u003esetGop\n```\n\n\n**--- How to WinSCP to your drone via gs over tunnel ---**\n\n```\n# On drone set up a route to your LAN (sub 192xxx with yours). This should persist after reboot\nip route add 192.168.8.0/24 via 10.5.0.1\n\n# On GS set up IP forwarding temporarily:\nsudo sysctl -w net.ipv4.ip_forward=1\n# Make Permenant:\nsudo nano /etc/sysctl.conf #uncomment or add this line: \nnet.ipv4.ip_forward = 1\nsudo sysctl -p\n# On GS (Sub 192.x.x.x with your LAN). This seems to not persist after reboot\nsudo iptables -t nat -A POSTROUTING -o gs-wfb -s 192.168.8.0/24 -j MASQUERADE\n\n# On Windows as Administrator (Sub 192xxx with your GS IP) - persists after reboot\ncmd\nroute add 10.5.0.0 mask 255.255.255.0 192.168.8.116 -p\n```\n\n\n**More details - Old readme. Still may be useful information**\n\n**--- ALink42p ---**\n\nudp listener and video-link mode changer for OpenIPC\n\n\ncopy to `/usr/bin` on OpenIPC camera and make it executable\n\n`ALink42p --help` for command line options\n\nCopy `txprofiles.conf` to `/etc` (Warning: contains tx power level settings.  Don't set your specific device' power too high)\n\nCopy `alink.conf` to `/etc` for general settings / custom mode-changing execution strings.\n\nNote: It won't start up without those files\n\nI'm running `/usr/bin/ALink42p \u0026` from `/etc/rc.local` startup script. You also need wfb-ng tunnel or run wfb_rx on the camera,\n\neg\n\nhow to MANUALLY START TUNNEL (not necessary now:  enable tunnel in /etc/datalink.conf)\n```\n# Tunnel pair\nwfb_rx -c 127.0.0.1 -u 5800 -K /etc/drone.key -p 160 -i 7669206 wlan0 \u003e /dev/null \u0026\nwfb_tx -p 32 -u 5801 -K /etc/drone.key -R 2097152 -B20 -M 0 -S 1 -L 1 -G long -k 1 -n 2 -i 7669206 -f data wlan0 \u003e /dev/null \u0026\nwfb_tun -T 0 \u0026\n# Although this may work out of the box already if you sysupgrade (?)\n# Then\nsleep 2\n/usr/bin/ALink42\n```\n\nOld Way without tunnel, just a simple rx on drone, no tunnel overhead, original way, superceded\n\n`wfb_rx -c 127.0.0.1 -u 5000 -K /etc/drone.key -p 1 -i 7669207 wlan0 \u0026`\n\n\n**--- adaptive_link_greg3.py ---**\n\nRun this on ground station - Radxa, in  my case\n\nhttps://github.com/sickgreg/steam-groundstations/blob/master/adaptive-link/adaptive_link_greg3.py (keep an eye on it for newer versions)\n\n`python3 adaptive_link_greg3.py --udp_ip 10.5.0.2 --udp_port 9999`\n\nfor no tunnel\n`python3 adaptive_link_greg3.py`\n\nYou also need wfb-tunnel on ground station... default worked for me\n\nOr the old way (non-tunnel)\n```\n#/bin/bash\n\nif [ -e /etc/default/wifibroadcast ]; then\n  . /etc/default/wifibroadcast\nfi\n\nsudo wfb_tx -p 1 -u 9998 -K /etc/gs.key -R 456000 -B20 -M 0 -S 1 -L 1 -G long -k 1 -n 2 -i 7669207 -f data $WFB_NICS\n```\n\n**--- setpower.sh ---**\n\nDon't forget to set output power for your card(s) so the drone can hear messages\n\nIn my case, i'm using 3 rtl8812au's and have been using this script (which also restarts everything, maybe do this first)\n\n```\n#/bin/bash\n\n# usage ./setpower.sh \u003cpwrlvl\u003e\necho Stopping wifibroadcast\nsudo systemctl stop wifibroadcast\necho Unloading 88XXau_wfb  module\nsudo modprobe -r 88XXau_wfb \u0026\u0026\necho Loading 88XXau_wfb module with power level $1\nsudo modprobe 88XXau_wfb rtw_tx_pwr_idx_override=$1\necho Restarting openipc.service\nsudo systemctl restart openipc.service\necho Starting wifibroadcast\nsudo systemctl start wifibroadcast\n```\n\nUsage\n`./setpower.sh 40`\n\n**--- spreadfwd.py ----**\nRuns on ground station\n\nListens on a single port and forwards messages to the same number of ports as there are adapters found in /etc/default/wifibroadcast in a round-robin fashion (taking it in turns to send a message)\n\nExample\n\nRun\n\n`python3 spreadfwd.py 9998 9999`\n\n 9998 is the UDP port you set in adaptive_link_greg3.py script\n \n  If in `/etc/default/wifibroadcast` file we have `WFB_NICS=\"rtl1 rtl2 rtl3\"`, the program will send to port 9999,10000,10001, so set --first_port_out to the same port you start wfb_tx on (eg 9999)\n  \n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenipc%2Fadaptive-link","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenipc%2Fadaptive-link","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenipc%2Fadaptive-link/lists"}