├── debian ├── source │ └── format ├── .gitignore ├── upstream │ └── metadata ├── pixelpilot-rk.pixelpilot.default ├── changelog ├── watch ├── pixelpilot-rk.pixelpilot.service ├── rules ├── copyright ├── control └── manpage.md ├── src ├── lvosd.h ├── gsmenu │ ├── gs_main.h │ ├── gs_wifi.h │ ├── gs_apfpv.h │ ├── gs_dvr.h │ ├── air_aalink.h │ ├── air_alink.h │ ├── air_camera.h │ ├── gs_connection_checker.h │ ├── air_presets.h │ ├── air_telemetry.h │ ├── air_txprofiles.h │ ├── gs_wfbng.h │ ├── gs_dvrplayer.h │ ├── gs_system.h │ ├── air_actions.h │ ├── gs_actions.h │ ├── air_wfbng.h │ ├── styles.h │ ├── ui.h │ ├── executor.h │ ├── air_actions.c │ ├── gs_actions.c │ ├── gs_main.c │ ├── air_telemetry.c │ ├── gs_wfbng.c │ ├── helper.h │ ├── styles.c │ ├── gs_connection_checker.c │ ├── air_aalink.c │ ├── air_presets.c │ ├── air_wfbng.c │ ├── gs_dvr.c │ ├── gs_wifi.c │ ├── gs_system.c │ ├── gs_dvrplayer.c │ ├── air_camera.c │ └── air_alink.c ├── icons │ ├── wfb.png │ ├── latency.png │ ├── memory.png │ ├── network.png │ ├── osd-bg-2.png │ ├── signal1.png │ ├── signal2.png │ ├── signal3.png │ ├── signal4.png │ ├── signal5.png │ ├── framerate.png │ ├── sdcard-white.png │ ├── device_thermostat.png │ ├── OpenIPC__OPENIPC_logo_white.png │ ├── battery_android_frame_full.png │ ├── device_thermostat.svg │ ├── memory.svg │ ├── battery_android_frame_full.svg │ ├── network.svg │ ├── framerate.svg │ └── latency.svg ├── WiFiRSSIMonitor.h ├── wfbcli.hpp ├── mavlink.h ├── menu.h ├── lvosd.c ├── mavlink │ ├── common │ │ ├── version.h │ │ └── mavlink.h │ ├── minimal │ │ ├── version.h │ │ ├── mavlink.h │ │ └── testsuite.h │ ├── standard │ │ ├── version.h │ │ ├── mavlink.h │ │ ├── testsuite.h │ │ └── standard.h │ ├── mavlink_get_info.h │ ├── checksum.h │ ├── mavlink_conversions.h │ └── mavlink_sha256.h ├── main.h ├── WiFiRSSIMonitor.hpp ├── input.h ├── simulator.c ├── os_mon.hpp ├── osd.hpp ├── osd.h ├── scheduling_helper.hpp ├── time_util.h ├── menu.c ├── dvr.h ├── gstrtpreceiver.h ├── drm.h ├── WiFiRSSIMonitor.cpp ├── dvr.cpp └── notes ├── .gitmodules ├── tools ├── container_run.sh ├── container_run_test.sh └── container_build.sh ├── .editorconfig ├── tests ├── files │ └── basic_tpl_text_widget.png └── test_osd.cpp ├── pixelpilot_config.h.in ├── sim.sh ├── .gitignore ├── pixelpilot.yaml ├── os_monitor_demo_osd.json ├── Makefile ├── .github └── workflows │ └── build.yml └── config_osd.json /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /src/lvosd.h: -------------------------------------------------------------------------------- 1 | #include "lvgl/lvgl.h" 2 | 3 | 4 | void pp_osd_main(void); -------------------------------------------------------------------------------- /src/gsmenu/gs_main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void create_main_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/gs_wifi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void create_wifi_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/gs_apfpv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void create_apfpv_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/gs_dvr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void create_gs_dvr_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/air_aalink.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void create_air_aalink_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/air_alink.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void create_air_alink_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/air_camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void create_air_camera_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/gs_connection_checker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void update_network_status(); 4 | -------------------------------------------------------------------------------- /src/icons/wfb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/wfb.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lvgl"] 2 | path = lvgl 3 | url = https://github.com/lvgl/lvgl.git 4 | -------------------------------------------------------------------------------- /src/gsmenu/air_presets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void create_air_presets_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/air_telemetry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void create_air_telemetry_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/air_txprofiles.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | void create_table(lv_obj_t * parent); 5 | -------------------------------------------------------------------------------- /src/icons/latency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/latency.png -------------------------------------------------------------------------------- /src/icons/memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/memory.png -------------------------------------------------------------------------------- /src/icons/network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/network.png -------------------------------------------------------------------------------- /src/icons/osd-bg-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/osd-bg-2.png -------------------------------------------------------------------------------- /src/icons/signal1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/signal1.png -------------------------------------------------------------------------------- /src/icons/signal2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/signal2.png -------------------------------------------------------------------------------- /src/icons/signal3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/signal3.png -------------------------------------------------------------------------------- /src/icons/signal4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/signal4.png -------------------------------------------------------------------------------- /src/icons/signal5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/signal5.png -------------------------------------------------------------------------------- /src/icons/framerate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/framerate.png -------------------------------------------------------------------------------- /tools/container_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LD_LIBRARY_PATH=/usr/local/lib/ /usr/local/bin/pixelpilot $@ 4 | -------------------------------------------------------------------------------- /src/icons/sdcard-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/sdcard-white.png -------------------------------------------------------------------------------- /debian/.gitignore: -------------------------------------------------------------------------------- 1 | autoreconf.* 2 | debhelper-build-stamp 3 | files 4 | tmp/ 5 | *.debhelper 6 | *.log 7 | *.substvars -------------------------------------------------------------------------------- /src/gsmenu/gs_wfbng.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "lvgl/lvgl.h" 3 | 4 | void create_gs_wfbng_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/icons/device_thermostat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/device_thermostat.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cpp] 4 | end_of_line = lf 5 | indent_style = space 6 | insert_final_newline = true 7 | -------------------------------------------------------------------------------- /tests/files/basic_tpl_text_widget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/tests/files/basic_tpl_text_widget.png -------------------------------------------------------------------------------- /pixelpilot_config.h.in: -------------------------------------------------------------------------------- 1 | #define APP_VERSION_MAJOR @pixelpilot_VERSION_MAJOR@ 2 | #define APP_VERSION_MINOR @pixelpilot_VERSION_MINOR@ 3 | -------------------------------------------------------------------------------- /src/gsmenu/gs_dvrplayer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void dvr_player_screen_init(void); 4 | void change_playbutton_label(const char * text); -------------------------------------------------------------------------------- /src/icons/OpenIPC__OPENIPC_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/OpenIPC__OPENIPC_logo_white.png -------------------------------------------------------------------------------- /src/icons/battery_android_frame_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenIPC/PixelPilot_rk/master/src/icons/battery_android_frame_full.png -------------------------------------------------------------------------------- /src/WiFiRSSIMonitor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // C-callable functions 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | void wifi_rssi_monitor_reset(void); 9 | 10 | #ifdef __cplusplus 11 | } 12 | #endif -------------------------------------------------------------------------------- /src/gsmenu/gs_system.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "lvgl/lvgl.h" 3 | 4 | enum RXMode { 5 | WFB, 6 | APFPV 7 | }; 8 | 9 | void toggle_rec_enabled(void); 10 | void create_gs_system_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/air_actions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../menu.h" 4 | extern MenuAction airactions[MAX_ACTIONS]; 5 | extern size_t airactions_count; 6 | 7 | void create_air_actions_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/gsmenu/gs_actions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../menu.h" 4 | 5 | extern MenuAction gsactions[MAX_ACTIONS]; 6 | extern size_t gsactions_count; 7 | 8 | void create_gs_actions_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /src/wfbcli.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WFBCLIPP_H 2 | #define WFBCLIPP_H 3 | 4 | extern int wfb_thread_signal; 5 | 6 | typedef struct { 7 | int port; 8 | const char *host; 9 | } wfb_thread_params; 10 | 11 | void *__WFB_CLI_THREAD__(void *param); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/gsmenu/air_wfbng.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct { 4 | int channel; 5 | const char *frequency; 6 | } FrequencyEntry; 7 | 8 | char *get_frequencies_string(FrequencyEntry *entries, int size); 9 | 10 | void create_air_wfbng_menu(lv_obj_t * parent); -------------------------------------------------------------------------------- /sim.sh: -------------------------------------------------------------------------------- 1 | #cmake -DUSE_SIMULATOR=ON -S . -DCMAKE_BUILD_TYPE=Debug -B build_sim 2 | for i in $(seq 1 25) 3 | do 4 | touch /tmp/$i_video$i.mp4 5 | done 6 | cmake -DUSE_SIMULATOR=ON -S . -B build_sim 7 | cmake --build build_sim 8 | sudo sudo "PATH=$(pwd):$PATH" ./build_sim/pixelpilot 9 | -------------------------------------------------------------------------------- /src/mavlink.h: -------------------------------------------------------------------------------- 1 | #ifndef MVLINK_H 2 | #define MVLINK_H 3 | 4 | extern int mavlink_port; 5 | extern bool mavlink_dvr_on_arm; 6 | extern int mavlink_thread_signal; 7 | 8 | void* __MAVLINK_THREAD__(void* arg); 9 | 10 | size_t numOfChars(const char s[]); 11 | 12 | char* insertString(char s1[], const char s2[], size_t pos); 13 | 14 | #endif -------------------------------------------------------------------------------- /debian/upstream/metadata: -------------------------------------------------------------------------------- 1 | # See https://wiki.debian.org/UpstreamMetadata for more info/fields. 2 | 3 | Bug-Database: https://github.com/OpenIPC/PixelPilot_rk/issues 4 | Bug-Submit: https://github.com/OpenIPC/PixelPilot_rk/issues/new 5 | Repository-Browse: https://github.com/OpenIPC/PixelPilot_rk 6 | Repository: https://github.com/OpenIPC/PixelPilot_rk.git 7 | -------------------------------------------------------------------------------- /debian/pixelpilot-rk.pixelpilot.default: -------------------------------------------------------------------------------- 1 | # Defaults for pixelpilot service 2 | # Likely to be overriden by the distribution 3 | 4 | CONFIG=/etc/pixelpilot/pixelpilot.yaml 5 | OSD_PATH=/etc/pixelpilot/config_osd.json 6 | SCREEN_MODE=1920x1080@60 7 | VIDEO_SCALE=1.0 8 | DVR_FPS=60 9 | DVR_PATH=/var/dvr 10 | DVR_TEMPLATE=record_%Y-%m-%d_%H-%M-%S.mp4 11 | EXTRA_OPTS= -------------------------------------------------------------------------------- /src/menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define MAX_ACTIONS 5 8 | #define MAX_LABEL_LEN 50 9 | #define MAX_ACTION_LEN 500 10 | 11 | typedef struct { 12 | char label[MAX_LABEL_LEN]; 13 | char action[MAX_ACTION_LEN]; 14 | } MenuAction; 15 | 16 | void pp_menu_main(void); -------------------------------------------------------------------------------- /src/icons/device_thermostat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lvosd.c: -------------------------------------------------------------------------------- 1 | #include "lvgl/lvgl.h" 2 | 3 | 4 | extern lv_obj_t * pp_osd_screen; 5 | lv_group_t * osd_group; 6 | 7 | void pp_osd_main(void) 8 | { 9 | pp_osd_screen = lv_obj_create(NULL); 10 | lv_obj_set_style_bg_opa(pp_osd_screen, LV_OPA_TRANSP, LV_PART_MAIN); 11 | lv_obj_set_style_bg_opa(pp_osd_screen, LV_OPA_TRANSP, LV_PART_MAIN); 12 | osd_group = lv_group_create(); 13 | } -------------------------------------------------------------------------------- /src/mavlink/common/version.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | * @brief MAVLink comm protocol built from common.xml 3 | * @see http://mavlink.org 4 | */ 5 | #pragma once 6 | 7 | #ifndef MAVLINK_VERSION_H 8 | #define MAVLINK_VERSION_H 9 | 10 | #define MAVLINK_BUILD_DATE "Wed Sep 27 2023" 11 | #define MAVLINK_WIRE_PROTOCOL_VERSION "2.0" 12 | #define MAVLINK_MAX_DIALECT_PAYLOAD_SIZE 255 13 | 14 | #endif // MAVLINK_VERSION_H 15 | -------------------------------------------------------------------------------- /src/mavlink/minimal/version.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | * @brief MAVLink comm protocol built from minimal.xml 3 | * @see http://mavlink.org 4 | */ 5 | #pragma once 6 | 7 | #ifndef MAVLINK_VERSION_H 8 | #define MAVLINK_VERSION_H 9 | 10 | #define MAVLINK_BUILD_DATE "Wed Sep 27 2023" 11 | #define MAVLINK_WIRE_PROTOCOL_VERSION "2.0" 12 | #define MAVLINK_MAX_DIALECT_PAYLOAD_SIZE 22 13 | 14 | #endif // MAVLINK_VERSION_H 15 | -------------------------------------------------------------------------------- /src/mavlink/standard/version.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | * @brief MAVLink comm protocol built from standard.xml 3 | * @see http://mavlink.org 4 | */ 5 | #pragma once 6 | 7 | #ifndef MAVLINK_VERSION_H 8 | #define MAVLINK_VERSION_H 9 | 10 | #define MAVLINK_BUILD_DATE "Wed Sep 27 2023" 11 | #define MAVLINK_WIRE_PROTOCOL_VERSION "2.0" 12 | #define MAVLINK_MAX_DIALECT_PAYLOAD_SIZE 22 13 | 14 | #endif // MAVLINK_VERSION_H 15 | -------------------------------------------------------------------------------- /src/icons/memory.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/battery_android_frame_full.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/container_run_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # We have to capture the output and explicitly check it for "FAILED" because 4 | # address sanitizer makes executable end with non-zero return code under QEMU. 5 | # So we can't rely solely on return codes 6 | ROOTDIR=/usr/src/PixelPilot_rk 7 | 8 | cd $ROOTDIR 9 | 10 | OUT=$(LD_LIBRARY_PATH=/usr/local/lib/ ./build/pixelpilot_tests $@ 2>&1) 11 | 12 | echo "$OUT" 13 | 14 | if [[ $OUT == *"FAILED"* ]]; then 15 | exit 1 16 | fi 17 | -------------------------------------------------------------------------------- /src/icons/network.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | pixelpilot-rk (1.3.0-1) unstable; urgency=medium 2 | 3 | * Configurable OSD 4 | * Integration with MSP OSD rendering on ground-station 5 | * Add pidfile 6 | * Fix OSD flickering 7 | * Integration with wfb-ng API to show on OSD 8 | * Support reading video from UNIX socket 9 | * Add gsmenu - ground station menu UI controlled via UART buttons 10 | * Add DVR player to gsmenu 11 | * "custom message" is now a named pipe 12 | * We now build DEB packages 13 | 14 | -- Sergey Prokhorov Fri, 10 Oct 2025 19:54:08 +0200 15 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | # Example watch control file for uscan. 2 | # Rename this file to "watch" and then you can run the "uscan" command 3 | # to check for upstream updates and more. 4 | # See uscan(1) for format. 5 | 6 | # Compulsory line, this is a version 4 file. 7 | version=4 8 | 9 | # GitHub hosted projects. 10 | opts="filenamemangle=s%(?:.*?)?v?(@ANY_VERSION@@ARCHIVE_EXT@)%@PACKAGE@-$1%" \ 11 | https://github.com/OpenIPC/PixelPilot_rk/tags \ 12 | (?:.*?/)v?@ANY_VERSION@@ARCHIVE_EXT@ 13 | 14 | # Direct Git. 15 | #opts="mode=git" https://github.com/OpenIpc/PixelPilot_rk.git \ 16 | # refs/tags/v([\d\.]+) 17 | -------------------------------------------------------------------------------- /src/gsmenu/styles.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include "lvgl/lvgl.h" 8 | 9 | extern lv_style_t style_rootmenu; 10 | extern lv_style_t style_openipc; 11 | extern lv_style_t style_openipc_dropdown; 12 | extern lv_style_t style_openipc_outline; 13 | extern lv_style_t style_openipc_textcolor; 14 | extern lv_style_t style_openipc_disabled; 15 | extern lv_style_t style_openipc_section; 16 | extern lv_style_t style_openipc_dark_background; 17 | extern lv_style_t style_openipc_lightdark_background; 18 | 19 | int style_init(void); 20 | 21 | #ifdef __cplusplus 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /src/icons/framerate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/gsmenu/ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../lvgl/lvgl.h" 4 | #include "ui.h" 5 | 6 | typedef void (*ReloadFunc)(lv_obj_t * page, lv_obj_t * target); 7 | 8 | typedef struct { 9 | const char *caption; 10 | lv_obj_t *target; 11 | ReloadFunc reload; 12 | } PageEntry; 13 | 14 | typedef struct { 15 | char type[100]; 16 | char page[100]; 17 | void (*page_load_callback)(lv_obj_t * page); 18 | lv_group_t *indev_group; 19 | size_t entry_count; 20 | PageEntry *page_entries; 21 | } menu_page_data_t; 22 | 23 | lv_obj_t * pp_header_create(lv_obj_t * screen); 24 | lv_obj_t * pp_menu_create(lv_obj_t * screen); 25 | -------------------------------------------------------------------------------- /debian/pixelpilot-rk.pixelpilot.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description="OpenIPC Pixelpilot Server" 3 | Requires=wifibroadcast.service 4 | After=wifibroadcast.service 5 | 6 | [Service] 7 | Type=simple 8 | EnvironmentFile=/etc/default/pixelpilot 9 | ExecStart=/usr/bin/pixelpilot --osd --osd-custom-message --osd-config ${OSD_PATH} --screen-mode ${SCREEN_MODE} --video-scale ${VIDEO_SCALE} --dvr-framerate ${DVR_FPS} --dvr-fmp4 --dvr-sequenced-files --dvr-template ${DVR_PATH}/${DVR_TEMPLATE} --config ${CONFIG} $EXTRA_OPTS 10 | Restart=always 11 | # Sleep between 3 and 10 seconds between restart attempts; it will keep restarting forever 12 | RestartSec=3 13 | RestartSteps=5 14 | RestartMaxDelaySec=10 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /src/mavlink/common/mavlink.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | * @brief MAVLink comm protocol built from common.xml 3 | * @see http://mavlink.org 4 | */ 5 | #pragma once 6 | #ifndef MAVLINK_H 7 | #define MAVLINK_H 8 | 9 | #define MAVLINK_PRIMARY_XML_HASH -6444967149487557671 10 | 11 | #ifndef MAVLINK_STX 12 | #define MAVLINK_STX 253 13 | #endif 14 | 15 | #ifndef MAVLINK_ENDIAN 16 | #define MAVLINK_ENDIAN MAVLINK_LITTLE_ENDIAN 17 | #endif 18 | 19 | #ifndef MAVLINK_ALIGNED_FIELDS 20 | #define MAVLINK_ALIGNED_FIELDS 1 21 | #endif 22 | 23 | #ifndef MAVLINK_CRC_EXTRA 24 | #define MAVLINK_CRC_EXTRA 1 25 | #endif 26 | 27 | #ifndef MAVLINK_COMMAND_24BIT 28 | #define MAVLINK_COMMAND_24BIT 1 29 | #endif 30 | 31 | #include "version.h" 32 | #include "common.h" 33 | 34 | #endif // MAVLINK_H 35 | -------------------------------------------------------------------------------- /src/mavlink/minimal/mavlink.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | * @brief MAVLink comm protocol built from minimal.xml 3 | * @see http://mavlink.org 4 | */ 5 | #pragma once 6 | #ifndef MAVLINK_H 7 | #define MAVLINK_H 8 | 9 | #define MAVLINK_PRIMARY_XML_HASH 7866375523044912686 10 | 11 | #ifndef MAVLINK_STX 12 | #define MAVLINK_STX 253 13 | #endif 14 | 15 | #ifndef MAVLINK_ENDIAN 16 | #define MAVLINK_ENDIAN MAVLINK_LITTLE_ENDIAN 17 | #endif 18 | 19 | #ifndef MAVLINK_ALIGNED_FIELDS 20 | #define MAVLINK_ALIGNED_FIELDS 1 21 | #endif 22 | 23 | #ifndef MAVLINK_CRC_EXTRA 24 | #define MAVLINK_CRC_EXTRA 1 25 | #endif 26 | 27 | #ifndef MAVLINK_COMMAND_24BIT 28 | #define MAVLINK_COMMAND_24BIT 1 29 | #endif 30 | 31 | #include "version.h" 32 | #include "minimal.h" 33 | 34 | #endif // MAVLINK_H 35 | -------------------------------------------------------------------------------- /src/mavlink/standard/mavlink.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | * @brief MAVLink comm protocol built from standard.xml 3 | * @see http://mavlink.org 4 | */ 5 | #pragma once 6 | #ifndef MAVLINK_H 7 | #define MAVLINK_H 8 | 9 | #define MAVLINK_PRIMARY_XML_HASH 2262899478281782564 10 | 11 | #ifndef MAVLINK_STX 12 | #define MAVLINK_STX 253 13 | #endif 14 | 15 | #ifndef MAVLINK_ENDIAN 16 | #define MAVLINK_ENDIAN MAVLINK_LITTLE_ENDIAN 17 | #endif 18 | 19 | #ifndef MAVLINK_ALIGNED_FIELDS 20 | #define MAVLINK_ALIGNED_FIELDS 1 21 | #endif 22 | 23 | #ifndef MAVLINK_CRC_EXTRA 24 | #define MAVLINK_CRC_EXTRA 1 25 | #endif 26 | 27 | #ifndef MAVLINK_COMMAND_24BIT 28 | #define MAVLINK_COMMAND_24BIT 1 29 | #endif 30 | 31 | #include "version.h" 32 | #include "standard.h" 33 | 34 | #endif // MAVLINK_H 35 | -------------------------------------------------------------------------------- /src/icons/latency.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | # See debhelper(7) (uncomment to enable). 4 | # Output every command that modifies files on the build system. 5 | #export DH_VERBOSE = 1 6 | 7 | 8 | # See FEATURE AREAS in dpkg-buildflags(1). 9 | #export DEB_BUILD_MAINT_OPTIONS = hardening=+all 10 | 11 | # See ENVIRONMENT in dpkg-buildflags(1). 12 | # Package maintainers to append CFLAGS. 13 | #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 14 | # Package maintainers to append LDFLAGS. 15 | #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 16 | 17 | 18 | %: 19 | dh $@ --buildsystem=cmake 20 | 21 | override_dh_auto_configure: 22 | dh_auto_configure -- \ 23 | -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) 24 | 25 | override_dh_installsystemd: 26 | dh_installsystemd --restart-after-upgrade --name=pixelpilot 27 | 28 | override_dh_installinit: 29 | dh_installinit --name=pixelpilot 30 | -------------------------------------------------------------------------------- /src/gsmenu/executor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ui.h" 3 | 4 | #define MAX_CMD_ARGS 5 5 | 6 | typedef void (*callback_fn)(void); 7 | 8 | typedef struct { 9 | lv_event_t * event; 10 | lv_obj_t* parent; 11 | bool blocking; 12 | bool work_complete; 13 | pthread_t thread_id; 14 | menu_page_data_t * menu_page_data; 15 | char* argument_string; 16 | char* command; 17 | lv_obj_t* arguments[MAX_CMD_ARGS]; 18 | lv_obj_t* spinner; 19 | char parameter[100]; 20 | int precision; 21 | callback_fn callback_fn; 22 | } thread_data_t; 23 | 24 | 25 | char* run_command(const char* command); 26 | void run_command_and_block(lv_event_t* e,const char * command, callback_fn callback); 27 | void generic_switch_event_cb(lv_event_t * e); 28 | void generic_checkbox_event_cb(lv_event_t * e); 29 | void generic_dropdown_event_cb(lv_event_t * e); 30 | void generic_slider_event_cb(lv_event_t * e); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # Cmake files 55 | CMakeCache.txt 56 | CMakeFiles/ 57 | cmake_install.cmake 58 | pixelpilot_config.h 59 | 60 | .vscode/ 61 | build/ 62 | build_sim/ 63 | .apt_cache/ 64 | output/ 65 | debian-*-generic-arm64.tar.xz 66 | disk.raw 67 | pixelpilot -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void sig_handler(int signum); 4 | 5 | void switch_pipeline_source(const char * source_type, const char * source_path); 6 | void fast_forward(double rate); 7 | void fast_rewind(double rate); 8 | void skip_duration(int64_t skip_ms); 9 | void normal_playback(); 10 | void pause_playback(); 11 | void resume_playback(); 12 | 13 | /* --- Console arguments parser --- */ 14 | #define __BeginParseConsoleArguments__(printHelpFunction) \ 15 | if (argc < 1 || (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "/?") \ 16 | || !strcmp(argv[1], "/h")))) { printHelpFunction(); return 1; } \ 17 | for (int ArgID = 1; ArgID < argc; ArgID++) { const char* Arg = argv[ArgID]; 18 | 19 | #define __OnArgument(Name) if (!strcmp(Arg, Name)) 20 | #define __ArgValue (argc > ArgID + 1 ? argv[++ArgID] : "") 21 | #define __EndParseConsoleArguments__ else { printf("ERROR: Unknown argument %s\n",Arg); return 1; } } -------------------------------------------------------------------------------- /src/WiFiRSSIMonitor.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WIFI_RSSI_MONITOR_H 2 | #define WIFI_RSSI_MONITOR_H 3 | 4 | #include 5 | 6 | extern "C" { 7 | #include "osd.h" 8 | } 9 | 10 | class WiFiRSSIMonitor { 11 | public: 12 | WiFiRSSIMonitor(); 13 | void run(); 14 | void publish_reset(); 15 | 16 | private: 17 | struct WiFiStats { 18 | int rssi_a; 19 | int rssi_b; 20 | int rssi_min; 21 | int rssi_percent; 22 | bool is_linked; 23 | 24 | WiFiStats(); 25 | }; 26 | 27 | std::string base_path_; 28 | 29 | WiFiStats parse_interface_stats(const std::string& file_path); 30 | void add_interface_stats_to_batch(void* batch, const std::string& interface_name, const WiFiStats& stats); 31 | void add_rssi_fact_to_batch(void* batch, const std::string& rssi_type, int value, osd_tag* interface_tag); 32 | void publish_interface_reset(void* batch, const std::string& interface_name); 33 | 34 | }; 35 | 36 | #endif -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Source: https://github.com/OpenIPC/PixelPilot_rk 3 | Upstream-Name: pixelpilot-rk 4 | Upstream-Contact: https://github.com/OpenIPC/PixelPilot_rk 5 | 6 | Files: 7 | * 8 | Copyright: 9 | 2025 OpenIPC project 10 | License: GPL-3+ 11 | This package is free software; you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation; either version 2 of the License, or 14 | (at your option) any later version. 15 | . 16 | This package is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | . 21 | You should have received a copy of the GNU General Public License 22 | along with this package. If not, see . 23 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: pixelpilot-rk 2 | Section: misc 3 | Priority: optional 4 | Maintainer: Sergey Prokhorov 5 | Rules-Requires-Root: no 6 | Build-Depends: 7 | debhelper-compat (= 13), 8 | cmake, 9 | libdrm-dev, 10 | libcairo-dev, 11 | librockchip-mpp-dev, 12 | libspdlog-dev, 13 | nlohmann-json3-dev, 14 | libmsgpack-dev, 15 | libgpiod-dev, 16 | libyaml-cpp-dev, 17 | libgstreamer1.0-dev, 18 | libgstreamer-plugins-base1.0-dev, 19 | Standards-Version: 4.7.2 20 | Homepage: https://github.com/OpenIPC/PixelPilot_rk 21 | Vcs-Browser: https://github.com/OpenIPC/PixelPilot_rk 22 | #Vcs-Git: https://salsa.debian.org/debian/pixelpilot-rk.git 23 | 24 | Package: pixelpilot-rk 25 | Architecture: amd64 armhf arm64 26 | Multi-Arch: foreign 27 | Depends: 28 | ${shlibs:Depends}, 29 | ${misc:Depends}, 30 | wfb-ng, 31 | jq, 32 | drm-info 33 | Description: Client for OpenIPC wfb-ng 34 | The client for wfb-ng that reads the video and telemetry streams, 35 | decodes video using Rockchip's MPP and displays on screen using Linux DRM 36 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | // input.h 2 | #ifndef INPUT_H 3 | #define INPUT_H 4 | 5 | #include 6 | #include "lvgl/lvgl.h" 7 | 8 | typedef enum { 9 | GSMENU_CONTROL_MODE_NAV = 0, 10 | GSMENU_CONTROL_MODE_EDIT, 11 | GSMENU_CONTROL_MODE_SLIDER, 12 | GSMENU_CONTROL_MODE_KEYBOARD 13 | } gsmenu_control_mode_t; 14 | 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | void simulate_key_press(uint32_t key_code); 21 | 22 | // Function to make stdin non-blocking 23 | void set_stdin_nonblock(void); 24 | // Function to restore terminal settings 25 | void restore_stdin(void); 26 | 27 | // Handle WASD input and convert to LVGL key codes 28 | void handle_keyboard_input(void); 29 | 30 | void toggle_rec_enabled(void); 31 | 32 | // Custom function to simulate keyboard input 33 | static void virtual_keyboard_read(lv_indev_t * indev, lv_indev_data_t * data); 34 | 35 | // Function to create the virtual keyboard 36 | lv_indev_t * create_virtual_keyboard(); 37 | 38 | void cleanup_gpio(void); 39 | 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | #endif // INPUT_H -------------------------------------------------------------------------------- /src/mavlink/standard/testsuite.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | * @brief MAVLink comm protocol testsuite generated from standard.xml 3 | * @see https://mavlink.io/en/ 4 | */ 5 | #pragma once 6 | #ifndef STANDARD_TESTSUITE_H 7 | #define STANDARD_TESTSUITE_H 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #ifndef MAVLINK_TEST_ALL 14 | #define MAVLINK_TEST_ALL 15 | static void mavlink_test_minimal(uint8_t, uint8_t, mavlink_message_t *last_msg); 16 | static void mavlink_test_standard(uint8_t, uint8_t, mavlink_message_t *last_msg); 17 | 18 | static void mavlink_test_all(uint8_t system_id, uint8_t component_id, mavlink_message_t *last_msg) 19 | { 20 | mavlink_test_minimal(system_id, component_id, last_msg); 21 | mavlink_test_standard(system_id, component_id, last_msg); 22 | } 23 | #endif 24 | 25 | #include "../minimal/testsuite.h" 26 | 27 | 28 | 29 | static void mavlink_test_standard(uint8_t system_id, uint8_t component_id, mavlink_message_t *last_msg) 30 | { 31 | 32 | } 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif // __cplusplus 37 | #endif // STANDARD_TESTSUITE_H 38 | -------------------------------------------------------------------------------- /src/simulator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "lvgl/lvgl.h" 6 | #include "menu.h" 7 | #include "input.h" 8 | #include "gsmenu/helper.h" 9 | 10 | 11 | int dvr_enabled = 0; 12 | uint64_t gtotal_tunnel_data = 0; 13 | bool disable_vsync = false; 14 | const char *dvr_template = "/tmp/record_%Y-%m-%d_%H-%M-%S.mp4"; 15 | void my_log_cb(lv_log_level_t level, const char * buf) 16 | { 17 | printf("%s",buf); 18 | } 19 | 20 | int main(int argc, char **argv) 21 | { 22 | lv_init(); 23 | lv_disp_t * disp = lv_sdl_window_create(1920,1080); 24 | 25 | // lv_log_register_print_cb(my_log_cb); 26 | 27 | lv_obj_t * bottom = lv_display_get_layer_bottom(disp); 28 | lv_obj_t *obj = lv_img_create(bottom); 29 | lv_image_set_src(obj, find_resource_file("osd-bg-2.png")); 30 | lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100)); 31 | lv_image_set_inner_align(obj, LV_IMAGE_ALIGN_STRETCH); 32 | 33 | pp_menu_main(); 34 | while (1) { 35 | handle_keyboard_input(); 36 | lv_task_handler(); 37 | usleep(5000); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/os_mon.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OS_MON_H 2 | #define OS_MON_H 3 | 4 | #include 5 | #include 6 | 7 | class ISensor { 8 | public: 9 | virtual ~ISensor() = default; 10 | static void detect(std::vector>& sensors); 11 | virtual void run() = 0; 12 | virtual bool is_valid() = 0; 13 | }; 14 | 15 | class OsSensors { 16 | public: 17 | // Enables CPU load sensor 18 | void addCPU(); 19 | std::size_t discoverCPU(); 20 | // Adds power supply sensor (currently supported is ina226 via ina2xx kernel driver) 21 | void addPower(const std::string &sensor_type, const std::string &hwmon_id); 22 | std::size_t discoverPower(); 23 | // Adds temperature sensor 24 | void addTemperature(const std::string &thermal_zone); 25 | std::size_t discoverTemperature(); 26 | // Automatically detects and adds all CPU, power and temperature sensors; returns the number 27 | // of discovered sensors 28 | std::size_t autodiscover(); 29 | 30 | std::size_t size(); 31 | 32 | void run(); 33 | 34 | private: 35 | std::vector> sensors; 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/gsmenu/air_actions.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "lvgl/lvgl.h" 6 | #include "helper.h" 7 | #include "executor.h" 8 | #include "styles.h" 9 | #include "helper.h" 10 | #include "air_actions.h" 11 | 12 | extern lv_group_t * default_group; 13 | lv_obj_t * air_reboot; 14 | lv_obj_t * air_custom_action; 15 | 16 | void create_air_actions_menu(lv_obj_t * parent) { 17 | 18 | menu_page_data_t* menu_page_data = malloc(sizeof(menu_page_data_t)); 19 | strcpy(menu_page_data->type, "air"); 20 | strcpy(menu_page_data->page, "actions"); 21 | menu_page_data->page_load_callback = NULL; 22 | menu_page_data->indev_group = lv_group_create(); 23 | lv_group_set_default(menu_page_data->indev_group); 24 | lv_obj_set_user_data(parent,menu_page_data); 25 | 26 | lv_obj_t * section = lv_menu_section_create(parent); 27 | lv_obj_add_style(section, &style_openipc_section, 0); 28 | 29 | 30 | air_reboot = create_button(section, "Reboot"); 31 | lv_obj_add_event_cb(lv_obj_get_child_by_type(air_reboot,0,&lv_button_class),generic_button_callback,LV_EVENT_CLICKED,menu_page_data); 32 | 33 | for (size_t i = 0; i < airactions_count; i++) { 34 | air_custom_action = create_button(section, airactions[i].label); 35 | lv_obj_add_event_cb(lv_obj_get_child_by_type(air_custom_action,0,&lv_button_class),custom_actions_cb,LV_EVENT_CLICKED,&airactions[i]); 36 | } 37 | 38 | lv_group_set_default(default_group); 39 | } 40 | -------------------------------------------------------------------------------- /src/osd.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OSDPP_H 2 | #define OSDPP_H 3 | 4 | extern "C" { 5 | #include "drm.h" 6 | } 7 | #include 8 | 9 | typedef struct { 10 | struct modeset_output *out; 11 | int fd; 12 | nlohmann::json config; 13 | } osd_thread_params; 14 | 15 | extern int osd_thread_signal; 16 | 17 | struct SharedMemoryRegion { 18 | uint16_t width; // Image width 19 | uint16_t height; // Image height 20 | unsigned char data[]; // Flexible array member for image data 21 | }; 22 | 23 | void *__OSD_THREAD__(void *param); 24 | 25 | #ifdef TEST 26 | class ExpressionTree; 27 | 28 | class TestExpressionTree { 29 | public: 30 | TestExpressionTree(); 31 | TestExpressionTree(const std::string& expression); 32 | ~TestExpressionTree(); 33 | 34 | std::vector tokenize(const std::string& input); 35 | void parse(const std::string &expression); 36 | double evaluate(double xValue); 37 | 38 | private: 39 | ExpressionTree *tree; 40 | }; 41 | 42 | class TplTextWidget; 43 | 44 | class TestTplTextWidget { 45 | public: 46 | TestTplTextWidget(int pos_x, int pos_y, std::string tpl, uint n_args); 47 | ~TestTplTextWidget(); 48 | 49 | void draw(void *cr); 50 | 51 | std::unique_ptr render_tpl(); 52 | 53 | void setBoolFact(uint idx, bool v); 54 | void setLongFact(uint idx, long v); 55 | void setUlongFact(uint idx, ulong v); 56 | void setDoubleFact(uint idx, double v); 57 | void setStringFact(uint idx, std::string v); 58 | 59 | private: 60 | TplTextWidget *widget; 61 | }; 62 | 63 | #endif 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/osd.h: -------------------------------------------------------------------------------- 1 | #ifndef OSD_H 2 | #define OSD_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | extern int enable_osd; 9 | extern bool osd_custom_message; 10 | extern pthread_mutex_t osd_mutex; 11 | 12 | #define TAG_MAX_LEN 64 13 | 14 | typedef struct { 15 | char key[TAG_MAX_LEN]; 16 | char val[TAG_MAX_LEN]; 17 | } osd_tag; 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | // Batch functions are when you publish several facts from the same place 23 | // It has optimized publishing algorithm - takes the lock only once per-batch 24 | void *osd_batch_init(uint n); 25 | void osd_publish_batch(void *batch); 26 | void osd_add_bool_fact(void *batch, char const *name, osd_tag *tags, int n_tags, bool value); 27 | void osd_add_int_fact(void *batch, char const *name, osd_tag *tags, int n_tags, long value); 28 | void osd_add_uint_fact(void *batch, char const *name, osd_tag *tags, int n_tags, ulong value); 29 | void osd_add_double_fact(void *batch, char const *name, osd_tag *tags, int n_tags, double value); 30 | void osd_add_str_fact(void *batch, char const *name, osd_tag *tags, int n_tags, const char *value); 31 | 32 | // Publish individual facts 33 | void osd_publish_bool_fact(char const *name, osd_tag *tags, int n_tags, bool value); 34 | void osd_publish_int_fact(char const *name, osd_tag *tags, int n_tags, long value); 35 | void osd_publish_uint_fact(char const *name, osd_tag *tags, int n_tags, ulong value); 36 | void osd_publish_double_fact(char const *name, osd_tag *tags, int n_tags, double value); 37 | void osd_publish_str_fact(char const *name, osd_tag *tags, int n_tags, const char *value); 38 | #ifdef __cplusplus 39 | } 40 | #endif 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /pixelpilot.yaml: -------------------------------------------------------------------------------- 1 | gsmenu: 2 | enabled: true 3 | gpio: 4 | # RuncamVRX 5 | left: 13 6 | right: 38 7 | up: 16 8 | down: 18 9 | center: 11 10 | # alternative 11 | # center: 12 | # chip: gpiochip3 13 | # pin: 1 14 | rec: 32 15 | # Ruby 16 | # left: 13 17 | # right: 11 18 | # up: 16 19 | # down: 18 20 | # rec: 32 21 | # Bonnet 22 | # left: 13 23 | # right: 12 24 | # up: 16 25 | # down: 18 26 | # center: 11 27 | # rec: 22 28 | # Emax Wyvern Link Goggle VRX 29 | # left: 38 30 | # right: 32 31 | # up: 16 32 | # down: 18 33 | # Define custom actions here 34 | # action is the command the button will trigger 35 | # actions: 36 | # air: 37 | # - label: Custom Action 1 38 | # action: sleep 5 39 | # - label: Custom Action 1 40 | # action: sleep 5 41 | # ground: 42 | # - label: Custom Action 1 43 | # action: sleep 5 44 | # enables collection of `os_mon.*` OSD facts 45 | os_sensors: 46 | cpu: true # Monitor CPU load percentage 47 | power: 48 | # Monitor power supply voltage and current (advanced: requires INA226 sensor to be installed) 49 | # It can be several, but normally you just install one to monitor battery 50 | - type: ina226 # currently the only type supported 51 | hwmon_id: hwmon3 # /sys/class/hwmon/{hwmon_id}/name should be ina226 52 | temperature: 53 | # Monitor Radxa chip temperatures, see /sys/class/thermal/thermal_zone*/ 54 | - thermal_zone: thermal_zone0 # CPU on Radxa 55 | - thermal_zone: thermal_zone1 # GPU on Radxa 56 | 57 | # OS sensors can be auto-discovered 58 | # os_sensors: 59 | # cpu: auto 60 | # power: auto 61 | # temperature: auto 62 | -------------------------------------------------------------------------------- /src/scheduling_helper.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by https://github.com/Consti10 on 09.04.24. 3 | // https://github.com/OpenHD/FPVue_RK3566/tree/openhd 4 | // 5 | 6 | #ifndef FPVUE_SCHEDULINGHELPER_H 7 | #define FPVUE_SCHEDULINGHELPER_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include "spdlog/spdlog.h" 13 | 14 | #include 15 | #include 16 | 17 | namespace SchedulingHelper { 18 | 19 | // Only 'low' in comparison to other realtime tasks 20 | static constexpr int PRIORITY_REALTIME_LOW=30; 21 | static constexpr int PRIORITY_REALTIME_MID=40; 22 | 23 | // this thread should run as close to realtime as possible 24 | // https://youtu.be/NrjXEaTSyrw?t=647 25 | // COMMENT: Please don't ever use 99 for your application, there are some kernel 26 | // threads that run at 99 that are really important So ... lets use 90 for now 27 | static void set_thread_params_max_realtime(const std::string& tag, 28 | const int priority = 90) { 29 | pthread_t target = pthread_self(); 30 | int policy = SCHED_FIFO; 31 | sched_param param{}; 32 | // param.sched_priority = sched_get_priority_max(policy); 33 | param.sched_priority = priority; 34 | auto result = pthread_setschedparam(target, policy, ¶m); 35 | if (result != 0) { 36 | spdlog::warn("Cannot setThreadParamsMaxRealtime {}", result); 37 | } else { 38 | spdlog::info("Changed prio for {} for to SCHED_FIFO: {}", tag, param.sched_priority); 39 | } 40 | } 41 | 42 | static bool check_root() { 43 | const auto uid = getuid(); 44 | const bool root = uid ? false : true; 45 | return root; 46 | } 47 | 48 | } // namespace SchedulingHelper 49 | 50 | #endif //FPVUE_SCHEDULINGHELPER_H 51 | -------------------------------------------------------------------------------- /src/mavlink/standard/standard.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | * @brief MAVLink comm protocol generated from standard.xml 3 | * @see http://mavlink.org 4 | */ 5 | #pragma once 6 | #ifndef MAVLINK_STANDARD_H 7 | #define MAVLINK_STANDARD_H 8 | 9 | #ifndef MAVLINK_H 10 | #error Wrong include order: MAVLINK_STANDARD.H MUST NOT BE DIRECTLY USED. Include mavlink.h from the same directory instead or set ALL AND EVERY defines from MAVLINK.H manually accordingly, including the #define MAVLINK_H call. 11 | #endif 12 | 13 | #define MAVLINK_STANDARD_XML_HASH 2262899478281782564 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | 19 | // MESSAGE LENGTHS AND CRCS 20 | 21 | #ifndef MAVLINK_MESSAGE_LENGTHS 22 | #define MAVLINK_MESSAGE_LENGTHS {} 23 | #endif 24 | 25 | #ifndef MAVLINK_MESSAGE_CRCS 26 | #define MAVLINK_MESSAGE_CRCS {{0, 50, 9, 9, 0, 0, 0}, {300, 217, 22, 22, 0, 0, 0}} 27 | #endif 28 | 29 | #include "../protocol.h" 30 | 31 | #define MAVLINK_ENABLED_STANDARD 32 | 33 | // ENUM DEFINITIONS 34 | 35 | 36 | 37 | // MAVLINK VERSION 38 | 39 | #ifndef MAVLINK_VERSION 40 | #define MAVLINK_VERSION 2 41 | #endif 42 | 43 | #if (MAVLINK_VERSION == 0) 44 | #undef MAVLINK_VERSION 45 | #define MAVLINK_VERSION 2 46 | #endif 47 | 48 | // MESSAGE DEFINITIONS 49 | 50 | 51 | // base include 52 | #include "../minimal/minimal.h" 53 | 54 | 55 | #if MAVLINK_STANDARD_XML_HASH == MAVLINK_PRIMARY_XML_HASH 56 | # define MAVLINK_MESSAGE_INFO {MAVLINK_MESSAGE_INFO_HEARTBEAT, MAVLINK_MESSAGE_INFO_PROTOCOL_VERSION} 57 | # define MAVLINK_MESSAGE_NAMES {{ "HEARTBEAT", 0 }, { "PROTOCOL_VERSION", 300 }} 58 | # if MAVLINK_COMMAND_24BIT 59 | # include "../mavlink_get_info.h" 60 | # endif 61 | #endif 62 | 63 | #ifdef __cplusplus 64 | } 65 | #endif // __cplusplus 66 | #endif // MAVLINK_STANDARD_H 67 | -------------------------------------------------------------------------------- /src/time_util.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by https://github.com/Consti10 on 31.03.24. 3 | // https://github.com/OpenHD/FPVue_RK3566/tree/openhd 4 | // 5 | 6 | 7 | #ifndef FPVUE_TIME_UTIL_H 8 | #define FPVUE_TIME_UTIL_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | /** 15 | * @return milliseconds 16 | */ 17 | uint64_t get_time_ms() { 18 | struct timespec spec; 19 | if (clock_gettime(1, &spec) == -1) { /* 1 is CLOCK_MONOTONIC */ 20 | abort(); 21 | } 22 | return spec.tv_sec * 1000 + spec.tv_nsec / 1e6; 23 | } 24 | 25 | void print_time_ms(const char* tag,uint64_t ms){ 26 | printf("%s %dms\n",tag,(int)ms); 27 | } 28 | 29 | 30 | struct TSAccumulator{ 31 | // In milliseconds 32 | uint64_t min_ms; 33 | uint64_t max_ms; 34 | uint64_t accumulated_ms; 35 | int count; 36 | uint64_t last_print_ms; 37 | }; 38 | 39 | 40 | void accumulate_and_print(const char *tag,uint64_t ms,struct TSAccumulator* tsAccumulator){ 41 | if(ms>tsAccumulator->max_ms){ 42 | tsAccumulator->max_ms=ms; 43 | } 44 | if(msmin_ms){ 45 | tsAccumulator->min_ms=ms; 46 | } 47 | tsAccumulator->accumulated_ms+=ms; 48 | tsAccumulator->count++; 49 | uint64_t elapsed_since_last_print_ms=get_time_ms()- tsAccumulator->last_print_ms; 50 | if(elapsed_since_last_print_ms>1000){ 51 | uint64_t average=tsAccumulator->accumulated_ms/tsAccumulator->count; 52 | printf("%s min:%ld max:%ld avg:%ld (ms)\n",tag,tsAccumulator->min_ms,tsAccumulator->max_ms,average); 53 | tsAccumulator->min_ms=UINT64_MAX; 54 | tsAccumulator->max_ms=0; 55 | tsAccumulator->count=0; 56 | tsAccumulator->accumulated_ms=0; 57 | tsAccumulator->last_print_ms=get_time_ms(); 58 | } 59 | } 60 | 61 | 62 | #endif //FPVUE_TIME_UTIL_H 63 | -------------------------------------------------------------------------------- /src/gsmenu/gs_actions.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "lvgl/lvgl.h" 6 | #include "../main.h" 7 | #include "helper.h" 8 | #include "executor.h" 9 | #include "styles.h" 10 | #include 11 | #include "../menu.h" 12 | #include "gs_actions.h" 13 | 14 | #ifdef USE_SIMULATOR 15 | void sig_handler(int exit_code) 16 | { 17 | exit(exit_code); 18 | } 19 | #endif 20 | 21 | extern lv_group_t * default_group; 22 | 23 | lv_obj_t * restart_pp; 24 | lv_obj_t * exit_pp; 25 | lv_obj_t * gs_reboot; 26 | lv_obj_t * gs_custom_action; 27 | int restart_value = 1; 28 | int exit_value = 2; 29 | 30 | void gs_actions_exit_pp(lv_event_t * event) 31 | { 32 | int *signal = lv_event_get_user_data(event); 33 | sig_handler(*signal); 34 | } 35 | 36 | void create_gs_actions_menu(lv_obj_t * parent) { 37 | 38 | menu_page_data_t* menu_page_data = malloc(sizeof(menu_page_data_t)); 39 | strcpy(menu_page_data->type, "gs"); 40 | strcpy(menu_page_data->page, "actions"); 41 | menu_page_data->page_load_callback = NULL; 42 | menu_page_data->indev_group = lv_group_create(); 43 | lv_group_set_default(menu_page_data->indev_group); 44 | lv_obj_set_user_data(parent,menu_page_data); 45 | 46 | lv_obj_t * section = lv_menu_section_create(parent); 47 | lv_obj_add_style(section, &style_openipc_section, 0); 48 | 49 | restart_pp = create_button(section, "Restart Pixelpilot"); 50 | lv_obj_add_event_cb(lv_obj_get_child_by_type(restart_pp,0,&lv_button_class),gs_actions_exit_pp,LV_EVENT_CLICKED,&restart_value); 51 | 52 | exit_pp = create_button(section, "Exit Pixelpilot"); 53 | lv_obj_add_event_cb(lv_obj_get_child_by_type(exit_pp,0,&lv_button_class),gs_actions_exit_pp,LV_EVENT_CLICKED,&exit_value); 54 | 55 | gs_reboot = create_button(section, "Reboot"); 56 | lv_obj_add_event_cb(lv_obj_get_child_by_type(gs_reboot,0,&lv_button_class),generic_button_callback,LV_EVENT_CLICKED,menu_page_data); 57 | 58 | 59 | for (size_t i = 0; i < gsactions_count; i++) { 60 | gs_custom_action = create_button(section, gsactions[i].label); 61 | lv_obj_add_event_cb(lv_obj_get_child_by_type(gs_custom_action,0,&lv_button_class),custom_actions_cb,LV_EVENT_CLICKED,&gsactions[i]); 62 | } 63 | 64 | lv_group_set_default(default_group); 65 | } 66 | -------------------------------------------------------------------------------- /src/menu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "lvgl/lvgl.h" 4 | #include "input.h" 5 | #include "menu.h" 6 | #include "gsmenu/ui.h" 7 | #include "gsmenu/styles.h" 8 | #include "gsmenu/gs_dvrplayer.h" 9 | #include "gsmenu/air_txprofiles.h" 10 | #include "lvosd.h" 11 | 12 | lv_obj_t * menu; 13 | lv_indev_t * indev_drv; 14 | lv_group_t * default_group; 15 | lv_obj_t * pp_menu_screen; 16 | lv_obj_t * pp_osd_screen; 17 | lv_obj_t * dvr_screen; 18 | lv_obj_t * txprofiles_screen; 19 | 20 | /** 21 | * PP Main Menu 22 | */ 23 | void pp_menu_main(void) 24 | { 25 | 26 | style_init(); 27 | 28 | // create_virtual_keyboard 29 | indev_drv = create_virtual_keyboard(); 30 | 31 | // Create an input group 32 | default_group = lv_group_create(); 33 | lv_group_set_default(default_group); 34 | lv_indev_set_group(indev_drv, default_group); 35 | 36 | pp_menu_screen = lv_obj_create(NULL); 37 | lv_obj_set_style_bg_opa(pp_menu_screen, LV_OPA_TRANSP, LV_PART_MAIN); 38 | lv_obj_clear_flag(pp_menu_screen, LV_OBJ_FLAG_SCROLLABLE); 39 | 40 | lv_obj_t * menu_cont = lv_obj_create(pp_menu_screen); 41 | lv_obj_set_size(menu_cont,lv_obj_get_width(pp_menu_screen) / 4 * 3, 42 | lv_obj_get_height(pp_menu_screen)/ 4 * 3); 43 | lv_obj_center(menu_cont); 44 | lv_obj_set_style_border_side(menu_cont, LV_BORDER_SIDE_NONE, LV_PART_MAIN | LV_STATE_DEFAULT); 45 | lv_obj_set_style_radius(menu_cont, 0, LV_PART_MAIN | LV_STATE_DEFAULT); 46 | lv_obj_set_style_pad_left(menu_cont, 0, LV_PART_MAIN | LV_STATE_DEFAULT); 47 | lv_obj_set_style_pad_top(menu_cont, 0, LV_PART_MAIN | LV_STATE_DEFAULT); 48 | lv_obj_set_style_pad_right(menu_cont, 0, LV_PART_MAIN | LV_STATE_DEFAULT); 49 | lv_obj_set_style_pad_bottom(menu_cont, 0, LV_PART_MAIN | LV_STATE_DEFAULT); 50 | 51 | pp_header_create(menu_cont); 52 | 53 | pp_menu_create(menu_cont); 54 | 55 | dvr_screen = lv_obj_create(NULL); 56 | lv_obj_set_style_bg_opa(dvr_screen, LV_OPA_TRANSP, LV_PART_MAIN); 57 | dvr_player_screen_init(); 58 | 59 | txprofiles_screen = lv_obj_create(NULL); 60 | lv_obj_set_style_bg_opa(txprofiles_screen, LV_OPA_TRANSP, LV_PART_MAIN); 61 | create_table(txprofiles_screen); 62 | 63 | pp_osd_main(); 64 | 65 | lv_screen_load(pp_osd_screen); 66 | } -------------------------------------------------------------------------------- /src/dvr.h: -------------------------------------------------------------------------------- 1 | #ifndef DVR_H 2 | #define DVR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "gstrtpreceiver.h" 12 | 13 | struct MP4E_mux_tag; 14 | struct mp4_h26x_writer_tag; 15 | 16 | struct video_params { 17 | uint32_t video_frm_width; 18 | uint32_t video_frm_height; 19 | VideoCodec codec; 20 | }; 21 | 22 | struct dvr_thread_params { 23 | char *filename_template; 24 | int mp4_fragmentation_mode = 0; 25 | bool dvr_filenames_with_sequence = false; 26 | int video_framerate = -1; 27 | video_params video_p; 28 | }; 29 | 30 | struct dvr_rpc { 31 | enum { 32 | RPC_FRAME, 33 | RPC_STOP, 34 | RPC_START, 35 | RPC_TOGGLE, 36 | RPC_SHUTDOWN, 37 | RPC_SET_PARAMS 38 | } command; 39 | /* union { */ 40 | std::shared_ptr> frame; 41 | /* video_params params; */ 42 | /* }; */ 43 | }; 44 | 45 | 46 | extern int dvr_enabled; 47 | 48 | class Dvr { 49 | public: 50 | explicit Dvr(dvr_thread_params params); 51 | virtual ~Dvr(); 52 | 53 | void frame(std::shared_ptr> frame); 54 | void set_video_params(uint32_t video_frm_width, 55 | uint32_t video_frm_height, 56 | VideoCodec codec); 57 | void start_recording(); 58 | void stop_recording(); 59 | void set_video_framerate(int rate); 60 | void toggle_recording(); 61 | void shutdown(); 62 | 63 | static void *__THREAD__(void *context); 64 | private: 65 | /* void *start(); */ 66 | /* void *stop(); */ 67 | void enqueue_dvr_command(dvr_rpc rpc); 68 | 69 | void loop(); 70 | int start(); 71 | void stop(); 72 | void init(); 73 | private: 74 | std::queue dvrQueue; 75 | std::mutex mtx; 76 | std::condition_variable cv; 77 | char *filename_template; 78 | int mp4_fragmentation_mode = 0; 79 | bool dvr_filenames_with_sequence = false; 80 | int video_framerate = -1; 81 | uint32_t video_frm_width; 82 | uint32_t video_frm_height; 83 | VideoCodec codec; 84 | int _ready_to_write = 0; 85 | 86 | FILE *dvr_file; 87 | MP4E_mux_tag *mux; 88 | mp4_h26x_writer_tag *mp4wr; 89 | }; 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /src/gsmenu/gs_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "ui.h" 6 | #include "../input.h" 7 | #include "helper.h" 8 | #include "styles.h" 9 | #include "executor.h" 10 | #include "gs_wifi.h" 11 | 12 | 13 | extern lv_obj_t * menu; 14 | extern gsmenu_control_mode_t control_mode; 15 | extern lv_group_t *main_group; 16 | extern lv_group_t *default_group; 17 | 18 | lv_obj_t * version ; 19 | lv_obj_t * disk ; 20 | lv_obj_t * wfb_nics ; 21 | lv_obj_t * channel ; 22 | lv_obj_t * screen_mode ; 23 | 24 | void gs_main_page_load_callback(lv_obj_t * page) 25 | { 26 | reload_label_value(page,version); 27 | reload_label_value(page,disk); 28 | reload_label_value(page,wfb_nics); 29 | reload_label_value(page,channel); 30 | reload_label_value(page,screen_mode); 31 | } 32 | 33 | void create_main_menu(lv_obj_t * parent) { 34 | 35 | menu_page_data_t* menu_page_data = malloc(sizeof(menu_page_data_t)); 36 | strcpy(menu_page_data->type, "gs"); 37 | strcpy(menu_page_data->page, "main"); 38 | menu_page_data->page_load_callback = gs_main_page_load_callback; 39 | menu_page_data->indev_group = main_group; 40 | lv_group_set_default(menu_page_data->indev_group); 41 | lv_obj_set_user_data(parent,menu_page_data); 42 | 43 | lv_obj_t * cont; 44 | lv_obj_t * label; 45 | lv_obj_t * section; 46 | lv_obj_t * obj; 47 | 48 | create_text(parent, NULL, "", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 49 | section = lv_menu_section_create(parent); 50 | lv_obj_add_style(section, &style_openipc_section, 0); 51 | cont = lv_menu_cont_create(section); 52 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 53 | 54 | channel = create_text(cont, LV_SYMBOL_SETTINGS, "Channel", "Channel", menu_page_data, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 55 | screen_mode = create_text(cont, LV_SYMBOL_SETTINGS, "HDMI-OUT", "HDMI-OUT", menu_page_data, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 56 | wfb_nics = create_text(cont, LV_SYMBOL_SETTINGS, "WFB_NICS", "WFB_NICS", menu_page_data, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 57 | disk = create_text(cont, LV_SYMBOL_SETTINGS, "Version", "Disk", menu_page_data, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 58 | version = create_text(cont, LV_SYMBOL_SETTINGS, "Version", "Version", menu_page_data, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 59 | 60 | lv_group_set_default(default_group); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/mavlink/mavlink_get_info.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef MAVLINK_USE_MESSAGE_INFO 4 | #define MAVLINK_HAVE_GET_MESSAGE_INFO 5 | 6 | /* 7 | return the message_info struct for a message 8 | */ 9 | MAVLINK_HELPER const mavlink_message_info_t *mavlink_get_message_info_by_id(uint32_t msgid) 10 | { 11 | static const mavlink_message_info_t mavlink_message_info[] = MAVLINK_MESSAGE_INFO; 12 | /* 13 | use a bisection search to find the right entry. A perfect hash may be better 14 | Note that this assumes the table is sorted with primary key msgid 15 | */ 16 | const uint32_t count = sizeof(mavlink_message_info)/sizeof(mavlink_message_info[0]); 17 | if (count == 0) { 18 | return NULL; 19 | } 20 | uint32_t low=0, high=count-1; 21 | while (low < high) { 22 | uint32_t mid = (low+high)/2; 23 | if (msgid < mavlink_message_info[mid].msgid) { 24 | high = mid; 25 | continue; 26 | } 27 | if (msgid > mavlink_message_info[mid].msgid) { 28 | low = mid+1; 29 | continue; 30 | } 31 | return &mavlink_message_info[mid]; 32 | } 33 | if (mavlink_message_info[low].msgid == msgid) { 34 | return &mavlink_message_info[low]; 35 | } 36 | return NULL; 37 | } 38 | 39 | /* 40 | return the message_info struct for a message 41 | */ 42 | MAVLINK_HELPER const mavlink_message_info_t *mavlink_get_message_info(const mavlink_message_t *msg) 43 | { 44 | return mavlink_get_message_info_by_id(msg->msgid); 45 | } 46 | 47 | /* 48 | return the message_info struct for a message 49 | */ 50 | MAVLINK_HELPER const mavlink_message_info_t *mavlink_get_message_info_by_name(const char *name) 51 | { 52 | static const struct { const char *name; uint32_t msgid; } mavlink_message_names[] = MAVLINK_MESSAGE_NAMES; 53 | /* 54 | use a bisection search to find the right entry. A perfect hash may be better 55 | Note that this assumes the table is sorted with primary key name 56 | */ 57 | const uint32_t count = sizeof(mavlink_message_names)/sizeof(mavlink_message_names[0]); 58 | if (count == 0) { 59 | return NULL; 60 | } 61 | uint32_t low=0, high=count-1; 62 | while (low < high) { 63 | uint32_t mid = (low+high)/2; 64 | int cmp = strcmp(mavlink_message_names[mid].name, name); 65 | if (cmp > 0) { 66 | high = mid; 67 | continue; 68 | } 69 | if (cmp < 0) { 70 | low = mid+1; 71 | continue; 72 | } 73 | low = mid; 74 | break; 75 | } 76 | if (strcmp(mavlink_message_names[low].name, name) == 0) { 77 | return mavlink_get_message_info_by_id(mavlink_message_names[low].msgid); 78 | } 79 | return NULL; 80 | } 81 | #endif // MAVLINK_USE_MESSAGE_INFO 82 | 83 | 84 | -------------------------------------------------------------------------------- /os_monitor_demo_osd.json: -------------------------------------------------------------------------------- 1 | { 2 | "format": "0.0.2", 3 | "assets_dir": "/usr/share/pixelpilot/", 4 | "widgets": [ 5 | { 6 | "type": "IconTplTextWidget", 7 | "name": "CPU load widget", 8 | "x": 10, 9 | "y": 30, 10 | "icon_path": "memory.png", 11 | "template": "CPU: %u IO: %u", 12 | "facts": [ 13 | {"name": "os_mon.cpu.load_total"}, 14 | {"name": "os_mon.cpu.load_iowait"} 15 | ] 16 | }, 17 | { 18 | "type": "IconTplTextWidget", 19 | "name": "Thermal sensors", 20 | "x": 10, 21 | "y": 60, 22 | "icon_path": "device_thermostat.png", 23 | "template": "CPU: %.0f⁰C, GPU: %.0f⁰C", 24 | "facts": [ 25 | {"name": "os_mon.temperature", 26 | "tags": {"name": "soc-thermal"}, 27 | "convert": "x / 1000"}, 28 | {"name": "os_mon.temperature", 29 | "tags": {"name": "gpu-thermal"}, 30 | "convert": "x / 1000"} 31 | ] 32 | }, 33 | { 34 | "type": "IconTplTextWidget", 35 | "name": "Battery usage widget", 36 | "__comment": "Make sure you have only one power sensor! Otherwise filter them by tags", 37 | "x": 10, 38 | "y": 90, 39 | "icon_path": "battery_android_frame_full.png", 40 | "template": "Power: %.2fV, %.2fA, %.2fW", 41 | "facts": [ 42 | {"name": "os_mon.power.voltage", 43 | "convert": "x / 1000"}, 44 | {"name": "os_mon.power.current", 45 | "convert": "x / 1000"}, 46 | {"name": "os_mon.power.power", 47 | "convert": "x / 1000000"} 48 | ] 49 | }, 50 | { 51 | "type": "BatteryCellWidget", 52 | "name": "Individual battery cell voltage", 53 | "x": 350, 54 | "y": 90, 55 | "template": "(cell: %.2fV)", 56 | "__comment1": "for LiPo set critical to 3.5; for LiIon critical to ~2.8-3", 57 | "critical_voltage": 3.5, 58 | "max_voltage": 4.2, 59 | "__comment2": "num_cells can be 'auto' - to be autodetected, 'even' - autodetect even number (2/4/6/8..) or the actual number of cells eg 4 or 6 etc", 60 | "num_cells": "even", 61 | "facts": [ 62 | {"name": "os_mon.power.voltage"} 63 | ] 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /src/gsmenu/air_telemetry.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "executor.h" 5 | #include "styles.h" 6 | 7 | #include "lvgl/lvgl.h" 8 | #include "helper.h" 9 | 10 | extern lv_group_t * default_group; 11 | 12 | #define ENTRIES 4 13 | lv_obj_t * serial; 14 | lv_obj_t * router; 15 | lv_obj_t * osd_fps; 16 | lv_obj_t * air_gs_rendering; 17 | 18 | lv_obj_t * air_telemetry_msposd_text; 19 | lv_obj_t * air_telemetry_msposd_section; 20 | 21 | void create_air_telemetry_menu(lv_obj_t * parent) { 22 | 23 | menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t) + sizeof(PageEntry) * ENTRIES); 24 | strcpy(menu_page_data->type, "air"); 25 | strcpy(menu_page_data->page, "telemetry"); 26 | menu_page_data->page_load_callback = generic_page_load_callback; 27 | menu_page_data->indev_group = lv_group_create(); 28 | menu_page_data->entry_count = 0; 29 | menu_page_data->page_entries = NULL; 30 | lv_group_set_default(menu_page_data->indev_group); 31 | lv_obj_set_user_data(parent,menu_page_data); 32 | 33 | lv_obj_t * cont; 34 | lv_obj_t * section; 35 | 36 | section = lv_menu_section_create(parent); 37 | lv_obj_add_style(section, &style_openipc_section, 0); 38 | cont = lv_menu_cont_create(section); 39 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 40 | serial = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Serial Port", "","serial",menu_page_data,false); 41 | router = create_dropdown(cont,LV_SYMBOL_SETTINGS,"Router","","router",menu_page_data,false); 42 | 43 | air_telemetry_msposd_text = create_text(parent, NULL, "MSPOSD Settings", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 44 | air_telemetry_msposd_section = lv_menu_section_create(parent); 45 | lv_obj_add_style(air_telemetry_msposd_section, &style_openipc_section, 0); 46 | cont = lv_menu_cont_create(air_telemetry_msposd_section); 47 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 48 | osd_fps = create_slider(cont,LV_SYMBOL_SETTINGS,"OSD FPS","osd_fps",menu_page_data,false,0); 49 | air_gs_rendering = create_switch(cont,LV_SYMBOL_SETTINGS,"GS Rendering","gs_rendering", menu_page_data,false); 50 | 51 | add_entry_to_menu_page(menu_page_data,"Loading serial ...", serial, reload_dropdown_value); 52 | add_entry_to_menu_page(menu_page_data,"Loading router ...", router, reload_dropdown_value); 53 | add_entry_to_menu_page(menu_page_data,"Loading osd_fps ...", osd_fps, reload_slider_value); 54 | add_entry_to_menu_page(menu_page_data,"Loading air_gs_rendering ...", air_gs_rendering, reload_switch_value); 55 | 56 | lv_group_set_default(default_group); 57 | } -------------------------------------------------------------------------------- /src/gsmenu/gs_wfbng.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "gs_wfbng.h" 5 | #include "air_wfbng.h" 6 | #include "lvgl/lvgl.h" 7 | #include "helper.h" 8 | #include "executor.h" 9 | #include "styles.h" 10 | 11 | extern lv_group_t * default_group; 12 | 13 | lv_obj_t * gs_channel; 14 | lv_obj_t * gs_search; 15 | lv_obj_t * bandwidth; 16 | lv_obj_t * gs_txpower; 17 | lv_obj_t * adaptivelink; 18 | 19 | 20 | void gs_wfbng_page_load_callback(lv_obj_t * page) 21 | { 22 | reload_dropdown_value(page,gs_channel); 23 | reload_dropdown_value(page,bandwidth); 24 | reload_slider_value(page,gs_txpower); 25 | reload_switch_value(page,adaptivelink); 26 | } 27 | 28 | void gs_wfbng_search_callback(lv_event_t * event) 29 | { 30 | run_command_and_block(event,"gsmenu.sh search channel",NULL); 31 | } 32 | 33 | 34 | void create_gs_wfbng_menu(lv_obj_t * parent) { 35 | 36 | menu_page_data_t* menu_page_data = malloc(sizeof(menu_page_data_t)); 37 | strcpy(menu_page_data->type, "gs"); 38 | strcpy(menu_page_data->page, "wfbng"); 39 | menu_page_data->page_load_callback = gs_wfbng_page_load_callback; 40 | menu_page_data->indev_group = lv_group_create(); 41 | lv_group_set_default(menu_page_data->indev_group); 42 | lv_obj_set_user_data(parent,menu_page_data); 43 | 44 | 45 | lv_obj_t * cont; 46 | lv_obj_t * section; 47 | 48 | create_text(parent, NULL, "WFB-NG", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 49 | section = lv_menu_section_create(parent); 50 | lv_obj_add_style(section, &style_openipc_section, 0); 51 | cont = lv_menu_cont_create(section); 52 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 53 | 54 | gs_channel = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Channel", "","gs_channel",menu_page_data,false); 55 | gs_search = create_button(cont, "Search"); 56 | lv_obj_add_event_cb(lv_obj_get_child_by_type(gs_search,0,&lv_button_class),gs_wfbng_search_callback,LV_EVENT_CLICKED,menu_page_data); 57 | bandwidth = create_dropdown(cont,LV_SYMBOL_SETTINGS, "bandwidth", "","bandwidth",menu_page_data,false); 58 | gs_txpower = create_slider(cont,LV_SYMBOL_SETTINGS,"TXPower (%)","txpower",menu_page_data,false,0); 59 | 60 | create_text(parent, NULL, "Adaptive Link", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 61 | section = lv_menu_section_create(parent); 62 | lv_obj_add_style(section, &style_openipc_section, 0); 63 | cont = lv_menu_cont_create(section); 64 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 65 | 66 | adaptivelink = create_switch(cont,LV_SYMBOL_SETTINGS,"Enabled","adaptivelink", menu_page_data,false); 67 | 68 | lv_group_set_default(default_group); 69 | } -------------------------------------------------------------------------------- /src/mavlink/checksum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(MAVLINK_USE_CXX_NAMESPACE) 4 | namespace mavlink { 5 | #elif defined(__cplusplus) 6 | extern "C" { 7 | #endif 8 | 9 | // Visual Studio versions before 2010 don't have stdint.h, so we just error out. 10 | #if (defined _MSC_VER) && (_MSC_VER < 1600) 11 | #error "The C-MAVLink implementation requires Visual Studio 2010 or greater" 12 | #endif 13 | 14 | #include 15 | 16 | /** 17 | * 18 | * CALCULATE THE CHECKSUM 19 | * 20 | */ 21 | 22 | #define X25_INIT_CRC 0xffff 23 | #define X25_VALIDATE_CRC 0xf0b8 24 | 25 | #ifndef HAVE_CRC_ACCUMULATE 26 | /** 27 | * @brief Accumulate the CRC16_MCRF4XX checksum by adding one char at a time. 28 | * 29 | * The checksum function adds the hash of one char at a time to the 30 | * 16 bit checksum (uint16_t). 31 | * 32 | * @param data new char to hash 33 | * @param crcAccum the already accumulated checksum 34 | **/ 35 | static inline void crc_accumulate(uint8_t data, uint16_t *crcAccum) 36 | { 37 | /*Accumulate one byte of data into the CRC*/ 38 | uint8_t tmp; 39 | 40 | tmp = data ^ (uint8_t)(*crcAccum &0xff); 41 | tmp ^= (tmp<<4); 42 | *crcAccum = (*crcAccum>>8) ^ (tmp<<8) ^ (tmp <<3) ^ (tmp>>4); 43 | } 44 | #endif 45 | 46 | 47 | /** 48 | * @brief Initialize the buffer for the MCRF4XX CRC16 49 | * 50 | * @param crcAccum the 16 bit MCRF4XX CRC16 51 | */ 52 | static inline void crc_init(uint16_t* crcAccum) 53 | { 54 | *crcAccum = X25_INIT_CRC; 55 | } 56 | 57 | 58 | /** 59 | * @brief Calculates the CRC16_MCRF4XX checksum on a byte buffer 60 | * 61 | * @param pBuffer buffer containing the byte array to hash 62 | * @param length length of the byte array 63 | * @return the checksum over the buffer bytes 64 | **/ 65 | static inline uint16_t crc_calculate(const uint8_t* pBuffer, uint16_t length) 66 | { 67 | uint16_t crcTmp; 68 | crc_init(&crcTmp); 69 | while (length--) { 70 | crc_accumulate(*pBuffer++, &crcTmp); 71 | } 72 | return crcTmp; 73 | } 74 | 75 | 76 | /** 77 | * @brief Accumulate the MCRF4XX CRC16 by adding an array of bytes 78 | * 79 | * The checksum function adds the hash of one char at a time to the 80 | * 16 bit checksum (uint16_t). 81 | * 82 | * @param data new bytes to hash 83 | * @param crcAccum the already accumulated checksum 84 | **/ 85 | static inline void crc_accumulate_buffer(uint16_t *crcAccum, const char *pBuffer, uint16_t length) 86 | { 87 | const uint8_t *p = (const uint8_t *)pBuffer; 88 | while (length--) { 89 | crc_accumulate(*p++, crcAccum); 90 | } 91 | } 92 | 93 | #if defined(MAVLINK_USE_CXX_NAMESPACE) || defined(__cplusplus) 94 | } 95 | #endif 96 | -------------------------------------------------------------------------------- /src/gsmenu/helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "ui.h" 7 | 8 | #include "../../lvgl/lvgl.h" 9 | 10 | typedef enum { 11 | LV_MENU_ITEM_BUILDER_VARIANT_1, 12 | LV_MENU_ITEM_BUILDER_VARIANT_2 13 | } lv_menu_builder_variant_t; 14 | 15 | void on_focus(lv_event_t* e); 16 | 17 | void generic_page_load_callback(lv_obj_t * page); 18 | 19 | lv_obj_t * create_text(lv_obj_t * parent, const char * icon, const char * txt, const char * parameter, menu_page_data_t* menu_page_data,bool blocking,lv_menu_builder_variant_t builder_variant); 20 | 21 | lv_obj_t * create_slider(lv_obj_t * parent, const char * icon, const char * txt, const char * parameter, menu_page_data_t* menu_page_data, bool blocking, int precision); 22 | lv_obj_t * create_switch(lv_obj_t * parent, const char * icon, const char * txt,const char * parameter, menu_page_data_t* menu_page_data,bool blocking); 23 | 24 | void dropdown_event_handler(lv_event_t * e); 25 | 26 | lv_obj_t * create_dropdown(lv_obj_t * parent, const char * icon, const char * label_txt, const char * txt,const char * parameter, menu_page_data_t* menu_page_data,bool blocking); 27 | 28 | lv_obj_t * create_checkbox(lv_obj_t * parent, const char * icon, const char * label_txt, const char * parameter, menu_page_data_t* menu_page_data,bool blocking); 29 | 30 | void generic_button_callback(lv_event_t * e); 31 | lv_obj_t * create_button(lv_obj_t * parent, const char * txt); 32 | 33 | lv_obj_t * create_backbutton(lv_obj_t * parent, const char * icon, const char * label_txt); 34 | 35 | lv_obj_t * create_textarea(lv_obj_t * parent, char * text, const char * label_txt, const char * parameter, menu_page_data_t* menu_page_data, bool password); 36 | 37 | lv_obj_t * create_spinbox(lv_obj_t * parent, const char * icon, const char * txt, int32_t min, int32_t max, 38 | int32_t val); 39 | 40 | lv_obj_t * find_first_focusable_obj(lv_obj_t * parent); 41 | void handle_sub_page_load(lv_event_t *e); 42 | char* get_paramater(lv_obj_t * page, char * param); 43 | void reload_label_value(lv_obj_t * page,lv_obj_t * parameter); 44 | void reload_switch_value(lv_obj_t * page,lv_obj_t * parameter); 45 | void reload_dropdown_value(lv_obj_t * page,lv_obj_t * parameter); 46 | void reload_checkbox_value(lv_obj_t * page,lv_obj_t * parameter); 47 | void reload_textarea_value(lv_obj_t * page,lv_obj_t * parameter); 48 | void reload_slider_value(lv_obj_t * page,lv_obj_t * parameter); 49 | void get_slider_value(lv_obj_t * parent); 50 | void get_dropdown_value(lv_obj_t * parent); 51 | void generic_back_event_handler(lv_event_t * e); 52 | 53 | const char* find_resource_file(const char* relative_path); 54 | 55 | void gsmenu_toggle_rxmode(); 56 | 57 | void add_entry_to_menu_page(menu_page_data_t *menu_page_data, const char* text, lv_obj_t* obj, ReloadFunc reload_func); 58 | void delete_menu_page_entry_by_obj(menu_page_data_t *menu_page_data, lv_obj_t* obj); 59 | void custom_actions_cb(lv_event_t * event); 60 | -------------------------------------------------------------------------------- /src/gstrtpreceiver.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by https://github.com/Consti10 on 09.04.24. 3 | // https://github.com/OpenHD/FPVue_RK3566/tree/openhd 4 | // 5 | 6 | #ifndef FPVUE_GSTRTPRECEIVER_H 7 | #define FPVUE_GSTRTPRECEIVER_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define MAX_PACKET_SIZE 4096 17 | #define RTP_HEADER_LEN 12 18 | 19 | enum class VideoCodec { 20 | UNKNOWN=0, 21 | H264, 22 | H265 23 | }; 24 | 25 | static VideoCodec video_codec(const char * str) { 26 | if (!strcmp(str, "h264")) { 27 | return VideoCodec::H264; 28 | } 29 | if (!strcmp(str, "h265")) { 30 | return VideoCodec::H265; 31 | } 32 | return VideoCodec::UNKNOWN; 33 | } 34 | 35 | /** 36 | * @brief Uses gstreamer and appsink to expose the functionality of receiving and parsing 37 | * rtp h264 and h265. 38 | */ 39 | class GstRtpReceiver { 40 | public: 41 | /** 42 | * The constructor is delayed, remember to use start_receiving() 43 | */ 44 | explicit GstRtpReceiver(int udp_port, const VideoCodec& codec); 45 | explicit GstRtpReceiver(const char *s, const VideoCodec& codec); 46 | virtual ~GstRtpReceiver(); 47 | // Depending on the codec, these are h264,h265 or mjpeg "frames" / frame buffers 48 | // The big advantage of gstreamer is that it seems to handle all those parsing quirks the best, 49 | // e.g. the frames on this cb should be easily passable to whatever decode api is available. 50 | typedef std::function> frame)> NEW_FRAME_CALLBACK; 51 | void start_receiving(NEW_FRAME_CALLBACK cb); 52 | void stop_receiving(); 53 | void switch_to_file_playback(const char* file_path); 54 | void switch_to_stream(); 55 | void fast_forward(double rate = 2.0); 56 | void fast_rewind(double rate = 2.0); 57 | void skip_duration(int64_t skip_ms); 58 | void normal_playback(); 59 | void pause(); 60 | void resume(); 61 | private: 62 | std::string construct_gstreamer_pipeline(); 63 | std::string construct_file_playback_pipeline(const char * file_path); 64 | void loop_pull_samples(); 65 | void on_new_sample(std::shared_ptr> sample); 66 | // The gstreamer pipeline 67 | GstElement * m_gst_pipeline=nullptr; 68 | NEW_FRAME_CALLBACK m_cb; 69 | VideoCodec m_video_codec; 70 | int m_port; 71 | // appsink 72 | GstElement *m_app_sink_element = nullptr; 73 | bool m_pull_samples_run; 74 | std::unique_ptr m_pull_samples_thread=nullptr; 75 | // appsrc 76 | const char* unix_socket = nullptr; 77 | int sock; 78 | bool m_read_socket_run = false; 79 | std::unique_ptr m_read_socket_thread; 80 | 81 | // dvr 82 | void set_playback_rate(double rate); 83 | double m_playback_rate = 1.0; 84 | bool m_is_paused = false; 85 | double m_pre_pause_rate = 1.0; 86 | }; 87 | 88 | 89 | #endif //FPVUE_GSTRTPRECEIVER_H 90 | -------------------------------------------------------------------------------- /src/gsmenu/styles.c: -------------------------------------------------------------------------------- 1 | #include "lvgl/lvgl.h" 2 | 3 | 4 | lv_style_t style_rootmenu; 5 | lv_style_t style_openipc; 6 | lv_style_t style_openipc_dropdown; 7 | lv_style_t style_openipc_outline; 8 | lv_style_t style_openipc_textcolor; 9 | lv_style_t style_openipc_disabled; 10 | lv_style_t style_openipc_section; 11 | lv_style_t style_openipc_dark_background; 12 | lv_style_t style_openipc_lightdark_background; 13 | 14 | 15 | int style_init(void) { 16 | lv_style_reset(&style_rootmenu); 17 | lv_style_init(&style_rootmenu); 18 | lv_style_set_bg_color(&style_rootmenu, lv_color_darken( lv_color_make(0xcd, 0xcd, 0xcd), 50)); 19 | lv_style_set_pad_top(&style_rootmenu, 0); 20 | lv_style_set_pad_bottom(&style_rootmenu, 0); 21 | lv_style_set_pad_left(&style_rootmenu, 0); 22 | lv_style_set_pad_right(&style_rootmenu, 0); 23 | lv_style_set_radius(&style_rootmenu, 0); 24 | lv_style_set_border_width(&style_rootmenu, 0); 25 | lv_style_set_border_color(&style_rootmenu, lv_color_hex(0xff4c60d8)); 26 | 27 | lv_style_reset(&style_openipc_section); 28 | lv_style_init(&style_openipc_section); 29 | lv_style_set_bg_color(&style_openipc_section, lv_color_lighten( lv_color_make(0xcd, 0xcd, 0xcd), 50)); 30 | 31 | lv_style_reset(&style_openipc_dark_background); 32 | lv_style_init(&style_openipc_dark_background); 33 | lv_style_set_bg_color(&style_openipc_dark_background, lv_color_darken( lv_color_make(0xcd, 0xcd, 0xcd), 90)); 34 | 35 | lv_style_reset(&style_openipc_lightdark_background); 36 | lv_style_init(&style_openipc_lightdark_background); 37 | lv_style_set_bg_color(&style_openipc_lightdark_background, lv_color_darken( lv_color_make(0xcd, 0xcd, 0xcd), 30)); 38 | 39 | lv_style_reset(&style_openipc); 40 | lv_style_init(&style_openipc); 41 | lv_style_set_bg_color(&style_openipc, lv_color_hex(0xff4c60d8)); 42 | lv_style_set_outline_color(&style_openipc, lv_color_hex(0xff4c60d8)); 43 | lv_style_set_arc_color(&style_openipc, lv_color_hex(0xff4c60d8)); 44 | 45 | lv_style_init(&style_openipc_dropdown); 46 | lv_style_set_bg_color(&style_openipc_dropdown, lv_color_hex(0xff4c60d8)); 47 | 48 | lv_style_reset(&style_openipc_outline); 49 | lv_style_init(&style_openipc_outline); 50 | lv_style_set_outline_color(&style_openipc_outline, lv_color_hex(0xff4c60d8)); 51 | lv_style_set_outline_width(&style_openipc_outline,7); 52 | 53 | lv_style_reset(&style_openipc_textcolor); 54 | lv_style_init(&style_openipc_textcolor); 55 | lv_style_set_text_color(&style_openipc_textcolor, lv_color_hex(0xff4c60d8)); 56 | 57 | lv_style_reset(&style_openipc_disabled); 58 | lv_style_init(&style_openipc_disabled); 59 | lv_style_set_bg_color(&style_openipc_disabled, lv_color_hex(0xff4c60d8)); 60 | lv_style_set_text_color(&style_openipc_disabled, lv_color_darken( lv_color_make(0xff, 0xff, 0xff), 50)); 61 | //lv_style_set_line_color(&style_openipc_disabled, lv_color_hex(0xffd8ce36)); 62 | //lv_style_set_border_color(&style_openipc_disabled, lv_color_hex(0xffe61212)); 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # DEBIAN_CODENAME=bookworm | bullseye 2 | DEBIAN_CODENAME=bookworm 3 | DEBIAN_HOST=https://cloud.debian.org/images/cloud/$(DEBIAN_CODENAME) 4 | DEBIAN_RELEASE=latest 5 | SKIP_SETUP=0 6 | # BUILD_TYPE=bin|debug|test 7 | BUILD_TYPE=bin 8 | 9 | ifeq ($(DEBIAN_CODENAME),bookworm) 10 | DEBIAN_SYSTEM=debian-12-generic-arm64.tar 11 | else 12 | DEBIAN_SYSTEM=debian-11-generic-arm64.tar 13 | endif 14 | 15 | OUTPUT=output 16 | DEB_VERSION=1.3.0 17 | 18 | all: 19 | cmake -B build 20 | cmake --build build -j`nproc` --target install 21 | 22 | $(DEBIAN_SYSTEM).xz: 23 | wget -nv $(DEBIAN_HOST)/$(DEBIAN_RELEASE)/$(DEBIAN_SYSTEM).xz 24 | 25 | disk.raw: $(DEBIAN_SYSTEM).xz 26 | tar -xf $(DEBIAN_SYSTEM).xz 27 | touch disk.raw 28 | 29 | .PHONY: qemu_build 30 | qemu_build: 31 | mkdir -p $(OUTPUT) 32 | make mount 33 | 34 | sudo rm $(OUTPUT)/etc/resolv.conf 35 | echo nameserver 1.1.1.1 | sudo tee -a $(OUTPUT)/etc/resolv.conf 36 | LC_ALL=en_US.UTF-8 sudo chroot $(OUTPUT) /usr/src/PixelPilot_rk/tools/container_build.sh --wipe-boot --debian-codename $(DEBIAN_CODENAME) --build-type $(BUILD_TYPE) $(if $(filter 1,$(SKIP_SETUP)),--skip-setup,) 37 | sudo chroot $(OUTPUT) /usr/src/PixelPilot_rk/tools/container_run.sh --version 38 | sudo cp $(OUTPUT)/usr/src/PixelPilot_rk/build/pixelpilot . 39 | make umount 40 | 41 | .PHONY: qemu_build_deb 42 | qemu_build_deb: 43 | mkdir -p $(OUTPUT) 44 | make mount 45 | 46 | sudo rm $(OUTPUT)/etc/resolv.conf 47 | echo nameserver 1.1.1.1 | sudo tee -a $(OUTPUT)/etc/resolv.conf 48 | LC_ALL=en_US.UTF-8 sudo chroot $(OUTPUT) /usr/src/PixelPilot_rk/tools/container_build.sh --wipe-boot --pkg-version $(DEB_VERSION) --debian-codename $(DEBIAN_CODENAME) --build-type deb 49 | make umount 50 | 51 | .PHONY: qemu_test 52 | qemu_test: 53 | mkdir -p $(OUTPUT) 54 | make mount 55 | 56 | sudo rm $(OUTPUT)/etc/resolv.conf 57 | echo nameserver 1.1.1.1 | sudo tee -a $(OUTPUT)/etc/resolv.conf 58 | LC_ALL=en_US.UTF-8 sudo chroot $(OUTPUT) /usr/src/PixelPilot_rk/tools/container_build.sh --debian-codename $(DEBIAN_CODENAME) --build-type test $(if $(filter 1,$(SKIP_SETUP)),--skip-setup,) 59 | sudo chroot $(OUTPUT) /usr/src/PixelPilot_rk/tools/container_run_test.sh 60 | make umount 61 | 62 | .PHONY: mount 63 | mount: disk.raw 64 | sudo mount `sudo losetup -P --show -f disk.raw`p1 $(OUTPUT) 65 | sudo mkdir -p $(OUTPUT)/usr/src/PixelPilot_rk 66 | sudo mount -o bind `pwd` $(OUTPUT)/usr/src/PixelPilot_rk 67 | mkdir .apt_cache || true 68 | sudo mount -o bind .apt_cache/ $(OUTPUT)/var/cache/apt 69 | 70 | .PHONY: umount 71 | umount: 72 | sudo umount $(OUTPUT)/var/cache/apt 73 | sudo umount $(OUTPUT)/usr/src/PixelPilot_rk 74 | sudo umount $(OUTPUT) 75 | sudo losetup --detach `losetup | grep disk.raw | cut -f 1 -d " "` 76 | 77 | .PHONY: clean 78 | clean: 79 | rm -f pixelpilot-rk-dbgsym_$(DEB_VERSION)*.deb 80 | rm -f pixelpilot-rk_$(DEB_VERSION)*.buildinfo pixelpilot-rk_$(DEB_VERSION)*.changes pixelpilot-rk_$(DEB_VERSION)*.deb pixelpilot-rk_*.orig.tar.gz 81 | rm -rf pixelpilot-rk_$(DEB_VERSION)/ 82 | rm -rf output 83 | rm -rf build 84 | rm -rf .apt_cache 85 | rm -f disk.raw 86 | -------------------------------------------------------------------------------- /src/gsmenu/gs_connection_checker.c: -------------------------------------------------------------------------------- 1 | #include "gs_connection_checker.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "../osd.h" 8 | 9 | extern uint64_t gtotal_tunnel_data; // global variable for easyer access in gsmenu 10 | // we missue the variable here for easyer integration 11 | // this is also used from wfb cli thread for incomming traffic 12 | 13 | #define PROC_NET_DEV "/proc/net/dev" 14 | #define MAX_LINE_LENGTH 256 15 | #define INTERFACE_PREFIX "wlx" 16 | 17 | unsigned long long get_rx_bytes(const char *interface_name) { 18 | FILE *file = fopen(PROC_NET_DEV, "r"); 19 | if (!file) { 20 | perror("Error opening /proc/net/dev"); 21 | return 0; 22 | } 23 | 24 | char line[MAX_LINE_LENGTH]; 25 | unsigned long long rx_bytes = 0; 26 | int found = 0; 27 | 28 | // Skip the first two header lines 29 | fgets(line, sizeof(line), file); 30 | fgets(line, sizeof(line), file); 31 | 32 | while (fgets(line, sizeof(line), file)) { 33 | char iface[64]; 34 | unsigned long long rb, rp, re, rd, rr, rf, rm, rmc; 35 | 36 | // Parse the line: interface_name: rx_bytes rx_packets rx_errs rx_drop rx_fifo rx_frame rx_compressed rx_multicast ... 37 | if (sscanf(line, " %63[^:]: %llu %llu %llu %llu %llu %llu %llu %llu", 38 | iface, &rb, &rp, &re, &rd, &rf, &rr, &rm, &rmc) >= 9) { 39 | 40 | // Remove trailing colon if present 41 | if (iface[strlen(iface)-1] == ':') { 42 | iface[strlen(iface)-1] = '\0'; 43 | } 44 | 45 | if (strncmp(iface, interface_name, strlen(interface_name)) == 0) { 46 | rx_bytes += rb; 47 | found = 1; 48 | // printf("Found interface %s: %llu bytes\n", iface, rb); 49 | } 50 | } 51 | } 52 | 53 | fclose(file); 54 | 55 | if (!found) { 56 | printf("No interfaces found with prefix '%s'\n", interface_name); 57 | } 58 | 59 | return rx_bytes; 60 | } 61 | 62 | // Update network status 63 | void update_network_status() { 64 | 65 | gtotal_tunnel_data = get_rx_bytes("wlx"); 66 | 67 | #ifndef USE_SIMULATOR 68 | // ugly hack to hide osd bars 69 | // ToDo: come back and use VRX side WLAN stats to populate rssi or lq 70 | osd_tag tags[2]; 71 | strcpy(tags[0].key, "id"); 72 | strcpy(tags[0].val, "video rx"); 73 | osd_publish_int_fact("wfbcli.rx.packets.all.total", tags, 1,2); // out of bound for osd config values 74 | 75 | strcpy(tags[1].key, "ant_id"); 76 | for(int i=0;i< 10;i++) { 77 | sprintf(tags[1].val, "%d", i * 256); 78 | osd_publish_int_fact("wfbcli.rx.ant_stats.rssi_avg", tags, 2,2); // out of bound for osd config values 79 | sprintf(tags[1].val, "%d", i * 256 + 1); 80 | osd_publish_int_fact("wfbcli.rx.ant_stats.rssi_avg", tags, 2,2); // out of bound for osd config values 81 | } 82 | #endif 83 | } 84 | -------------------------------------------------------------------------------- /src/gsmenu/air_aalink.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "executor.h" 5 | #include "styles.h" 6 | #include "lvgl/lvgl.h" 7 | #include "helper.h" 8 | #include "../input.h" 9 | 10 | extern lv_group_t * default_group; 11 | extern lv_indev_t * indev_drv; 12 | extern gsmenu_control_mode_t control_mode; 13 | 14 | lv_obj_t * ap_fpv_channel; 15 | lv_obj_t * txPower; 16 | lv_obj_t * mcsShift; 17 | lv_obj_t * temp; 18 | lv_obj_t * throughput; 19 | lv_obj_t * osdscale; 20 | lv_obj_t * mcssource; 21 | 22 | void create_air_aalink_menu(lv_obj_t * parent) { 23 | 24 | menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); 25 | strcpy(menu_page_data->type, "air"); 26 | strcpy(menu_page_data->page, "aalink"); 27 | menu_page_data->page_load_callback = generic_page_load_callback; 28 | menu_page_data->indev_group = lv_group_create(); 29 | menu_page_data->entry_count = 0; 30 | menu_page_data->page_entries = NULL; 31 | lv_group_set_default(menu_page_data->indev_group); 32 | lv_obj_set_user_data(parent,menu_page_data); 33 | 34 | lv_obj_t * cont; 35 | lv_obj_t * section; 36 | 37 | create_text(parent, NULL, "WLAN Settings", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 38 | section = lv_menu_section_create(parent); 39 | lv_obj_add_style(section, &style_openipc_section, 0); 40 | cont = lv_menu_cont_create(section); 41 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 42 | 43 | ap_fpv_channel = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Channel","","channel",menu_page_data,false); 44 | 45 | create_text(parent, NULL, "AALink Settings", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 46 | section = lv_menu_section_create(parent); 47 | lv_obj_add_style(section, &style_openipc_section, 0); 48 | cont = lv_menu_cont_create(section); 49 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 50 | 51 | txPower = create_slider(cont,LV_SYMBOL_SETTINGS,"VTX Power Output","SCALE_TX_POWER",menu_page_data,false,1); 52 | mcsShift = create_slider(cont,LV_SYMBOL_SETTINGS,"Link resilience (dB)","THRESH_SHIFT",menu_page_data,false,0); 53 | osdscale = create_slider(cont,LV_SYMBOL_SETTINGS,"OSD Size","OSD_SCALE",menu_page_data,false,1); 54 | throughput = create_slider(cont,LV_SYMBOL_SETTINGS,"Maximum Throughput (%)","THROUGHPUT_PCT",menu_page_data,false,0); 55 | temp = create_slider(cont,LV_SYMBOL_SETTINGS,"Temp Throttle Threshold (°C)","HIGH_TEMP",menu_page_data,false,0); 56 | mcssource = create_dropdown(cont,LV_SYMBOL_SETTINGS, "LQ Consideration","","MCS_SOURCE",menu_page_data,false); 57 | 58 | 59 | add_entry_to_menu_page(menu_page_data,"Loading Channel ...", ap_fpv_channel, reload_dropdown_value ); 60 | add_entry_to_menu_page(menu_page_data,"Loading VTX Power Output ...", txPower, reload_slider_value); 61 | add_entry_to_menu_page(menu_page_data,"Loading Link resilience (dB) ...", mcsShift, reload_slider_value); 62 | add_entry_to_menu_page(menu_page_data,"Loading OSD Size ...", osdscale, reload_slider_value); 63 | add_entry_to_menu_page(menu_page_data,"Loading Maximum Throughput ...", throughput, reload_slider_value); 64 | add_entry_to_menu_page(menu_page_data,"Loading Temp Throttle Threshold (°C)", temp, reload_slider_value); 65 | add_entry_to_menu_page(menu_page_data,"Loading LQ Consideration ...", mcssource, reload_dropdown_value); 66 | 67 | lv_group_set_default(default_group); 68 | } -------------------------------------------------------------------------------- /src/gsmenu/air_presets.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "executor.h" 5 | #include "styles.h" 6 | 7 | #include "lvgl/lvgl.h" 8 | #include "helper.h" 9 | 10 | extern lv_group_t * default_group; 11 | 12 | lv_obj_t * preset; 13 | lv_obj_t * apply; 14 | lv_obj_t * update; 15 | lv_obj_t * profile_info; 16 | 17 | void preset_event_cb(lv_event_t * e) { 18 | lv_obj_t * user_data = lv_event_get_user_data(e); 19 | lv_obj_t * dd = lv_obj_get_child_by_type(user_data,0,&lv_dropdown_class); 20 | char arg[100] = ""; 21 | lv_dropdown_get_selected_str(dd,arg,99); 22 | 23 | char command[200] = "gsmenu.sh get air presets info "; 24 | strcat(command,"\""); 25 | strcat(command,arg); 26 | strcat(command,"\""); 27 | lv_label_set_text(lv_obj_get_child_by_type(profile_info,0,&lv_label_class),run_command(command)); 28 | } 29 | 30 | void air_presets_apply_callback(lv_event_t * e) { 31 | lv_obj_t * user_data = lv_event_get_user_data(e); 32 | lv_obj_t * dd = lv_obj_get_child_by_type(user_data,0,&lv_dropdown_class); 33 | char arg[100] = ""; 34 | lv_dropdown_get_selected_str(dd,arg,99); 35 | 36 | char command[200] = "gsmenu.sh set air presets "; 37 | strcat(command,"\""); 38 | strcat(command,arg); 39 | strcat(command,"\""); 40 | run_command_and_block(e,command,NULL); 41 | } 42 | 43 | void air_presets_update_callback_callback(void) { 44 | get_dropdown_value(preset); 45 | lv_obj_send_event(lv_obj_get_child_by_type(preset,0,&lv_dropdown_class),LV_EVENT_VALUE_CHANGED,preset); 46 | } 47 | 48 | void air_presets_update_callback(lv_event_t * e) { 49 | run_command_and_block(e,"gsmenu.sh get air presets update",air_presets_update_callback_callback); 50 | } 51 | 52 | void air_presets_page_load_callback(lv_obj_t *page) { 53 | lv_obj_send_event(lv_obj_get_child_by_type(preset,0,&lv_dropdown_class),LV_EVENT_VALUE_CHANGED,preset); 54 | } 55 | 56 | void create_air_presets_menu(lv_obj_t * parent) { 57 | 58 | menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); 59 | strcpy(menu_page_data->type, "air"); 60 | strcpy(menu_page_data->page, "presets"); 61 | menu_page_data->page_load_callback = air_presets_page_load_callback; 62 | menu_page_data->indev_group = lv_group_create(); 63 | menu_page_data->entry_count = 0; 64 | menu_page_data->page_entries = NULL; 65 | lv_group_set_default(menu_page_data->indev_group); 66 | lv_obj_set_user_data(parent,menu_page_data); 67 | 68 | lv_obj_t * cont; 69 | lv_obj_t * section; 70 | 71 | section = lv_menu_section_create(parent); 72 | lv_obj_add_style(section, &style_openipc_section, 0); 73 | cont = lv_menu_cont_create(section); 74 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 75 | 76 | preset = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Preset", "","preset",menu_page_data,false); 77 | lv_obj_add_event_cb(lv_obj_get_child_by_type(preset,0,&lv_dropdown_class), preset_event_cb, LV_EVENT_VALUE_CHANGED,preset); 78 | lv_obj_remove_event_cb(lv_obj_get_child_by_type(preset,0,&lv_dropdown_class), generic_dropdown_event_cb); 79 | 80 | apply = create_button(cont, "Apply"); 81 | lv_obj_add_event_cb(lv_obj_get_child_by_type(apply,0,&lv_button_class),air_presets_apply_callback,LV_EVENT_CLICKED,preset); 82 | 83 | update = create_button(cont, "Update (Online)"); 84 | lv_obj_add_event_cb(lv_obj_get_child_by_type(update,0,&lv_button_class),air_presets_update_callback,LV_EVENT_CLICKED,NULL); 85 | 86 | cont = lv_menu_cont_create(section); 87 | profile_info = create_text(cont, NULL, "Preset Info", "preset_info", menu_page_data, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 88 | 89 | lv_group_set_default(default_group); 90 | } -------------------------------------------------------------------------------- /src/gsmenu/air_wfbng.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "lvgl/lvgl.h" 6 | #include "helper.h" 7 | #include "air_wfbng.h" 8 | #include "executor.h" 9 | #include "styles.h" 10 | 11 | extern lv_group_t * default_group; 12 | 13 | lv_obj_t * driver_txpower_override; 14 | lv_obj_t * air_channel; 15 | lv_obj_t * air_bandwidth; 16 | lv_obj_t * mcs_index; 17 | lv_obj_t * stbc; 18 | lv_obj_t * ldpc; 19 | lv_obj_t * fec_k; 20 | lv_obj_t * fec_n; 21 | lv_obj_t * mlink; 22 | lv_obj_t * air_adaptivelink; 23 | 24 | void create_air_wfbng_menu(lv_obj_t * parent) { 25 | 26 | menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); 27 | strcpy(menu_page_data->type, "air"); 28 | strcpy(menu_page_data->page, "wfbng"); 29 | menu_page_data->page_load_callback = generic_page_load_callback; 30 | menu_page_data->indev_group = lv_group_create(); 31 | menu_page_data->entry_count = 0; 32 | menu_page_data->page_entries = NULL; 33 | lv_group_set_default(menu_page_data->indev_group); 34 | lv_obj_set_user_data(parent,menu_page_data); 35 | 36 | lv_obj_t * section = lv_menu_section_create(parent); 37 | lv_obj_add_style(section, &style_openipc_section, 0); 38 | lv_obj_t * cont = lv_menu_cont_create(section); 39 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 40 | 41 | 42 | driver_txpower_override = create_dropdown(cont,LV_SYMBOL_SETTINGS,"Power", "","power",menu_page_data,false); 43 | air_channel = create_dropdown(cont,LV_SYMBOL_SETTINGS,"Frequency", "","air_channel",menu_page_data,false); 44 | air_bandwidth = create_dropdown(cont,LV_SYMBOL_SETTINGS, "bandwidth", "","width",menu_page_data,false); 45 | mcs_index = create_slider(cont,LV_SYMBOL_SETTINGS, "MCS Index","mcs_index",menu_page_data,false,0); 46 | stbc = create_switch(cont,LV_SYMBOL_SETTINGS,"STBC","stbc", menu_page_data,false); 47 | ldpc = create_switch(cont,LV_SYMBOL_SETTINGS,"LDPC","ldpc", menu_page_data,false); 48 | fec_k = create_slider(cont,LV_SYMBOL_SETTINGS, "FEC_K","fec_k",menu_page_data,false,0); 49 | fec_n = create_slider(cont,LV_SYMBOL_SETTINGS, "FEC_N","fec_n",menu_page_data,false,0); 50 | mlink = create_dropdown(cont,LV_SYMBOL_SETTINGS,"MLink", "","mlink",menu_page_data,false); 51 | 52 | create_text(parent, NULL, "Adaptive Link", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 53 | section = lv_menu_section_create(parent); 54 | lv_obj_add_style(section, &style_openipc_section, 0); 55 | cont = lv_menu_cont_create(section); 56 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 57 | 58 | air_adaptivelink = create_switch(cont,LV_SYMBOL_SETTINGS,"Enabled","adaptivelink", menu_page_data,false); 59 | 60 | add_entry_to_menu_page(menu_page_data,"Loading driver_txpower_override ...", driver_txpower_override, reload_dropdown_value); 61 | add_entry_to_menu_page(menu_page_data,"Loading air_channel ...", air_channel, reload_dropdown_value); 62 | add_entry_to_menu_page(menu_page_data,"Loading air_bandwidth ...", air_bandwidth, reload_dropdown_value); 63 | add_entry_to_menu_page(menu_page_data,"Loading mcs_index ...", mcs_index, reload_slider_value); 64 | add_entry_to_menu_page(menu_page_data,"Loading stbc ...", stbc, reload_switch_value); 65 | add_entry_to_menu_page(menu_page_data,"Loading ldpc ...", ldpc, reload_switch_value); 66 | add_entry_to_menu_page(menu_page_data,"Loading fec_k ...", fec_k, reload_slider_value); 67 | add_entry_to_menu_page(menu_page_data,"Loading fec_n ...", fec_n, reload_slider_value); 68 | add_entry_to_menu_page(menu_page_data,"Loading mlink ...", mlink, reload_dropdown_value); 69 | add_entry_to_menu_page(menu_page_data,"Loading air_adaptivelink ...", air_adaptivelink, reload_switch_value); 70 | 71 | lv_group_set_default(default_group); 72 | } 73 | -------------------------------------------------------------------------------- /src/drm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * drm.h offers a list of methods to use linux DRM and perform modeset to display video frames and the OSD. 3 | * It uses two different planes for the OSD and the video feed. 4 | * The OSD is drawn using lib cairo. 5 | */ 6 | 7 | #ifndef DRM_H 8 | #define DRM_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define OSD_BUF_COUNT 2 28 | 29 | struct drm_object { 30 | drmModeObjectProperties *props; 31 | drmModePropertyRes **props_info; 32 | uint32_t id; 33 | }; 34 | 35 | struct modeset_buf { 36 | uint32_t width; 37 | uint32_t height; 38 | uint32_t stride; 39 | uint32_t size; 40 | uint32_t handle; 41 | uint8_t *map; 42 | uint32_t fb; 43 | }; 44 | 45 | struct modeset_output { 46 | struct drm_object connector; 47 | struct drm_object crtc; 48 | drmModeCrtc *saved_crtc; 49 | 50 | drmModeModeInfo mode; 51 | uint32_t mode_blob_id; 52 | uint32_t crtc_index; 53 | int video_crtc_width; 54 | int video_crtc_height; 55 | 56 | // OSD variables 57 | drmModeAtomicReq *osd_request; 58 | unsigned int osd_buf_switch; 59 | struct modeset_buf osd_bufs[OSD_BUF_COUNT]; 60 | struct drm_object osd_plane; 61 | 62 | // Video variables 63 | drmModeAtomicReq *video_request; 64 | struct drm_object video_plane; 65 | uint32_t video_frm_width; 66 | uint32_t video_frm_height; 67 | int video_fb_x, video_fb_y, video_fb_width, video_fb_height; 68 | int video_fb_id; 69 | float video_scale_factor; 70 | 71 | // Used to calculate latency 72 | uint64_t decoding_pts; 73 | int video_poc; 74 | 75 | bool cleanup; 76 | }; 77 | 78 | 79 | int modeset_open(int *out, const char *node); 80 | 81 | int64_t get_property_value(int fd, drmModeObjectPropertiesPtr props,const char *name); 82 | 83 | void modeset_get_object_properties(int fd, struct drm_object *obj, uint32_t type); 84 | 85 | int set_drm_object_property(drmModeAtomicReq *req, struct drm_object *obj, const char *name, uint64_t value); 86 | 87 | int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, struct modeset_output *out); 88 | 89 | const char* drm_fourcc_to_string(uint32_t fourcc); 90 | 91 | int modeset_find_plane(int fd, struct modeset_output *out, struct drm_object *plane_out, uint32_t plane_format, uint32_t plane_id_override); 92 | 93 | void modeset_drm_object_fini(struct drm_object *obj); 94 | 95 | int modeset_setup_objects(int fd, struct modeset_output *out); 96 | 97 | void modeset_destroy_objects(int fd, struct modeset_output *out); 98 | 99 | int modeset_create_fb(int fd, struct modeset_buf *buf); 100 | 101 | void modeset_destroy_fb(int fd, struct modeset_buf *buf); 102 | 103 | int modeset_setup_framebuffers(int fd, drmModeConnector *conn, struct modeset_output *out); 104 | 105 | void modeset_output_destroy(int fd, struct modeset_output *out); 106 | 107 | struct modeset_output *modeset_output_create(int fd, drmModeRes *res, drmModeConnector *conn, uint16_t mode_width, uint16_t mode_height, uint32_t mode_vrefresh, uint32_t video_plane_id, uint32_t osd_plane_id, float video_scale_factor); 108 | 109 | struct modeset_output *modeset_prepare(int fd, uint16_t mode_width, uint16_t mode_height, uint32_t mode_vrefresh, uint32_t video_plane_id, uint32_t osd_plane_id, float video_scale_factor); 110 | 111 | void *modeset_print_modes(int fd); 112 | 113 | int modeset_perform_modeset(int fd, struct modeset_output *out, drmModeAtomicReq * req, struct drm_object *plane, int fb_id, uint32_t width, uint32_t height, int zpos); 114 | 115 | int modeset_atomic_prepare_commit(int fd, struct modeset_output *out, drmModeAtomicReq *req, struct drm_object *plane, int fb_id, uint32_t width, uint32_t height, int zpos); 116 | 117 | void restore_planes_zpos(int fd, struct modeset_output *output_list); 118 | 119 | void modeset_cleanup(int fd, struct modeset_output *output_list); 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /tools/container_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is intended to be used from inside a container. 4 | 5 | set -x 6 | 7 | ROOTDIR=/usr/src/PixelPilot_rk 8 | BUILD_TYPE="deb" 9 | DEBIAN_CODENAME=bookworm 10 | SKIP_SETUP=0 11 | 12 | print_help() { 13 | echo "$0 --wipe-boot --pkg-version X.Y.Z --root-dir /path/to/surces --build-type --skip-setup --help" 14 | } 15 | 16 | 17 | while [[ $# -gt 0 ]]; do 18 | case $1 in 19 | --wipe-boot) 20 | rm -r /boot/* #save space 21 | shift 22 | ;; 23 | --pkg-version) 24 | PKG_VERSION=$2 25 | shift 2 26 | ;; 27 | --root-dir) 28 | ROOTDIR=$2 29 | shift 2 30 | ;; 31 | --build-type) 32 | BUILD_TYPE=$2 33 | shift 2 34 | ;; 35 | --debian-codename) 36 | DEBIAN_CODENAME=$2 37 | shift 2 38 | ;; 39 | --skip-setup) 40 | SKIP_SETUP=1 41 | shift 42 | ;; 43 | --help) 44 | print_help 45 | exit 0 46 | ;; 47 | -*) 48 | echo "Unknown option $1" 49 | print_help 50 | exit 1 51 | ;; 52 | esac 53 | done 54 | 55 | if [ $SKIP_SETUP -lt 1 ]; then 56 | # needed for GPG tools to work 57 | if [ ! -e /dev/null ]; then 58 | mknod /dev/null c 1 3 59 | chmod 666 /dev/null 60 | fi 61 | 62 | # install radxa APT repo, see: 63 | # * https://radxa-repo.github.io/bookworm/ 64 | # * https://radxa-repo.github.io/bullseye/ 65 | # * https://radxa-repo.github.io/rk3566-bookworm/ 66 | keyring="${ROOTDIR}/keyring.deb" 67 | version="$(curl -L https://github.com/radxa-pkg/radxa-archive-keyring/releases/latest/download/VERSION)" 68 | curl -L --output "$keyring" "https://github.com/radxa-pkg/radxa-archive-keyring/releases/download/${version}/radxa-archive-keyring_${version}_all.deb" 69 | dpkg -i $keyring 70 | rm $keyring 71 | 72 | case $DEBIAN_CODENAME in 73 | bookworm) 74 | tee /etc/apt/sources.list.d/70-radxa.list <<< "deb [signed-by=/usr/share/keyrings/radxa-archive-keyring.gpg] https://radxa-repo.github.io/bookworm/ bookworm main" 75 | tee /etc/apt/sources.list.d/80-radxa-rk3566.list <<< "deb [signed-by=/usr/share/keyrings/radxa-archive-keyring.gpg] https://radxa-repo.github.io/rk3566-bookworm rk3566-bookworm main" 76 | ;; 77 | bullseye) 78 | tee /etc/apt/sources.list.d/70-radxa.list <<< "deb [signed-by=/usr/share/keyrings/radxa-archive-keyring.gpg] https://radxa-repo.github.io/bullseye/ bullseye main" 79 | tee /etc/apt/sources.list.d/80-rockchip.list <<< "deb [signed-by=/usr/share/keyrings/radxa-archive-keyring.gpg] https://radxa-repo.github.io/bullseye rockchip-bullseye main" 80 | ;; 81 | esac 82 | apt-get update 83 | 84 | case $BUILD_TYPE in 85 | deb) 86 | apt-get install -y cmake build-essential git pkg-config devscripts equivs 87 | ;; 88 | bin|debug) 89 | apt-get install -y cmake build-essential git pkg-config librockchip-mpp-dev libcairo-dev libdrm-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libspdlog-dev nlohmann-json3-dev libmsgpack-dev libgpiod-dev libyaml-cpp-dev 90 | ;; 91 | test) 92 | apt-get install -y cmake build-essential git pkg-config librockchip-mpp-dev libcairo-dev libdrm-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libspdlog-dev nlohmann-json3-dev libmsgpack-dev libgpiod-dev libyaml-cpp-dev catch2 93 | esac 94 | fi 95 | 96 | cd $ROOTDIR 97 | 98 | case $BUILD_TYPE in 99 | deb) 100 | ORIG_ARCHIVE=pixelpilot-rk_${PKG_VERSION}.orig.tar.gz 101 | SRCDIR=pixelpilot-rk_${PKG_VERSION} 102 | BUILD_DEPS_FILE=pixelpilot-rk-build-deps_${PKG_VERSION}-1_all.deb 103 | 104 | # Generate the "orig" package and then unpack it in _ directory to get a clean source tree 105 | git ls-files --recurse-submodules | tar -caf $ORIG_ARCHIVE -T- 106 | rm -rf $SRCDIR 107 | mkdir $SRCDIR 108 | tar -axf $ORIG_ARCHIVE -C $SRCDIR 109 | cd $SRCDIR 110 | 111 | # Install build dependencies 112 | mk-build-deps 113 | apt-get install -y ./$BUILD_DEPS_FILE 114 | rm pixelpilot-rk-build-deps* 115 | 116 | dpkg-buildpackage -uc -us -b 117 | 118 | #debuild -S -I 119 | ;; 120 | bin) 121 | cmake -B build 122 | cmake --build build -j`nproc` --target install 123 | ;; 124 | debug) 125 | cmake -B build -DCMAKE_BUILD_TYPE=Debug 126 | cmake --build build -j`nproc` --target install 127 | ;; 128 | test) 129 | cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=ON 130 | cmake --build build -j`nproc` 131 | esac 132 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - '*' 8 | pull_request: 9 | branches: 10 | - master 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | name: Build for ${{ matrix.debian_codename }} 16 | strategy: 17 | matrix: 18 | debian_codename: 19 | - bullseye 20 | - bookworm 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | with: 26 | submodules: recursive 27 | 28 | - name: Cache debian 29 | id: cache-debian 30 | uses: actions/cache@v4 31 | with: 32 | path: | 33 | .apt_cache 34 | debian-*-generic-arm64.tar 35 | key: ${{ matrix.debian_codename }}-build-files 36 | 37 | - name: Install deps 38 | run: | 39 | sudo apt-get update 40 | sudo apt-get install -y qemu-user-static 41 | 42 | - name: Build 43 | run: | 44 | set -x 45 | make qemu_build DEBIAN_CODENAME=${{ matrix.debian_codename }} 46 | sudo mv pixelpilot ${{ matrix.debian_codename }}_pixelpilot 47 | sudo rm -r .apt_cache/archives/{lock,partial} 48 | 49 | - name: Upload binary 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: ${{ matrix.debian_codename }}-bin 53 | path: | 54 | ${{ matrix.debian_codename }}_pixelpilot 55 | 56 | build_deb: 57 | name: Build DEB pkg for ${{ matrix.debian_codename }} 58 | strategy: 59 | matrix: 60 | debian_codename: 61 | - bullseye 62 | - bookworm 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: Checkout 66 | uses: actions/checkout@v4 67 | with: 68 | submodules: recursive 69 | 70 | - name: Cache debian 71 | id: cache-debian 72 | uses: actions/cache@v4 73 | with: 74 | path: | 75 | .apt_cache 76 | debian-*-generic-arm64.tar 77 | key: ${{ matrix.debian_codename }}-build_deb-files 78 | 79 | - name: Install deps 80 | run: | 81 | sudo apt-get update 82 | sudo apt-get install -y qemu-user-static 83 | 84 | - name: Build deb package 85 | run: | 86 | set -x 87 | make qemu_build_deb DEBIAN_CODENAME=${{ matrix.debian_codename }} 88 | for f in `ls pixelpilot-rk*_arm64.deb`; do 89 | mv $f "${{ matrix.debian_codename }}_${f}"; 90 | done 91 | sudo rm -r .apt_cache/archives/{lock,partial} 92 | 93 | - name: Upload binary 94 | uses: actions/upload-artifact@v4 95 | with: 96 | name: ${{ matrix.debian_codename }}-deb 97 | path: | 98 | ${{ matrix.debian_codename }}_pixelpilot-rk_*_arm64.deb 99 | ${{ matrix.debian_codename }}_pixelpilot-rk-dbgsym_*_arm64.deb 100 | 101 | test: 102 | name: Test (${{ matrix.debian_codename }}) 103 | runs-on: ubuntu-latest 104 | strategy: 105 | matrix: 106 | debian_codename: 107 | - bookworm 108 | steps: 109 | - name: Checkout 110 | uses: actions/checkout@v4 111 | with: 112 | submodules: recursive 113 | 114 | - name: Cache debian 115 | id: cache-debian 116 | uses: actions/cache@v4 117 | with: 118 | path: | 119 | .apt_cache 120 | debian-*-generic-arm64.tar 121 | key: ${{ matrix.debian_codename }}-test-files 122 | 123 | - name: Install deps 124 | run: | 125 | sudo apt-get update 126 | sudo apt-get install -y qemu-user-static 127 | 128 | - name: QEMU Test 129 | run: | 130 | make qemu_test DEBIAN_CODENAME=${{ matrix.debian_codename }} 131 | sudo rm -r .apt_cache/archives/{lock,partial} 132 | 133 | release: 134 | name: Release Artifacts 135 | runs-on: ubuntu-latest 136 | needs: 137 | - build 138 | - build_deb 139 | steps: 140 | - name: Checkout 141 | uses: actions/checkout@v4 142 | 143 | - name: Download Binaries 144 | uses: actions/download-artifact@v5 145 | with: 146 | run-id: ${{ github.run_id }} # Download all artifacts 147 | merge-multiple: true 148 | 149 | - name: Versioned release 150 | if: startsWith(github.ref, 'refs/tags/') 151 | uses: softprops/action-gh-release@v2 152 | with: 153 | files: | 154 | bullseye_pixelpilot 155 | bullseye_pixelpilot-rk_*_arm64.deb 156 | bullseye_pixelpilot-rk-dbgsym_*_arm64.deb 157 | bookworm_pixelpilot 158 | bookworm_pixelpilot-rk_*_arm64.deb 159 | bookworm_pixelpilot-rk-dbgsym_*_arm64.deb 160 | config_osd.json 161 | pixelpilot.yaml 162 | gsmenu.sh 163 | 164 | - name: Upload latest 165 | if: github.event_name != 'pull_request' 166 | uses: softprops/action-gh-release@v2 167 | with: 168 | tag_name: latest 169 | files: | 170 | bullseye_pixelpilot 171 | bullseye_pixelpilot-rk_*_arm64.deb 172 | bullseye_pixelpilot-rk-dbgsym_*_arm64.deb 173 | bookworm_pixelpilot 174 | bookworm_pixelpilot-rk_*_arm64.deb 175 | bookworm_pixelpilot-rk-dbgsym_*_arm64.deb 176 | config_osd.json 177 | pixelpilot.yaml 178 | gsmenu.sh 179 | -------------------------------------------------------------------------------- /src/gsmenu/gs_dvr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "lvgl/lvgl.h" 7 | #include "../main.h" 8 | #include "helper.h" 9 | #include "executor.h" 10 | #include "styles.h" 11 | #include "gs_dvrplayer.h" 12 | 13 | extern char* dvr_template; 14 | extern lv_group_t * default_group; 15 | extern lv_obj_t * dvr_screen; 16 | extern lv_indev_t * indev_drv; 17 | extern lv_group_t * dvr_group; 18 | lv_group_t * dvr_page_group; 19 | 20 | 21 | char path[256]; 22 | lv_obj_t* rec_list = NULL; 23 | 24 | // Structure to hold file information 25 | typedef struct { 26 | char* filepath; 27 | } file_data_t; 28 | 29 | // Button event handler 30 | void button_event_handler(lv_event_t* e) { 31 | lv_event_code_t code = lv_event_get_code(e); 32 | if (code == LV_EVENT_CLICKED) { 33 | lv_obj_t* btn = lv_event_get_target(e); 34 | file_data_t* data = (file_data_t*)lv_obj_get_user_data(btn); 35 | printf("Selected video: %s\n", data->filepath); 36 | #ifndef USE_SIMULATOR 37 | switch_pipeline_source("file",data->filepath); 38 | #else 39 | printf("switch_pipeline_source(\"file\",data->filepath);\n"); 40 | #endif 41 | lv_screen_load(dvr_screen); 42 | lv_indev_set_group(indev_drv,dvr_group); 43 | change_playbutton_label(LV_SYMBOL_PAUSE); 44 | } 45 | 46 | else if (code == LV_EVENT_DELETE) { 47 | lv_obj_t* btn = lv_event_get_target(e); 48 | file_data_t* data = (file_data_t*)lv_obj_get_user_data(btn); 49 | if (data) { 50 | free(data->filepath); 51 | free(data); 52 | } 53 | } 54 | } 55 | 56 | 57 | void dvr_menu_load_callback(lv_obj_t * page) { 58 | 59 | menu_page_data_t * menu_page_data = (menu_page_data_t *) lv_obj_get_user_data(page); 60 | lv_indev_set_group(indev_drv,menu_page_data->indev_group); 61 | lv_group_set_default(menu_page_data->indev_group); 62 | 63 | if (rec_list) { 64 | lv_obj_clean(rec_list); 65 | } else { 66 | rec_list = lv_list_create(page); 67 | lv_obj_set_size(rec_list, LV_PCT(100), LV_SIZE_CONTENT); // Make the list fill the parent 68 | lv_obj_center(rec_list); 69 | lv_obj_add_style(rec_list, &style_openipc, LV_PART_SELECTED | LV_STATE_CHECKED); 70 | lv_obj_add_style(rec_list, &style_openipc_section, LV_PART_MAIN); 71 | } 72 | 73 | DIR* dir; 74 | struct dirent* ent; 75 | 76 | // Open directory and add file names to the list 77 | if ((dir = opendir(path)) != NULL) { 78 | while ((ent = readdir(dir)) != NULL) { 79 | // Check for .mp4 files 80 | char* ext = strrchr(ent->d_name, '.'); 81 | if (ext && strcmp(ext, ".mp4") == 0) { 82 | // Add a new button to the list with the filename as its text 83 | lv_obj_t* btn = lv_list_add_btn(rec_list, LV_SYMBOL_VIDEO, ent->d_name); 84 | 85 | // Store the full file path in the button's user data 86 | size_t path_len = strlen(path) + strlen(ent->d_name) + 1; // +1 for '\0' 87 | file_data_t* data = malloc(sizeof(file_data_t)); 88 | data->filepath = malloc(path_len); 89 | snprintf(data->filepath, path_len, "%s%s", path, ent->d_name); // Path already has a trailing slash 90 | lv_obj_set_user_data(btn, data); 91 | 92 | lv_obj_add_event_cb(btn, button_event_handler, LV_EVENT_ALL, NULL); 93 | lv_obj_add_event_cb(btn, generic_back_event_handler, LV_EVENT_KEY,NULL); 94 | 95 | lv_obj_add_style(btn, &style_openipc, LV_PART_MAIN | LV_STATE_FOCUS_KEY); 96 | lv_obj_add_style(btn, &style_openipc_section, LV_PART_MAIN); 97 | } 98 | } 99 | closedir(dir); 100 | } else { 101 | // Handle case where directory cannot be opened 102 | lv_obj_t* btn = lv_list_add_btn(rec_list, LV_SYMBOL_WARNING, "Could not open directory"); 103 | lv_obj_add_event_cb(btn, generic_back_event_handler, LV_EVENT_KEY,NULL); 104 | lv_obj_add_style(btn, &style_openipc, LV_PART_MAIN | LV_STATE_FOCUS_KEY); 105 | } 106 | if (lv_obj_get_child_count(rec_list) == 0) { 107 | lv_obj_t* btn = lv_list_add_btn(rec_list, LV_SYMBOL_VIDEO, "No recordings found"); 108 | lv_obj_add_event_cb(btn, generic_back_event_handler, LV_EVENT_KEY,NULL); 109 | lv_obj_add_style(btn, &style_openipc, LV_PART_MAIN | LV_STATE_FOCUS_KEY); 110 | } 111 | lv_group_set_default(default_group); 112 | } 113 | 114 | void create_gs_dvr_menu(lv_obj_t * parent) { 115 | 116 | menu_page_data_t* menu_page_data = malloc(sizeof(menu_page_data_t)); 117 | strcpy(menu_page_data->type, "gs"); 118 | strcpy(menu_page_data->page, "dvr"); 119 | menu_page_data->page_load_callback = dvr_menu_load_callback; 120 | menu_page_data->indev_group = lv_group_create(); 121 | dvr_page_group = menu_page_data->indev_group; 122 | lv_group_set_default(menu_page_data->indev_group); 123 | lv_obj_set_user_data(parent,menu_page_data); 124 | 125 | lv_obj_t * section = lv_menu_section_create(parent); 126 | lv_obj_add_style(section, &style_openipc_section, 0); 127 | 128 | // Extract directory path from dvr_template 129 | if (dvr_template) { // Check if dvr_template is not NULL 130 | const char *last_slash = strrchr(dvr_template, '/'); 131 | if (last_slash) { 132 | size_t path_len = last_slash - dvr_template + 1; 133 | strncpy(path, dvr_template, path_len); 134 | path[path_len] = '\0'; // Null-terminate 135 | } else { 136 | strcpy(path, "./"); // Fallback to current directory if no slash found 137 | } 138 | printf("Extracted path: \"%s\"\n", path); 139 | 140 | dvr_menu_load_callback(parent); 141 | } else { 142 | strcpy(path, "./"); // Fallback to current directory if dvr_template is null 143 | } 144 | 145 | lv_group_set_default(default_group); 146 | } 147 | -------------------------------------------------------------------------------- /tests/test_osd.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | #include 4 | 5 | #include "../src/osd.hpp" 6 | 7 | TEST_CASE("Expression tokenizer tests", "[ExpressionTree]") 8 | { 9 | TestExpressionTree tree; 10 | 11 | REQUIRE(tree.tokenize("1") == std::vector{"1"}); 12 | REQUIRE(tree.tokenize("3 * x") == std::vector{"3", "*", "x"}); 13 | REQUIRE(tree.tokenize("3*x") == std::vector{"3", "*", "x"}); 14 | REQUIRE(tree.tokenize("(3 + 2) * x") == 15 | std::vector{"(", "3", "+", "2", ")", "*", "x"}); 16 | REQUIRE(tree.tokenize("1 + 2 / 3 * 4 - ( 5 + x )") == 17 | std::vector{"1", "+", "2", "/", "3", "*", "4", "-", 18 | "(", "5", "+", "x", ")"}); 19 | REQUIRE(tree.tokenize("1+2/3*4-(5+x)") == 20 | std::vector{"1", "+", "2", "/", "3", "*", "4", "-", 21 | "(", "5", "+", "x", ")"}); 22 | } 23 | 24 | TEST_CASE("Expression evaluation tests", "[ExpressionTree]") 25 | { 26 | TestExpressionTree tree; 27 | auto x = GENERATE(0.0, 1.0, 2.0, 3.0, 1000.0); 28 | 29 | SECTION("constant") { 30 | tree.parse("1"); 31 | REQUIRE(tree.evaluate(0) == 1.0); 32 | } 33 | SECTION("Just x") { 34 | tree.parse("x"); 35 | REQUIRE(tree.evaluate(x) == x); 36 | } 37 | SECTION("x + 1") { 38 | tree.parse("x + 1"); 39 | REQUIRE(tree.evaluate(x) == x + 1); 40 | } 41 | SECTION("(x + 2) * 3") { 42 | tree.parse("(x + 2) * 3"); 43 | REQUIRE(tree.evaluate(4) == 18.0); 44 | REQUIRE(tree.evaluate(x) == ((x + 2) * 3)); 45 | } 46 | SECTION("x + 2 * 3") { 47 | tree.parse("x + 2 * 3"); 48 | REQUIRE(tree.evaluate(4) == 10.0); 49 | REQUIRE(tree.evaluate(x) == (x + 2 * 3)); 50 | } 51 | SECTION("x + 2 * x") { 52 | tree.parse("x + 2 * x"); 53 | REQUIRE(tree.evaluate(4) == 12.0); 54 | REQUIRE(tree.evaluate(x) == (x + 2 * x)); 55 | } 56 | } 57 | 58 | TEST_CASE("TplTextWidget supports all fact data-types and float precision", "[TplTextWidget]") { 59 | // Template covers: bool, int, uint, float (default), float (0/2/4 precision), string 60 | TestTplTextWidget widget( 61 | 10, 50, 62 | "Bool: %b, Int: %i, Uint: %u, Float: %f, Float0: %.0f, Float2: %.2f, Float4: %.4f, String: %s, Undef: %s", 63 | 9 64 | ); 65 | widget.setBoolFact(0, true); // %b 66 | widget.setLongFact(1, (long)-123); // %i 67 | widget.setUlongFact(2, (ulong)456); // %u 68 | widget.setDoubleFact(3, 3.1415926535); // %f 69 | widget.setDoubleFact(4, 3.2515926535); // %.0f 70 | widget.setDoubleFact(5, 3.3415926535); // %.2f 71 | widget.setDoubleFact(6, 3.4415926535); // %.4f 72 | widget.setStringFact(7, std::string("hello")); // %s 73 | // not setting 8th so it is UNDEF 74 | 75 | std::string result = *widget.render_tpl(); 76 | 77 | REQUIRE( 78 | result == 79 | "Bool: t, Int: -123, Uint: 456, Float: 3.14, Float0: 3, Float2: 3.34, Float4: 3.4416, String: hello, Undef: ?" 80 | ); 81 | } 82 | 83 | int compare_surfaces_with_tolerance(cairo_surface_t* a, cairo_surface_t* b, int tolerance = 5, int max_report = 10) { 84 | if (!a || !b) { 85 | std::cerr << "One or both surfaces are null." << std::endl; 86 | return -1; 87 | } 88 | int width = cairo_image_surface_get_width(a); 89 | int height = cairo_image_surface_get_height(a); 90 | if (width != cairo_image_surface_get_width(b) || height != cairo_image_surface_get_height(b)) { 91 | std::cerr << "Surface size mismatch: " << width << "x" << height 92 | << " vs " << cairo_image_surface_get_width(b) << "x" 93 | << cairo_image_surface_get_height(b) << std::endl; 94 | return -1; 95 | } 96 | int stride = cairo_image_surface_get_stride(a); 97 | const unsigned char* data_a = cairo_image_surface_get_data(a); 98 | const unsigned char* data_b = cairo_image_surface_get_data(b); 99 | 100 | int diff_count = 0; 101 | for (int y = 0; y < height; ++y) { 102 | for (int x = 0; x < width; ++x) { 103 | int offset = y * stride + x * 4; 104 | uint8_t* pa = (uint8_t*)(data_a + offset); 105 | uint8_t* pb = (uint8_t*)(data_b + offset); 106 | int diff = 0; 107 | for (int c = 0; c < 4; ++c) { // ARGB 108 | diff += std::abs(pa[c] - pb[c]); 109 | } 110 | if (diff > tolerance * 4) { 111 | if (diff_count < max_report) { 112 | std::cerr << "Pixel difference at (" << x << "," << y << "): " 113 | << "A=(" << (int)pa[0] << "," << (int)pa[1] << "," << (int)pa[2] << "," << (int)pa[3] << ") " 114 | << "B=(" << (int)pb[0] << "," << (int)pb[1] << "," << (int)pb[2] << "," << (int)pb[3] << ") " 115 | << "Total diff: " << diff << std::endl; 116 | } 117 | ++diff_count; 118 | } 119 | } 120 | } 121 | if (diff_count > max_report) { 122 | std::cerr << "(... " << (diff_count - max_report) << " more differences ...)" << std::endl; 123 | } 124 | if (diff_count == 0) { 125 | std::cout << "Surfaces are visually identical (within tolerance)." << std::endl; 126 | } else { 127 | std::cerr << "Total differing pixels (with tolerance): " << diff_count << std::endl; 128 | } 129 | return diff_count; 130 | } 131 | 132 | TEST_CASE("Basic TplTextWidget rendering", "[TplTextWidget]") 133 | { 134 | // 1. Render widget to template 135 | cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 300, 100); 136 | cairo_t* cr = cairo_create(surface); 137 | cairo_select_font_face(cr, "Arial", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 138 | cairo_set_font_size(cr, 20); 139 | // XXX: do we need to clear? 140 | 141 | TestTplTextWidget widget(10, 50, "Hello %u", 1); 142 | widget.setUlongFact(0, 42); 143 | widget.draw(cr); 144 | cairo_fill(cr); 145 | cairo_destroy(cr); 146 | 147 | // Load reference 148 | cairo_surface_t *ref_surface = cairo_image_surface_create_from_png( 149 | "tests/files/basic_tpl_text_widget.png"); 150 | REQUIRE(cairo_surface_status(ref_surface) == CAIRO_STATUS_SUCCESS); 151 | 152 | // Compare 153 | REQUIRE(compare_surfaces_with_tolerance(surface, ref_surface, 5, 10) == 0); 154 | 155 | // Cleanup 156 | cairo_surface_destroy(surface); 157 | cairo_surface_destroy(ref_surface); 158 | } 159 | -------------------------------------------------------------------------------- /src/gsmenu/gs_wifi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "ui.h" 6 | #include "../input.h" 7 | #include "helper.h" 8 | #include "styles.h" 9 | #include "executor.h" 10 | #include "gs_wifi.h" 11 | 12 | static void scan_wifi_event_handler(lv_event_t * e); 13 | static void connect_wifi_event_handler(lv_event_t * e); 14 | static void disconnect_wifi_event_handler(lv_event_t * e); 15 | static void ta_event_cb(lv_event_t * e); 16 | 17 | extern lv_obj_t * menu; 18 | extern gsmenu_control_mode_t control_mode; 19 | extern lv_group_t * default_group; 20 | 21 | lv_obj_t * ssid; 22 | lv_obj_t * password; 23 | lv_obj_t * wlan; 24 | lv_obj_t * hotspot; 25 | lv_obj_t * ipinfo; 26 | 27 | void wifi_page_load_callback(lv_obj_t * page) 28 | { 29 | reload_switch_value(page,hotspot); 30 | reload_switch_value(page,wlan); 31 | reload_textarea_value(page,ssid); 32 | reload_textarea_value(page,password); 33 | reload_label_value(page,ipinfo); 34 | } 35 | 36 | static void btn_event_cb(lv_event_t * e) 37 | { 38 | lv_event_code_t code = lv_event_get_code(e); 39 | lv_obj_t * btn = lv_event_get_target(e); 40 | lv_obj_t * kb = lv_event_get_user_data(e); 41 | lv_obj_t * target_ta = lv_obj_get_user_data(btn); // Retrieve associated textarea 42 | 43 | if(code == LV_EVENT_CLICKED) { 44 | if (target_ta) { 45 | lv_keyboard_set_textarea(kb, target_ta); 46 | lv_obj_remove_flag(kb, LV_OBJ_FLAG_HIDDEN); 47 | lv_obj_scroll_to_view_recursive(target_ta, LV_ANIM_OFF); 48 | lv_indev_wait_release(lv_event_get_param(e)); 49 | lv_group_focus_obj(kb); 50 | } 51 | } 52 | } 53 | 54 | static void kb_event_cb(lv_event_t * e) 55 | { 56 | lv_event_code_t code = lv_event_get_code(e); 57 | lv_obj_t * ta = lv_event_get_target(e); 58 | lv_obj_t * kb = lv_event_get_user_data(e); 59 | 60 | if (code == LV_EVENT_FOCUSED) { 61 | control_mode = GSMENU_CONTROL_MODE_KEYBOARD; 62 | } 63 | else if (code == LV_EVENT_DEFOCUSED) 64 | { 65 | control_mode = GSMENU_CONTROL_MODE_NAV; 66 | } 67 | else if(code == LV_EVENT_READY || code == LV_EVENT_CANCEL) { 68 | lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN); 69 | lv_indev_reset(NULL, ta); /*To forget the last clicked object to make it focusable again*/ 70 | lv_group_focus_obj(lv_obj_get_child_by_type(lv_obj_get_parent(lv_keyboard_get_textarea(kb)),0,&lv_button_class)); 71 | lv_obj_update_layout(lv_obj_get_parent(kb)); 72 | } 73 | } 74 | 75 | static void interlocking_switch_callback(lv_event_t * e) { 76 | lv_obj_t * target = lv_event_get_target(e); 77 | lv_obj_t * parent = lv_event_get_user_data(e); 78 | 79 | if(lv_obj_has_state(target, LV_STATE_CHECKED)) { 80 | if (parent == hotspot) { 81 | lv_obj_add_flag(wlan, LV_OBJ_FLAG_HIDDEN); 82 | lv_obj_add_flag(ssid, LV_OBJ_FLAG_HIDDEN); 83 | lv_obj_add_flag(password, LV_OBJ_FLAG_HIDDEN); 84 | lv_obj_remove_state(lv_obj_get_child_by_type(wlan,0,&lv_switch_class),LV_STATE_CHECKED); 85 | } else { 86 | lv_obj_remove_state(lv_obj_get_child_by_type(hotspot,0,&lv_switch_class),LV_STATE_CHECKED); 87 | } 88 | } else { 89 | if (parent == hotspot) { 90 | lv_obj_clear_flag(wlan, LV_OBJ_FLAG_HIDDEN); 91 | lv_obj_clear_flag(ssid, LV_OBJ_FLAG_HIDDEN); 92 | lv_obj_clear_flag(password, LV_OBJ_FLAG_HIDDEN); 93 | } 94 | } 95 | } 96 | 97 | void create_wifi_menu(lv_obj_t * parent) { 98 | 99 | menu_page_data_t* menu_page_data = malloc(sizeof(menu_page_data_t)); 100 | strcpy(menu_page_data->type, "gs"); 101 | strcpy(menu_page_data->page, "wifi"); 102 | menu_page_data->page_load_callback = wifi_page_load_callback; 103 | menu_page_data->indev_group = lv_group_create(); 104 | lv_group_set_default(menu_page_data->indev_group); 105 | lv_obj_set_user_data(parent,menu_page_data); 106 | 107 | lv_obj_t * section = lv_menu_section_create(parent); 108 | lv_obj_add_style(section, &style_openipc_section, 0); 109 | 110 | lv_obj_t * cont = lv_menu_cont_create(section); 111 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 112 | 113 | hotspot = create_switch(cont,NULL,"Hotspot","hotspot", menu_page_data,false); 114 | lv_obj_add_event_cb(lv_obj_get_child_by_type(hotspot,0,&lv_switch_class), interlocking_switch_callback, LV_EVENT_VALUE_CHANGED, hotspot); 115 | 116 | 117 | ssid = create_textarea(cont, "", "SSID", "ssid", menu_page_data, false); 118 | password = create_textarea(cont, "", "Password", "password", menu_page_data, true); 119 | 120 | wlan = create_switch(cont,NULL,"Connected","wlan", menu_page_data,false); 121 | thread_data_t* data = lv_obj_get_user_data(lv_obj_get_child_by_type(wlan,0,&lv_switch_class)); 122 | data->arguments[0] = lv_obj_get_child_by_type(ssid,0,&lv_textarea_class); 123 | data->arguments[1] = lv_obj_get_child_by_type(password,0,&lv_textarea_class); 124 | lv_obj_add_event_cb(lv_obj_get_child_by_type(wlan,0,&lv_switch_class), interlocking_switch_callback, LV_EVENT_VALUE_CHANGED, wlan); 125 | 126 | 127 | lv_obj_t * kb = lv_keyboard_create(lv_obj_get_parent(section)); 128 | lv_obj_add_flag(kb, LV_OBJ_FLAG_SCROLL_ON_FOCUS); 129 | lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN); 130 | lv_obj_add_style(kb, &style_openipc_outline, LV_PART_MAIN | LV_STATE_FOCUS_KEY); 131 | lv_obj_add_style(kb, &style_openipc, LV_PART_ITEMS| LV_STATE_FOCUS_KEY); 132 | lv_obj_add_style(kb, &style_openipc_dark_background, LV_PART_ITEMS| LV_STATE_DEFAULT); 133 | lv_obj_add_style(kb, &style_openipc_textcolor, LV_PART_ITEMS| LV_STATE_FOCUS_KEY); 134 | lv_obj_add_style(kb, &style_openipc_lightdark_background, LV_PART_MAIN | LV_STATE_DEFAULT); 135 | 136 | lv_obj_add_event_cb(lv_obj_get_child_by_type(ssid,0,&lv_button_class), btn_event_cb, LV_EVENT_ALL, kb); 137 | lv_obj_add_event_cb(lv_obj_get_child_by_type(password,0,&lv_button_class), btn_event_cb, LV_EVENT_ALL, kb); 138 | lv_obj_add_event_cb(kb, kb_event_cb, LV_EVENT_ALL,kb); 139 | lv_keyboard_set_textarea(kb, NULL); 140 | 141 | create_text(parent, NULL, "Network", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 142 | section = lv_menu_section_create(parent); 143 | lv_obj_add_style(section, &style_openipc_section, 0); 144 | 145 | cont = lv_menu_cont_create(section); 146 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 147 | 148 | ipinfo = create_text(cont, LV_SYMBOL_SETTINGS, "Network", "IP", menu_page_data, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 149 | lv_obj_t * ipinfo_label = lv_obj_get_child_by_type(ipinfo,0, &lv_label_class); 150 | lv_group_add_obj(menu_page_data->indev_group,ipinfo_label); 151 | 152 | 153 | lv_group_set_default(default_group); 154 | } 155 | -------------------------------------------------------------------------------- /src/WiFiRSSIMonitor.cpp: -------------------------------------------------------------------------------- 1 | #include "WiFiRSSIMonitor.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "spdlog/spdlog.h" 10 | 11 | extern "C" { 12 | #include "osd.h" 13 | } 14 | 15 | namespace fs = std::filesystem; 16 | 17 | // Constructor implementation 18 | WiFiRSSIMonitor::WiFiRSSIMonitor() : base_path_("/proc/net/rtl88x2eu") {} 19 | 20 | // WiFiStats constructor implementation 21 | WiFiRSSIMonitor::WiFiStats::WiFiStats() : rssi_a(0), rssi_b(0), rssi_min(0), rssi_percent(0), is_linked(false) {} 22 | 23 | void WiFiRSSIMonitor::run() { 24 | 25 | if (!fs::exists(base_path_)) { 26 | spdlog::error("RTL88x2eu proc path not found: {}", base_path_); 27 | return; 28 | } 29 | 30 | // Initialize batch - estimate 5 facts per interface 31 | void* batch = osd_batch_init(20); 32 | 33 | // Find all WiFi interfaces and collect their stats 34 | for (const auto& entry : fs::directory_iterator(base_path_)) { 35 | if (!entry.is_directory()) continue; 36 | 37 | std::string interface_name = entry.path().filename(); 38 | std::string debug_file = entry.path() / "trx_info_debug"; 39 | 40 | if (fs::exists(debug_file)) { 41 | WiFiStats stats = parse_interface_stats(debug_file); 42 | add_interface_stats_to_batch(batch, interface_name, stats); 43 | } 44 | } 45 | 46 | // Publish all collected facts 47 | osd_publish_batch(batch); 48 | } 49 | 50 | WiFiRSSIMonitor::WiFiStats WiFiRSSIMonitor::parse_interface_stats(const std::string& file_path) { 51 | 52 | WiFiStats stats; 53 | std::ifstream file(file_path); 54 | 55 | if (!file.is_open()) { 56 | return stats; 57 | } 58 | 59 | std::string line; 60 | while (std::getline(file, line)) { 61 | // Parse RSSI A and B 62 | if (line.find("rssi_a =") != std::string::npos) { 63 | std::regex rssi_ab_regex(R"(rssi_a\s*=\s*(\d+)\(%\),\s*rssi_b\s*=\s*(\d+)\(%\))"); 64 | std::smatch match; 65 | if (std::regex_search(line, match, rssi_ab_regex) && match.size() == 3) { 66 | stats.rssi_a = std::stoi(match[1]); 67 | stats.rssi_b = std::stoi(match[2]); 68 | } 69 | } 70 | // Parse RSSI percentage 71 | else if (line.find("rssi :") != std::string::npos) { 72 | std::regex rssi_regex(R"(rssi\s*:\s*(\d+)\s*\(\%\))"); 73 | std::smatch match; 74 | if (std::regex_search(line, match, rssi_regex) && match.size() > 1) { 75 | stats.rssi_percent = std::stoi(match[1]); 76 | } 77 | } 78 | else if (line.find("is_linked =") != std::string::npos) { 79 | std::regex linked_regex(R"(is_linked\s*=\s*(\d+))"); 80 | std::smatch match; 81 | if (std::regex_search(line, match, linked_regex) && match.size() > 1) { 82 | stats.is_linked = (std::stoi(match[1]) == 1); 83 | } 84 | } 85 | } 86 | 87 | file.close(); 88 | return stats; 89 | } 90 | 91 | void WiFiRSSIMonitor::add_interface_stats_to_batch(void* batch, const std::string& interface_name, const WiFiStats& stats) { 92 | if (!stats.is_linked) { 93 | return; // Don't publish stats for disconnected interfaces 94 | } 95 | 96 | // Prepare common tags 97 | osd_tag interface_tag; 98 | strncpy(interface_tag.key, "interface", TAG_MAX_LEN - 1); 99 | strncpy(interface_tag.val, interface_name.c_str(), TAG_MAX_LEN - 1); 100 | interface_tag.key[TAG_MAX_LEN - 1] = '\0'; 101 | interface_tag.val[TAG_MAX_LEN - 1] = '\0'; 102 | 103 | // Publish RSSI A 104 | add_rssi_fact_to_batch(batch, "rssi_a", stats.rssi_a, &interface_tag); 105 | 106 | // Publish RSSI B 107 | add_rssi_fact_to_batch(batch, "rssi_b", stats.rssi_b, &interface_tag); 108 | 109 | // Publish RSSI Overall Percentage 110 | add_rssi_fact_to_batch(batch, "rssi_percent", stats.rssi_percent, &interface_tag); 111 | 112 | // Publish connection status 113 | add_rssi_fact_to_batch(batch, "connected", 1, &interface_tag); 114 | } 115 | 116 | void WiFiRSSIMonitor::add_rssi_fact_to_batch(void* batch, const std::string& rssi_type, int value, osd_tag* interface_tag) { 117 | osd_tag tags[2]; 118 | 119 | // Copy interface tag 120 | memcpy(&tags[0], interface_tag, sizeof(osd_tag)); 121 | 122 | // Add type tag 123 | strncpy(tags[1].key, "type", TAG_MAX_LEN - 1); 124 | strncpy(tags[1].val, rssi_type.c_str(), TAG_MAX_LEN - 1); 125 | tags[1].key[TAG_MAX_LEN - 1] = '\0'; 126 | tags[1].val[TAG_MAX_LEN - 1] = '\0'; 127 | 128 | // Add fact to batch 129 | std::string fact_name = "os_mon.wifi.rssi"; 130 | osd_add_int_fact(batch, fact_name.c_str(), tags, 2, value); 131 | } 132 | 133 | void WiFiRSSIMonitor::publish_reset() { 134 | if (!fs::exists(base_path_)) { 135 | spdlog::warn("RTL88x2eu proc path not found for reset: {}", base_path_); 136 | return; 137 | } 138 | 139 | // Initialize batch - estimate 5 facts per interface 140 | void* batch = osd_batch_init(20); 141 | 142 | // Find all WiFi interfaces and publish reset values 143 | for (const auto& entry : fs::directory_iterator(base_path_)) { 144 | if (!entry.is_directory()) continue; 145 | 146 | std::string interface_name = entry.path().filename(); 147 | publish_interface_reset(batch, interface_name); 148 | } 149 | 150 | // Publish all reset facts 151 | osd_publish_batch(batch); 152 | 153 | spdlog::debug("Published WiFi RSSI reset values for all interfaces"); 154 | } 155 | 156 | void WiFiRSSIMonitor::publish_interface_reset(void* batch, const std::string& interface_name) { 157 | // Prepare common tags 158 | osd_tag interface_tag; 159 | strncpy(interface_tag.key, "interface", TAG_MAX_LEN - 1); 160 | strncpy(interface_tag.val, interface_name.c_str(), TAG_MAX_LEN - 1); 161 | interface_tag.key[TAG_MAX_LEN - 1] = '\0'; 162 | interface_tag.val[TAG_MAX_LEN - 1] = '\0'; 163 | 164 | // Publish all RSSI values as -1 (reset/error value) 165 | add_rssi_fact_to_batch(batch, "rssi_a", -1, &interface_tag); 166 | add_rssi_fact_to_batch(batch, "rssi_b", -1, &interface_tag); 167 | add_rssi_fact_to_batch(batch, "rssi_min", -1, &interface_tag); 168 | add_rssi_fact_to_batch(batch, "rssi_percent", -1, &interface_tag); 169 | add_rssi_fact_to_batch(batch, "connected", 0, &interface_tag); // 0 = disconnected 170 | } 171 | 172 | // C-callable function implementations 173 | extern "C" { 174 | 175 | void wifi_rssi_monitor_reset(void) { 176 | static WiFiRSSIMonitor monitor; 177 | monitor.publish_reset(); 178 | } 179 | 180 | } // extern "C" -------------------------------------------------------------------------------- /src/gsmenu/gs_system.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../main.h" 5 | #include "gs_system.h" 6 | #include "lvgl/lvgl.h" 7 | #include "helper.h" 8 | #include "executor.h" 9 | #include "styles.h" 10 | 11 | extern lv_group_t * default_group; 12 | enum RXMode RXMODE = WFB; 13 | 14 | lv_obj_t * rx_mode; 15 | lv_obj_t * gs_rendering; 16 | lv_obj_t * connector; 17 | lv_obj_t * resolution; 18 | lv_obj_t * rec_enabled; 19 | lv_obj_t * rec_fps; 20 | lv_obj_t * vsync_disabled; 21 | lv_obj_t * video_scale; 22 | 23 | extern lv_obj_t * ap_fpv_ssid; 24 | extern lv_obj_t * ap_fpv_password; 25 | 26 | typedef struct Dvr* Dvr; // Forward declaration 27 | void dvr_start_recording(Dvr* dvr); 28 | void dvr_stop_recording(Dvr* dvr); 29 | void dvr_set_video_framerate(Dvr* dvr,int f); 30 | extern Dvr *dvr; 31 | extern int dvr_enabled; 32 | extern bool disable_vsync; 33 | 34 | void gs_system_page_load_callback(lv_obj_t * page) 35 | { 36 | 37 | reload_switch_value(page,gs_rendering); 38 | reload_dropdown_value(page,rx_mode); 39 | RXMODE = lv_dropdown_get_selected(lv_obj_get_child_by_type(rx_mode,0,&lv_dropdown_class)); 40 | reload_dropdown_value(page,connector); 41 | reload_dropdown_value(page,resolution); 42 | reload_dropdown_value(page,rec_fps); 43 | reload_slider_value(page, video_scale); 44 | if (dvr_enabled) lv_obj_add_state(lv_obj_get_child_by_type(rec_enabled,0,&lv_switch_class), LV_STATE_CHECKED); 45 | else lv_obj_clear_state(lv_obj_get_child_by_type(rec_enabled,0,&lv_switch_class), LV_STATE_CHECKED); 46 | 47 | if (disable_vsync) lv_obj_add_state(lv_obj_get_child_by_type(vsync_disabled,0,&lv_switch_class), LV_STATE_CHECKED); 48 | else lv_obj_clear_state(lv_obj_get_child_by_type(vsync_disabled,0,&lv_switch_class), LV_STATE_CHECKED); 49 | 50 | } 51 | 52 | void toggle_rec_enabled() 53 | { 54 | lv_obj_t * rec_switch = lv_obj_get_child_by_type(rec_enabled,0,&lv_switch_class); 55 | if (lv_obj_has_state(rec_switch, LV_STATE_CHECKED)) lv_obj_clear_state(rec_switch, LV_STATE_CHECKED); 56 | else lv_obj_add_state(rec_switch, LV_STATE_CHECKED); 57 | lv_obj_send_event(rec_switch, LV_EVENT_VALUE_CHANGED, NULL); 58 | } 59 | 60 | void rec_enabled_cb(lv_event_t *e) { 61 | lv_event_code_t event = lv_event_get_code(e); 62 | if (event == LV_EVENT_VALUE_CHANGED) { 63 | lv_obj_t *ta = lv_event_get_target(e); 64 | if (lv_obj_has_state(ta, LV_STATE_CHECKED)) { 65 | #ifndef USE_SIMULATOR 66 | dvr_start_recording(dvr); 67 | #else 68 | printf("dvr_start_recording(dvr);\n"); 69 | #endif 70 | } else { 71 | #ifndef USE_SIMULATOR 72 | dvr_stop_recording(dvr); 73 | #else 74 | printf("dvr_stop_recording(dvr);\n"); 75 | #endif 76 | } 77 | } 78 | } 79 | 80 | 81 | void disable_vsync_cb(lv_event_t *e) { 82 | lv_event_code_t event = lv_event_get_code(e); 83 | if (event == LV_EVENT_VALUE_CHANGED) { 84 | lv_obj_t *ta = lv_event_get_target(e); 85 | disable_vsync = lv_obj_has_state(ta, LV_STATE_CHECKED); 86 | } 87 | } 88 | 89 | void rec_fps_cb(lv_event_t *e) { 90 | lv_event_code_t event = lv_event_get_code(e); 91 | if (event == LV_EVENT_VALUE_CHANGED) { 92 | lv_obj_t *ta = lv_event_get_target(e); 93 | char val[100] = ""; 94 | lv_dropdown_get_selected_str(ta,val,99); 95 | int fps = atoi(val); 96 | #ifndef USE_SIMULATOR 97 | dvr_set_video_framerate(dvr,fps); 98 | #else 99 | printf("dvr_set_video_framerate(dvr,%i);\n",fps); 100 | #endif 101 | } 102 | } 103 | 104 | void rx_mode_cb(lv_event_t *e) { 105 | lv_event_code_t event = lv_event_get_code(e); 106 | if (event == LV_EVENT_VALUE_CHANGED) { 107 | lv_obj_t *ta = lv_event_get_target(e); 108 | RXMODE = lv_dropdown_get_selected(ta); 109 | gsmenu_toggle_rxmode(); 110 | } 111 | } 112 | 113 | void create_gs_system_menu(lv_obj_t * parent) { 114 | 115 | menu_page_data_t* menu_page_data = malloc(sizeof(menu_page_data_t)); 116 | strcpy(menu_page_data->type, "gs"); 117 | strcpy(menu_page_data->page, "system"); 118 | menu_page_data->page_load_callback = gs_system_page_load_callback; 119 | menu_page_data->indev_group = lv_group_create(); 120 | lv_group_set_default(menu_page_data->indev_group); 121 | lv_obj_set_user_data(parent,menu_page_data); 122 | 123 | lv_obj_t * cont; 124 | lv_obj_t * label; 125 | lv_obj_t * section; 126 | lv_obj_t * obj; 127 | 128 | create_text(parent, NULL, "General", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 129 | section = lv_menu_section_create(parent); 130 | lv_obj_add_style(section, &style_openipc_section, 0); 131 | cont = lv_menu_cont_create(section); 132 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 133 | 134 | rx_mode = create_dropdown(cont,LV_SYMBOL_SETTINGS, "RX Mode","","rx_mode",menu_page_data,false); 135 | lv_obj_add_event_cb(lv_obj_get_child_by_type(rx_mode,0,&lv_dropdown_class), rx_mode_cb, LV_EVENT_VALUE_CHANGED,NULL); 136 | thread_data_t* data = lv_obj_get_user_data(lv_obj_get_child_by_type(rx_mode,0,&lv_dropdown_class)); 137 | data->arguments[0] = lv_obj_get_child_by_type(ap_fpv_ssid,0,&lv_textarea_class); 138 | data->arguments[1] = lv_obj_get_child_by_type(ap_fpv_password,0,&lv_textarea_class); 139 | reload_dropdown_value(parent,rx_mode); 140 | RXMODE = lv_dropdown_get_selected(lv_obj_get_child_by_type(rx_mode,0,&lv_dropdown_class)); 141 | 142 | gs_rendering = create_switch(cont,LV_SYMBOL_SETTINGS,"GS Rendering","gs_rendering", menu_page_data,false); 143 | connector = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Connector","","connector",menu_page_data,false); 144 | resolution = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Resolution","","resolution",menu_page_data,false); 145 | video_scale = create_slider(cont, LV_SYMBOL_SETTINGS, "Video scale factor", "video_scale", menu_page_data, false, 2); 146 | 147 | vsync_disabled = create_switch(cont,LV_SYMBOL_SETTINGS,"Disable VSYNC","disable_vsync", NULL,false); 148 | lv_obj_add_event_cb(lv_obj_get_child_by_type(vsync_disabled,0,&lv_switch_class), disable_vsync_cb, LV_EVENT_VALUE_CHANGED,NULL); 149 | 150 | create_text(parent, NULL, "Recording", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 151 | section = lv_menu_section_create(parent); 152 | lv_obj_add_style(section, &style_openipc_section, 0); 153 | cont = lv_menu_cont_create(section); 154 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 155 | 156 | rec_enabled = create_switch(cont,LV_SYMBOL_SETTINGS,"Enabled","rec_enabled", menu_page_data, true); 157 | lv_obj_add_event_cb(lv_obj_get_child_by_type(rec_enabled,0,&lv_switch_class), rec_enabled_cb, LV_EVENT_VALUE_CHANGED,NULL); 158 | 159 | rec_fps = create_dropdown(section,LV_SYMBOL_SETTINGS, "Recording FPS", "","rec_fps",menu_page_data,false); 160 | lv_obj_add_event_cb(lv_obj_get_child_by_type(rec_fps,0,&lv_dropdown_class), rec_fps_cb, LV_EVENT_VALUE_CHANGED,NULL); 161 | 162 | lv_group_set_default(default_group); 163 | } -------------------------------------------------------------------------------- /config_osd.json: -------------------------------------------------------------------------------- 1 | { 2 | "format": "0.0.1", 3 | "assets_dir": "/usr/share/pixelpilot/", 4 | "widgets": [ 5 | { 6 | "type": "IconSelectorWidget", 7 | "name": "RSSI of antenna 1", 8 | "x": -96, 9 | "y": 0, 10 | "facts": [ 11 | {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "0", "id": "video rx"}} 12 | ], 13 | "ranges_and_icons": [ 14 | {"range": [-49, 1], "icon_path": "signal1.png"}, 15 | {"range": [-59, -50],"icon_path": "signal2.png"}, 16 | {"range": [-69, -60],"icon_path": "signal3.png"}, 17 | {"range": [-79, -70],"icon_path": "signal4.png"}, 18 | {"range": [-130, -80],"icon_path": "signal5.png"} 19 | ] 20 | }, 21 | { 22 | "type": "IconSelectorWidget", 23 | "name": "RSSI of antenna 2", 24 | "x": -192, 25 | "y": 0, 26 | "facts": [ 27 | {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "1", "id": "video rx"}} 28 | ], 29 | "ranges_and_icons": [ 30 | {"range": [-49, 1], "icon_path": "signal1.png"}, 31 | {"range": [-59, -50],"icon_path": "signal2.png"}, 32 | {"range": [-69, -60],"icon_path": "signal3.png"}, 33 | {"range": [-79, -70],"icon_path": "signal4.png"}, 34 | {"range": [-130, -80],"icon_path": "signal5.png"} 35 | ], 36 | "calculation": "wfbcli_rx_ant_stats_rssi_avg_ant_id_1_id_video_rx" 37 | }, 38 | { 39 | "type": "IconSelectorWidget", 40 | "name": "RSSI of antenna 3", 41 | "x": -288, 42 | "y": 0, 43 | "facts": [ 44 | {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "256", "id": "video rx"}} 45 | ], 46 | "ranges_and_icons": [ 47 | {"range": [-49, 1], "icon_path": "signal1.png"}, 48 | {"range": [-59, -50],"icon_path": "signal2.png"}, 49 | {"range": [-69, -60],"icon_path": "signal3.png"}, 50 | {"range": [-79, -70],"icon_path": "signal4.png"}, 51 | {"range": [-130, -80],"icon_path": "signal5.png"} 52 | ] 53 | }, 54 | { 55 | "type": "IconSelectorWidget", 56 | "name": "RSSI of antenna 4", 57 | "x": -384, 58 | "y": 0, 59 | "facts": [ 60 | {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "257", "id": "video rx"}} 61 | ], 62 | "ranges_and_icons": [ 63 | {"range": [-49, 1], "icon_path": "signal1.png"}, 64 | {"range": [-59, -50],"icon_path": "signal2.png"}, 65 | {"range": [-69, -60],"icon_path": "signal3.png"}, 66 | {"range": [-79, -70],"icon_path": "signal4.png"}, 67 | {"range": [-130, -80],"icon_path": "signal5.png"} 68 | ] 69 | }, 70 | { 71 | "type": "IconSelectorWidget", 72 | "name": "RSSI of antenna 5", 73 | "x": -480, 74 | "y": 0, 75 | "facts": [ 76 | {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "512", "id": "video rx"}} 77 | ], 78 | "ranges_and_icons": [ 79 | {"range": [-49, 1], "icon_path": "signal1.png"}, 80 | {"range": [-59, -50],"icon_path": "signal2.png"}, 81 | {"range": [-69, -60],"icon_path": "signal3.png"}, 82 | {"range": [-79, -70],"icon_path": "signal4.png"}, 83 | {"range": [-130, -80],"icon_path": "signal5.png"} 84 | ] 85 | }, 86 | { 87 | "type": "IconSelectorWidget", 88 | "name": "RSSI of antenna 6", 89 | "x": -576, 90 | "y": 0, 91 | "facts": [ 92 | {"name": "wfbcli.rx.ant_stats.rssi_avg","tags": {"ant_id": "513", "id": "video rx"}} 93 | ], 94 | "ranges_and_icons": [ 95 | {"range": [-49, 1], "icon_path": "signal1.png"}, 96 | {"range": [-59, -50],"icon_path": "signal2.png"}, 97 | {"range": [-69, -60],"icon_path": "signal3.png"}, 98 | {"range": [-79, -70],"icon_path": "signal4.png"}, 99 | {"range": [-130, -80],"icon_path": "signal5.png"} 100 | ] 101 | }, 102 | { 103 | "name": "Metrics background", 104 | "type": "BoxWidget", 105 | "x": -270, 106 | "y": 100, 107 | "width": 270, 108 | "height": 100, 109 | "color": { 110 | "r": 0.0, 111 | "g": 0.0, 112 | "b": 0.0, 113 | "alpha": 0.4 114 | }, 115 | "facts": [] 116 | }, 117 | { 118 | "name": "Video FPS and resolution", 119 | "type": "VideoWidget", 120 | "x": -250, 121 | "y": 126, 122 | "icon_path": "framerate.png", 123 | "template": "%u fps | %ux%u", 124 | "per_second_window_s": 2, 125 | "per_second_bucket_ms": 200, 126 | "facts": [ 127 | { 128 | "__comment": "Will be converted to per-second", 129 | "name": "video.displayed_frame" 130 | }, 131 | { 132 | "name": "video.width" 133 | }, 134 | { 135 | "name": "video.height" 136 | } 137 | ] 138 | }, 139 | { 140 | "name": "Video link throughput", 141 | "type": "VideoBitrateWidget", 142 | "x": -250, 143 | "y": 156, 144 | "icon_path": "network.png", 145 | "template": "%f Mbps", 146 | "per_second_window_s": 2, 147 | "per_second_bucket_ms": 100, 148 | "facts": [ 149 | { 150 | "__comment": "Should be sum per-second, scaled to Megs", 151 | "name": "gstreamer.received_bytes" 152 | } 153 | ] 154 | }, 155 | { 156 | "name": "DVR status", 157 | "type": "DvrStatusWidget", 158 | "x": -250, 159 | "y": 186, 160 | "icon_path": "sdcard-white.png", 161 | "text": "Recording", 162 | "facts": [ 163 | {"name": "dvr.recording"} 164 | ] 165 | }, 166 | { 167 | "name": "Custom fading message", 168 | "type": "PopupWidget", 169 | "x": 400, 170 | "y": 50, 171 | "timeout_ms": 10000, 172 | "facts": [ 173 | {"name": "osd.custom_message"} 174 | ] 175 | }, 176 | { 177 | "name": "msposd", 178 | "type": "ExternalSurfaceWidget", 179 | "x": 0, 180 | "y": 0, 181 | "width": 0, 182 | "height": 0, 183 | "facts": [] 184 | } 185 | ] 186 | } 187 | -------------------------------------------------------------------------------- /src/mavlink/mavlink_conversions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef MAVLINK_NO_CONVERSION_HELPERS 4 | 5 | /* enable math defines on Windows */ 6 | #ifdef _MSC_VER 7 | #ifndef _USE_MATH_DEFINES 8 | #define _USE_MATH_DEFINES 9 | #endif 10 | #endif 11 | #include 12 | 13 | #ifndef M_PI_2 14 | #define M_PI_2 ((float)asin(1)) 15 | #endif 16 | 17 | /** 18 | * @file mavlink_conversions.h 19 | * 20 | * These conversion functions follow the NASA rotation standards definition file 21 | * available online. 22 | * 23 | * Their intent is to lower the barrier for MAVLink adopters to use gimbal-lock free 24 | * (both rotation matrices, sometimes called DCM, and quaternions are gimbal-lock free) 25 | * rotation representations. Euler angles (roll, pitch, yaw) will be phased out of the 26 | * protocol as widely as possible. 27 | * 28 | * @author James Goppert 29 | * @author Thomas Gubler 30 | */ 31 | 32 | 33 | /** 34 | * Converts a quaternion to a rotation matrix 35 | * 36 | * @param quaternion a [w, x, y, z] ordered quaternion (null-rotation being 1 0 0 0) 37 | * @param dcm a 3x3 rotation matrix 38 | */ 39 | MAVLINK_HELPER void mavlink_quaternion_to_dcm(const float quaternion[4], float dcm[3][3]) 40 | { 41 | double a = (double)quaternion[0]; 42 | double b = (double)quaternion[1]; 43 | double c = (double)quaternion[2]; 44 | double d = (double)quaternion[3]; 45 | double aSq = a * a; 46 | double bSq = b * b; 47 | double cSq = c * c; 48 | double dSq = d * d; 49 | dcm[0][0] = aSq + bSq - cSq - dSq; 50 | dcm[0][1] = 2 * (b * c - a * d); 51 | dcm[0][2] = 2 * (a * c + b * d); 52 | dcm[1][0] = 2 * (b * c + a * d); 53 | dcm[1][1] = aSq - bSq + cSq - dSq; 54 | dcm[1][2] = 2 * (c * d - a * b); 55 | dcm[2][0] = 2 * (b * d - a * c); 56 | dcm[2][1] = 2 * (a * b + c * d); 57 | dcm[2][2] = aSq - bSq - cSq + dSq; 58 | } 59 | 60 | 61 | /** 62 | * Converts a rotation matrix to euler angles 63 | * 64 | * @param dcm a 3x3 rotation matrix 65 | * @param roll the roll angle in radians 66 | * @param pitch the pitch angle in radians 67 | * @param yaw the yaw angle in radians 68 | */ 69 | MAVLINK_HELPER void mavlink_dcm_to_euler(const float dcm[3][3], float* roll, float* pitch, float* yaw) 70 | { 71 | float phi, theta, psi; 72 | theta = asinf(-dcm[2][0]); 73 | 74 | if (fabsf(theta - (float)M_PI_2) < 1.0e-3f) { 75 | phi = 0.0f; 76 | psi = (atan2f(dcm[1][2] - dcm[0][1], 77 | dcm[0][2] + dcm[1][1]) + phi); 78 | 79 | } else if (fabsf(theta + (float)M_PI_2) < 1.0e-3f) { 80 | phi = 0.0f; 81 | psi = atan2f(dcm[1][2] - dcm[0][1], 82 | dcm[0][2] + dcm[1][1] - phi); 83 | 84 | } else { 85 | phi = atan2f(dcm[2][1], dcm[2][2]); 86 | psi = atan2f(dcm[1][0], dcm[0][0]); 87 | } 88 | 89 | *roll = phi; 90 | *pitch = theta; 91 | *yaw = psi; 92 | } 93 | 94 | 95 | /** 96 | * Converts a quaternion to euler angles 97 | * 98 | * @param quaternion a [w, x, y, z] ordered quaternion (null-rotation being 1 0 0 0) 99 | * @param roll the roll angle in radians 100 | * @param pitch the pitch angle in radians 101 | * @param yaw the yaw angle in radians 102 | */ 103 | MAVLINK_HELPER void mavlink_quaternion_to_euler(const float quaternion[4], float* roll, float* pitch, float* yaw) 104 | { 105 | float dcm[3][3]; 106 | mavlink_quaternion_to_dcm(quaternion, dcm); 107 | mavlink_dcm_to_euler((const float(*)[3])dcm, roll, pitch, yaw); 108 | } 109 | 110 | 111 | /** 112 | * Converts euler angles to a quaternion 113 | * 114 | * @param roll the roll angle in radians 115 | * @param pitch the pitch angle in radians 116 | * @param yaw the yaw angle in radians 117 | * @param quaternion a [w, x, y, z] ordered quaternion (null-rotation being 1 0 0 0) 118 | */ 119 | MAVLINK_HELPER void mavlink_euler_to_quaternion(float roll, float pitch, float yaw, float quaternion[4]) 120 | { 121 | float cosPhi_2 = cosf(roll / 2); 122 | float sinPhi_2 = sinf(roll / 2); 123 | float cosTheta_2 = cosf(pitch / 2); 124 | float sinTheta_2 = sinf(pitch / 2); 125 | float cosPsi_2 = cosf(yaw / 2); 126 | float sinPsi_2 = sinf(yaw / 2); 127 | quaternion[0] = (cosPhi_2 * cosTheta_2 * cosPsi_2 + 128 | sinPhi_2 * sinTheta_2 * sinPsi_2); 129 | quaternion[1] = (sinPhi_2 * cosTheta_2 * cosPsi_2 - 130 | cosPhi_2 * sinTheta_2 * sinPsi_2); 131 | quaternion[2] = (cosPhi_2 * sinTheta_2 * cosPsi_2 + 132 | sinPhi_2 * cosTheta_2 * sinPsi_2); 133 | quaternion[3] = (cosPhi_2 * cosTheta_2 * sinPsi_2 - 134 | sinPhi_2 * sinTheta_2 * cosPsi_2); 135 | } 136 | 137 | 138 | /** 139 | * Converts a rotation matrix to a quaternion 140 | * Reference: 141 | * - Shoemake, Quaternions, 142 | * http://www.cs.ucr.edu/~vbz/resources/quatut.pdf 143 | * 144 | * @param dcm a 3x3 rotation matrix 145 | * @param quaternion a [w, x, y, z] ordered quaternion (null-rotation being 1 0 0 0) 146 | */ 147 | MAVLINK_HELPER void mavlink_dcm_to_quaternion(const float dcm[3][3], float quaternion[4]) 148 | { 149 | float tr = dcm[0][0] + dcm[1][1] + dcm[2][2]; 150 | if (tr > 0.0f) { 151 | float s = sqrtf(tr + 1.0f); 152 | quaternion[0] = s * 0.5f; 153 | s = 0.5f / s; 154 | quaternion[1] = (dcm[2][1] - dcm[1][2]) * s; 155 | quaternion[2] = (dcm[0][2] - dcm[2][0]) * s; 156 | quaternion[3] = (dcm[1][0] - dcm[0][1]) * s; 157 | } else { 158 | /* Find maximum diagonal element in dcm 159 | * store index in dcm_i */ 160 | int dcm_i = 0; 161 | int i; 162 | for (i = 1; i < 3; i++) { 163 | if (dcm[i][i] > dcm[dcm_i][dcm_i]) { 164 | dcm_i = i; 165 | } 166 | } 167 | 168 | int dcm_j = (dcm_i + 1) % 3; 169 | int dcm_k = (dcm_i + 2) % 3; 170 | 171 | float s = sqrtf((dcm[dcm_i][dcm_i] - dcm[dcm_j][dcm_j] - 172 | dcm[dcm_k][dcm_k]) + 1.0f); 173 | quaternion[dcm_i + 1] = s * 0.5f; 174 | s = 0.5f / s; 175 | quaternion[dcm_j + 1] = (dcm[dcm_i][dcm_j] + dcm[dcm_j][dcm_i]) * s; 176 | quaternion[dcm_k + 1] = (dcm[dcm_k][dcm_i] + dcm[dcm_i][dcm_k]) * s; 177 | quaternion[0] = (dcm[dcm_k][dcm_j] - dcm[dcm_j][dcm_k]) * s; 178 | } 179 | } 180 | 181 | 182 | /** 183 | * Converts euler angles to a rotation matrix 184 | * 185 | * @param roll the roll angle in radians 186 | * @param pitch the pitch angle in radians 187 | * @param yaw the yaw angle in radians 188 | * @param dcm a 3x3 rotation matrix 189 | */ 190 | MAVLINK_HELPER void mavlink_euler_to_dcm(float roll, float pitch, float yaw, float dcm[3][3]) 191 | { 192 | float cosPhi = cosf(roll); 193 | float sinPhi = sinf(roll); 194 | float cosThe = cosf(pitch); 195 | float sinThe = sinf(pitch); 196 | float cosPsi = cosf(yaw); 197 | float sinPsi = sinf(yaw); 198 | 199 | dcm[0][0] = cosThe * cosPsi; 200 | dcm[0][1] = -cosPhi * sinPsi + sinPhi * sinThe * cosPsi; 201 | dcm[0][2] = sinPhi * sinPsi + cosPhi * sinThe * cosPsi; 202 | 203 | dcm[1][0] = cosThe * sinPsi; 204 | dcm[1][1] = cosPhi * cosPsi + sinPhi * sinThe * sinPsi; 205 | dcm[1][2] = -sinPhi * cosPsi + cosPhi * sinThe * sinPsi; 206 | 207 | dcm[2][0] = -sinThe; 208 | dcm[2][1] = sinPhi * cosThe; 209 | dcm[2][2] = cosPhi * cosThe; 210 | } 211 | 212 | #endif // MAVLINK_NO_CONVERSION_HELPERS 213 | -------------------------------------------------------------------------------- /src/gsmenu/gs_dvrplayer.c: -------------------------------------------------------------------------------- 1 | #include "lvgl.h" 2 | #include 3 | #include 4 | #include "../main.h" 5 | #include "styles.h" 6 | 7 | extern lv_indev_t * indev_drv; 8 | 9 | extern lv_obj_t * pp_menu_screen; 10 | extern lv_obj_t * dvr_screen; 11 | 12 | extern lv_group_t * default_group; 13 | extern lv_group_t * dvr_page_group; 14 | lv_group_t * dvr_group; 15 | 16 | lv_obj_t * btn_container = NULL; 17 | lv_obj_t * btn_play_pause = NULL; 18 | lv_timer_t *hide_timer = NULL; 19 | 20 | // Animation callback for fade out 21 | static void set_opa_anim(void * obj, int32_t opa) { 22 | lv_obj_set_style_opa((lv_obj_t *)obj, opa, LV_PART_MAIN); 23 | } 24 | 25 | // Timer callback to hide the controls 26 | static void hide_controls_cb(lv_timer_t *timer) 27 | { 28 | lv_anim_t a; 29 | lv_anim_init(&a); 30 | lv_anim_set_var(&a, btn_container); 31 | lv_anim_set_values(&a, LV_OPA_COVER, LV_OPA_TRANSP); 32 | lv_anim_set_time(&a, 300); 33 | lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)set_opa_anim); 34 | 35 | lv_anim_start(&a); 36 | } 37 | 38 | // Timer Reset 39 | static void timer_reset_handler(lv_event_t * e) 40 | { 41 | lv_obj_set_style_opa(btn_container, LV_OPA_COVER, LV_PART_MAIN); 42 | // Reset the timer 43 | if (hide_timer) { 44 | lv_timer_delete(hide_timer); 45 | hide_timer = lv_timer_create(hide_controls_cb, 5000, NULL); 46 | lv_timer_set_repeat_count(hide_timer, 1); 47 | lv_timer_set_auto_delete(hide_timer,false); 48 | } else { 49 | hide_timer = lv_timer_create(hide_controls_cb, 5000, NULL); 50 | lv_timer_set_repeat_count(hide_timer, 1); 51 | lv_timer_set_auto_delete(hide_timer,false); 52 | } 53 | } 54 | 55 | void change_playbutton_label(const char * text) { 56 | lv_obj_t * screen = lv_screen_active(); 57 | if(btn_play_pause) { 58 | lv_obj_t * label = lv_obj_get_child(btn_play_pause, 0); 59 | lv_label_set_text(label, text); 60 | } 61 | } 62 | 63 | // Event handler for the play/pause button 64 | static void play_pause_event_handler(lv_event_t * e) 65 | { 66 | lv_obj_t * btn = lv_event_get_target(e); 67 | lv_obj_t * label = lv_obj_get_child(btn, 0); 68 | const char * current_text = lv_label_get_text(label); 69 | 70 | timer_reset_handler(e); 71 | 72 | // Toggle the icon and functionality 73 | if (strcmp(current_text, LV_SYMBOL_PLAY) == 0) { 74 | change_playbutton_label(LV_SYMBOL_PAUSE); 75 | #ifndef USE_SIMULATOR 76 | resume_playback(); 77 | #else 78 | printf("resume_playback();\n"); 79 | #endif 80 | } else if (strcmp(current_text, LV_SYMBOL_PAUSE) == 0) { 81 | change_playbutton_label(LV_SYMBOL_PLAY); 82 | #ifndef USE_SIMULATOR 83 | pause_playback(); 84 | #else 85 | printf("pause_playback();\n"); 86 | #endif 87 | } 88 | } 89 | 90 | // Event handler for the fast-rewind button 91 | static void fr_event_handler(lv_event_t * e) 92 | { 93 | timer_reset_handler(e); 94 | #ifndef USE_SIMULATOR 95 | skip_duration(-10000); // Skip back 10 seconds 96 | #else 97 | printf("skip_duration(-10000);n"); 98 | #endif 99 | } 100 | 101 | // Event handler for the fast-forward button 102 | static void ff_event_handler(lv_event_t * e) 103 | { 104 | timer_reset_handler(e); 105 | #ifndef USE_SIMULATOR 106 | skip_duration(10000); // Skip forward 10 seconds 107 | #else 108 | printf("skip_duration(10000);\n"); 109 | #endif 110 | } 111 | 112 | // Event handler for the stop button 113 | static void stop_event_handler(lv_event_t * e) 114 | { 115 | timer_reset_handler(e); 116 | 117 | change_playbutton_label(LV_SYMBOL_PLAY); 118 | 119 | #ifndef USE_SIMULATOR 120 | switch_pipeline_source("stream",NULL); 121 | #else 122 | printf("switch_pipeline_source(\"stream\",NULL);\n"); 123 | #endif 124 | 125 | lv_screen_load(pp_menu_screen); 126 | lv_indev_set_group(indev_drv,dvr_page_group); 127 | } 128 | 129 | // Focus on Play/Pause on screen load 130 | static void screen_load_default_focus(lv_event_t * e) 131 | { 132 | lv_group_focus_obj(btn_play_pause); 133 | } 134 | 135 | void create_video_controls(lv_obj_t * parent) 136 | { 137 | // Create a container for the buttons with a flex layout 138 | btn_container = lv_obj_create(parent); 139 | lv_obj_remove_style(btn_container, NULL, LV_PART_SCROLLBAR); 140 | lv_obj_set_size(btn_container, lv_pct(100), LV_SIZE_CONTENT); 141 | lv_obj_set_flex_flow(btn_container, LV_FLEX_FLOW_ROW); 142 | lv_obj_set_flex_align(btn_container, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); 143 | lv_obj_align(btn_container, LV_ALIGN_BOTTOM_MID, 0, -20); // Position at the bottom 144 | lv_obj_add_style(btn_container, &style_openipc_lightdark_background, LV_PART_MAIN | LV_STATE_DEFAULT); 145 | 146 | 147 | // --- Button Definitions --- 148 | lv_obj_t * btn; 149 | lv_obj_t * label; 150 | 151 | // Fast-Rewind Button 152 | btn = lv_button_create(btn_container); 153 | lv_obj_add_event_cb(btn, fr_event_handler, LV_EVENT_CLICKED, NULL); 154 | label = lv_label_create(btn); 155 | lv_label_set_text(label, LV_SYMBOL_LEFT); 156 | lv_obj_center(label); 157 | lv_obj_add_style(btn, &style_openipc, LV_PART_MAIN | LV_STATE_DEFAULT); 158 | lv_obj_add_style(btn, &style_openipc_outline, LV_PART_MAIN | LV_STATE_FOCUS_KEY); 159 | lv_obj_add_event_cb(btn,timer_reset_handler,LV_EVENT_FOCUSED, NULL); 160 | 161 | // Play/Pause Button 162 | btn_play_pause = lv_button_create(btn_container); 163 | lv_obj_add_event_cb(btn_play_pause, play_pause_event_handler, LV_EVENT_CLICKED, NULL); 164 | label = lv_label_create(btn_play_pause); 165 | lv_label_set_text(label, LV_SYMBOL_PLAY); // Initial state is 'Play' 166 | lv_obj_center(btn_play_pause); 167 | lv_obj_add_style(btn_play_pause, &style_openipc, LV_PART_MAIN | LV_STATE_DEFAULT); 168 | lv_obj_add_style(btn_play_pause, &style_openipc_outline, LV_PART_MAIN | LV_STATE_FOCUS_KEY); 169 | lv_obj_add_event_cb(btn_play_pause,timer_reset_handler,LV_EVENT_FOCUSED, NULL); 170 | 171 | // Fast-Forward Button 172 | btn = lv_button_create(btn_container); 173 | lv_obj_add_event_cb(btn, ff_event_handler, LV_EVENT_CLICKED, NULL); 174 | label = lv_label_create(btn); 175 | lv_label_set_text(label, LV_SYMBOL_RIGHT); 176 | lv_obj_center(label); 177 | lv_obj_add_style(btn, &style_openipc, LV_PART_MAIN | LV_STATE_DEFAULT); 178 | lv_obj_add_style(btn, &style_openipc_outline, LV_PART_MAIN | LV_STATE_FOCUS_KEY); 179 | lv_obj_add_event_cb(btn,timer_reset_handler,LV_EVENT_FOCUSED, NULL); 180 | 181 | // Stop Button 182 | btn = lv_button_create(btn_container); 183 | lv_obj_add_event_cb(btn, stop_event_handler, LV_EVENT_CLICKED, NULL); 184 | label = lv_label_create(btn); 185 | lv_label_set_text(label, LV_SYMBOL_STOP); 186 | lv_obj_center(label); 187 | lv_obj_add_style(btn, &style_openipc, LV_PART_MAIN | LV_STATE_DEFAULT); 188 | lv_obj_add_style(btn, &style_openipc_outline, LV_PART_MAIN | LV_STATE_FOCUS_KEY); 189 | lv_obj_add_event_cb(btn,timer_reset_handler,LV_EVENT_FOCUSED, NULL); 190 | } 191 | 192 | void dvr_player_screen_init(void) 193 | { 194 | 195 | dvr_group = lv_group_create(); 196 | lv_group_set_default(dvr_group); 197 | 198 | // Create the video control buttons 199 | create_video_controls(dvr_screen); 200 | lv_obj_add_event_cb(dvr_screen,timer_reset_handler,LV_EVENT_SCREEN_LOADED,NULL); 201 | lv_obj_add_event_cb(dvr_screen,screen_load_default_focus,LV_EVENT_SCREEN_LOADED,NULL); 202 | 203 | lv_group_set_default(default_group); 204 | 205 | } -------------------------------------------------------------------------------- /src/mavlink/minimal/testsuite.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | * @brief MAVLink comm protocol testsuite generated from minimal.xml 3 | * @see https://mavlink.io/en/ 4 | */ 5 | #pragma once 6 | #ifndef MINIMAL_TESTSUITE_H 7 | #define MINIMAL_TESTSUITE_H 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #ifndef MAVLINK_TEST_ALL 14 | #define MAVLINK_TEST_ALL 15 | 16 | static void mavlink_test_minimal(uint8_t, uint8_t, mavlink_message_t *last_msg); 17 | 18 | static void mavlink_test_all(uint8_t system_id, uint8_t component_id, mavlink_message_t *last_msg) 19 | { 20 | 21 | mavlink_test_minimal(system_id, component_id, last_msg); 22 | } 23 | #endif 24 | 25 | 26 | 27 | 28 | static void mavlink_test_heartbeat(uint8_t system_id, uint8_t component_id, mavlink_message_t *last_msg) 29 | { 30 | #ifdef MAVLINK_STATUS_FLAG_OUT_MAVLINK1 31 | mavlink_status_t *status = mavlink_get_channel_status(MAVLINK_COMM_0); 32 | if ((status->flags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1) && MAVLINK_MSG_ID_HEARTBEAT >= 256) { 33 | return; 34 | } 35 | #endif 36 | mavlink_message_t msg; 37 | uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; 38 | uint16_t i; 39 | mavlink_heartbeat_t packet_in = { 40 | 963497464,17,84,151,218,3 41 | }; 42 | mavlink_heartbeat_t packet1, packet2; 43 | memset(&packet1, 0, sizeof(packet1)); 44 | packet1.custom_mode = packet_in.custom_mode; 45 | packet1.type = packet_in.type; 46 | packet1.autopilot = packet_in.autopilot; 47 | packet1.base_mode = packet_in.base_mode; 48 | packet1.system_status = packet_in.system_status; 49 | packet1.mavlink_version = packet_in.mavlink_version; 50 | 51 | 52 | #ifdef MAVLINK_STATUS_FLAG_OUT_MAVLINK1 53 | if (status->flags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1) { 54 | // cope with extensions 55 | memset(MAVLINK_MSG_ID_HEARTBEAT_MIN_LEN + (char *)&packet1, 0, sizeof(packet1)-MAVLINK_MSG_ID_HEARTBEAT_MIN_LEN); 56 | } 57 | #endif 58 | memset(&packet2, 0, sizeof(packet2)); 59 | mavlink_msg_heartbeat_encode(system_id, component_id, &msg, &packet1); 60 | mavlink_msg_heartbeat_decode(&msg, &packet2); 61 | MAVLINK_ASSERT(memcmp(&packet1, &packet2, sizeof(packet1)) == 0); 62 | 63 | memset(&packet2, 0, sizeof(packet2)); 64 | mavlink_msg_heartbeat_pack(system_id, component_id, &msg , packet1.type , packet1.autopilot , packet1.base_mode , packet1.custom_mode , packet1.system_status ); 65 | mavlink_msg_heartbeat_decode(&msg, &packet2); 66 | MAVLINK_ASSERT(memcmp(&packet1, &packet2, sizeof(packet1)) == 0); 67 | 68 | memset(&packet2, 0, sizeof(packet2)); 69 | mavlink_msg_heartbeat_pack_chan(system_id, component_id, MAVLINK_COMM_0, &msg , packet1.type , packet1.autopilot , packet1.base_mode , packet1.custom_mode , packet1.system_status ); 70 | mavlink_msg_heartbeat_decode(&msg, &packet2); 71 | MAVLINK_ASSERT(memcmp(&packet1, &packet2, sizeof(packet1)) == 0); 72 | 73 | memset(&packet2, 0, sizeof(packet2)); 74 | mavlink_msg_to_send_buffer(buffer, &msg); 75 | for (i=0; iflags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1) && MAVLINK_MSG_ID_PROTOCOL_VERSION >= 256) { 97 | return; 98 | } 99 | #endif 100 | mavlink_message_t msg; 101 | uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; 102 | uint16_t i; 103 | mavlink_protocol_version_t packet_in = { 104 | 17235,17339,17443,{ 151, 152, 153, 154, 155, 156, 157, 158 },{ 175, 176, 177, 178, 179, 180, 181, 182 } 105 | }; 106 | mavlink_protocol_version_t packet1, packet2; 107 | memset(&packet1, 0, sizeof(packet1)); 108 | packet1.version = packet_in.version; 109 | packet1.min_version = packet_in.min_version; 110 | packet1.max_version = packet_in.max_version; 111 | 112 | mav_array_memcpy(packet1.spec_version_hash, packet_in.spec_version_hash, sizeof(uint8_t)*8); 113 | mav_array_memcpy(packet1.library_version_hash, packet_in.library_version_hash, sizeof(uint8_t)*8); 114 | 115 | #ifdef MAVLINK_STATUS_FLAG_OUT_MAVLINK1 116 | if (status->flags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1) { 117 | // cope with extensions 118 | memset(MAVLINK_MSG_ID_PROTOCOL_VERSION_MIN_LEN + (char *)&packet1, 0, sizeof(packet1)-MAVLINK_MSG_ID_PROTOCOL_VERSION_MIN_LEN); 119 | } 120 | #endif 121 | memset(&packet2, 0, sizeof(packet2)); 122 | mavlink_msg_protocol_version_encode(system_id, component_id, &msg, &packet1); 123 | mavlink_msg_protocol_version_decode(&msg, &packet2); 124 | MAVLINK_ASSERT(memcmp(&packet1, &packet2, sizeof(packet1)) == 0); 125 | 126 | memset(&packet2, 0, sizeof(packet2)); 127 | mavlink_msg_protocol_version_pack(system_id, component_id, &msg , packet1.version , packet1.min_version , packet1.max_version , packet1.spec_version_hash , packet1.library_version_hash ); 128 | mavlink_msg_protocol_version_decode(&msg, &packet2); 129 | MAVLINK_ASSERT(memcmp(&packet1, &packet2, sizeof(packet1)) == 0); 130 | 131 | memset(&packet2, 0, sizeof(packet2)); 132 | mavlink_msg_protocol_version_pack_chan(system_id, component_id, MAVLINK_COMM_0, &msg , packet1.version , packet1.min_version , packet1.max_version , packet1.spec_version_hash , packet1.library_version_hash ); 133 | mavlink_msg_protocol_version_decode(&msg, &packet2); 134 | MAVLINK_ASSERT(memcmp(&packet1, &packet2, sizeof(packet1)) == 0); 135 | 136 | memset(&packet2, 0, sizeof(packet2)); 137 | mavlink_msg_to_send_buffer(buffer, &msg); 138 | for (i=0; i 2 | #include 3 | #include 4 | #include "executor.h" 5 | #include "styles.h" 6 | #include "lvgl/lvgl.h" 7 | #include "helper.h" 8 | 9 | extern lv_group_t * default_group; 10 | 11 | lv_obj_t * mirror; 12 | lv_obj_t * flip; 13 | lv_obj_t * contrast; 14 | lv_obj_t * hue; 15 | lv_obj_t * saturation; 16 | lv_obj_t * luminace; 17 | lv_obj_t * video_mode; 18 | lv_obj_t * size; 19 | lv_obj_t * fps; 20 | lv_obj_t * bitrate; 21 | lv_obj_t * video_codec; 22 | lv_obj_t * gopsize; 23 | lv_obj_t * rc_mode; 24 | lv_obj_t * rec_enable; 25 | lv_obj_t * rec_split; 26 | lv_obj_t * rec_maxusage; 27 | lv_obj_t * exposure; 28 | lv_obj_t * antiflicker; 29 | lv_obj_t * sensor_file; 30 | lv_obj_t * fpv_enable; 31 | lv_obj_t * noiselevel; 32 | 33 | 34 | extern lv_obj_t * rec_fps; 35 | void air_rec_fps_cb(lv_event_t *e) { 36 | char val[100] = ""; 37 | 38 | lv_obj_t *ta = lv_event_get_target(e); 39 | lv_dropdown_get_selected_str(ta, val, sizeof(val) - 1); 40 | 41 | lv_obj_t *obj = lv_obj_get_child_by_type(rec_fps, 0, &lv_dropdown_class); 42 | int index = lv_dropdown_get_option_index(obj,val); 43 | lv_dropdown_set_selected(obj, index); 44 | lv_obj_send_event(obj,LV_EVENT_VALUE_CHANGED,NULL); 45 | } 46 | 47 | void create_air_camera_menu(lv_obj_t * parent) { 48 | 49 | menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); 50 | strcpy(menu_page_data->type, "air"); 51 | strcpy(menu_page_data->page, "camera"); 52 | menu_page_data->page_load_callback = generic_page_load_callback; 53 | menu_page_data->indev_group = lv_group_create(); 54 | menu_page_data->entry_count = 0; 55 | menu_page_data->page_entries = NULL; 56 | lv_group_set_default(menu_page_data->indev_group); 57 | lv_obj_set_user_data(parent,menu_page_data); 58 | 59 | lv_obj_t * cont; 60 | lv_obj_t * section; 61 | 62 | create_text(parent, NULL, "Video", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 63 | section = lv_menu_section_create(parent); 64 | lv_obj_add_style(section, &style_openipc_section, 0); 65 | cont = lv_menu_cont_create(section); 66 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 67 | size = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Size","","size",menu_page_data,false); 68 | video_mode = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Video Mode","","video_mode",menu_page_data,false); 69 | 70 | fps = create_dropdown(cont,LV_SYMBOL_SETTINGS, "FPS","","fps",menu_page_data,false); 71 | // change rec fps when changeing camera fps 72 | lv_obj_add_event_cb(lv_obj_get_child_by_type(fps,0,&lv_dropdown_class), air_rec_fps_cb, LV_EVENT_VALUE_CHANGED,fps); 73 | 74 | bitrate = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Bitrate","","bitrate",menu_page_data,false); 75 | video_codec = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Codec","","codec",menu_page_data,false); 76 | gopsize = create_slider(cont,LV_SYMBOL_SETTINGS,"Gopsize","gopsize",menu_page_data,false,0); 77 | rc_mode = create_dropdown(cont,LV_SYMBOL_SETTINGS, "RC Mode","","rc_mode",menu_page_data,false); 78 | 79 | create_text(parent, NULL, "Image", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 80 | section = lv_menu_section_create(parent); 81 | lv_obj_add_style(section, &style_openipc_section, 0); 82 | cont = lv_menu_cont_create(section); 83 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 84 | 85 | mirror = create_switch(cont,LV_SYMBOL_SETTINGS,"Mirror","mirror", menu_page_data,false); 86 | flip = create_switch(cont,LV_SYMBOL_SETTINGS,"Flip","flip", menu_page_data,false); 87 | contrast = create_slider(cont,LV_SYMBOL_SETTINGS,"Contrast","contrast",menu_page_data,false,0); 88 | hue = create_slider(cont,LV_SYMBOL_SETTINGS,"Hue","hue",menu_page_data,false,0); 89 | saturation = create_slider(cont,LV_SYMBOL_SETTINGS,"Saturation","saturation",menu_page_data,false,0); 90 | luminace = create_slider(cont,LV_SYMBOL_SETTINGS,"Luminance","luminace",menu_page_data,false,0); 91 | 92 | create_text(parent, NULL, "Recording", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 93 | section = lv_menu_section_create(parent); 94 | lv_obj_add_style(section, &style_openipc_section, 0); 95 | cont = lv_menu_cont_create(section); 96 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 97 | rec_enable = create_switch(cont,LV_SYMBOL_SETTINGS,"Enabled","rec_enable", menu_page_data,false); 98 | rec_split = create_slider(cont,LV_SYMBOL_SETTINGS,"Split","rec_split",menu_page_data,false,0); 99 | rec_maxusage = create_slider(cont,LV_SYMBOL_SETTINGS,"Maxusage","rec_maxusage",menu_page_data,false,0); 100 | 101 | create_text(parent, NULL, "ISP", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 102 | section = lv_menu_section_create(parent); 103 | lv_obj_add_style(section, &style_openipc_section, 0); 104 | cont = lv_menu_cont_create(section); 105 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 106 | exposure = create_slider(cont,LV_SYMBOL_SETTINGS,"Exposure","exposure",menu_page_data,false,0); 107 | antiflicker = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Antiflicker","","antiflicker",menu_page_data,false); 108 | sensor_file = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Sensor File","","sensor_file",menu_page_data,false); 109 | 110 | create_text(parent, NULL, "FPV", NULL, NULL, false, LV_MENU_ITEM_BUILDER_VARIANT_1); 111 | section = lv_menu_section_create(parent); 112 | lv_obj_add_style(section, &style_openipc_section, 0); 113 | cont = lv_menu_cont_create(section); 114 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 115 | fpv_enable = create_switch(cont,LV_SYMBOL_SETTINGS,"Enabled","fpv_enable", menu_page_data,false); 116 | noiselevel = create_slider(cont,LV_SYMBOL_SETTINGS,"Noiselevel","noiselevel",menu_page_data,false,0); 117 | 118 | 119 | add_entry_to_menu_page(menu_page_data,"Loading mirror ...", mirror, reload_switch_value ); 120 | add_entry_to_menu_page(menu_page_data,"Loading flip ...", flip, reload_switch_value ); 121 | add_entry_to_menu_page(menu_page_data,"Loading contrast ...", contrast, reload_slider_value ); 122 | add_entry_to_menu_page(menu_page_data,"Loading hue ...", hue, reload_slider_value ); 123 | add_entry_to_menu_page(menu_page_data,"Loading saturation ...", saturation, reload_slider_value ); 124 | add_entry_to_menu_page(menu_page_data,"Loading luminace ...", luminace, reload_slider_value ); 125 | add_entry_to_menu_page(menu_page_data,"Loading size ...", size, reload_dropdown_value ); 126 | add_entry_to_menu_page(menu_page_data,"Loading video_mode ...", video_mode, reload_dropdown_value ); 127 | add_entry_to_menu_page(menu_page_data,"Loading fps ...", fps, reload_dropdown_value ); 128 | add_entry_to_menu_page(menu_page_data,"Loading bitrate ...", bitrate, reload_dropdown_value ); 129 | add_entry_to_menu_page(menu_page_data,"Loading video_codec ...", video_codec, reload_dropdown_value ); 130 | add_entry_to_menu_page(menu_page_data,"Loading gopsize ...", gopsize, reload_slider_value ); 131 | add_entry_to_menu_page(menu_page_data,"Loading rc_mode ...", rc_mode, reload_dropdown_value ); 132 | add_entry_to_menu_page(menu_page_data,"Loading rec_enable ...", rec_enable, reload_switch_value ); 133 | add_entry_to_menu_page(menu_page_data,"Loading rec_split ...", rec_split, reload_slider_value ); 134 | add_entry_to_menu_page(menu_page_data,"Loading rec_maxusage ...", rec_maxusage, reload_slider_value ); 135 | add_entry_to_menu_page(menu_page_data,"Loading exposure ...", exposure, reload_slider_value ); 136 | add_entry_to_menu_page(menu_page_data,"Loading antiflicker ...", antiflicker, reload_dropdown_value ); 137 | add_entry_to_menu_page(menu_page_data,"Loading sensor_file ...", sensor_file, reload_dropdown_value ); 138 | add_entry_to_menu_page(menu_page_data,"Loading fpv_enable ...", fpv_enable, reload_switch_value ); 139 | add_entry_to_menu_page(menu_page_data,"Loading noiselevel ...", noiselevel, reload_slider_value); 140 | 141 | lv_group_set_default(default_group); 142 | } -------------------------------------------------------------------------------- /src/mavlink/mavlink_sha256.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | sha-256 implementation for MAVLink based on Heimdal sources, with 5 | modifications to suit mavlink headers 6 | */ 7 | /* 8 | * Copyright (c) 1995 - 2001 Kungliga Tekniska Högskolan 9 | * (Royal Institute of Technology, Stockholm, Sweden). 10 | * All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 16 | * 1. Redistributions of source code must retain the above copyright 17 | * notice, this list of conditions and the following disclaimer. 18 | * 19 | * 2. Redistributions in binary form must reproduce the above copyright 20 | * notice, this list of conditions and the following disclaimer in the 21 | * documentation and/or other materials provided with the distribution. 22 | * 23 | * 3. Neither the name of the Institute nor the names of its contributors 24 | * may be used to endorse or promote products derived from this software 25 | * without specific prior written permission. 26 | * 27 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 28 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 31 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 33 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 34 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 36 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 37 | * SUCH DAMAGE. 38 | */ 39 | 40 | /* 41 | allow implementation to provide their own sha256 with the same API 42 | */ 43 | #ifndef HAVE_MAVLINK_SHA256 44 | 45 | #ifdef MAVLINK_USE_CXX_NAMESPACE 46 | namespace mavlink { 47 | #endif 48 | 49 | #ifndef MAVLINK_HELPER 50 | #define MAVLINK_HELPER 51 | #endif 52 | 53 | typedef struct { 54 | uint32_t sz[2]; 55 | uint32_t counter[8]; 56 | union { 57 | unsigned char save_bytes[64]; 58 | uint32_t save_u32[16]; 59 | } u; 60 | } mavlink_sha256_ctx; 61 | 62 | #define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) 63 | #define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) 64 | 65 | #define ROTR(x,n) (((x)>>(n)) | ((x) << (32 - (n)))) 66 | 67 | #define Sigma0(x) (ROTR(x,2) ^ ROTR(x,13) ^ ROTR(x,22)) 68 | #define Sigma1(x) (ROTR(x,6) ^ ROTR(x,11) ^ ROTR(x,25)) 69 | #define sigma0(x) (ROTR(x,7) ^ ROTR(x,18) ^ ((x)>>3)) 70 | #define sigma1(x) (ROTR(x,17) ^ ROTR(x,19) ^ ((x)>>10)) 71 | 72 | static const uint32_t mavlink_sha256_constant_256[64] = { 73 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 74 | 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 75 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 76 | 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 77 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 78 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 79 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 80 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 81 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 82 | 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 83 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 84 | 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 85 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 86 | 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 87 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 88 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 89 | }; 90 | 91 | MAVLINK_HELPER void mavlink_sha256_init(mavlink_sha256_ctx *m) 92 | { 93 | m->sz[0] = 0; 94 | m->sz[1] = 0; 95 | m->counter[0] = 0x6a09e667; 96 | m->counter[1] = 0xbb67ae85; 97 | m->counter[2] = 0x3c6ef372; 98 | m->counter[3] = 0xa54ff53a; 99 | m->counter[4] = 0x510e527f; 100 | m->counter[5] = 0x9b05688c; 101 | m->counter[6] = 0x1f83d9ab; 102 | m->counter[7] = 0x5be0cd19; 103 | } 104 | 105 | static inline void mavlink_sha256_calc(mavlink_sha256_ctx *m, uint32_t *in) 106 | { 107 | uint32_t AA, BB, CC, DD, EE, FF, GG, HH; 108 | uint32_t data[64]; 109 | int i; 110 | 111 | AA = m->counter[0]; 112 | BB = m->counter[1]; 113 | CC = m->counter[2]; 114 | DD = m->counter[3]; 115 | EE = m->counter[4]; 116 | FF = m->counter[5]; 117 | GG = m->counter[6]; 118 | HH = m->counter[7]; 119 | 120 | for (i = 0; i < 16; ++i) 121 | data[i] = in[i]; 122 | for (i = 16; i < 64; ++i) 123 | data[i] = sigma1(data[i-2]) + data[i-7] + 124 | sigma0(data[i-15]) + data[i - 16]; 125 | 126 | for (i = 0; i < 64; i++) { 127 | uint32_t T1, T2; 128 | 129 | T1 = HH + Sigma1(EE) + Ch(EE, FF, GG) + mavlink_sha256_constant_256[i] + data[i]; 130 | T2 = Sigma0(AA) + Maj(AA,BB,CC); 131 | 132 | HH = GG; 133 | GG = FF; 134 | FF = EE; 135 | EE = DD + T1; 136 | DD = CC; 137 | CC = BB; 138 | BB = AA; 139 | AA = T1 + T2; 140 | } 141 | 142 | m->counter[0] += AA; 143 | m->counter[1] += BB; 144 | m->counter[2] += CC; 145 | m->counter[3] += DD; 146 | m->counter[4] += EE; 147 | m->counter[5] += FF; 148 | m->counter[6] += GG; 149 | m->counter[7] += HH; 150 | } 151 | 152 | MAVLINK_HELPER void mavlink_sha256_update(mavlink_sha256_ctx *m, const void *v, uint32_t len) 153 | { 154 | const unsigned char *p = (const unsigned char *)v; 155 | uint32_t old_sz = m->sz[0]; 156 | uint32_t offset; 157 | 158 | m->sz[0] += len * 8; 159 | if (m->sz[0] < old_sz) 160 | ++m->sz[1]; 161 | offset = (old_sz / 8) % 64; 162 | while(len > 0){ 163 | uint32_t l = 64 - offset; 164 | if (len < l) { 165 | l = len; 166 | } 167 | memcpy(m->u.save_bytes + offset, p, l); 168 | offset += l; 169 | p += l; 170 | len -= l; 171 | if(offset == 64){ 172 | int i; 173 | uint32_t current[16]; 174 | const uint32_t *u = m->u.save_u32; 175 | for (i = 0; i < 16; i++){ 176 | const uint8_t *p1 = (const uint8_t *)&u[i]; 177 | uint8_t *p2 = (uint8_t *)¤t[i]; 178 | p2[0] = p1[3]; 179 | p2[1] = p1[2]; 180 | p2[2] = p1[1]; 181 | p2[3] = p1[0]; 182 | } 183 | mavlink_sha256_calc(m, current); 184 | offset = 0; 185 | } 186 | } 187 | } 188 | 189 | /* 190 | get first 48 bits of final sha256 hash 191 | */ 192 | MAVLINK_HELPER void mavlink_sha256_final_48(mavlink_sha256_ctx *m, uint8_t result[6]) 193 | { 194 | unsigned char zeros[72]; 195 | unsigned offset = (m->sz[0] / 8) % 64; 196 | unsigned int dstart = (120 - offset - 1) % 64 + 1; 197 | uint8_t *p = (uint8_t *)&m->counter[0]; 198 | 199 | *zeros = 0x80; 200 | memset (zeros + 1, 0, sizeof(zeros) - 1); 201 | zeros[dstart+7] = (m->sz[0] >> 0) & 0xff; 202 | zeros[dstart+6] = (m->sz[0] >> 8) & 0xff; 203 | zeros[dstart+5] = (m->sz[0] >> 16) & 0xff; 204 | zeros[dstart+4] = (m->sz[0] >> 24) & 0xff; 205 | zeros[dstart+3] = (m->sz[1] >> 0) & 0xff; 206 | zeros[dstart+2] = (m->sz[1] >> 8) & 0xff; 207 | zeros[dstart+1] = (m->sz[1] >> 16) & 0xff; 208 | zeros[dstart+0] = (m->sz[1] >> 24) & 0xff; 209 | 210 | mavlink_sha256_update(m, zeros, dstart + 8); 211 | 212 | // this ordering makes the result consistent with taking the first 213 | // 6 bytes of more conventional sha256 functions. It assumes 214 | // little-endian ordering of m->counter 215 | result[0] = p[3]; 216 | result[1] = p[2]; 217 | result[2] = p[1]; 218 | result[3] = p[0]; 219 | result[4] = p[7]; 220 | result[5] = p[6]; 221 | } 222 | 223 | // prevent conflicts with users of the header 224 | #undef Ch 225 | #undef ROTR 226 | #undef Sigma0 227 | #undef Sigma1 228 | #undef sigma0 229 | #undef sigma1 230 | 231 | #ifdef MAVLINK_USE_CXX_NAMESPACE 232 | } // namespace mavlink 233 | #endif 234 | 235 | #endif // HAVE_MAVLINK_SHA256 236 | -------------------------------------------------------------------------------- /debian/manpage.md: -------------------------------------------------------------------------------- 1 | % pixelpilot-rk(SECTION) | User Commands 2 | % 3 | % "October 1 2025" 4 | 5 | [comment]: # The lines above form a Pandoc metadata block. They must be 6 | [comment]: # the first ones in the file. 7 | [comment]: # See https://pandoc.org/MANUAL.html#metadata-blocks for details. 8 | 9 | [comment]: # pandoc -s -f markdown -t man package.md -o package.1 10 | [comment]: # 11 | [comment]: # A manual page package.1 will be generated. You may view the 12 | [comment]: # manual page with: nroff -man package.1 | less. A typical entry 13 | [comment]: # in a Makefile or Makefile.am is: 14 | [comment]: # 15 | [comment]: # package.1: package.md 16 | [comment]: # pandoc --standalone --from=markdown --to=man $< --output=$@ 17 | [comment]: # 18 | [comment]: # The pandoc binary is found in the pandoc package. Please remember 19 | [comment]: # that if you create the nroff version in one of the debian/rules 20 | [comment]: # file targets, such as build, you will need to include pandoc in 21 | [comment]: # your Build-Depends control field. 22 | 23 | [comment]: # lowdown is a low dependency, lightweight alternative to 24 | [comment]: # pandoc as a markdown to manpage translator. Use with: 25 | [comment]: # 26 | [comment]: # package.1: package.md 27 | [comment]: # lowdown -s -Tman -o $@ $< 28 | [comment]: # 29 | [comment]: # And add lowdown to the Build-Depends control field. 30 | 31 | [comment]: # Remove the lines starting with '[comment]:' in this file in order 32 | [comment]: # to avoid warning messages. 33 | 34 | # NAME 35 | 36 | pixelpilot-rk - OpenIPC video display client for wfb-ng 37 | 38 | # SYNOPSIS 39 | 40 | ``` 41 | Usage: 42 | pixelpilot [Arguments] 43 | 44 | Arguments: 45 | --config - Load pixelpilot config from file (Default: /etc/pixelpilot.yaml) 46 | 47 | -p - UDP port for RTP video stream (Default: 5600) 48 | 49 | --socket - read data from socket 50 | 51 | --mavlink-port - UDP port for mavlink telemetry (Default: 14550) 52 | 53 | --mavlink-dvr-on-arm - Start recording when armed 54 | 55 | --codec - Video codec, should be the same as on VTX (Default: h265 ) 56 | 57 | --log-level - Log verbosity level, debug|info|warn|error (Default: info) 58 | 59 | --osd - Enable OSD 60 | 61 | --osd-config - Path to OSD configuration file 62 | 63 | --osd-refresh - Defines the delay between osd refresh (Default: 1000 ms) 64 | 65 | --osd-custom-message - Enables the display of /run/pixelpilot.msg (beta feature, may be removed) 66 | 67 | --dvr-template - Save the video feed (no osd) to the provided filename template. 68 | DVR is toggled by SIGUSR1 signal 69 | Supports placeholders %Y - year, %m - month, %d - day, 70 | %H - hour, %M - minute, %S - second. Ex: /media/DVR/%Y-%m-%d_%H-%M-%S.mp4 71 | 72 | --dvr-sequenced-files - Prepend a sequence number to the names of the dvr files 73 | 74 | --dvr-start - Start DVR immediately 75 | 76 | --dvr-framerate - Force the dvr framerate for smoother dvr, ex: 60 77 | 78 | --dvr-fmp4 - Save the video feed as a fragmented mp4 79 | 80 | --screen-mode - Override default screen mode. x@ ex: 1920x1080@120 81 | 82 | --video-plane-id - Override default drm plane used for video by plane-id 83 | 84 | --video-scale - Scale video output size (0.5 <= factor <= 1.0) (Default: 1.0) 85 | 86 | --osd-plane-id - Override default drm plane used for osd by plane-id 87 | 88 | --disable-vsync - Disable VSYNC commits 89 | 90 | --screen-mode-list - Print the list of supported screen modes and exit. 91 | 92 | --wfb-api-port - Port of wfb-server for cli statistics. (Default: 8003) 93 | Use "0" to disable this stats 94 | 95 | --version - Show program version 96 | 97 | ``` 98 | 99 | # DESCRIPTION 100 | 101 | WFB-ng client (Video Decoder) for Rockchip platform powered by the Rockchip MPP library. It also 102 | displays a simple cairo based OSD that shows the bandwidth, decoding latency, and framerate of 103 | the decoded video, and wfb-ng link statistics. 104 | 105 | # OPTIONS 106 | 107 | The program follows the usual GNU command line syntax, with long options 108 | starting with two dashes ('-'). A summary of options is included below. For 109 | a complete description, see the **info**(1) files. 110 | 111 | **--config \** 112 | : Load pixelpilot config from file (Default: /etc/pixelpilot.yaml) 113 | 114 | **-p \** 115 | : UDP port for RTP video stream (Default: 5600) 116 | 117 | **--socket \** 118 | : read data from socket 119 | 120 | **--mavlink-port \** 121 | : UDP port for mavlink telemetry (Default: 14550) 122 | 123 | **--mavlink-dvr-on-arm** 124 | : Start recording when armed 125 | 126 | **--codec \** 127 | : Video codec, should be the same as on VTX (Default: h265 ) 128 | 129 | **--log-level \** 130 | : Log verbosity level, debug|info|warn|error (Default: info) 131 | 132 | **--osd** 133 | : Enable OSD 134 | 135 | **--osd-config \** 136 | : Path to OSD configuration file 137 | 138 | **--osd-refresh \** 139 | : Defines the delay between osd refresh (Default: 1000 ms) 140 | 141 | **--osd-custom-message** 142 | : Enables the display of /run/pixelpilot.msg (beta feature, may be removed) 143 | 144 | **--dvr-template \** 145 | : Save the video feed (no osd) to the provided filename template. 146 | DVR is toggled by SIGUSR1 signal 147 | Supports placeholders %Y - year, %m - month, %d - day, 148 | %H - hour, %M - minute, %S - second. Ex: /media/DVR/%Y-%m-%d_%H-%M-%S.mp4 149 | 150 | **--dvr-sequenced-files** 151 | : Prepend a sequence number to the names of the dvr files 152 | 153 | **--dvr-start** 154 | : Start DVR immediately 155 | 156 | **--dvr-framerate \** 157 | : Force the dvr framerate for smoother dvr, ex: 60 158 | 159 | **--dvr-fmp4** 160 | : Save the video feed as a fragmented mp4 161 | 162 | **--screen-mode \** 163 | : Override default screen mode. `x@` ex: 1920x1080@120 164 | 165 | **--video-plane-id** 166 | : Override default drm plane used for video by plane-id 167 | 168 | **--video-scale \** 169 | : Scale video output size (0.5 <= factor <= 1.0) (Default: 1.0) 170 | 171 | **--osd-plane-id** 172 | : Override default drm plane used for osd by plane-id 173 | 174 | **--disable-vsync** 175 | : Disable VSYNC commits 176 | 177 | **--screen-mode-list** 178 | : Print the list of supported screen modes and exit. 179 | 180 | **--wfb-api-port** 181 | : Port of wfb-server for cli statistics. (Default: 8003) 182 | Use "0" to disable this stats 183 | 184 | **--version** 185 | : Show program version 186 | 187 | # FILES 188 | 189 | /etc/default/pixelpilot 190 | : Start-up configuration 191 | 192 | # ENVIRONMENT 193 | 194 | 195 | # DIAGNOSTICS 196 | 197 | To increase the log verbosity, use `--log-level` parameter, however keep in mind that 198 | `debug` level is only enabled in debug builds. 199 | 200 | # BUGS 201 | 202 | The upstream BTS can be found at https://github.com/OpenIPC/PixelPilot_rk 203 | 204 | # SEE ALSO 205 | 206 | **wfb-ng**(1) 207 | 208 | # AUTHOR 209 | 210 | Sergey Prokhorov 211 | : Wrote this manpage for the Debian system. 212 | 213 | # COPYRIGHT 214 | 215 | Copyright © 2025 Sergey Prokhorov 216 | 217 | This manual page was written for the Debian system (and may be used by 218 | others). 219 | 220 | Permission is granted to copy, distribute and/or modify this document under 221 | the terms of the GNU General Public License, Version 2 or (at your option) 222 | any later version published by the Free Software Foundation. 223 | 224 | On Debian systems, the complete text of the GNU General Public License 225 | can be found in /usr/share/common-licenses/GPL. 226 | 227 | [comment]: # Local Variables: 228 | [comment]: # mode: markdown 229 | [comment]: # End: 230 | -------------------------------------------------------------------------------- /src/dvr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "spdlog/spdlog.h" 9 | 10 | #include "dvr.h" 11 | #include "minimp4.h" 12 | 13 | #include "gstrtpreceiver.h" 14 | extern "C" { 15 | #include "osd.h" 16 | } 17 | 18 | namespace fs = std::filesystem; 19 | 20 | int dvr_enabled = 0; 21 | const int SEQUENCE_PADDING = 4; // Configurable padding for sequence numbers 22 | 23 | int write_callback(int64_t offset, const void *buffer, size_t size, void *token){ 24 | FILE *f = (FILE*)token; 25 | fseek(f, offset, SEEK_SET); 26 | return fwrite(buffer, 1, size, f) != size; 27 | } 28 | 29 | Dvr::Dvr(dvr_thread_params params) { 30 | filename_template = params.filename_template; 31 | mp4_fragmentation_mode = params.mp4_fragmentation_mode; 32 | dvr_filenames_with_sequence = params.dvr_filenames_with_sequence; 33 | video_framerate = params.video_framerate; 34 | video_frm_width = params.video_p.video_frm_width; 35 | video_frm_height = params.video_p.video_frm_height; 36 | codec = params.video_p.codec; 37 | dvr_file = NULL; 38 | mp4wr = (mp4_h26x_writer_t *)malloc(sizeof(mp4_h26x_writer_t)); 39 | } 40 | 41 | Dvr::~Dvr() {} 42 | 43 | void Dvr::frame(std::shared_ptr> frame) { 44 | dvr_rpc rpc = { 45 | .command = dvr_rpc::RPC_FRAME, 46 | .frame = frame 47 | }; 48 | enqueue_dvr_command(rpc); 49 | } 50 | 51 | void Dvr::set_video_params(uint32_t video_frm_w, 52 | uint32_t video_frm_h, 53 | VideoCodec codec) { 54 | video_frm_width = video_frm_w; 55 | video_frm_height = video_frm_h; 56 | codec = codec; 57 | dvr_rpc rpc = { 58 | .command = dvr_rpc::RPC_SET_PARAMS 59 | }; 60 | enqueue_dvr_command(rpc); 61 | } 62 | 63 | void Dvr::start_recording() { 64 | dvr_rpc rpc = { 65 | .command = dvr_rpc::RPC_START 66 | }; 67 | enqueue_dvr_command(rpc); 68 | } 69 | 70 | void Dvr::stop_recording() { 71 | dvr_rpc rpc = { 72 | .command = dvr_rpc::RPC_STOP 73 | }; 74 | enqueue_dvr_command(rpc); 75 | } 76 | 77 | void Dvr::set_video_framerate(int rate) { 78 | video_framerate = rate; 79 | spdlog::info("Changeing video framerate to {}",video_framerate); 80 | } 81 | 82 | void Dvr::toggle_recording() { 83 | dvr_rpc rpc = { 84 | .command = dvr_rpc::RPC_TOGGLE 85 | }; 86 | enqueue_dvr_command(rpc); 87 | }; 88 | 89 | void Dvr::shutdown() { 90 | dvr_rpc rpc = { 91 | .command = dvr_rpc::RPC_SHUTDOWN 92 | }; 93 | enqueue_dvr_command(rpc); 94 | }; 95 | 96 | void Dvr::enqueue_dvr_command(dvr_rpc rpc) { 97 | { 98 | std::lock_guard lock(mtx); 99 | dvrQueue.push(rpc); 100 | } 101 | cv.notify_one(); 102 | } 103 | 104 | void *Dvr::__THREAD__(void *param) { 105 | pthread_setname_np(pthread_self(), "__DVR"); 106 | ((Dvr *)param)->loop(); 107 | return nullptr; 108 | } 109 | 110 | void Dvr::loop() { 111 | while (true) { 112 | std::unique_lock lock(mtx); 113 | cv.wait(lock, [this] { return !this->dvrQueue.empty(); }); 114 | if (dvrQueue.empty()) { 115 | break; 116 | } 117 | if (!dvrQueue.empty()) { 118 | dvr_rpc rpc = dvrQueue.front(); 119 | dvrQueue.pop(); 120 | lock.unlock(); 121 | switch (rpc.command) { 122 | case dvr_rpc::RPC_SET_PARAMS: 123 | { 124 | SPDLOG_DEBUG("got rpc SET_PARAMS"); 125 | if (dvr_file == NULL) { 126 | break; 127 | } 128 | init(); 129 | break; 130 | } 131 | case dvr_rpc::RPC_START: 132 | { 133 | SPDLOG_DEBUG("got rpc START"); 134 | if (dvr_file != NULL) { 135 | break; 136 | } 137 | start(); 138 | if (video_frm_width > 0 && video_frm_height > 0) { 139 | init(); 140 | } 141 | break; 142 | } 143 | case dvr_rpc::RPC_STOP: 144 | { 145 | SPDLOG_DEBUG("got rpc STOP"); 146 | if (dvr_file == NULL) { 147 | break; 148 | } 149 | stop(); 150 | break; 151 | } 152 | case dvr_rpc::RPC_TOGGLE: 153 | { 154 | SPDLOG_DEBUG("got rpc TOGGLE"); 155 | if (dvr_file == NULL) { 156 | start(); 157 | if (video_frm_width > 0 && video_frm_height > 0) { 158 | init(); 159 | } 160 | } else { 161 | stop(); 162 | } 163 | break; 164 | } 165 | case dvr_rpc::RPC_FRAME: 166 | { 167 | if (!_ready_to_write) { 168 | break; 169 | } 170 | std::shared_ptr> frame = rpc.frame; 171 | auto res = mp4_h26x_write_nal(mp4wr, frame->data(), frame->size(), 90000/video_framerate); 172 | if (!(MP4E_STATUS_OK == res || MP4E_STATUS_BAD_ARGUMENTS == res)) { 173 | spdlog::warn("mp4_h26x_write_nal failed with error {}", res); 174 | } 175 | break; 176 | } 177 | case dvr_rpc::RPC_SHUTDOWN: 178 | goto end; 179 | } 180 | } 181 | } 182 | end: 183 | if (dvr_file != NULL) { 184 | stop(); 185 | } 186 | spdlog::info("DVR thread done."); 187 | } 188 | 189 | int Dvr::start() { 190 | char *fname_tpl = filename_template; 191 | std::string rec_dir, filename_pattern; 192 | fs::path pathObj(filename_template); 193 | rec_dir = pathObj.parent_path().string(); 194 | filename_pattern = pathObj.filename().string(); 195 | std::string paddedNumber = ""; 196 | 197 | // Ensure the directory exists 198 | if (!fs::exists(rec_dir)) 199 | { 200 | spdlog::error("Error: Directory does not exist: {}", rec_dir); 201 | return -1; 202 | } 203 | 204 | if (dvr_filenames_with_sequence) { 205 | // Get the next file number 206 | std::regex pattern(R"(^(\d+)_.*)"); // Matches filenames that start with digits followed by '_' 207 | int maxNumber = -1; 208 | int nextFileNumber = 0; 209 | 210 | for (const auto &entry : fs::directory_iterator(rec_dir)) { 211 | if (entry.is_regular_file()) 212 | { 213 | std::string filename = entry.path().filename().string(); 214 | std::smatch match; 215 | 216 | if (std::regex_match(filename, match, pattern)) 217 | { 218 | int number = std::stoi(match[1].str()); 219 | maxNumber = std::max(maxNumber, number); 220 | } 221 | } 222 | } 223 | if (maxNumber == -1) { 224 | nextFileNumber = 0; 225 | } else { 226 | nextFileNumber = maxNumber + 1; 227 | } 228 | 229 | // Zero-pad the number 230 | std::ostringstream stream; 231 | stream << std::setw(SEQUENCE_PADDING) << std::setfill('0') << nextFileNumber; 232 | paddedNumber = stream.str() + "_"; 233 | } 234 | 235 | // Generate timestamped filename 236 | std::time_t now = std::time(nullptr); 237 | std::tm *localTime = std::localtime(&now); 238 | 239 | char formattedFilename[256]; 240 | std::strftime(formattedFilename, sizeof(formattedFilename), filename_pattern.c_str(), localTime); 241 | 242 | // Construct final filename 243 | std::string finalFilename = rec_dir + "/" + paddedNumber + formattedFilename; 244 | 245 | if ((dvr_file = fopen(finalFilename.c_str(), "w")) == NULL) { 246 | spdlog::error("unable to open DVR file {}", finalFilename); 247 | return -1; 248 | } 249 | osd_publish_bool_fact("dvr.recording", NULL, 0, true); 250 | dvr_enabled = 1; 251 | mux = MP4E_open(0 /*sequential_mode*/, mp4_fragmentation_mode, dvr_file, write_callback); 252 | return 0; 253 | } 254 | 255 | void Dvr::init() { 256 | spdlog::info("setting up dvr and mux to {}x{}", video_frm_width, video_frm_height); 257 | if (MP4E_STATUS_OK != mp4_h26x_write_init(mp4wr, mux, 258 | video_frm_width, 259 | video_frm_height, 260 | codec==VideoCodec::H265)) { 261 | spdlog::error("mp4_h26x_write_init failed"); 262 | mux = NULL; 263 | dvr_file = NULL; 264 | } 265 | _ready_to_write = 1; 266 | } 267 | 268 | void Dvr::stop() { 269 | MP4E_close(mux); 270 | mp4_h26x_write_close(mp4wr); 271 | fclose(dvr_file); 272 | dvr_file = NULL; 273 | osd_publish_bool_fact("dvr.recording", NULL, 0, false); 274 | dvr_enabled = 0; 275 | _ready_to_write = 0; 276 | } 277 | 278 | 279 | // C-compatible interface 280 | extern "C" { 281 | void dvr_start_recording(Dvr* dvr) { 282 | if (dvr) { 283 | dvr->start_recording(); 284 | } 285 | } 286 | 287 | void dvr_stop_recording(Dvr* dvr) { 288 | if (dvr) { 289 | dvr->stop_recording(); 290 | } 291 | } 292 | 293 | void dvr_set_video_framerate(Dvr* dvr, int f) { 294 | if (dvr) { 295 | dvr->set_video_framerate(f); 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/gsmenu/air_alink.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "executor.h" 5 | #include "styles.h" 6 | #include "lvgl/lvgl.h" 7 | #include "helper.h" 8 | #include "../input.h" 9 | 10 | extern lv_group_t * default_group; 11 | extern lv_indev_t * indev_drv; 12 | extern gsmenu_control_mode_t control_mode; 13 | 14 | extern lv_obj_t * txprofiles_screen; 15 | extern lv_group_t * tx_profile_group; 16 | lv_obj_t * txprofiles; 17 | 18 | // # set desired power output (0 pitmode, 4 highest power)(scales with MCS) 19 | lv_obj_t * power_level_0_to_4; 20 | // ### if gs heartbeat lost for x ms, set link low (fallback) default 1000 21 | lv_obj_t * fallback_ms; 22 | // # keep link low for min x s default 1 23 | lv_obj_t * hold_fallback_mode_s; 24 | // ### limit time between any link change and the next default 200 25 | lv_obj_t * min_between_changes_ms; 26 | // # wait x seconds before increasing link speed default 3 27 | lv_obj_t * hold_modes_down_s; 28 | 29 | // ### smooth out rssi/snr readings for link increase / decrease 30 | lv_obj_t * hysteresis_percent; // default 5 31 | lv_obj_t * hysteresis_percent_down; // default 5 32 | lv_obj_t * exp_smoothing_factor; // default 0.1 33 | lv_obj_t * exp_smoothing_factor_down; // default 1.0 34 | 35 | // ### allow lost GS packet to request new keyframe 36 | lv_obj_t * allow_request_keyframe; // default 1 37 | // # allow drone driver-tx_dropped to request new keyframe 38 | lv_obj_t * allow_rq_kf_by_tx_d; // default 1 39 | // # how often to check driver-xtx 40 | lv_obj_t * check_xtx_period_ms; // default 2250 41 | // # limit time between keyframe requests 42 | lv_obj_t * request_keyframe_interval_ms; // default 1112 43 | // # request a keyframe at every link change 44 | lv_obj_t * idr_every_change; // default 0 45 | 46 | // ### enable higher quality in center of image 47 | // roi_focus_mode=0 48 | 49 | // ### allow dynamic fec 50 | // allow_dynamic_fec=0 51 | // # by 1 decreasing k, or 0 increasing n 52 | // fec_k_adjust=1 53 | // # disable when bitrate is <= 4000 54 | // spike_fix_dynamic_fec=0 55 | 56 | // ### attempt to help encoder bitrate spikes by strategically lowering FPS when on high resolutions 57 | // allow_spike_fix_fps=0 58 | // # reduce bitrate on driver-tx dropped for 1 second or until xtx stops 59 | // allow_xtx_reduce_bitrate=1 60 | // # reduce bitrate to 61 | // xtx_reduce_bitrate_factor=0.8 62 | 63 | // ### how much info on OSD (0 to 6). 4 = all on one line. 6 = all on multiple lines 64 | lv_obj_t * osd_level; // default 0 65 | // # make custom text smaller/bigger 66 | lv_obj_t * multiply_font_size_by; // default 1 67 | 68 | static void txprofiles_callback(lv_event_t * e) 69 | { 70 | lv_screen_load(txprofiles_screen); 71 | lv_indev_set_group(indev_drv,tx_profile_group); 72 | lv_obj_t * first_obj = find_first_focusable_obj(txprofiles_screen); 73 | lv_group_focus_obj(first_obj); 74 | } 75 | 76 | void create_air_alink_menu(lv_obj_t * parent) { 77 | 78 | menu_page_data_t *menu_page_data = malloc(sizeof(menu_page_data_t)); 79 | strcpy(menu_page_data->type, "air"); 80 | strcpy(menu_page_data->page, "alink"); 81 | menu_page_data->page_load_callback = generic_page_load_callback; 82 | menu_page_data->indev_group = lv_group_create(); 83 | menu_page_data->entry_count = 0; 84 | menu_page_data->page_entries = NULL; 85 | lv_group_set_default(menu_page_data->indev_group); 86 | lv_obj_set_user_data(parent,menu_page_data); 87 | 88 | lv_obj_t * cont; 89 | lv_obj_t * section; 90 | 91 | section = lv_menu_section_create(parent); 92 | lv_obj_add_style(section, &style_openipc_section, 0); 93 | cont = lv_menu_cont_create(section); 94 | lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); 95 | 96 | txprofiles = create_button(cont, "TX-Profiles"); 97 | lv_obj_add_event_cb(lv_obj_get_child_by_type(txprofiles,0,&lv_button_class),txprofiles_callback,LV_EVENT_CLICKED,NULL); 98 | 99 | power_level_0_to_4 = create_dropdown(cont,LV_SYMBOL_SETTINGS, "Desired power output (0 pitmode, 4 highest power)","","power_level_0_to_4",menu_page_data,false); 100 | fallback_ms = create_slider(cont,LV_SYMBOL_SETTINGS,"If GS heartbeat lost for x ms, set link low (fallback) (ms)","fallback_ms",menu_page_data,false,0); 101 | hold_fallback_mode_s = create_slider(cont,LV_SYMBOL_SETTINGS,"Keep link low for min x s","hold_fallback_mode_s",menu_page_data,false,0); 102 | min_between_changes_ms = create_slider(cont,LV_SYMBOL_SETTINGS, "Limit time between any link change (ms)","min_between_changes_ms",menu_page_data,false,0); 103 | hold_modes_down_s = create_slider(cont,LV_SYMBOL_SETTINGS, "Wait x seconds before increasing link speed (s)","hold_modes_down_s",menu_page_data,false,0); 104 | hysteresis_percent = create_slider(cont,LV_SYMBOL_SETTINGS, "Hysteresis (%)","hysteresis_percent",menu_page_data,false,0); 105 | hysteresis_percent_down = create_slider(cont,LV_SYMBOL_SETTINGS, "Hysteresis down (%)","hysteresis_percent_down",menu_page_data,false,0); 106 | exp_smoothing_factor = create_slider(cont,LV_SYMBOL_SETTINGS, "exp_smoothing_factor","exp_smoothing_factor",menu_page_data,false,1); 107 | exp_smoothing_factor_down = create_slider(cont,LV_SYMBOL_SETTINGS, "exp_smoothing_factor_down","exp_smoothing_factor_down",menu_page_data,false,1); 108 | allow_request_keyframe = create_switch(cont,LV_SYMBOL_SETTINGS,"Allow lost GS packet to request new keyframe","allow_request_keyframe", menu_page_data,false); 109 | allow_rq_kf_by_tx_d = create_switch(cont,LV_SYMBOL_SETTINGS,"Allow drone driver-tx_dropped to request new keyframe","allow_rq_kf_by_tx_d", menu_page_data,false); 110 | check_xtx_period_ms = create_slider(cont,LV_SYMBOL_SETTINGS, "How often to check driver-xtx","check_xtx_period_ms",menu_page_data,false,0); 111 | request_keyframe_interval_ms = create_slider(cont,LV_SYMBOL_SETTINGS, "Limit time between keyframe requests","request_keyframe_interval_ms",menu_page_data,false,0); 112 | idr_every_change = create_switch(cont,LV_SYMBOL_SETTINGS,"Request a keyframe at every link change","idr_every_change", menu_page_data,false); 113 | osd_level = create_dropdown(cont,LV_SYMBOL_SETTINGS, "OSD Details 4 = all on one line. 6 = all on multiple lines","","osd_level",menu_page_data,false); 114 | multiply_font_size_by = create_slider(cont,LV_SYMBOL_SETTINGS, "Font Size","multiply_font_size_by",menu_page_data,false,1); 115 | 116 | add_entry_to_menu_page(menu_page_data,"Loading power_level_0_to_4 ...", power_level_0_to_4, reload_dropdown_value); 117 | add_entry_to_menu_page(menu_page_data,"Loading fallback_ms ...", fallback_ms, reload_slider_value); 118 | add_entry_to_menu_page(menu_page_data,"Loading hold_fallback_mode_s ...", hold_fallback_mode_s, reload_slider_value); 119 | add_entry_to_menu_page(menu_page_data,"Loading min_between_changes_ms ...", min_between_changes_ms, reload_slider_value); 120 | add_entry_to_menu_page(menu_page_data,"Loading hold_modes_down_s ...", hold_modes_down_s, reload_slider_value); 121 | add_entry_to_menu_page(menu_page_data,"Loading hysteresis_percent ...", hysteresis_percent, reload_slider_value); 122 | add_entry_to_menu_page(menu_page_data,"Loading hysteresis_percent_down ...", hysteresis_percent_down, reload_slider_value); 123 | add_entry_to_menu_page(menu_page_data,"Loading exp_smoothing_factor ...", exp_smoothing_factor, reload_slider_value); 124 | add_entry_to_menu_page(menu_page_data,"Loading exp_smoothing_factor_down ...", exp_smoothing_factor_down, reload_slider_value); 125 | add_entry_to_menu_page(menu_page_data,"Loading allow_request_keyframe ...", allow_request_keyframe, reload_switch_value); 126 | add_entry_to_menu_page(menu_page_data,"Loading allow_rq_kf_by_tx_d ...", allow_rq_kf_by_tx_d, reload_switch_value); 127 | add_entry_to_menu_page(menu_page_data,"Loading check_xtx_period_ms ...", check_xtx_period_ms, reload_slider_value); 128 | add_entry_to_menu_page(menu_page_data,"Loading request_keyframe_interval_ms ...", request_keyframe_interval_ms, reload_slider_value); 129 | add_entry_to_menu_page(menu_page_data,"Loading idr_every_change ...", idr_every_change, reload_switch_value); 130 | add_entry_to_menu_page(menu_page_data,"Loading osd_level ...", osd_level, reload_dropdown_value); 131 | add_entry_to_menu_page(menu_page_data,"Loading multiply_font_size_by ...", multiply_font_size_by, reload_slider_value); 132 | 133 | lv_group_set_default(default_group); 134 | } -------------------------------------------------------------------------------- /src/notes: -------------------------------------------------------------------------------- 1 | // Mavlink elements 2 | uint32_t x_center = buf->width / 2; 3 | if (osd_vars.osd_mode > 0){ 4 | // Artificial Horizon 5 | int32_t offset_pitch = osd_vars.telemetry_pitch * 4; 6 | int32_t offset_roll = osd_vars.telemetry_roll * 4; 7 | int32_t y_pos_left = ((int32_t)buf->height / 2 - 2 + offset_pitch + offset_roll); 8 | int32_t y_pos_right = ((int32_t)buf->height / 2 - 2 + offset_pitch - offset_roll); 9 | - 10 | for (int i = 0; i < 4; i++) { 11 | if (y_pos_left > 0 && y_pos_left < buf->height && 12 | y_pos_right > 0 && y_pos_right < buf->height) { 13 | //fbg_line(cr, x_center - 180, y_pos_left + i, x_center + 180, y_pos_right + i, 255, 255, 255); 14 | } 15 | } 16 | - 17 | // Vertical Speedometer 18 | int32_t offset_vspeed = osd_vars.telemetry_vspeed * 5; 19 | int32_t y_pos_vspeed = ((int32_t)buf->height / 2 - offset_vspeed); 20 | for (int i = 0; i < 8; i++) { 21 | if (y_pos_vspeed > 0 && y_pos_vspeed < buf->height) { 22 | //fbg_line(cr, x_center + 242 + i, buf->height / 2, x_center + 242 + i, y_pos_vspeed, 255, 255, 255); 23 | } 24 | } 25 | - 26 | for (int i = 0; i < 25; i++) { 27 | uint32_t width = (i == 12) ? 10 : 0; 28 | - 29 | // fbg_line(cr, x_center - 240 - width, 30 | // buf->height / 2 - 120 + i * 10, x_center - 220, 31 | // buf->height / 2 - 120 + i * 10, 255, 255, 255); 32 | // fbg_line(cr, x_center - 240 - width, 33 | // buf->height / 2 - 120 + i * 10 + 1, x_center - 220, 34 | // buf->height / 2 - 120 + i * 10 + 1, 255, 255, 255); 35 | - 36 | // fbg_line(cr, x_center + 220, buf->height / 2 - 120 + i * 10, 37 | // x_center + 240 + width, buf->height / 2 - 120 + i * 10, 255, 255, 255); 38 | // fbg_line(cr, x_center + 220, buf->height / 2 - 120 + i * 10 + 1, 39 | // x_center + 240 + width, buf->height / 2 - 120 + i * 10 + 1, 255, 255, 255); 40 | } 41 | - 42 | // OSD telemetry 43 | sprintf(msg, "ALT:%.00fM", osd_vars.telemetry_altitude); 44 | cairo_move_to(cr, x_center + (20) + 260, buf->height / 2 - 8); 45 | cairo_show_text(cr, msg); 46 | sprintf(msg, "SPD:%.00fKM/H", osd_vars.telemetry_gspeed); 47 | cairo_move_to(cr, x_center - (16 * 3) - 360, buf->height / 2 - 8); 48 | cairo_show_text(cr, msg); 49 | sprintf(msg, "VSPD:%.00fM/S", osd_vars.telemetry_vspeed); 50 | cairo_move_to(cr, x_center + (20) + 260, buf->height / 2 + 22); 51 | cairo_show_text(cr, msg); 52 | } 53 | - 54 | sprintf(msg, "BAT:%.02fV", osd_vars.telemetry_battery / 1000); 55 | cairo_move_to(cr, 40, buf->height - 30); 56 | cairo_show_text(cr, msg); 57 | sprintf(msg, "CONS:%.00fmAh", osd_vars.telemetry_current_consumed); 58 | cairo_move_to(cr, 40, buf->height - 60); 59 | cairo_show_text(cr, msg); 60 | sprintf(msg, "CUR:%.02fA", osd_vars.telemetry_current / 100); 61 | cairo_move_to(cr, 40, buf->height - 90); 62 | cairo_show_text(cr, msg); 63 | sprintf(msg, "THR:%.00f%%", osd_vars.telemetry_throttle); 64 | cairo_move_to(cr, 40, buf->height - 120); 65 | cairo_show_text(cr, msg); 66 | 67 | if (osd_vars.osd_mode > 0){ 68 | sprintf(msg, "SATS:%.00f", osd_vars.telemetry_sats); 69 | cairo_move_to(cr,buf->width - 140, buf->height - 30); 70 | cairo_show_text(cr, msg); 71 | sprintf(msg, "HDG:%.00f", osd_vars.telemetry_hdg); 72 | cairo_move_to(cr,buf->width - 140, buf->height - 120); 73 | cairo_show_text(cr, msg); 74 | sprintf(osd_vars.c1, "%.00f", osd_vars.telemetry_lat); 75 | - 76 | if (osd_vars.telemetry_lat < 10000000) { 77 | insertString(osd_vars.c1, "LAT:0.", 0); 78 | } 79 | - 80 | if (osd_vars.telemetry_lat > 9999999) { 81 | if (numOfChars(osd_vars.c1) == 8) { 82 | insertString(osd_vars.c1, ".", 1); 83 | } else { 84 | insertString(osd_vars.c1, ".", 2); 85 | } 86 | insertString(osd_vars.c1, "LAT:", 0); 87 | } 88 | cairo_move_to(cr, buf->width - 240, buf->height - 90); 89 | cairo_show_text(cr, osd_vars.c1); 90 | - 91 | sprintf(osd_vars.c2, "%.00f", osd_vars.telemetry_lon); 92 | if (osd_vars.telemetry_lon < 10000000) { 93 | insertString(osd_vars.c2, "LON:0.", 0); 94 | } 95 | if (osd_vars.telemetry_lon > 9999999) { 96 | if (numOfChars(osd_vars.c2) == 8) { 97 | insertString(osd_vars.c2, ".", 1); 98 | } else { 99 | insertString(osd_vars.c2, ".", 2); 100 | } 101 | insertString(osd_vars.c2, "LON:", 0); 102 | } 103 | cairo_move_to(cr, buf->width - 240, buf->height - 60); 104 | cairo_show_text(cr, osd_vars.c2); 105 | //sprintf(msg, "PITCH:%.00f", telemetry_pitch); 106 | //cairo_move_to(cr, msg, x_center + 440, buf->height - 140); 107 | //sprintf(msg, "ROLL:%.00f", telemetry_roll); 108 | //cairo_move_to(cr, msg, x_center + 440, buf->height - 170); 109 | sprintf(msg, "DIST:%.03fM", osd_vars.telemetry_distance); 110 | cairo_move_to(cr, x_center - 350, buf->height - 30); 111 | cairo_show_text(cr, msg); 112 | } 113 | 114 | sprintf(msg, "RSSI:%.00f", osd_vars.telemetry_rssi); 115 | cairo_move_to(cr, x_center - 50, buf->height - 30); 116 | cairo_show_text(cr, msg); 117 | - 118 | // Print rate stats 119 | struct timespec current_timestamp; 120 | if (!clock_gettime(CLOCK_MONOTONIC_COARSE, ¤t_timestamp)) { 121 | double interval = getTimeInterval(¤t_timestamp, &last_timestamp); 122 | if (interval > 1) { 123 | last_timestamp = current_timestamp; 124 | rx_rate = ((float)stats_rx_bytes+(((float)stats_rx_bytes*25)/100)) / 1024.0f * 8; 125 | stats_rx_bytes = 0; 126 | } 127 | } 128 | - 129 | uint64_t diff; 130 | struct timespec start, end; 131 | clock_gettime(CLOCK_MONOTONIC, &start); 132 | sleep(1); 133 | clock_gettime(CLOCK_MONOTONIC, &end); 134 | diff = BILLION * (end.tv_sec - start.tv_sec) + end.tv_nsec - start.tv_nsec; 135 | - 136 | char hud_frames_rx[32]; 137 | if (osd_vars.osd_mode > 0){ 138 | cairo_move_to(cr, x_center - strlen(hud_frames_rx) / 2 * 16, 40); 139 | cairo_show_text(cr, hud_frames_rx); 140 | } else { 141 | cairo_move_to(cr, buf->width - 300, buf->height - 60); 142 | cairo_show_text(cr, hud_frames_rx); 143 | sprintf(msg, "TIME:%.2d:%.2d", minutes,seconds); 144 | cairo_move_to(cr, buf->width - 300, buf->height - 90); 145 | cairo_show_text(cr, msg); 146 | if (osd_vars.telemetry_arm > 1700){ 147 | seconds = seconds + diff/1000000000; 148 | } 149 | if(seconds > 59){ 150 | seconds = 0; 151 | ++minutes; 152 | } 153 | if(minutes > 59){ 154 | seconds = 0; 155 | minutes = 0; 156 | } 157 | } 158 | float percent = rx_rate / (1024 * 10); 159 | if (percent > 1) { 160 | percent = 1; 161 | } 162 | - 163 | uint32_t width = (strlen(hud_frames_rx) * 16) * percent; 164 | if (osd_vars.osd_mode > 0){ 165 | cairo_set_source_rgba(cr, 255, 255, 255, 0.8); // R, G, B, A 166 | cairo_rectangle(cr, x_center - strlen(hud_frames_rx) / 2 * 16, 64, width, 8); 167 | } else { 168 | cairo_set_source_rgba(cr, 255, 255, 255, 0.8); // R, G, B, A 169 | cairo_rectangle(cr, buf->width - 300, buf->height - 36, width, 8); 170 | } --------------------------------------------------------------------------------