From 7169c6ce31607fedfe7fa9327e56c09b97970bbd Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Dec 2024 09:26:47 -0400 Subject: [PATCH] add web server, rtsp server, other fixes --- Makefile | 80 +++++++++++++++++--------- gs.key | 1 + rtsp-server.service | 14 +++++ rtsp_server.py | 35 ++++++++++++ webapp.service | 14 ++--- wifibroadcast.cfg | 135 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 246 insertions(+), 33 deletions(-) create mode 100644 gs.key create mode 100644 rtsp-server.service create mode 100644 rtsp_server.py create mode 100644 wifibroadcast.cfg diff --git a/Makefile b/Makefile index 13a6e81..4367fa3 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,11 @@ DRY_RUN=false # Define dependencies DEPENDENCIES := nano htop nload sshpass python3-pip git ninja-build pkg-config gcc g++ systemd dkms python3-all python3-all-dev libpcap-dev libsodium-dev libevent-dev python3-pip python3-pyroute2 python3-msgpack \ - python3-future python3-twisted python3-serial python3-jinja2 iw virtualenv debhelper dh-python fakeroot build-essential python3-autobahn + python3-future python3-twisted python3-serial python3-jinja2 iw virtualenv debhelper dh-python fakeroot build-essential python3-autobahn python3-websockets node gstreamer1.0-tools gstreamer1.0-plugins-base \ + gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-rtsp libgstrtspserver-1.0-0 python3-gi # Define installation targets and options -.PHONY: enable install interactive dependencies uninstall default full-install spirilink-driver spirilink-software setup-webapp-service configure-spirilink +.PHONY: enable install interactive dependencies uninstall default full-install spirilink-driver spirilink-software setup-webapp-service configure-spirilink install-key setup-rtsp-service default: interactive install @@ -43,6 +44,8 @@ full-install: $(MAKE) spirilink-software $(MAKE) configure-spirilink $(MAKE) setup-webapp-service + $(MAKE) install-key + $(MAKE) setup-rtsp-service @echo "Full installation complete." manual-select: @@ -55,7 +58,9 @@ manual-select: @echo "[5] SpiriLink Software" @echo "[6] Configure SpiriLink" @echo "[7] Setup WebApp Service" - @echo "[8] Exit" + @echo "[8] Install gs.key" + @echo "[9] Setup RTSP Service" + @echo "[0] Exit" @read -p "Enter your choices separated by spaces (e.g., 1 2 3): " choices; \ selected="$$choices"; \ for choice in $$selected; do \ @@ -66,13 +71,25 @@ manual-select: if [ "$$choice" = "5" ]; then $(MAKE) spirilink-software; fi; \ if [ "$$choice" = "6" ]; then $(MAKE) configure-spirilink; fi; \ if [ "$$choice" = "7" ]; then $(MAKE) setup-webapp-service; fi; \ - if [ "$$choice" = "8" ]; then exit 0; fi; \ + if [ "$$choice" = "8" ]; then $(MAKE) install-key; fi; \ + if [ "$$choice" = "9" ]; then $(MAKE) setup-rtsp-service; fi; \ + if [ "$$choice" = "0" ]; then exit 0; fi; \ done; install: @echo "Starting installation..." @echo "Installation complete." +# Install gs.key +.PHONY: install-key +install-key: + @echo "Installing gs.key" + @if [ -f $(CURDIR)/gs.key ]; then \ + $(SUDO) cp $(CURDIR)/gs.key /etc/; \ + else \ + echo "Error: gs.key not found in deployment folder."; \ + fi; + # Install dependencies from the array .PHONY: dependencies dependencies: @@ -127,46 +144,42 @@ configure-spirilink: echo "unmanaged-devices=interface-name:spir0" | $(SUDO) tee -a /etc/NetworkManager/NetworkManager.conf > /dev/null; \ fi @$(SUDO) systemctl restart NetworkManager - @echo "Configuring wifibroadcast default interface..." - @$(SUDO) bash -c 'echo "WFB_NICS=\"spir0\"" > /etc/default/wifibroadcast' @echo "SpiriLink configuration complete." # Install SpiriLink software .PHONY: spirilink-software spirilink-software: - @echo "\nInstalling SpiriLink software..." - @if [ -d ~/tmp/spirilink ]; then \ + @echo "Installing SpiriLink software..." + @if [ -d ~/home/spiri/spirilink ]; then \ read -p "SpiriLink software source already exists. Reinstall? (Y/N): " reinstall; \ if [ "$$reinstall" = "Y" ] || [ "$$reinstall" = "y" ]; then \ - cd ~/tmp/spirilink && git pull && \ - $(SUDO) make deb && $(SUDO) dpkg -i ~/tmp/spirilink/deb_dist/wfb*.deb; \ + cd ~/home/spiri/spirilink && git pull && \ + $(SUDO) ./scripts/install_gs.sh spir0; \ + $(SUDO) bash -c 'echo "WFB_NICS=\"spir0\"" > /etc/default/wifibroadcast'; \ + $(SUDO) cp ~/home/spiri/spirilink/wifibroadcast.cfg /etc/; \ else \ echo "Skipping SpiriLink software installation."; \ fi; \ else \ - git clone https://git.spirirobotics.com/aepko/SpiriLink.git ~/tmp/spirilink && \ - cd ~/tmp/spirilink && \ - $(SUDO) make deb && $(SUDO) dpkg -i ~/tmp/spirilink/deb_dist/wfb*.deb; \ + git clone https://git.spirirobotics.com/aepko/SpiriLink.git ~/home/spiri/spirilink && \ + cd ~/home/spiri/spirilink && \ + $(SUDO) ./scripts/install_gs.sh spir0; \ + $(SUDO) bash -c 'echo "WFB_NICS=\"spir0\"" > /etc/default/wifibroadcast'; \ + $(SUDO) cp ~/home/spiri/spirilink/wifibroadcast.cfg /etc/; \ fi; - @echo "\nSpiriLink software installed." + @echo "SpiriLink software installed." + # Set up web app service .PHONY: setup-webapp-service setup-webapp-service: @echo "Setting up web app service..." - @if [ -d ~/spiri-base ]; then \ - cd ~/spiri-base && git pull || (echo "Error updating repository. Check network and repository access." && exit 1); \ + @if [ -d ~/Spiri-Base ]; then \ + cd ~/Spiri-Base && git pull || (echo "Error updating repository. Check network and repository access." && exit 1); \ else \ - git clone https://git.spirirobotics.com/aepko/Spiri-Base.git ~/spiri-base || (echo "Error cloning repository. Check network and repository access." && exit 1); \ - cd ~/spiri-base && git sparse-checkout init --cone && git sparse-checkout set .output; \ - fi; - @if [ -d ~/spiri-base/.output ]; then \ - $(SUDO) mkdir -p /var/www/webapp; \ - $(SUDO) cp -r ~/spiri-base/.output/* /var/www/webapp/; \ - else \ - echo "Error: .output folder not found. Ensure the repository contains the necessary files."; \ - exit 1; \ + git clone https://git.spirirobotics.com/aepko/Spiri-Base.git ~/Spiri-Base || (echo "Error cloning repository. Check network and repository access." && exit 1); \ + cd ~/Spiri-Base && git sparse-checkout init --cone && git sparse-checkout set .output; \ fi; @if [ -f $(CURDIR)/webapp.service ]; then \ $(SUDO) cp $(CURDIR)/webapp.service /etc/systemd/system/webapp.service; \ @@ -175,7 +188,22 @@ setup-webapp-service: else \ echo "Error: webapp.service not found in deployment folder."; \ fi; - @echo "\nWeb app service set up and running." + @echo "Web app service set up and running." + +# Set up RTSP service +.PHONY: setup-rtsp-service +setup-rtsp-service: + @echo "Setting up RTSP server..." + $(SUDO) mkdir -p ~/rtsp + $(SUDO) cp $(CURDIR)/scripts/rtsp_server.py ~/rtsp/ + @if [ -f $(CURDIR)/scripts/rtsp-server.service ]; then \ + $(SUDO) cp $(CURDIR)/scripts/rtsp-server.service /etc/systemd/system/rtsp-server.service; \ + $(SUDO) systemctl enable rtsp-server.service; \ + $(SUDO) systemctl start rtsp-server.service; \ + else \ + echo "Error: rtsp-server.service not found in scripts folder."; \ + fi; + @echo "RTSP server set up and running." # Uninstall all installed components .PHONY: uninstall diff --git a/gs.key b/gs.key new file mode 100644 index 0000000..b9951d9 --- /dev/null +++ b/gs.key @@ -0,0 +1 @@ +»·ínƒ¤jŠ›Š ùŽÎ+Ü—‡¸ G²_¢Œ¬{FÄŠa•ûp’tzfè<æ@½k¾µ²QSz˜¢t¢c \ No newline at end of file diff --git a/rtsp-server.service b/rtsp-server.service new file mode 100644 index 0000000..5177e3c --- /dev/null +++ b/rtsp-server.service @@ -0,0 +1,14 @@ +[Unit] +Description=RTSP Server for FPV Video +After=network-online.target + +[Service] +ExecStart=/usr/bin/python3 /home/spiri/rtsp/rtsp_server.py +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal +Type=simple + +[Install] +WantedBy=multi-user.target diff --git a/rtsp_server.py b/rtsp_server.py new file mode 100644 index 0000000..e623f03 --- /dev/null +++ b/rtsp_server.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +import gi +gi.require_version('Gst', '1.0') +gi.require_version('GstRtspServer', '1.0') +from gi.repository import Gst, GstRtspServer, GLib + +class CustomRTSPMediaFactory(GstRtspServer.RTSPMediaFactory): + def __init__(self): + super().__init__() + self.set_shared(True) + + def do_create_element(self, url): + pipeline = ( + "udpsrc port=5600 caps=\"application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H265\" ! " + "rtph265depay ! h265parse ! rtph265pay name=pay0 pt=96" + ) + return Gst.parse_launch(pipeline) + +class CustomRTSPServer(GstRtspServer.RTSPServer): + def __init__(self): + super().__init__() + factory = CustomRTSPMediaFactory() + mount_points = self.get_mount_points() + mount_points.add_factory("/video", factory) + self.attach(None) + +def main(): + Gst.init(None) + server = CustomRTSPServer() + loop = GLib.MainLoop() # Updated to GLib + print("RTSP Server running at rtsp://127.0.0.1:8554/video") + loop.run() + +if __name__ == "__main__": + main() diff --git a/webapp.service b/webapp.service index e09be2d..a2599fc 100644 --- a/webapp.service +++ b/webapp.service @@ -1,15 +1,15 @@ [Unit] -Description=SpiriBase WebApp Service +Description=Spiri Base Node.js Application After=network.target [Service] -Type=simple -WorkingDirectory=/var/www/webapp -ExecStart=/usr/bin/node /var/www/webapp/.output/server/index.mjs -Restart=on-failure +ExecStart=/usr/bin/node /home/spiri/Spiri-Base/.output/server/index.mjs +WorkingDirectory=/home/spiri/Spiri-Base +Restart=always +User=spiri +Group=spiri Environment=NODE_ENV=production -User=www-data -Group=www-data +Environment=PORT=80 [Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/wifibroadcast.cfg b/wifibroadcast.cfg new file mode 100644 index 0000000..26c1f67 --- /dev/null +++ b/wifibroadcast.cfg @@ -0,0 +1,135 @@ +[common] +primary = True # Set to False if you use several wfb instances on one card. Only primary instance will set radio channel. +log_file = None # Set to "wifibroadcast.log" to disable log to stdout +binary_log_file = None # Machine readable log for post-processing + # File name should have '%%s' inside for profile mapping. + # For example: 'binlog_%%s.log' + +set_nm_unmanaged = True # Set radio interface in 'unmanaged state' in NetworkManager +radio_mtu = 1445 # Used for mavlink aggregation and for tunnel packets - should be less or equal to MAX_PAYLOAD_SIZE, don't change if doubt +tunnel_agg_timeout= 0.005 # aggragate tuntap packets if less than radio_mtu but no longer than 5ms +mavlink_agg_timeout = 0.1 # aggragate mavlink packets if less than radio_mtu but no longer than 100ms +mavlink_err_rate = True # If true then inject RX error rate else absolute values + +log_interval = 1000 # [ms] Default interval which wfb_rx/tx use for statistics reporting +tx_sel_rssi_delta = 3 # [dB] hysteresis for antenna selection by RSSI +tx_sel_counter_abs_delta = 3 # [pkt] hysteresis for antenna selection by RX packet counter +tx_sel_counter_rel_delta = 0.1 # default is max(3 packets or 10% of packets from the best antenna) + +tx_rcv_buf_size = 2097152 # UDP SO_RCVBUF. Set 0 to use net.core.rmem_default. Increase in case of non-cbr data stream + # This should not be greater than net.core.rmem_max + +wifi_channel = 161 # radio channel @5825 MHz, range: 5815-5835 MHz, width 20MHz + # Also you can set own frequency channel for each wifi card, for example: + # {'wlan0': 161, 'wlan1': 165} + +wifi_region = 'BO' # Set CRDA region +wifi_txpower = None # Leave None to use default power settings from driver. + # There is a special value 'off' for RX only cards. + # For 8812au set to -dBm * 100. I.e for 30dBm set to -3000 + # For 8812eu set to dBm * 100. I.e for 30dBm set to 3000 + # Also you can set own txpower for each wifi card, for example: + # {'wlan0': -100, 'wlan1': 100, 'wlan2': 'off'} + + +temp_measurement_interval = 10 # [s] (8812eu only) Internal RF path temp measurement. +temp_overheat_warning = 60 # [*C] (8812eu only) Overheat warning threshold. + +[gs] +streams = [{'name': 'video', 'stream_rx': 0x00, 'stream_tx': None, 'service_type': 'udp_direct_rx', 'profiles': ['base', 'gs_base', 'video', 'gs_video']}, + {'name': 'mavlink', 'stream_rx': 0x10, 'stream_tx': 0x90, 'service_type': 'mavlink', 'profiles': ['base', 'gs_base', 'mavlink', 'gs_mavlink']}, + {'name': 'tunnel', 'stream_rx': 0x20, 'stream_tx': 0xa0, 'service_type': 'tunnel', 'profiles': ['base', 'gs_base', 'tunnel', 'gs_tunnel']} + ] + +stats_port = 8003 # used by wfb-cli +api_port = 8103 # public JSON API +link_domain = "default" + +########################################################################################################################### +# Low level profiles can be used to build top level profiles via inheritance # +# You can define any number of them and split options between them as you want - only resulting set of options has matter # +########################################################################################################################### + +## Low level profiles have protocol-dependant fields + +[base] +stream_rx = None +stream_tx = None +keypair = None +mirror = False # Set to true if you want to mirror packet via all cards for redundancy. Not recommended if cards are on one frequency channel. + +# Radio settings for TX and RX +bandwidth = 20 # bandwidth 20 or 40 MHz +force_vht = False # Use VHT for 20 and 40 MHz bandwidth + +# Radiotap flags for TX: +short_gi = False # use short GI or not +stbc = 1 # stbc streams: 1, 2, 3 or 0 if unused +ldpc = 1 # use LDPC FEC. Currently available only for 8812au and must be supported both on TX and RX. +mcs_index = 1 # mcs index + +# Packet queueing control +use_qdisc = False # Use or bypass kernel qdisc. Set to True if you want to use traffic shaper +fwmark = 0 # If use qdisc then set fwmark to this value for data packets and to fwmark + 1 for FEC packets +control_port = 0 # Override in tx sections if you want to manually control wfb_tx processes via wfb_tx_cmd utility + +[gs_base] +keypair = 'gs.key' + +[radio_base] +frame_type = 'data' # Use data or rts frames +fec_k = 1 # FEC K (For tx side. Rx will get FEC settings from session packet) +fec_n = 2 # FEC N (For tx side. Rx will get FEC settings from session packet) +fec_timeout = 0 # [ms], 0 to disable. If no new packets during timeout, emit one empty packet if FEC block is open +fec_delay = 0 # [us], 0 to disable. Issue FEC packets with delay between them. + +[video] +frame_type = 'data' # Use data or rts frames +fec_k = 8 # FEC K (For tx side. Rx will get FEC settings from session packet) +fec_n = 12 # FEC N (For tx side. Rx will get FEC settings from session packet) +fec_timeout = 0 # [ms], 0 to disable. If no new packets during timeout, emit one empty packet if FEC block is open +fec_delay = 0 # [us], 0 to disable. Issue FEC packets with delay between them. + +peer = None + +[mavlink] +log_messages = False # Log all messages to binary log for post processing +frame_type = 'data' # Use data or rts frames +fec_k = 1 # FEC K (For tx side. Rx will get FEC settings from session packet) +fec_n = 2 # FEC N (For tx side. Rx will get FEC settings from session packet) +fec_timeout = 0 # [ms], 0 to disable. If no new packets during timeout, emit one empty packet if FEC block is open +fec_delay = 0 # [us], 0 to disable. Issue FEC packets with delay between them. + +peer = None +osd = None + +inject_rssi = True # inject RADIO_STATUS packets +mavlink_sys_id = 3 # for injected rssi packets +mavlink_comp_id = 68 # MAV_COMP_ID_TELEMETRY_RADIO + +call_on_arm = None # call program on arm +call_on_disarm = None # call program on disarm +mavlink_tcp_port = None # listen for connections from QGC or from onboard computer + +[tunnel] +frame_type = 'data' # Use data or rts frames +fec_k = 1 # FEC K (For tx side. Rx will get FEC settings from session packet) +fec_n = 2 # FEC N (For tx side. Rx will get FEC settings from session packet) +fec_timeout = 0 # [ms], 0 to disable. If no new packets during timeout, emit one empty packet if FEC block is open +fec_delay = 0 # [us], 0 to disable. Issue FEC packets with delay between them. + +[gs_video] +fwmark = 20 # traffic shaper label +peer = 'connect://127.0.0.1:5600' # outgoing connection for video sink (GS) + +[gs_mavlink] +fwmark = 10 # traffic shaper label +peer = 'connect://127.0.0.1:14550' # outgoing connection +# peer = 'listen://0.0.0.0:14550' # incoming connection +# osd = 'connect://127.0.0.1:14551' # mirroring mavlink packets to OSD + +[gs_tunnel] +fwmark = 30 # traffic shaper label +ifname = 'gs-wfb' +ifaddr = '10.5.0.1/24' +default_route = False \ No newline at end of file