├── .github └── workflows │ └── build.yml ├── .gitignore ├── FAKEHD.md ├── LICENSE ├── Makefile ├── Makefile.dji ├── Makefile.unix ├── README.md ├── bold.png ├── config ├── airunit │ ├── config.json │ ├── schema.json │ ├── schemaV2.json │ └── uiSchemaV2.json └── goggles │ ├── config.json │ ├── schema.json │ ├── schemaV2.json │ └── uiSchemaV2.json ├── dictionaries └── dictionary_1.bin ├── docs ├── fonts │ ├── font.png │ ├── font_2.png │ ├── font_ardu.png │ ├── font_ardu_2.png │ ├── font_ardu_hd.png │ ├── font_ardu_hd_2.png │ ├── font_bf_hd.png │ ├── font_bf_hd_2.png │ ├── font_hd.png │ ├── font_hd_2.png │ ├── font_inav.png │ ├── font_inav_2.png │ ├── font_inav_hd.png │ ├── font_inav_hd_2.png │ ├── font_quic.png │ ├── font_quic_2.png │ ├── font_quic_hd.png │ ├── font_quic_hd_2.png │ ├── font_ultra.png │ ├── font_ultra_2.png │ ├── font_ultra_hd.png │ ├── font_ultra_hd_2.png │ └── gen.sh └── img │ ├── .DS_Store │ ├── fakehd_after.png │ ├── fakehd_before.png │ ├── fakehd_centered.png │ ├── fakehd_columns_b.png │ ├── fakehd_columns_m.png │ ├── fakehd_columns_t.png │ ├── fakehd_rows.png │ └── ports-vtx.png ├── fonts ├── font.png ├── font_ardu.png ├── font_ardu_hd.png ├── font_btfl.png ├── font_btfl_hd.png ├── font_hd.png ├── font_inav.png ├── font_inav_hd.png ├── font_quic.png ├── font_quic_hd.png ├── font_ultr.png └── font_ultr_hd.png ├── ipk ├── airunit │ ├── control │ │ ├── conffiles │ │ ├── control │ │ ├── postinst │ │ └── prerm │ └── data │ │ └── opt │ │ ├── bin │ │ └── airunit-osd-start.sh │ │ └── etc │ │ └── dinit.d │ │ └── msp-osd-airside └── goggle │ ├── control │ ├── conffiles │ ├── control │ ├── postinst │ ├── preinst │ └── prerm │ └── data │ └── opt │ └── etc │ └── dinit.d │ └── msp-osd-goggles ├── jni ├── Android.mk ├── Application.mk ├── displayport_osd_shim.c ├── fakehd │ ├── fakehd.c │ └── fakehd.h ├── font │ ├── font.c │ └── font.h ├── hw │ ├── dji_display.c │ ├── dji_display.h │ ├── dji_radio_shm.c │ ├── dji_radio_shm.h │ ├── dji_services.c │ ├── dji_services.h │ └── duml_hal.h ├── json │ ├── osd_config.c │ ├── osd_config.h │ ├── parson.c │ └── parson.h ├── libduml_hal.so ├── libspng │ ├── spng.c │ └── spng.h ├── lz4 │ ├── lz4.c │ └── lz4.h ├── msp │ ├── msp.c │ ├── msp.h │ ├── msp_displayport.c │ └── msp_displayport.h ├── msp_displayport_mux.c ├── net │ ├── data_protocol.h │ ├── network.c │ ├── network.h │ ├── serial.c │ └── serial.h ├── osd.h ├── osd_dji_overlay_udp.c ├── osd_dji_udp.c ├── osd_sfml_udp.c ├── rec │ ├── rec.c │ ├── rec.h │ ├── rec_pb.c │ ├── rec_pb.h │ ├── rec_shim.c │ ├── rec_shim.h │ ├── rec_util.c │ └── rec_util.h ├── toast │ ├── toast.c │ └── toast.h └── util │ ├── debug.h │ ├── display_info.h │ ├── fs_util.c │ ├── fs_util.h │ └── time_util.h └── libshims └── duml_hal.c /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | # Triggers the workflow on push or pull request events but only for the main branch 3 | push: 4 | branches: [ main ] 5 | tags: 6 | - '*' 7 | 8 | # pull_request: 9 | # branches: [ main ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | jobs: 15 | release: 16 | name: Create new releas and attach IPKs 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: write 20 | needs: build 21 | steps: 22 | - name: Branch name 23 | id: branch_name 24 | run: | 25 | echo ::set-output name=IS_TAG::${{ startsWith(github.ref, 'refs/tags/') }} 26 | echo ::set-output name=SOURCE_TAG::${GITHUB_REF#refs/tags/} 27 | echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/*/} 28 | 29 | - name: Download artifact 30 | env: 31 | IS_TAG: ${{ steps.branch_name.outputs.IS_TAG }} 32 | SOURCE_TAG: ${{ steps.branch_name.outputs.SOURCE_TAG }} 33 | if: "${{ env.IS_TAG == 'true' }}" 34 | uses: actions/download-artifact@v4 35 | with: 36 | name: ${{ env.ARTIFACT_NAME }} 37 | 38 | - name: View content 39 | env: 40 | IS_TAG: ${{ steps.branch_name.outputs.IS_TAG }} 41 | SOURCE_TAG: ${{ steps.branch_name.outputs.SOURCE_TAG }} 42 | if: "${{ env.IS_TAG == 'true' }}" 43 | run: ls -R 44 | 45 | - name: Create tagged release 46 | id: create_release 47 | env: 48 | IS_TAG: ${{ steps.branch_name.outputs.IS_TAG }} 49 | SOURCE_TAG: ${{ steps.branch_name.outputs.SOURCE_TAG }} 50 | if: "${{ env.IS_TAG == 'true' }}" 51 | uses: ncipollo/release-action@v1 52 | with: 53 | tag: ${{ env.SOURCE_TAG }} 54 | name: ${{ env.SOURCE_TAG }} 55 | token: ${{ secrets.GITHUB_TOKEN }} 56 | prerelease: false 57 | artifacts: "./*/*.ipk" 58 | 59 | build: 60 | name: Build .ipk packages 61 | runs-on: ubuntu-latest 62 | 63 | steps: 64 | - name: Checkout Code 65 | uses: actions/checkout@v2 66 | 67 | - name: Get GitHub Build Number (ENV) 68 | id: get_buildno 69 | run: echo "GITHUBBUILDNUMBER=${{ github.run_number }}" >> $GITHUB_ENV 70 | continue-on-error: true 71 | 72 | - name: Get repository name 73 | run: echo "REPOSITORY_NAME=$(echo '${{ github.repository }}' | cut -d '/' -f2)" >> $GITHUB_ENV 74 | shell: bash 75 | 76 | - name: Make artifact name 77 | id: make_artifactname 78 | run: | 79 | ARTIFACT_NAME="${{ env.REPOSITORY_NAME }}-${{ github.run_number }}" 80 | echo "${ARTIFACT_NAME}" 81 | echo "ARTIFACT_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV 82 | 83 | - uses: nttld/setup-ndk@v1 84 | id: setup-ndk 85 | with: 86 | ndk-version: r23b 87 | 88 | - name: Build .ipk 89 | env: 90 | ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} 91 | run: | 92 | export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/:$PATH 93 | make ipk 94 | 95 | - name: Upload release artifacts 96 | uses: actions/upload-artifact@v4 97 | with: 98 | name: ${{ env.ARTIFACT_NAME }} 99 | path: | 100 | ./ipk/*.ipk 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | osd_dji 3 | msp_displayport_mux 4 | ipk/goggle/build 5 | ipk/airunit/build 6 | *.ipk 7 | repo 8 | obj 9 | libs -------------------------------------------------------------------------------- /FAKEHD.md: -------------------------------------------------------------------------------- 1 | # FakeHD 2 | 3 | Betaflight's (before 4.4) OSD supports a 30 * 16 Grid, which looks large/blocky when displayed in the DJI Goggles. 4 | 5 | For versions of Betaflight before 4.4 (or other FC firmwares without HD support), as a workaround, we have a mode called "FakeHD". It chops up the OSD into sections and positions them evenly around an HD grid (with gaps between) - the way this is done is configurable, explained below. This then allows the use of smaller fonts - so it looks nicer/more in keeping with the built in Goggles OSD (but you still only have 30 columns / 16 rows to configure.... and you have the gaps to contend with). 6 | 7 | A diagram to help... 8 | 9 | | Before (in Configurator) | After (in Goggles) | 10 | | ------------------------------------------------------ | --------------------------------------------------- | 11 | | ![FakeHD Before](/docs/img/fakehd_before.png "Before") | ![FakeHD After](/docs/img/fakehd_after.png "After") | 12 | 13 | ##### To configure/enable: 14 | 15 | Visit https://fpv.wtf/package/fpv-wtf/msp-osd with your goggles connected, and check "Fake HD" 16 | 17 | Optionally, place custom fonts in the root of your sd card, using the names `font_bf_hd.png` 18 | 19 | Configuration of the grid is also possible; see below. 20 | 21 | No air unit/vista config is required. 22 | 23 | ##### Menu Switching - Getting rid of gaps when displaying Menu / Post Flight Stats + displaying centered: 24 | 25 | In order to have menus (accessible in Betaflight using stick commands) and post-flight stats appear in the center of the screen while using FakeHD, rather than having gaps + looking broken, you should set up menu switching. 26 | 27 | FakeHD can use the presence/absence of a character in the OSD as a switch to indicate when you are in regular OSD mode or in the menu/stats and switch to centering temporarily when needed. 28 | 29 | By default, the `Throttle Position` icon is used (character 4) - but you can set any character you want. It needs to be something that doesn't flash or change in the regular OSD, and ideally (but not essential) something that is never in the menu/post flight stats. The icons next to various elements are obvious choices here. You can set this using the `fakehd_menu_switch` configuration parameter. 30 | 31 | Betaflight has a list here: https://github.com/betaflight/betaflight/blob/master/docs/osd.md 32 | 33 | 34 | If you want to use FakeHD with some other Flight Controller, you will need to find an appropriate icon. (Let us know - we can include the information here). 35 | 36 | Finally, if you don't have anything in your OSD that works for menu switching, you can hide the menu switching character and the subsequent 5 characters, allowing you to add the `Throttle Position` element but not have to see it on screen. This is enabled by setting `fakehd_hide_menu_switch` to true. 37 | 38 | Notes: 39 | 40 | - Because of this switching feature, if you change to a different quad or OSD config (specifically the switch element is in a different place), FakeHD will center - you will need to reboot your Goggles to get it to recognise the switch element in a different location. 41 | 42 | - Also because of this switching, if you are editing OSD in the configurator with the goggles on to preview and you move the switching element around, it will cause the gaps to be disabled and everything to center. The new location of the switching element will be found next time you reboot the goggles and it'll work as normal. 43 | 44 | ##### I don't want gaps at all... 45 | 46 | Set config `fakehd_lock_center` to true and the center locking used for the menu / post flight stats will be enabled permanently (ie: you fly with it as well) - basically it places your 30 x 16 SD grid into the middle of an HD grid, there's never any gaps - see diagram below + compare to diagrams above. 47 | 48 | | After/Centered (in Goggles) `fakehd_lock_center` | 49 | | ------------------------------------------------------------------------------ | 50 | | After / Centered | 51 | 52 | ##### Customising the default FakeHD grid. 53 | 54 | By default, FakeHD positions your SD grid into the HD grid as per the before/after diagram above. 55 | 56 | If this doesn't work for you for whatever reason, some customisation is available. It's necessarily a little complicated. 57 | 58 | Each row can be set to one of: 59 | 60 | | Code | Description | 61 | | ---- | ------------------------------------------------------------------------------------------------------------------------------ | 62 | | L | Left aligned, the SD row occupies cell 1-30, no gaps | 63 | | C | Center aligned, the SD row occupies cell 16-45, no gaps | 64 | | R | Right aligned, , the SD row occupies cell 31-60, no gaps | 65 | | W | Split A - Row is split in 3, the FakeHD default, filling cells 1-10, 26-35, 51-60 | 66 | | T | Split B - Row is split in 2, touching the sides - filling cells 1-15 + 46-60 | 67 | | F | Split C - Row is split in 2 and away from the sides - filling cells 11-25 + 36-50 | 68 | | D | DJI Special - Row is centered but pushed a little left; used to posiution the bottom row between the existing DJI OSD elements | 69 | 70 | Columns 71 | 72 | And then the columns as a whole can be set to one of: 73 | 74 | | Code | Description | 75 | | ---- | --------------------------------------------------------------------------- | 76 | | T | Top aligned, OSD occupies rows 1-16 | 77 | | M | Center aligned, OSD occupies cells 4-19, no gaps | 78 | | B | Bottom aligned, , the OSD occupies rows 7-22 | 79 | | S | Split - FakeHD default - split in 3, OSD occupies rows 1 - 5, 9 - 13, 17-22 | 80 | 81 | Using the default row config; here's what they all look like: 82 | 83 | | T | M | B | S | 84 | | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------ | 85 | | T | M | B | S | 86 | 87 | ###### To configure rows 88 | 89 | Rows config accepts a 16 character long string; each character configuring it's corresponding row. The default FakeHD config would be set like this: 90 | 91 | `fakehd_rows = WWWWWWCCWWWWWWWD` 92 | 93 | The characters are case sensitive, but the configurator will reject invalid characters. 94 | 95 | ###### To configure columns 96 | 97 | Columns accepts a single character configuring how the whole grid is aligned. The default would be set like this: 98 | 99 | `fakehd_columns = S` 100 | 101 | The characters are case sensitive, but the configurator will reject invalid characters. 102 | 103 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ./Makefile.dji -------------------------------------------------------------------------------- /Makefile.dji: -------------------------------------------------------------------------------- 1 | CC=armv7a-linux-androideabi19-clang 2 | CFLAGS=-I. -O2 3 | LIB_SHIMS = libshims/libduml_hal.so 4 | 5 | .PHONY: repo 6 | 7 | %.o: %.c $(DEPS) 8 | $(CC) -c -o $@ $< $(CFLAGS) 9 | 10 | #this doesn't work by default 11 | #an extra duss_result_t(frame_pop_handler) 12 | #is generated that the compiler doesn't like 13 | libshims/%.c: %.h 14 | stubgen -g -e .c -l -N -t libshims -p "../" -n $< 15 | 16 | libshims/lib%.so: libshims/%.c 17 | $(CC) -Wno-c2x-extensions -O2 -shared -Wl,-soname,libduml_hal.so -o $@ $< $(CFLAGS) 18 | 19 | libshims: $(LIB_SHIMS) 20 | 21 | all: jni/* 22 | ndk-build 23 | 24 | install: all 25 | install -d ipk/goggle/build/data/opt/fonts/ 26 | install fonts/*.png ipk/goggle/build/data/opt/fonts/ 27 | install -d ipk/goggle/build/data/opt/mspdictionaries/ 28 | install dictionaries/*.bin ipk/goggle/build/data/opt/mspdictionaries/ 29 | install -d ipk/goggle/build/data/opt/etc/preload.d/ 30 | install libs/armeabi-v7a/libdisplayport_osd_shim.so ipk/goggle/build/data/opt/etc/preload.d/ 31 | install -d ipk/airunit/build/data/opt/bin/ 32 | install libs/armeabi-v7a/msp_displayport_mux ipk/airunit/build/data/opt/bin/ 33 | install -d ipk/airunit/build/data/opt/mspdictionaries/ 34 | install dictionaries/*.bin ipk/airunit/build/data/opt/mspdictionaries/ 35 | install -d ipk/airunit/build/data/opt/etc/package-config/msp-osd/ 36 | install config/airunit/* ipk/airunit/build/data/opt/etc/package-config/msp-osd/ 37 | install -d ipk/goggle/build/data/opt/etc/package-config/msp-osd/ 38 | install config/goggles/* ipk/goggle/build/data/opt/etc/package-config/msp-osd/ 39 | 40 | goggle_ipk: install 41 | $(eval PKG_NAME := $(shell cat ./ipk/goggle/control/control | grep Package | cut -d" " -f2)) 42 | $(eval ARCH := $(shell cat ./ipk/goggle/control/control | grep Architecture | cut -d" " -f2)) 43 | $(eval VERSION :=$(shell cat ./ipk/goggle/control/control | grep Version | cut -d" " -f2)) 44 | $(eval IPK_NAME := "${PKG_NAME}_${VERSION}_${ARCH}.ipk") 45 | cp -r ipk/goggle/data ipk/goggle/build/ 46 | echo "2.0" > ipk/goggle/build/debian-binary 47 | cp -r ipk/goggle/control ipk/goggle/build/ 48 | cd ipk/goggle/build/control && tar czvf ../control.tar.gz . 49 | cd ipk/goggle/build/data && tar czvf ../data.tar.gz . 50 | cd ipk/goggle/build && tar czvf "../../${IPK_NAME}" ./control.tar.gz ./data.tar.gz ./debian-binary 51 | 52 | airunit_ipk: install 53 | $(eval PKG_NAME := $(shell cat ./ipk/airunit/control/control | grep Package | cut -d" " -f2)) 54 | $(eval ARCH := $(shell cat ./ipk/airunit/control/control | grep Architecture | cut -d" " -f2)) 55 | $(eval VERSION :=$(shell cat ./ipk/airunit/control/control | grep Version | cut -d" " -f2)) 56 | $(eval IPK_NAME := "${PKG_NAME}_${VERSION}_${ARCH}.ipk") 57 | mkdir -p ipk/airunit/build 58 | echo "2.0" > ipk/airunit/build/debian-binary 59 | cp -r ipk/airunit/data ipk/airunit/build/ 60 | cp -r ipk/airunit/control ipk/airunit/build/ 61 | cd ipk/airunit/build/control && tar czvf ../control.tar.gz . 62 | cd ipk/airunit/build/data && tar czvf ../data.tar.gz . 63 | cd ipk/airunit/build && tar czvf "../../${IPK_NAME}" ./control.tar.gz ./data.tar.gz ./debian-binary 64 | 65 | ipk: install goggle_ipk airunit_ipk 66 | 67 | repo: ipk 68 | mkdir -p repo 69 | cp ipk/*.ipk repo/ 70 | ../opkg-utils-0.5.0/opkg-make-index ./repo/ > repo/Packages 71 | http-server -p 8042 ./repo/ 72 | 73 | clean: 74 | rm -rf **/*.o 75 | rm -rf *.o 76 | rm -rf repo 77 | rm -rf libshims/*.so 78 | rm -rf msp_displayport_mux 79 | rm -rf osd_dji 80 | rm -rf ipk/goggle/build 81 | rm -rf ipk/airunit/build 82 | rm -rf ipk/*.ipk 83 | rm -rf obj 84 | rm -rf libs 85 | -------------------------------------------------------------------------------- /Makefile.unix: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-I. -O2 3 | SRCDIR = jni/ 4 | DEPS = $(addprefix $(SRCDIR), msp/msp.h msp/msp_displayport.h net/network.h net/serial.h) 5 | OSD_OBJ = $(addprefix $(SRCDIR), osd_sfml_udp.o net/network.o msp/msp.o msp/msp_displayport.o) 6 | DISPLAYPORT_MUX_OBJ = $(addprefix $(SRCDIR), msp_displayport_mux.o net/serial.o net/network.o msp/msp.o) 7 | OSD_LIBS=-lcsfml-graphics 8 | 9 | %.o: %.c $(DEPS) 10 | $(CC) -c -o $@ $< $(CFLAGS) 11 | 12 | osd_sfml: $(OSD_OBJ) 13 | $(CC) -o $@ $^ $(CFLAGS) $(OSD_LIBS) 14 | 15 | msp_displayport_mux: $(DISPLAYPORT_MUX_OBJ) 16 | $(CC) -o $@ $^ $(CFLAGS) 17 | 18 | clean: 19 | rm -rf *.o 20 | rm -rf **/*.o 21 | rm -f msp_displayport_mux 22 | rm -f osd_sfml 23 | -------------------------------------------------------------------------------- /bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/bold.png -------------------------------------------------------------------------------- /config/airunit/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "fast_serial": false, 3 | "cache_serial": false, 4 | "osd_update_rate_hz": 10, 5 | "compress_osd": true, 6 | "disable_betaflight_hd": false 7 | } -------------------------------------------------------------------------------- /config/airunit/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "fast_serial": { 4 | "name": "Fast Serial", 5 | "widget": "checkbox" 6 | }, 7 | "cache_serial": { 8 | "name": "Cache Responses", 9 | "widget": "checkbox" 10 | }, 11 | "compress_osd": { 12 | "name": "Compress OSD", 13 | "widget": "checkbox" 14 | }, 15 | "osd_update_rate_hz": { 16 | "name": "OSD update rate", 17 | "widget": "range", 18 | "min": 2, 19 | "max": 20, 20 | "step":1 21 | }, 22 | "disable_betaflight_hd": { 23 | "name": "Compress OSD", 24 | "widget": "checkbox" 25 | } 26 | }, 27 | "units": [ 28 | "msp-osd-airside" 29 | ] 30 | } -------------------------------------------------------------------------------- /config/airunit/schemaV2.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "MSP OSD", 3 | "description": "Airside OSD config", 4 | "type": "object", 5 | "units": [ 6 | "msp-osd-airside" 7 | ], 8 | "required": [], 9 | "properties": { 10 | "compress_osd": { 11 | "title": "Compress OSD", 12 | "type": "boolean", 13 | "description": "", 14 | "enum": [ 15 | true, 16 | false 17 | ] 18 | }, 19 | "osd_update_rate_hz": { 20 | "title": "OSD update rate (for Compressed OSD)", 21 | "type": "number", 22 | "description": "", 23 | "minimum": 1, 24 | "maximum": 20 25 | }, 26 | "disable_betaflight_hd": { 27 | "title": "Disable Betaflight HD Mode", 28 | "type": "boolean", 29 | "description": "", 30 | "enum": [ 31 | true, 32 | false 33 | ] 34 | }, 35 | "fast_serial": { 36 | "type": "boolean", 37 | "title": "Fast Serial", 38 | "description": "", 39 | "enum": [ 40 | true, 41 | false 42 | ] 43 | }, 44 | "cache_serial": { 45 | "type": "boolean", 46 | "title": "Cache Responses", 47 | "description": "", 48 | "enum": [ 49 | true, 50 | false 51 | ] 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /config/airunit/uiSchemaV2.json: -------------------------------------------------------------------------------- 1 | { 2 | "compress_osd": { 3 | "ui:help": "Enable sending full frames of compressed data. Disable to send raw MSP data - we strongly recommend leaving this on. [Read more](https://github.com/fpv-wtf/msp-osd#Compressed-Transmission)" 4 | }, 5 | "osd_update_rate_hz": { 6 | "ui:help": "Configure the update rate in hz for the OSD when using compressed transmission (default 10)" 7 | }, 8 | "disable_betaflight_hd": { 9 | "ui:help": "Disable HD Mode, which is otherwise set by default if configured in Betaflight 4.4" 10 | }, 11 | "fast_serial": { 12 | "ui:help": "Change serial baud rate to 230400 baud, which can improve OSD performance in some situations - FC UART config must be changed to match." 13 | }, 14 | "cache_serial": { 15 | "ui:help": "Cache unimportant MSP messages for seldom-used features (like PID tuning in the DJI Goggles Settings Menu) to reduce serial pressure" 16 | } 17 | } -------------------------------------------------------------------------------- /config/goggles/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "show_waiting": true, 3 | "show_au_data": false, 4 | "fakehd_enable": false, 5 | "fakehd_hide_throttle_element": false, 6 | "fakehd_lock_center": false, 7 | "fakehd_menu_switch": 4, 8 | "fakehd_hide_menu_switch": false, 9 | "fakehd_layout_debug": false, 10 | "fakehd_columns": "S", 11 | "fakehd_rows": "WWWWWWCCWWWWWWWD", 12 | "rec_enabled": true, 13 | "rec_pb_enabled": true 14 | } 15 | -------------------------------------------------------------------------------- /config/goggles/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "show_waiting": { 4 | "name": "Show Waiting Message", 5 | "widget": "checkbox" 6 | }, 7 | "show_au_data": { 8 | "name": "Show VTx Temperature and Voltage", 9 | "widget": "checkbox" 10 | }, 11 | "fakehd_enable": { 12 | "name": "Enable FakeHD Mode", 13 | "widget": "checkbox" 14 | }, 15 | "fakehd_hide_throttle_element": { 16 | "name": "FakeHD hide throttle element", 17 | "widget": "checkbox" 18 | }, 19 | "fakehd_lock_center": { 20 | "name": "Lock FakeHD to centered mode", 21 | "widget": "checkbox" 22 | }, 23 | "fakehd_menu_switch": { 24 | "name": "FakeHD menu switch character", 25 | "widget": "range", 26 | "min": 1, 27 | "max": 512, 28 | "step":1 29 | }, 30 | "fakehd_hide_menu_switch": { 31 | "name": "FakeHD hide the menu switch", 32 | "widget": "checkbox" 33 | }, 34 | "fakehd_layout_debug": { 35 | "name": "Undocumented feature to fill the grid with numbers for layout debugging", 36 | "widget": "checkbox" 37 | }, 38 | "fakehd_columns": { 39 | "name": "FakeHD column config", 40 | "widget": "text", 41 | "pattern": "[TMBS]", 42 | "minLength": 1, 43 | "maxLength": 1 44 | }, 45 | "fakehd_rows": { 46 | "name": "FakeHD row config", 47 | "widget": "text", 48 | "pattern": "[LCRWTFD]{16}", 49 | "minLength": 16, 50 | "maxLength": 16 51 | }, 52 | "rec_enabled": { 53 | "name": "Enable OSD recording", 54 | "widget": "checkbox" 55 | }, 56 | "rec_pb_enabled": { 57 | "name": "Enable OSD playback", 58 | "widget": "checkbox" 59 | }, 60 | "hide_diagnostics": { 61 | "name": "Hide diagnostic information", 62 | "widget": "checkbox" 63 | } 64 | }, 65 | "units": [ 66 | "msp-osd-goggles" 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /config/goggles/schemaV2.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "MSP OSD - Goggles", 3 | "description": "", 4 | "type": "object", 5 | "units": [ "msp-osd-goggles" ], 6 | "required": [], 7 | "properties": { 8 | "show_waiting": { 9 | "type": "boolean", 10 | "title": "Show Waiting Message", 11 | "description": "", 12 | "enum": [ 13 | true, 14 | false 15 | ] 16 | }, 17 | "show_au_data": { 18 | "type": "boolean", 19 | "title": "Show VTx Temperature and Voltage", 20 | "description": "", 21 | "enum": [ 22 | true, 23 | false 24 | ] 25 | }, 26 | "rec_enabled": { 27 | "type": "boolean", 28 | "title": "Enable OSD recording", 29 | "description": "", 30 | "enum": [ 31 | true, 32 | false 33 | ] 34 | }, 35 | "rec_pb_enabled": { 36 | "type": "boolean", 37 | "title": "Enable OSD playback", 38 | "description": "", 39 | "enum": [ 40 | true, 41 | false 42 | ] 43 | }, 44 | "hide_diagnostics": { 45 | "type": "boolean", 46 | "title": "Hide diagnostic information", 47 | "description": "", 48 | "enum": [ 49 | true, 50 | false 51 | ] 52 | }, 53 | "fakehd_enable": { 54 | "type": "boolean", 55 | "title": "Enable FakeHD Mode", 56 | "description": "", 57 | "enum": [ 58 | true, 59 | false 60 | ] 61 | } 62 | }, 63 | "dependencies": { 64 | "fakehd_enable": { 65 | "oneOf": [ 66 | { 67 | "properties": { 68 | "fakehd_enable": { 69 | "enum": [ 70 | false 71 | ] 72 | } 73 | } 74 | }, 75 | { 76 | "properties": { 77 | "fakehd_enable": { 78 | "enum": [ 79 | true 80 | ] 81 | }, 82 | "fakehd_lock_center": { 83 | "type": "boolean", 84 | "title": "Lock FakeHD to centered mode", 85 | "description": "", 86 | "enum": [ 87 | true, 88 | false 89 | ] 90 | }, 91 | "fakehd_menu_switch": { 92 | "type": "number", 93 | "title": "FakeHD menu switch character", 94 | "description": "", 95 | "minimum": 1, 96 | "maximum": 512 97 | }, 98 | "fakehd_hide_menu_switch": { 99 | "type": "boolean", 100 | "title": "FakeHD hide the menu switch", 101 | "description": "", 102 | "enum": [ 103 | true, 104 | false 105 | ] 106 | }, 107 | "fakehd_columns": { 108 | "type": "string", 109 | "title": "FakeHD column config", 110 | "description": "", 111 | "oneOf": [ 112 | { 113 | "const": "T", 114 | "title": "Top" 115 | }, 116 | { 117 | "const": "M", 118 | "title": "Middle" 119 | }, 120 | { 121 | "const": "B", 122 | "title": "Bottom" 123 | }, 124 | { 125 | "const": "S", 126 | "title": "Split" 127 | } 128 | ] 129 | }, 130 | "fakehd_rows": { 131 | "type": "string", 132 | "title": "FakeHD row config", 133 | "description": "16 characters; each one of LCRWTFD", 134 | "maxLength": 16, 135 | "minLength": 16, 136 | "pattern": "^[LCRWTFD]{16}$" 137 | } 138 | } 139 | } 140 | ] 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /config/goggles/uiSchemaV2.json: -------------------------------------------------------------------------------- 1 | { 2 | "show_waiting": { 3 | "ui:help": "Enables or disables WAITING FOR OSD message" 4 | }, 5 | "show_au_data": { 6 | "ui:help": "Enables AU data (temp/voltage) overlay on the right" 7 | }, 8 | "rec_enabled": { 9 | "ui:help": "Enable OSD recording to .msp files alongside video" 10 | }, 11 | "rec_pb_enabled": { 12 | "ui:help": "Enable OSD playback if .msp file is stored alongside video (ie: OSD recording is enabled)" 13 | }, 14 | "hide_diagnostics": { 15 | "ui:help": "Hide the diagnostic information in the bottom right" 16 | }, 17 | "fakehd_enable": { 18 | "ui:help": "Enables FakeHD. [Read more](https://github.com/fpv-wtf/msp-osd#fakehd)" 19 | }, 20 | "fakehd_lock_center": { 21 | "ui:help": "Lock FakeHD in centered mode all the time; no gaps/spreading out even when you are flying. [Read more](https://github.com/fpv-wtf/msp-osd#i-dont-want-gaps-at-all)" 22 | }, 23 | "fakehd_menu_switch": { 24 | "ui:help": "FakeHD will use this character as the menu switch to detect when you are in menus/postflight and triggger centering. [Read more](https://github.com/fpv-wtf/msp-osd#menu-switching---getting-rid-of-gaps-when-displaying-menu--post-flight-stats--displaying-centered)" 25 | }, 26 | "fakehd_hide_menu_switch": { 27 | "ui:help": "FakeHD will hide the menu switch set above; and the next 5 characters" 28 | }, 29 | "fakehd_columns": { 30 | "ui:help": "FakeHD column alignment config. [Read more](https://github.com/fpv-wtf/msp-osd#customising-the-default-fakehd-grid)" 31 | }, 32 | "fakehd_rows": { 33 | "ui:help": "FakeHD row alignment config, each character configures the alignment for one row. [Read more](https://github.com/fpv-wtf/msp-osd#customising-the-default-fakehd-grid)" 34 | } 35 | } -------------------------------------------------------------------------------- /dictionaries/dictionary_1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/dictionaries/dictionary_1.bin -------------------------------------------------------------------------------- /docs/fonts/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font.png -------------------------------------------------------------------------------- /docs/fonts/font_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_2.png -------------------------------------------------------------------------------- /docs/fonts/font_ardu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_ardu.png -------------------------------------------------------------------------------- /docs/fonts/font_ardu_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_ardu_2.png -------------------------------------------------------------------------------- /docs/fonts/font_ardu_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_ardu_hd.png -------------------------------------------------------------------------------- /docs/fonts/font_ardu_hd_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_ardu_hd_2.png -------------------------------------------------------------------------------- /docs/fonts/font_bf_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_bf_hd.png -------------------------------------------------------------------------------- /docs/fonts/font_bf_hd_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_bf_hd_2.png -------------------------------------------------------------------------------- /docs/fonts/font_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_hd.png -------------------------------------------------------------------------------- /docs/fonts/font_hd_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_hd_2.png -------------------------------------------------------------------------------- /docs/fonts/font_inav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_inav.png -------------------------------------------------------------------------------- /docs/fonts/font_inav_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_inav_2.png -------------------------------------------------------------------------------- /docs/fonts/font_inav_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_inav_hd.png -------------------------------------------------------------------------------- /docs/fonts/font_inav_hd_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_inav_hd_2.png -------------------------------------------------------------------------------- /docs/fonts/font_quic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_quic.png -------------------------------------------------------------------------------- /docs/fonts/font_quic_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_quic_2.png -------------------------------------------------------------------------------- /docs/fonts/font_quic_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_quic_hd.png -------------------------------------------------------------------------------- /docs/fonts/font_quic_hd_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_quic_hd_2.png -------------------------------------------------------------------------------- /docs/fonts/font_ultra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_ultra.png -------------------------------------------------------------------------------- /docs/fonts/font_ultra_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_ultra_2.png -------------------------------------------------------------------------------- /docs/fonts/font_ultra_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_ultra_hd.png -------------------------------------------------------------------------------- /docs/fonts/font_ultra_hd_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/fonts/font_ultra_hd_2.png -------------------------------------------------------------------------------- /docs/fonts/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | echo "You need https://github.com/shellixyz/hd_fpv_osd_font_tool"; 5 | echo "And you need montage from imagemagick"; 6 | 7 | 8 | 9 | echo "converting"; 10 | find ../../fonts -iname '*.bin' | cut -d '/' -f 4 | cut -d '.' -f 1 | xargs -I '%' ~/.cargo/bin/hd_fpv_osd_font_tool convert djibin:../../fonts/%.bin tiledir:dir_%; 11 | 12 | echo "montaging"; 13 | find ../../fonts -iname '*.bin' | cut -d '/' -f 4 | cut -d '.' -f 1 | xargs -I '{}' montage 'dir_{}/*.png' -tile 16x -geometry 24x36+1+1 {}.png; 14 | 15 | echo "cleaning"; 16 | rm -rf ./dir_font* 17 | 18 | 19 | echo "Finished" -------------------------------------------------------------------------------- /docs/img/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/img/.DS_Store -------------------------------------------------------------------------------- /docs/img/fakehd_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/img/fakehd_after.png -------------------------------------------------------------------------------- /docs/img/fakehd_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/img/fakehd_before.png -------------------------------------------------------------------------------- /docs/img/fakehd_centered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/img/fakehd_centered.png -------------------------------------------------------------------------------- /docs/img/fakehd_columns_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/img/fakehd_columns_b.png -------------------------------------------------------------------------------- /docs/img/fakehd_columns_m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/img/fakehd_columns_m.png -------------------------------------------------------------------------------- /docs/img/fakehd_columns_t.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/img/fakehd_columns_t.png -------------------------------------------------------------------------------- /docs/img/fakehd_rows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/img/fakehd_rows.png -------------------------------------------------------------------------------- /docs/img/ports-vtx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/docs/img/ports-vtx.png -------------------------------------------------------------------------------- /fonts/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font.png -------------------------------------------------------------------------------- /fonts/font_ardu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_ardu.png -------------------------------------------------------------------------------- /fonts/font_ardu_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_ardu_hd.png -------------------------------------------------------------------------------- /fonts/font_btfl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_btfl.png -------------------------------------------------------------------------------- /fonts/font_btfl_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_btfl_hd.png -------------------------------------------------------------------------------- /fonts/font_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_hd.png -------------------------------------------------------------------------------- /fonts/font_inav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_inav.png -------------------------------------------------------------------------------- /fonts/font_inav_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_inav_hd.png -------------------------------------------------------------------------------- /fonts/font_quic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_quic.png -------------------------------------------------------------------------------- /fonts/font_quic_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_quic_hd.png -------------------------------------------------------------------------------- /fonts/font_ultr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_ultr.png -------------------------------------------------------------------------------- /fonts/font_ultr_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/fonts/font_ultr_hd.png -------------------------------------------------------------------------------- /ipk/airunit/control/conffiles: -------------------------------------------------------------------------------- 1 | /opt/etc/package-config/msp-osd/config.json 2 | -------------------------------------------------------------------------------- /ipk/airunit/control/control: -------------------------------------------------------------------------------- 1 | Package: msp-osd 2 | Version: 0.10.1 3 | Maintainer: bri3d 4 | Description: MSP OSD service for the DJI HD FPV airunit. 5 | Architecture: pigeon-airside 6 | Depends: dinit, wtfos-opkg-config, wtfos-package-config 7 | Homepage: https://github.com/fpv-wtf/msp-osd 8 | -------------------------------------------------------------------------------- /ipk/airunit/control/postinst: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | /opt/sbin/dinitctl -u enable msp-osd-airside || true 3 | -------------------------------------------------------------------------------- /ipk/airunit/control/prerm: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | /opt/sbin/dinitctl -u disable msp-osd-airside || true 3 | 4 | -------------------------------------------------------------------------------- /ipk/airunit/data/opt/bin/airunit-osd-start.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | setprop dji.hdvt_uav_service 0 3 | setprop dji.shuttle_service 0 4 | 5 | if [ ! -e "/dev/ttyS1_moved" ] 6 | then 7 | mv /dev/ttyS1 /dev/ttyS1_moved 8 | fi 9 | 10 | /opt/bin/msp_displayport_mux 192.168.41.2 /dev/ttyS1_moved /dev/ttyS1 & 11 | echo $! > /opt/var/run/airunit-osd-dji.pid 12 | setprop dji.hdvt_uav_service 1 13 | setprop dji.shuttle_service 1 14 | 15 | -------------------------------------------------------------------------------- /ipk/airunit/data/opt/etc/dinit.d/msp-osd-airside: -------------------------------------------------------------------------------- 1 | type = bgprocess 2 | command = /opt/bin/airunit-osd-start.sh 3 | pid-file = /opt/var/run/airunit-osd-dji.pid 4 | restart = true 5 | -------------------------------------------------------------------------------- /ipk/goggle/control/conffiles: -------------------------------------------------------------------------------- 1 | /opt/etc/package-config/msp-osd/config.json 2 | -------------------------------------------------------------------------------- /ipk/goggle/control/control: -------------------------------------------------------------------------------- 1 | Package: msp-osd 2 | Version: 0.12.4 3 | Maintainer: bri3d 4 | Description: MSP OSD service for the DJI HD FPV goggles. 5 | Architecture: pigeon-glasses 6 | Depends: wtfos-modloader, wtfos-package-config 7 | Homepage: https://github.com/fpv-wtf/msp-osd 8 | -------------------------------------------------------------------------------- /ipk/goggle/control/postinst: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | /opt/sbin/dinitctl -u enable msp-osd-goggles || true 3 | -------------------------------------------------------------------------------- /ipk/goggle/control/preinst: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | if [[ -f /opt/fonts ]]; then 3 | rm -f /opt/fonts 4 | fi 5 | /opt/sbin/dinitctl -u disable msp-osd-goggles || true 6 | -------------------------------------------------------------------------------- /ipk/goggle/control/prerm: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | /opt/sbin/dinitctl -u disable msp-osd-goggles || true 3 | -------------------------------------------------------------------------------- /ipk/goggle/data/opt/etc/dinit.d/msp-osd-goggles: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = modmanager enable diy_glasses displayport_osd_shim 3 | stop-command = modmanager disable diy_glasses displayport_osd_shim -------------------------------------------------------------------------------- /jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | include $(CLEAR_VARS) 3 | 4 | LOCAL_MODULE := duml_hal 5 | LOCAL_SRC_FILES := libduml_hal.so 6 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include 7 | include $(PREBUILT_SHARED_LIBRARY) 8 | include $(CLEAR_VARS) 9 | 10 | LOCAL_CFLAGS += -fPIC -std=c99 -O3 11 | LOCAL_LDFLAGS += -fPIC 12 | LOCAL_LDLIBS := -llog -lz 13 | LOCAL_ARM_NEON := true 14 | LOCAL_MODULE := displayport_osd_shim 15 | LOCAL_SHARED_LIBRARIES := duml_hal 16 | LOCAL_SRC_FILES := \ 17 | displayport_osd_shim.c \ 18 | fakehd/fakehd.c \ 19 | font/font.c \ 20 | hw/dji_display.c \ 21 | hw/dji_radio_shm.c \ 22 | hw/dji_services.c \ 23 | json/osd_config.c \ 24 | json/parson.c \ 25 | lz4/lz4.c \ 26 | msp/msp_displayport.c \ 27 | msp/msp.c \ 28 | net/network.c \ 29 | osd_dji_overlay_udp.c \ 30 | rec/rec_pb.c \ 31 | rec/rec_shim.c \ 32 | rec/rec_util.c \ 33 | rec/rec.c \ 34 | toast/toast.c \ 35 | util/fs_util.c \ 36 | libspng/spng.c 37 | include $(BUILD_SHARED_LIBRARY) 38 | 39 | include $(CLEAR_VARS) 40 | 41 | LOCAL_SRC_FILES:= \ 42 | hw/dji_radio_shm.c \ 43 | json/osd_config.c \ 44 | json/parson.c \ 45 | msp_displayport_mux.c \ 46 | msp/msp.c \ 47 | msp/msp_displayport.c \ 48 | net/network.c \ 49 | net/serial.c \ 50 | util/fs_util.c \ 51 | lz4/lz4.c 52 | LOCAL_MODULE := msp_displayport_mux 53 | include $(BUILD_EXECUTABLE) 54 | include $(CLEAR_VARS) 55 | -------------------------------------------------------------------------------- /jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_PLATFORM=android-23 2 | APP_ABI=armeabi-v7a 3 | APP_CFLAGS += -DSTDC_HEADERS 4 | APP_OPTIM := release 5 | -------------------------------------------------------------------------------- /jni/displayport_osd_shim.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "osd.h" 8 | #include "hw/dji_display.h" 9 | #include "hw/dji_services.h" 10 | 11 | // Which window in the creation order is the overlay / top menu. Usually 1. 12 | #define MENU_WINDOW_ORDER 1 13 | 14 | static duss_disp_instance_handle_t *disp_instance = NULL; 15 | static duss_hal_obj_handle_t ion_handle = NULL; 16 | static int started = 0; 17 | static int are_v2 = 0; 18 | static int window_count = 0; 19 | static void *menu_window = NULL; 20 | 21 | // Patch a seemingly unused window management thread in libtp1801_gui.so to inject our code into the dji_glasses process. 22 | void _ZN24MMSFBWindowManagerThread10threadMainEv(void *this) { 23 | printf("ENTERING MAIN \n"); 24 | are_v2 = dji_goggles_are_v2(); 25 | while(1) { 26 | if(started == 0 && disp_instance != NULL && ion_handle != NULL) { 27 | printf("ENTERING OSD! fbdev disp %x ion %x\n", disp_instance, ion_handle); 28 | started = 1; 29 | osd_directfb(disp_instance, ion_handle); 30 | } 31 | usleep(50000); 32 | } 33 | } 34 | 35 | static void (*_ZN23GlassVideoChnlUIManager19setNextVideoChannelE19GlassVideoChannelId2)(void *this, uint32_t channel_id) = NULL; 36 | static void *tp1801_gui_lib = NULL; 37 | 38 | void _ZN23GlassVideoChnlUIManager19setNextVideoChannelE19GlassVideoChannelId(void *this, uint32_t channel_id) { 39 | if (_ZN23GlassVideoChnlUIManager19setNextVideoChannelE19GlassVideoChannelId2 == NULL) { 40 | _ZN23GlassVideoChnlUIManager19setNextVideoChannelE19GlassVideoChannelId2 = dlsym(RTLD_NEXT, "_ZN23GlassVideoChnlUIManager19setNextVideoChannelE19GlassVideoChannelId"); 41 | if (_ZN23GlassVideoChnlUIManager19setNextVideoChannelE19GlassVideoChannelId2 == NULL) { 42 | tp1801_gui_lib = dlopen("/system/lib/libtp1801_gui.so", 1); 43 | _ZN23GlassVideoChnlUIManager19setNextVideoChannelE19GlassVideoChannelId2 = dlsym(tp1801_gui_lib, "_ZN23GlassVideoChnlUIManager19setNextVideoChannelE19GlassVideoChannelId"); 44 | if (_ZN23GlassVideoChnlUIManager19setNextVideoChannelE19GlassVideoChannelId2 == NULL) { 45 | printf("dlsym: %s\n", dlerror()); 46 | } 47 | } 48 | } 49 | // RTOS video channels: 50 | // 1 = Playback 51 | // 2 = Unknown 52 | // 3 = Live Video 53 | // 4 = AV-IN 54 | if(channel_id == 3) { 55 | osd_enable(); 56 | } else { 57 | osd_disable(); 58 | } 59 | _ZN23GlassVideoChnlUIManager19setNextVideoChannelE19GlassVideoChannelId2(this, channel_id); 60 | } 61 | 62 | static void *hal_lib = NULL; 63 | 64 | static duss_result_t (*duss_hal_mem_alloc2)(duss_hal_obj_handle_t handle, duss_hal_mem_handle_t *mem_handle, uint32_t size, uint32_t param1, uint32_t param2, uint32_t param3) = 0; 65 | // Patch libduml_hal's duss_hal_mem_alloc. 66 | // Use the first invocation of this to steal the duss_hal_obj_handle_t pointing to the ion shared memory service. 67 | duss_result_t duss_hal_mem_alloc(duss_hal_obj_handle_t handle, duss_hal_mem_handle_t *mem_handle, uint32_t size, uint32_t param1, uint32_t param2, uint32_t param3) { 68 | if (duss_hal_mem_alloc2 == NULL) { 69 | duss_hal_mem_alloc2 = dlsym(RTLD_NEXT, "duss_hal_mem_alloc"); 70 | if (duss_hal_mem_alloc2 == 0){ 71 | if (hal_lib == NULL){ 72 | hal_lib = dlopen("/system/lib/libduml_hal.so", 1); 73 | } 74 | duss_hal_mem_alloc2 = dlsym(hal_lib, "duss_hal_mem_alloc"); 75 | if (duss_hal_mem_alloc2 == 0) { 76 | printf("dlsym: %s\n", dlerror()); 77 | return -1; 78 | } 79 | } 80 | } 81 | if (ion_handle == NULL) { 82 | ion_handle = handle; 83 | } 84 | return duss_hal_mem_alloc2(handle, mem_handle, size, param1, param2, param3); 85 | } 86 | 87 | static duss_result_t (*duss_hal_display_aquire_plane2)(duss_disp_instance_handle_t * , duss_disp_plane_type_t , duss_disp_plane_id_t * ) = 0; 88 | 89 | // Patch libduml_hal's duss_hal_display_aquire_plane. 90 | // Use the first invocation of this with plane == 5 to steal the duss_hal_instance_handle_t pointing to the display driver. 91 | duss_result_t duss_hal_display_aquire_plane(duss_disp_instance_handle_t *disp, duss_disp_plane_type_t plane_type, duss_disp_plane_id_t *plane_id) { 92 | if (duss_hal_display_aquire_plane2 == NULL) { 93 | duss_hal_display_aquire_plane2 = dlsym(RTLD_NEXT, "duss_hal_display_aquire_plane"); 94 | if (duss_hal_display_aquire_plane2 == 0){ 95 | if(hal_lib == NULL) { 96 | hal_lib = dlopen("/system/lib/libduml_hal.so", 1); 97 | } 98 | duss_hal_display_aquire_plane2 = dlsym(hal_lib, "duss_hal_display_aquire_plane"); 99 | if (duss_hal_display_aquire_plane2 == 0) { 100 | printf("dlsym: %s\n", dlerror()); 101 | return -1; 102 | } 103 | } 104 | } 105 | if(disp_instance == NULL && *plane_id == 5) { 106 | disp_instance = disp; 107 | } 108 | return duss_hal_display_aquire_plane2(disp, plane_type, plane_id); 109 | } 110 | 111 | duss_result_t (*duss_hal_display_plane_blending_set2)(duss_disp_instance_handle_t *disp, duss_disp_plane_id_t plane_id, duss_disp_plane_blending_t *blending) = NULL; 112 | duss_result_t duss_hal_display_plane_blending_set(duss_disp_instance_handle_t *disp, duss_disp_plane_id_t plane_id, duss_disp_plane_blending_t *blending) { 113 | if (duss_hal_display_plane_blending_set2 == NULL) { 114 | duss_hal_display_plane_blending_set2 = dlsym(RTLD_NEXT, "duss_hal_display_plane_blending_set"); 115 | if (duss_hal_display_plane_blending_set2 == NULL) { 116 | if (hal_lib == NULL) { 117 | hal_lib = dlopen("/system/lib/libduml_hal.so", 1); 118 | } 119 | duss_hal_display_plane_blending_set2 = dlsym(hal_lib, "duss_hal_display_plane_blending_set"); 120 | if (duss_hal_display_plane_blending_set2 == NULL) { 121 | printf("dlsym: %s\n", dlerror()); 122 | return -1; 123 | } 124 | } 125 | } 126 | // Patch blending order for DJI UI on V1 Goggles to match V2, so we can draw under them. 127 | if (blending->order == 1) { 128 | blending->order = 4; 129 | } 130 | return duss_hal_display_plane_blending_set2(disp, plane_id, blending); 131 | } -------------------------------------------------------------------------------- /jni/fakehd/fakehd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "json/osd_config.h" 6 | #include "toast/toast.h" 7 | 8 | #define FAKEHD_ENABLE_KEY "fakehd_enable" 9 | #define FAKEHD_LOCK_CENTER_KEY "fakehd_lock_center" 10 | #define FAKEHD_HIDE_THROTTLE_KEY "fakehd_hide_throttle_element" // for compat 11 | #define FAKEHD_MENU_SWITCH_KEY "fakehd_menu_switch" 12 | #define FAKEHD_HIDE_MENU_SWITCH_KEY "fakehd_hide_menu_switch" // for compat 13 | #define FAKEHD_LAYOUT_DEBUG_KEY "fakehd_layout_debug" 14 | #define FAKEHD_COLUMNS_KEY "fakehd_columns" 15 | #define FAKEHD_ROWS_KEY "fakehd_rows" 16 | 17 | #define INPUT_ROWS 16 18 | #define INPUT_COLS 30 19 | 20 | int fakehd_enabled = 0; 21 | static int fakehd_hide_menu_switch = 0; 22 | static int fakehd_lock_center = 0; 23 | static int fakehd_layout_debug = 0; 24 | static int fakehd_menu_switch_char = 4; // betaflight throttle icon 25 | static int fakehd_trigger_x = 99; 26 | static int fakehd_trigger_y = 99; 27 | static char fakehd_columns = 'S'; 28 | static char fakehd_rows[INPUT_COLS] = "WWWWWWCCWWWWWWWD"; 29 | 30 | #ifdef DEBUG 31 | #define DEBUG_PRINT(fmt, args...) fprintf(stderr, fmt, ##args) 32 | #else 33 | #define DEBUG_PRINT(fmt, args...) 34 | #endif 35 | 36 | void load_fakehd_config() 37 | { 38 | DEBUG_PRINT("checking for fakehd enabled\n"); 39 | if (get_boolean_config_value(FAKEHD_ENABLE_KEY)) 40 | { 41 | DEBUG_PRINT("fakehd enabled\n"); 42 | toast("FAKEHD ENABLED"); 43 | 44 | fakehd_enabled = 1; 45 | } 46 | else 47 | { 48 | DEBUG_PRINT("fakehd disabled\n"); 49 | } 50 | 51 | DEBUG_PRINT("checking for fakehd layout debug\n"); 52 | if (get_boolean_config_value(FAKEHD_LAYOUT_DEBUG_KEY)) 53 | { 54 | DEBUG_PRINT("fakehd layout debug\n"); 55 | fakehd_layout_debug = 1; 56 | } 57 | else 58 | { 59 | DEBUG_PRINT("fakehd layout debug off\n"); 60 | } 61 | 62 | DEBUG_PRINT("checking for fakehd hide throttle \n"); 63 | if (get_boolean_config_value(FAKEHD_HIDE_MENU_SWITCH_KEY)) 64 | { 65 | DEBUG_PRINT("fakehd hide throttle\n"); 66 | fakehd_hide_menu_switch = 1; 67 | } 68 | else 69 | { 70 | DEBUG_PRINT("fakehd no hide throttle\n"); 71 | } 72 | DEBUG_PRINT("checking for fakehd lock center \n"); 73 | if (get_boolean_config_value(FAKEHD_LOCK_CENTER_KEY)) 74 | { 75 | DEBUG_PRINT("fakehd lock center\n"); 76 | fakehd_lock_center = 1; 77 | } 78 | else 79 | { 80 | DEBUG_PRINT("fakehd no lock center\n"); 81 | } 82 | 83 | int trigger = get_integer_config_value(FAKEHD_MENU_SWITCH_KEY); 84 | if (trigger) 85 | { 86 | DEBUG_PRINT("fakehd found custom trigger\n"); 87 | fakehd_menu_switch_char = trigger; 88 | toast("FHD MENU SWITCH %c", 4); 89 | } 90 | // trigger 91 | // rows 92 | const char * rows = get_string_config_value(FAKEHD_ROWS_KEY); 93 | if (rows) { 94 | DEBUG_PRINT("fakehd found custom row conf\n"); 95 | memcpy(fakehd_rows, rows, INPUT_COLS); 96 | } 97 | 98 | const char * cols = get_string_config_value(FAKEHD_COLUMNS_KEY); 99 | if (cols) 100 | { 101 | DEBUG_PRINT("fakehd found col conf\n"); 102 | fakehd_columns = cols[0]; 103 | } 104 | DEBUG_PRINT("fakehd finished config init\n"); 105 | } 106 | 107 | static void fakehd_get_column_config(int cols[INPUT_ROWS]) 108 | { 109 | switch (fakehd_columns) 110 | { 111 | case 'T': 112 | memcpy(cols, (int[]){0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, sizeof(cols[0]) * INPUT_ROWS); 113 | break; 114 | case 'M': 115 | memcpy(cols, (int[]){3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}, sizeof(cols[0]) * INPUT_ROWS); 116 | break; 117 | case 'B': 118 | memcpy(cols, (int[]){6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21}, sizeof(cols[0]) * INPUT_ROWS); 119 | break; 120 | case 'S': 121 | default: 122 | memcpy(cols, (int[]){0, 1, 2, 3, 4, 8, 9, 10, 11, 12, 16, 17, 18, 19, 20, 21}, sizeof(cols[0]) * INPUT_ROWS); 123 | break; 124 | } 125 | 126 | // If more flexibility needed / when config allows - I suggest the 'default' switch block is separated 127 | // and used to lookup the mapping from the config file, letting the user define extras? 128 | 129 | } 130 | 131 | static void fakehd_get_row_config(int rownum, int row[INPUT_COLS]) 132 | { 133 | char rowmode = fakehd_rows[rownum]; 134 | switch (rowmode) 135 | { 136 | case 'L': 137 | memcpy(row, (int[]){0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}, sizeof(row[0]) * INPUT_COLS); 138 | break; 139 | case 'C': 140 | memcpy(row, (int[]){15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44}, sizeof(row[0]) * INPUT_COLS); 141 | break; 142 | case 'R': 143 | memcpy(row, (int[]){30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59}, sizeof(row[0]) * INPUT_COLS); 144 | break; 145 | case 'T': 146 | memcpy(row, (int[]){0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59}, sizeof(row[0]) * INPUT_COLS); 147 | break; 148 | case 'F': 149 | memcpy(row, (int[]){10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}, sizeof(row[0]) * INPUT_COLS); 150 | break; 151 | case 'D': 152 | memcpy(row, (int[]){12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41}, sizeof(row[0]) * INPUT_COLS); 153 | break; 154 | case 'W': 155 | default: 156 | memcpy(row, (int[]){0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59}, sizeof(row[0]) * INPUT_COLS); 157 | break; 158 | } 159 | 160 | // If more flexibility needed / when config allows - I suggest the 'default' switch block is separated 161 | // and used to lookup the mapping from the config file, letting the user define extras? 162 | } 163 | 164 | 165 | // when possible, this should be called on reconnect. it will do what's needed to put fakehd back 166 | // into fresh booted state 167 | void fakehd_reset() { 168 | // clear saved centering trigger position 169 | int fakehd_trigger_x = 99; 170 | int fakehd_trigger_y = 99; 171 | } 172 | 173 | void fakehd_enable() 174 | { 175 | fakehd_enabled = 1; 176 | } 177 | 178 | void fakehd_disable() 179 | { 180 | fakehd_enabled = 0; 181 | fakehd_reset(); 182 | } 183 | 184 | int fakehd_is_enabled() { 185 | return fakehd_enabled; 186 | } 187 | 188 | void fakehd_map_sd_character_map_to_hd(uint16_t sd_character_map[60][22], uint16_t hd_character_map[60][22]) 189 | { 190 | int row[INPUT_COLS]; 191 | int col[INPUT_ROWS]; 192 | 193 | fakehd_get_column_config(col); 194 | 195 | int render_x = 0; 196 | int render_y = 0; 197 | for (int y = INPUT_ROWS-1; y >= 0; y--) 198 | { 199 | fakehd_get_row_config(y, row); 200 | for (int x = INPUT_COLS-1; x >= 0; x--) 201 | { 202 | // to visualise the layout better in dev 203 | if (fakehd_layout_debug && sd_character_map[x][y] == 0) { 204 | sd_character_map[x][y] = 48 + (x % 10); 205 | } 206 | 207 | // skip if it's not a character 208 | if (sd_character_map[x][y] != 0) 209 | { 210 | // if current element is fly min or throttle icon 211 | // record the current position as the 'trigger' position 212 | if (fakehd_trigger_x == 99 && sd_character_map[x][y] == fakehd_menu_switch_char) 213 | { 214 | DEBUG_PRINT("found fakehd triggger \n"); 215 | fakehd_trigger_x = x; 216 | fakehd_trigger_y = y; 217 | } 218 | 219 | // if we have seen a trigger (see above) - and it's now gone, switch to centering 220 | // this is intented to center the menu + postflight stats, which don't contain 221 | // timer/battery symbols 222 | if ( 223 | fakehd_lock_center || 224 | (fakehd_trigger_x != 99 && 225 | sd_character_map[fakehd_trigger_x][fakehd_trigger_y] != fakehd_menu_switch_char)) 226 | { 227 | render_x = x + 15; 228 | render_y = y + 3; 229 | } 230 | else 231 | { 232 | render_y = col[y]; 233 | render_x = row[x]; 234 | } 235 | // 0 out the throttle element if configured to do so 236 | // and also the three adjacent positions where the thottle percent will be 237 | if (fakehd_trigger_x != 99 && 238 | fakehd_hide_menu_switch && 239 | sd_character_map[x][y] == fakehd_menu_switch_char) 240 | { 241 | hd_character_map[render_x][render_y] = 0; 242 | (render_x <= 57) && (hd_character_map[render_x + 1][render_y] = 0); 243 | (render_x <= 56) && (hd_character_map[render_x + 2][render_y] = 0); 244 | (render_x <= 55) && (hd_character_map[render_x + 3][render_y] = 0); 245 | (render_x <= 54) && (hd_character_map[render_x + 4][render_y] = 0); 246 | (render_x <= 53) && (hd_character_map[render_x + 5][render_y] = 0); 247 | } 248 | else 249 | { 250 | // otherwise, the normal path 251 | hd_character_map[render_x][render_y] = sd_character_map[x][y]; 252 | } 253 | } 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /jni/fakehd/fakehd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void load_fakehd_config(); 4 | void fakehd_disable(); 5 | void fakehd_enable(); 6 | int fakehd_is_enabled(); 7 | void fakehd_reset(); 8 | void fakehd_map_sd_character_map_to_hd(uint16_t sd_character_map[60][22], uint16_t hd_character_map[60][22]); -------------------------------------------------------------------------------- /jni/font/font.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | // #include 8 | 9 | #include "../libspng/spng.h" 10 | #include "font.h" 11 | #include "../util/debug.h" 12 | 13 | #define BYTES_PER_PIXEL 4 14 | #define HD_FONT_WIDTH 24 15 | 16 | /* Font helper methods */ 17 | 18 | void get_font_path_with_extension(char *font_path_dest, const char *font_path, const char *extension, uint8_t len, uint8_t is_hd, const char *font_variant) 19 | { 20 | char name_buf[len]; 21 | char res_buf[len]; 22 | 23 | if (font_variant != NULL && strlen(font_variant) > 0) 24 | { 25 | snprintf(name_buf, len, "%s_%s", font_path, font_variant); 26 | } else { 27 | snprintf(name_buf, len, "%s", font_path); 28 | } 29 | 30 | if (is_hd) 31 | { 32 | // surely there's a better way... 33 | snprintf(res_buf, len, "%s", "_hd"); 34 | } else { 35 | snprintf(res_buf, len, "%s", ""); 36 | } 37 | snprintf(font_path_dest, len, "%s%s%s", name_buf, res_buf, extension); 38 | DEBUG_PRINT("Font path: %s\n", font_path_dest); 39 | } 40 | 41 | static int open_font(const char *filename, display_info_t *display_info, const char *font_variant) 42 | { 43 | char file_path[255]; 44 | int is_hd = (display_info->font_width == HD_FONT_WIDTH) ? 1 : 0; 45 | get_font_path_with_extension(file_path, filename, ".png", 255, is_hd, font_variant); 46 | DEBUG_PRINT("Opening font: %s\n", file_path); 47 | struct stat st; 48 | memset(&st, 0, sizeof(st)); 49 | stat(file_path, &st); 50 | size_t filesize = st.st_size; 51 | if(!(filesize > 0)) { 52 | DEBUG_PRINT("Font file did not exist: %s\n", file_path); 53 | return -1; 54 | } 55 | 56 | FILE *fd = fopen(file_path, "rb"); 57 | if (!fd) { 58 | DEBUG_PRINT("Could not open file %s\n", file_path); 59 | return -1; 60 | } 61 | 62 | spng_ctx *ctx = spng_ctx_new(0); 63 | DEBUG_PRINT("Allocated PNG context\n"); 64 | // Set some kind of reasonable PNG limit so we don't get blown up 65 | size_t limit = 1024 * 1024 * 64; 66 | spng_set_chunk_limits(ctx, limit, limit); 67 | DEBUG_PRINT("Set PNG chunk limits\n"); 68 | spng_set_png_file(ctx, fd); 69 | DEBUG_PRINT("Set PNG file\n"); 70 | 71 | struct spng_ihdr ihdr; 72 | int ret = spng_get_ihdr(ctx, &ihdr); 73 | DEBUG_PRINT("Got PNG header\n"); 74 | 75 | if(ret) 76 | { 77 | printf("spng_get_ihdr() error: %s\n", spng_strerror(ret)); 78 | goto err; 79 | } 80 | 81 | if(ihdr.height != display_info->font_height * NUM_CHARS) { 82 | printf("font invalid height, got %d wanted %d\n", ihdr.height, display_info->font_height * NUM_CHARS); 83 | goto err; 84 | } 85 | 86 | if(ihdr.width % display_info->font_width != 0) { 87 | printf("font invalid width, not a multiple of %d\n", display_info->font_width); 88 | goto err; 89 | } 90 | 91 | DEBUG_PRINT("Image pixel size %d x %d\n", ihdr.width, ihdr.height); 92 | 93 | int num_pages = ihdr.width / display_info->font_width; 94 | 95 | DEBUG_PRINT("Font has %d pages\n", num_pages); 96 | 97 | size_t image_size = 0; 98 | int fmt = SPNG_FMT_RGBA8; 99 | ret = spng_decoded_image_size(ctx, fmt, &image_size); 100 | if(ret) { 101 | goto err; 102 | } 103 | 104 | DEBUG_PRINT("Allocating image size %d\n", image_size); 105 | 106 | void* font_data = malloc(image_size); 107 | ret = spng_decode_image(ctx, font_data, image_size, SPNG_FMT_RGBA8, 0); 108 | if(ret) { 109 | printf("Failed to decode PNG!\n"); 110 | free(font_data); 111 | goto err; 112 | } 113 | 114 | for(int page = 0; page < num_pages; page++) { 115 | DEBUG_PRINT("Loading font page %d of %d, placing %x\n", page, num_pages, display_info->fonts); 116 | display_info->fonts[page] = malloc(display_info->font_width * display_info->font_height * NUM_CHARS * BYTES_PER_PIXEL); 117 | DEBUG_PRINT("Allocated %d bytes for font page buf at%x\n", display_info->font_width * display_info->font_height * NUM_CHARS * BYTES_PER_PIXEL, display_info->fonts[page]); 118 | for(int char_num = 0; char_num < NUM_CHARS; char_num++) { 119 | for(int y = 0; y < display_info->font_height; y++) { 120 | // Copy each character line at a time into the correct font buffer 121 | int char_width_bytes = display_info->font_width * BYTES_PER_PIXEL; 122 | int char_size_bytes_dest = (display_info->font_width * display_info->font_height * BYTES_PER_PIXEL); 123 | int char_size_bytes_src = (ihdr.width * display_info->font_height * BYTES_PER_PIXEL); 124 | memcpy((uint8_t *)display_info->fonts[page] + (char_num * char_size_bytes_dest) + (y * char_width_bytes), (uint8_t *)font_data + (char_num * char_size_bytes_src) + (ihdr.width * y * BYTES_PER_PIXEL) + (page * char_width_bytes), char_width_bytes); 125 | } 126 | } 127 | } 128 | 129 | free(font_data); 130 | spng_ctx_free(ctx); 131 | fclose(fd); 132 | return 0; 133 | err: 134 | spng_ctx_free(ctx); 135 | fclose(fd); 136 | return -1; 137 | } 138 | 139 | void load_font(display_info_t *display_info, const char *font_variant) { 140 | 141 | // Note: load_font will not replace an existing font. 142 | if(display_info->fonts[0] == NULL) { 143 | int loaded_font = 0; 144 | DEBUG_PRINT("IN LOAD_FONT\n"); 145 | // create a copy of font_variant 146 | char font_variant_lower[5] = ""; 147 | if (font_variant != NULL) 148 | { 149 | DEBUG_PRINT("Lowercasing variant\n"); 150 | size_t length = strlen(font_variant); 151 | for (size_t i = 0; i < length && i < 4; i++) // Ensure not to exceed array bounds 152 | { 153 | font_variant_lower[i] = tolower(font_variant[i]); 154 | } 155 | } 156 | else 157 | { 158 | DEBUG_PRINT("Font variant is NULL\n"); 159 | } 160 | 161 | DEBUG_PRINT("Loading font %s\n", font_variant_lower); 162 | 163 | char *fallback_font_variant = ""; 164 | if (strcmp(font_variant_lower, "btfl") == 0) 165 | { 166 | DEBUG_PRINT("Setting fallback font variant to bf\n"); 167 | fallback_font_variant = "bf"; 168 | } 169 | else if (strcmp(font_variant_lower, "ultr") == 0) 170 | { 171 | DEBUG_PRINT("Setting fallback font variant to ultra\n"); 172 | fallback_font_variant = "ultra"; 173 | } 174 | 175 | // try the three paths for the current font 176 | DEBUG_PRINT("Loading from: %s %s\n", SDCARD_FONT_PATH, font_variant_lower); 177 | loaded_font = open_font(SDCARD_FONT_PATH, display_info, font_variant_lower); 178 | if (loaded_font < 0 && strcmp(fallback_font_variant, "") != 0) 179 | { 180 | DEBUG_PRINT("Loading fallback variant from: %s %s\n", SDCARD_FONT_PATH, fallback_font_variant); 181 | loaded_font = open_font(SDCARD_FONT_PATH, display_info, fallback_font_variant); 182 | } 183 | if (loaded_font < 0) 184 | { 185 | DEBUG_PRINT("Loading from: %s %s\n", FALLBACK_FONT_PATH, font_variant_lower); 186 | loaded_font = open_font(FALLBACK_FONT_PATH, display_info, font_variant_lower); 187 | } 188 | if (loaded_font < 0 && strcmp(fallback_font_variant, "") != 0) 189 | { 190 | DEBUG_PRINT("Loading fallback variant from: %s %s\n", FALLBACK_FONT_PATH, fallback_font_variant); 191 | loaded_font = open_font(FALLBACK_FONT_PATH, display_info, fallback_font_variant); 192 | } 193 | if (loaded_font < 0) 194 | { 195 | DEBUG_PRINT("Loading from: %s %s\n", ENTWARE_FONT_PATH, font_variant_lower); 196 | loaded_font = open_font(ENTWARE_FONT_PATH, display_info, font_variant_lower); 197 | } 198 | 199 | 200 | // finally, if we have no fonts for this FC, fallback to the default font 201 | if (loaded_font) 202 | { 203 | DEBUG_PRINT("Loading generic from: %s\n", SDCARD_FONT_PATH); 204 | loaded_font = open_font(SDCARD_FONT_PATH, display_info, ""); 205 | if (loaded_font < 0) 206 | { 207 | DEBUG_PRINT("Loading generic from: %s\n", FALLBACK_FONT_PATH); 208 | loaded_font = open_font(FALLBACK_FONT_PATH, display_info, ""); 209 | } 210 | if (loaded_font < 0) 211 | { 212 | DEBUG_PRINT("Loading generic from: %s\n", ENTWARE_FONT_PATH); 213 | loaded_font = open_font(ENTWARE_FONT_PATH, display_info, ""); 214 | } 215 | } 216 | } 217 | } 218 | 219 | void close_font(display_info_t *display_info) { 220 | for(int i = 0; i < NUM_FONT_PAGES; i++) { 221 | if(display_info->fonts[i] != NULL) { 222 | free(display_info->fonts[i]); 223 | display_info->fonts[i] = NULL; 224 | } 225 | } 226 | } 227 | 228 | -------------------------------------------------------------------------------- /jni/font/font.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../util/display_info.h" 6 | 7 | #define FALLBACK_FONT_PATH "/blackbox/font" 8 | #define ENTWARE_FONT_PATH "/opt/fonts/font" 9 | #define SDCARD_FONT_PATH "/storage/sdcard0/font" 10 | 11 | #define FALLBACK_FONT NULL 12 | 13 | typedef enum 14 | { 15 | FONT_VARIANT_GENERIC, 16 | FONT_VARIANT_BETAFLIGHT, 17 | FONT_VARIANT_INAV, 18 | FONT_VARIANT_ARDUPILOT, 19 | FONT_VARIANT_KISS_ULTRA, 20 | FONT_VARIANT_QUICKSILVER, 21 | FONT_VARIANT_COUNT 22 | } font_variant_e; 23 | 24 | void load_font(display_info_t *display_info, const char *font_variant); 25 | void close_font(display_info_t *display_info); 26 | void get_font_path_with_extension(char *font_path_dest, const char *font_path, const char *extension, uint8_t len, uint8_t is_hd, const char *font_variant); 27 | -------------------------------------------------------------------------------- /jni/hw/dji_display.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "dji_display.h" 3 | #include "util/debug.h" 4 | 5 | #define GOGGLES_V1_VOFFSET 575 6 | #define GOGGLES_V2_VOFFSET 215 7 | 8 | dji_display_state_t *dji_display_state_alloc(uint8_t is_v2_goggles) { 9 | dji_display_state_t *display_state = calloc(1, sizeof(dji_display_state_t)); 10 | display_state->disp_instance_handle = (duss_disp_instance_handle_t *)calloc(1, sizeof(duss_disp_instance_handle_t)); 11 | display_state->fb_0 = (duss_frame_buffer_t *)calloc(1,sizeof(duss_frame_buffer_t)); 12 | display_state->fb_1 = (duss_frame_buffer_t *)calloc(1,sizeof(duss_frame_buffer_t)); 13 | display_state->pb_0 = (duss_disp_plane_blending_t *)calloc(1, sizeof(duss_disp_plane_blending_t)); 14 | display_state->is_v2_goggles = is_v2_goggles; 15 | display_state->frame_drawn = 0; 16 | return display_state; 17 | } 18 | 19 | void dji_display_state_free(dji_display_state_t *display_state) { 20 | free(display_state->disp_instance_handle); 21 | free(display_state->fb_0); 22 | free(display_state->fb_1); 23 | free(display_state->pb_0); 24 | free(display_state); 25 | } 26 | 27 | void dji_display_close_framebuffer(dji_display_state_t *display_state) { 28 | duss_hal_display_port_enable(display_state->disp_instance_handle, 3, 0); 29 | duss_hal_display_release_plane(display_state->disp_instance_handle, display_state->plane_id); 30 | duss_hal_display_close(display_state->disp_handle, &display_state->disp_instance_handle); 31 | duss_hal_mem_free(display_state->ion_buf_0); 32 | duss_hal_mem_free(display_state->ion_buf_1); 33 | duss_hal_device_close(display_state->disp_handle); 34 | duss_hal_device_stop(display_state->ion_handle); 35 | duss_hal_device_close(display_state->ion_handle); 36 | duss_hal_deinitialize(); 37 | } 38 | 39 | void dji_display_open_framebuffer(dji_display_state_t *display_state, duss_disp_plane_id_t plane_id) { 40 | uint32_t hal_device_open_unk = 0; 41 | duss_result_t res = 0; 42 | 43 | display_state->plane_id = plane_id; 44 | 45 | // PLANE BLENDING 46 | 47 | display_state->pb_0->is_enable = 1; 48 | display_state->pb_0->voffset = GOGGLES_V1_VOFFSET; // TODO just check hwid to figure this out 49 | display_state->pb_0->hoffset = 0; 50 | display_state->pb_0->order = 2; 51 | 52 | // Global alpha - disable as we want per pixel alpha. 53 | 54 | display_state->pb_0->glb_alpha_en = 0; 55 | display_state->pb_0->glb_alpha_val = 0; 56 | 57 | // Blending algorithm 1 seems to work. 58 | 59 | display_state->pb_0->blending_alg = 1; 60 | 61 | duss_hal_device_desc_t device_descs[3] = { 62 | {"/dev/dji_display", &duss_hal_attach_disp, &duss_hal_detach_disp, 0x0}, 63 | {"/dev/ion", &duss_hal_attach_ion_mem, &duss_hal_detach_ion_mem, 0x0}, 64 | {0,0,0,0} 65 | }; 66 | 67 | duss_hal_initialize(device_descs); 68 | 69 | res = duss_hal_device_open("/dev/dji_display",&hal_device_open_unk,&display_state->disp_handle); 70 | if (res != 0) { 71 | printf("failed to open dji_display device"); 72 | exit(0); 73 | } 74 | res = duss_hal_display_open(display_state->disp_handle, &display_state->disp_instance_handle, 0); 75 | if (res != 0) { 76 | printf("failed to open display hal"); 77 | exit(0); 78 | } 79 | 80 | res = duss_hal_display_reset(display_state->disp_instance_handle); 81 | if (res != 0) { 82 | printf("failed to reset display"); 83 | exit(0); 84 | } 85 | 86 | // No idea what this "plane mode" actually does but it's different on V2 87 | uint8_t acquire_plane_mode = display_state->is_v2_goggles ? 6 : 0; 88 | 89 | res = duss_hal_display_aquire_plane(display_state->disp_instance_handle,acquire_plane_mode,&plane_id); 90 | if (res != 0) { 91 | printf("failed to acquire plane"); 92 | exit(0); 93 | } 94 | res = duss_hal_display_port_enable(display_state->disp_instance_handle, 3, 1); 95 | if (res != 0) { 96 | printf("failed to enable display port"); 97 | exit(0); 98 | } 99 | 100 | res = duss_hal_display_plane_blending_set(display_state->disp_instance_handle, plane_id, display_state->pb_0); 101 | 102 | if (res != 0) { 103 | printf("failed to set blending"); 104 | exit(0); 105 | } 106 | res = duss_hal_device_open("/dev/ion", &hal_device_open_unk, &display_state->ion_handle); 107 | if (res != 0) { 108 | printf("failed to open shared VRAM"); 109 | exit(0); 110 | } 111 | res = duss_hal_device_start(display_state->ion_handle,0); 112 | if (res != 0) { 113 | printf("failed to start VRAM device"); 114 | exit(0); 115 | } 116 | 117 | res = duss_hal_mem_alloc(display_state->ion_handle,&display_state->ion_buf_0,0x473100,0x400,0,0x17); 118 | if (res != 0) { 119 | printf("failed to allocate VRAM"); 120 | exit(0); 121 | } 122 | res = duss_hal_mem_map(display_state->ion_buf_0, &display_state->fb0_virtual_addr); 123 | if (res != 0) { 124 | printf("failed to map VRAM"); 125 | exit(0); 126 | } 127 | res = duss_hal_mem_get_phys_addr(display_state->ion_buf_0, &display_state->fb0_physical_addr); 128 | if (res != 0) { 129 | printf("failed to get FB0 phys addr"); 130 | exit(0); 131 | } 132 | printf("first buffer VRAM mapped virtual memory is at %p : %p\n", display_state->fb0_virtual_addr, display_state->fb0_physical_addr); 133 | 134 | res = duss_hal_mem_alloc(display_state->ion_handle,&display_state->ion_buf_1,0x473100,0x400,0,0x17); 135 | if (res != 0) { 136 | printf("failed to allocate FB1 VRAM"); 137 | exit(0); 138 | } 139 | res = duss_hal_mem_map(display_state->ion_buf_1,&display_state->fb1_virtual_addr); 140 | if (res != 0) { 141 | printf("failed to map FB1 VRAM"); 142 | exit(0); 143 | } 144 | res = duss_hal_mem_get_phys_addr(display_state->ion_buf_1, &display_state->fb1_physical_addr); 145 | if (res != 0) { 146 | printf("failed to get FB1 phys addr"); 147 | exit(0); 148 | } 149 | printf("second buffer VRAM mapped virtual memory is at %p : %p\n", display_state->fb1_virtual_addr, display_state->fb1_physical_addr); 150 | 151 | for(int i = 0; i < 2; i++) { 152 | duss_frame_buffer_t *fb = i ? display_state->fb_1 : display_state->fb_0; 153 | fb->buffer = i ? display_state->ion_buf_1 : display_state->ion_buf_0; 154 | fb->pixel_format = display_state->is_v2_goggles ? DUSS_PIXFMT_RGBA8888_GOGGLES_V2 : DUSS_PIXFMT_RGBA8888; // 20012 instead on V2 155 | fb->frame_id = i; 156 | fb->planes[0].bytes_per_line = 0x1680; 157 | fb->planes[0].offset = 0; 158 | fb->planes[0].plane_height = 810; 159 | fb->planes[0].bytes_written = 0x473100; 160 | fb->width = 1440; 161 | fb->height = 810; 162 | fb->plane_count = 1; 163 | } 164 | } 165 | 166 | 167 | void dji_display_open_framebuffer_injected(dji_display_state_t *display_state, duss_disp_instance_handle_t *disp, duss_hal_obj_handle_t ion_handle, duss_disp_plane_id_t plane_id) { 168 | uint32_t hal_device_open_unk = 0; 169 | duss_result_t res = 0; 170 | display_state->disp_instance_handle = disp; 171 | display_state->ion_handle = ion_handle; 172 | display_state->plane_id = plane_id; 173 | 174 | // PLANE BLENDING 175 | 176 | display_state->pb_0->is_enable = 1; 177 | 178 | // TODO just check hwid to figure this out. Not actually V1/V2 related but an HW version ID. 179 | 180 | display_state->pb_0->voffset = GOGGLES_V1_VOFFSET; 181 | display_state->pb_0->hoffset = 0; 182 | 183 | // On Goggles V1, the UI and video are in Z-Order 1. 184 | // On Goggles V2, they're in Z-Order 4, but we inline patch them to Z-Order 1 (see displayport_osd_shim.c) 185 | 186 | display_state->pb_0->order = 2; 187 | 188 | // Global alpha - disable as we want per pixel alpha. 189 | 190 | display_state->pb_0->glb_alpha_en = 0; 191 | display_state->pb_0->glb_alpha_val = 0; 192 | 193 | // These aren't documented. Blending algorithm 0 is employed for menus and 1 for screensaver. 194 | 195 | display_state->pb_0->blending_alg = 1; 196 | 197 | // No idea what this "plane mode" actually does but it's different on V2 198 | uint8_t acquire_plane_mode = display_state->is_v2_goggles ? 6 : 0; 199 | 200 | DEBUG_PRINT("acquire plane\n"); 201 | res = duss_hal_display_aquire_plane(display_state->disp_instance_handle,acquire_plane_mode,&plane_id); 202 | if (res != 0) { 203 | DEBUG_PRINT("failed to acquire plane"); 204 | exit(0); 205 | } 206 | 207 | res = duss_hal_display_plane_blending_set(display_state->disp_instance_handle, plane_id, display_state->pb_0); 208 | 209 | if (res != 0) { 210 | DEBUG_PRINT("failed to set blending"); 211 | exit(0); 212 | } 213 | DEBUG_PRINT("alloc ion buf\n"); 214 | res = duss_hal_mem_alloc(display_state->ion_handle,&display_state->ion_buf_0,0x473100,0x400,0,0x17); 215 | if (res != 0) { 216 | DEBUG_PRINT("failed to allocate VRAM"); 217 | exit(0); 218 | } 219 | res = duss_hal_mem_map(display_state->ion_buf_0, &display_state->fb0_virtual_addr); 220 | if (res != 0) { 221 | DEBUG_PRINT("failed to map VRAM"); 222 | exit(0); 223 | } 224 | res = duss_hal_mem_get_phys_addr(display_state->ion_buf_0, &display_state->fb0_physical_addr); 225 | if (res != 0) { 226 | DEBUG_PRINT("failed to get FB0 phys addr"); 227 | exit(0); 228 | } 229 | DEBUG_PRINT("first buffer VRAM mapped virtual memory is at %p : %p\n", display_state->fb0_virtual_addr, display_state->fb0_physical_addr); 230 | 231 | res = duss_hal_mem_alloc(display_state->ion_handle,&display_state->ion_buf_1,0x473100,0x400,0,0x17); 232 | if (res != 0) { 233 | DEBUG_PRINT("failed to allocate FB1 VRAM"); 234 | exit(0); 235 | } 236 | res = duss_hal_mem_map(display_state->ion_buf_1,&display_state->fb1_virtual_addr); 237 | if (res != 0) { 238 | DEBUG_PRINT("failed to map FB1 VRAM"); 239 | exit(0); 240 | } 241 | res = duss_hal_mem_get_phys_addr(display_state->ion_buf_1, &display_state->fb1_physical_addr); 242 | if (res != 0) { 243 | DEBUG_PRINT("failed to get FB1 phys addr"); 244 | exit(0); 245 | } 246 | DEBUG_PRINT("second buffer VRAM mapped virtual memory is at %p : %p\n", display_state->fb1_virtual_addr, display_state->fb1_physical_addr); 247 | 248 | for(int i = 0; i < 2; i++) { 249 | duss_frame_buffer_t *fb = i ? display_state->fb_1 : display_state->fb_0; 250 | fb->buffer = i ? display_state->ion_buf_1 : display_state->ion_buf_0; 251 | fb->pixel_format = display_state->is_v2_goggles ? DUSS_PIXFMT_RGBA8888_GOGGLES_V2 : DUSS_PIXFMT_RGBA8888; // 20012 instead on V2 252 | fb->frame_id = i; 253 | fb->planes[0].bytes_per_line = 0x1680; 254 | fb->planes[0].offset = 0; 255 | fb->planes[0].plane_height = 810; 256 | fb->planes[0].bytes_written = 0x473100; 257 | fb->width = 1440; 258 | fb->height = 810; 259 | fb->plane_count = 1; 260 | } 261 | } 262 | 263 | void dji_display_push_frame(dji_display_state_t *display_state) { 264 | if (display_state->frame_drawn == 0) { 265 | duss_frame_buffer_t *fb = display_state->fb_0; 266 | duss_hal_mem_sync(fb->buffer, 1); 267 | display_state->frame_drawn = 1; 268 | printf("fbdebug pushing frame\n"); 269 | duss_hal_display_push_frame(display_state->disp_instance_handle, display_state->plane_id, fb); 270 | } else { 271 | DEBUG_PRINT("!!! Dropped frame due to pending frame push!\n"); 272 | } 273 | memcpy(display_state->fb0_virtual_addr, display_state->fb1_virtual_addr, sizeof(uint32_t) * 1440 * 810); 274 | } 275 | 276 | void *dji_display_get_fb_address(dji_display_state_t *display_state) { 277 | return display_state->fb1_virtual_addr; 278 | } 279 | 280 | -------------------------------------------------------------------------------- /jni/hw/dji_display.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "duml_hal.h" 3 | 4 | typedef struct dji_display_state_s { 5 | duss_disp_plane_id_t plane_id; 6 | duss_hal_obj_handle_t disp_handle; 7 | duss_hal_obj_handle_t ion_handle; 8 | duss_disp_vop_id_t vop_id; 9 | duss_hal_mem_handle_t ion_buf_0; 10 | duss_hal_mem_handle_t ion_buf_1; 11 | void * fb0_virtual_addr; 12 | void * fb0_physical_addr; 13 | void * fb1_virtual_addr; 14 | void * fb1_physical_addr; 15 | duss_disp_instance_handle_t *disp_instance_handle; 16 | duss_frame_buffer_t *fb_0; 17 | duss_frame_buffer_t *fb_1; 18 | duss_disp_plane_blending_t *pb_0; 19 | uint8_t is_v2_goggles; 20 | uint8_t frame_drawn; 21 | } dji_display_state_t; 22 | 23 | void dji_display_push_frame(dji_display_state_t *display_state); 24 | void dji_display_open_framebuffer(dji_display_state_t *display_state, duss_disp_plane_id_t plane_id); 25 | void dji_display_open_framebuffer_injected(dji_display_state_t *display_state, duss_disp_instance_handle_t *disp, duss_hal_obj_handle_t ion_handle, duss_disp_plane_id_t plane_id); 26 | void dji_display_close_framebuffer(dji_display_state_t *display_state); 27 | dji_display_state_t *dji_display_state_alloc(uint8_t is_v2_goggles); 28 | void dji_display_state_free(dji_display_state_t *display_state); 29 | void *dji_display_get_fb_address(dji_display_state_t *display_state); 30 | -------------------------------------------------------------------------------- /jni/hw/dji_radio_shm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "dji_radio_shm.h" 7 | 8 | void open_dji_radio_shm(dji_shm_state_t *shm) { 9 | int fd = open("/dev/mem", O_RDWR); 10 | assert(fd > 0); 11 | shm->mapped_address = mmap64(NULL, RTOS_SHM_SIZE, PROT_READ, MAP_SHARED, fd, RTOS_SHM_ADDRESS); 12 | assert(shm->mapped_address != MAP_FAILED); 13 | close(fd); 14 | shm->modem_info = (modem_shmem_info_t *)((uint8_t *)shm->mapped_address + 0x100); 15 | shm->product_info = (product_shm_info_t *)((uint8_t *)shm->mapped_address + 0xC0); 16 | } 17 | 18 | void close_dji_radio_shm(dji_shm_state_t *shm) { 19 | munmap(shm->mapped_address, RTOS_SHM_SIZE); 20 | shm->mapped_address = NULL; 21 | shm->modem_info = NULL; 22 | shm->product_info = NULL; 23 | } 24 | 25 | uint16_t dji_radio_latency_ms(dji_shm_state_t *shm) { 26 | return shm->product_info->frame_delay_e2e; 27 | } 28 | 29 | uint16_t dji_radio_mbits(dji_shm_state_t *shm) { 30 | return shm->modem_info->channel_status; 31 | } -------------------------------------------------------------------------------- /jni/hw/dji_radio_shm.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | 5 | #define RTOS_SHM_ADDRESS 0xfffc1000 6 | #define RTOS_SHM_SIZE 0x1000 7 | 8 | #define RTOS_PRODUCT_OFFSET 0xc0 9 | #define RTOS_MODEM_OFFSET 0x100 10 | 11 | typedef struct modem_shmem_info_s { 12 | uint32_t frm_idx; 13 | uint16_t frm_isI; 14 | uint16_t frm_len; 15 | uint16_t frm_dsti; 16 | uint16_t frm_dstf; 17 | uint16_t channel_status; 18 | uint16_t dec_err_status; 19 | uint16_t cur_time; 20 | uint16_t delta_time; 21 | uint16_t dbg_msc; 22 | uint8_t dbg_ap_ready; 23 | uint8_t dbg_cp_ready; 24 | uint32_t local_id; 25 | uint8_t cp_state; 26 | uint8_t cp_report; 27 | uint8_t cp_report_seq; 28 | uint8_t client_type; 29 | uint32_t cp_boot_status0; 30 | uint32_t cp_boot_status1; 31 | uint32_t board_version; 32 | uint32_t board_sub_version; 33 | uint8_t machine_role; 34 | uint8_t is_reverse; 35 | int8_t cp_tx_power; 36 | uint8_t gnd_type; 37 | uint32_t cp_sssfn; 38 | uint32_t ulow_en; 39 | uint8_t mipi_rx_response; 40 | uint8_t liveview_broken_status; 41 | uint8_t reserved01; 42 | uint8_t reserved02; 43 | uint8_t ap_reboot_flag; 44 | uint8_t ap_reboot_ack_flag; 45 | uint8_t secure_sync_flag; 46 | uint8_t reserve00; 47 | uint8_t area_state; 48 | uint8_t area_substate; 49 | uint8_t GsCtrl; 50 | uint8_t GsSubState; 51 | uint8_t WaterLevel[4]; 52 | uint16_t RxCntStastic[4]; 53 | uint16_t TxCntStastic[4]; 54 | uint8_t Reserved[8]; 55 | uint32_t com_uart_status; 56 | uint32_t fcr_rx_status; 57 | uint32_t fcr_tx_status; 58 | uint32_t frm_idx_for_display; 59 | uint32_t frm_delay_for_display; 60 | uint16_t wifi_sdr_mode; 61 | uint16_t frm_isI_for_display; 62 | uint32_t country_code; 63 | uint16_t frm_len_for_display; 64 | uint16_t delta_time_for_display; 65 | uint8_t uint8_t_reboot_reason; 66 | uint8_t field_0x85; 67 | uint8_t field_0x86; 68 | uint8_t field_0x87; 69 | uint64_t cpa7_version; 70 | uint64_t dsp_version; 71 | uint8_t u8_dual_band_capability; 72 | } __attribute__((packed)) modem_shmem_info_t; 73 | 74 | typedef struct product_shm_info_s { 75 | uint16_t frm_width; 76 | uint16_t frm_height; 77 | uint8_t fps; 78 | uint8_t enc_strategy; 79 | uint16_t lcdc_underflow_cnt; 80 | uint32_t enc_sto_frm_dropped; 81 | uint32_t enc_lv_frm_dropped; 82 | uint32_t mipi_csi_frm_dropped; 83 | uint32_t display_frm_dropped; 84 | uint64_t audio_pts; 85 | uint32_t local_fps_num; 86 | uint32_t local_fps_den; 87 | uint16_t frame_delay_e2e; 88 | uint16_t cam_frame_interval; 89 | uint16_t outliner_frame_interval; 90 | uint16_t outliner_frame_interval_cnt; 91 | uint16_t max_frame_delay_e2e; 92 | uint16_t min_frame_delay_e2e; 93 | uint16_t avg_frame_delay_e2e; 94 | uint16_t if_switch; 95 | uint8_t if_change_pipe; 96 | uint8_t if_pb_pause; 97 | uint8_t liveview_pipeline_running; 98 | uint8_t avIn_pipeline_running; 99 | uint8_t avIn_stream_type; 100 | uint8_t pb_flush; 101 | uint8_t disp_pannel_need_reset; 102 | uint8_t pad; 103 | } __attribute__((packed)) product_shm_info_t; 104 | 105 | typedef struct dji_shm_state_s { 106 | void *mapped_address; 107 | product_shm_info_t *product_info; 108 | modem_shmem_info_t *modem_info; 109 | } dji_shm_state_t; 110 | 111 | uint16_t dji_radio_latency_ms(dji_shm_state_t *shm); 112 | uint16_t dji_radio_mbits(dji_shm_state_t *shm); 113 | void close_dji_radio_shm(dji_shm_state_t *shm); 114 | void open_dji_radio_shm(dji_shm_state_t *shm); -------------------------------------------------------------------------------- /jni/hw/dji_services.c: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef __ANDROID_API__ 3 | #include 4 | #endif 5 | 6 | #define V2_SERVICE_NAME "dji.glasses_wm150_service" 7 | #define V1_SERVICE_NAME "dji.glasses_service" 8 | #define DEVICE_PROPERTY_NAME "ro.product.device" 9 | #define V2_GOGGLES_DEVICE "pigeon_wm170_gls" 10 | 11 | void dji_stop_goggles(int is_v2) { 12 | #ifdef __ANDROID_API__ 13 | __system_property_set(is_v2 ? V2_SERVICE_NAME : V1_SERVICE_NAME, "0"); 14 | #endif 15 | } 16 | 17 | void dji_start_goggles(int is_v2) { 18 | #ifdef __ANDROID_API__ 19 | __system_property_set(is_v2 ? V2_SERVICE_NAME : V1_SERVICE_NAME, "1"); 20 | #endif 21 | } 22 | 23 | int dji_goggles_are_v2() { 24 | #ifdef __ANDROID_API__ 25 | char goggles_version_response[255]; 26 | int len = __system_property_get(DEVICE_PROPERTY_NAME, &goggles_version_response); 27 | return(strcmp(goggles_version_response, V2_GOGGLES_DEVICE) == 0); 28 | #else 29 | return 0; 30 | #endif 31 | } -------------------------------------------------------------------------------- /jni/hw/dji_services.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void dji_stop_goggles(int is_v2); 4 | void dji_start_goggles(int is_v2); 5 | int dji_goggles_are_v2(); -------------------------------------------------------------------------------- /jni/hw/duml_hal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | typedef int32_t duss_result_t; 5 | 6 | typedef uint8_t duss_hal_state_t; 7 | typedef uint8_t duss_hal_class_t; 8 | 9 | struct sem_t { 10 | uint32_t count; 11 | }; 12 | 13 | struct duss_osal_mutex_attrib_t { 14 | char * name; 15 | }; 16 | 17 | struct duss_osal_mutex_handle_t { 18 | struct duss_osal_mutex_attrib_t attrib; 19 | struct sem_t sema; 20 | }; 21 | 22 | typedef struct duss_osal_mutex_handle_t duss_osal_mutex_handle_t, *Pduss_osal_mutex_handle_t; 23 | 24 | 25 | typedef struct duss_hal_obj_dev_t duss_hal_obj_dev_t, *Pduss_hal_obj_dev_t; 26 | 27 | typedef struct duss_hal_obj * duss_hal_obj_handle_t; 28 | 29 | struct duss_hal_obj_dev_t { 30 | char * name; 31 | duss_hal_state_t obj_state; 32 | duss_hal_class_t obj_class; 33 | uint16_t obj_index; 34 | int32_t obj_refcnt; 35 | struct duss_osal_mutex_handle_t * obj_lock; 36 | struct duss_osal_mutex_handle_t * app_lock; 37 | duss_result_t (* open)(duss_hal_obj_handle_t, void *); 38 | duss_result_t (* close)(duss_hal_obj_handle_t); 39 | duss_result_t (* set_cfg)(duss_hal_obj_handle_t, void *); 40 | duss_result_t (* get_cfg)(duss_hal_obj_handle_t, void *); 41 | duss_result_t (* start)(duss_hal_obj_handle_t, void *); 42 | duss_result_t (* stop)(duss_hal_obj_handle_t, void *); 43 | }; 44 | 45 | struct duss_hal_obj { 46 | struct duss_hal_obj_dev_t dev; 47 | void * dev_class; 48 | void * dev_priv; 49 | }; 50 | 51 | struct duss_hal_device_desc_t { 52 | char * name; 53 | duss_result_t (* attach)(char *, duss_hal_obj_handle_t *); 54 | duss_result_t (* detach)(duss_hal_obj_handle_t); 55 | duss_hal_obj_handle_t obj; 56 | }; 57 | 58 | typedef enum duss_pixel_format { 59 | DUSS_PIXFMT_COMPRESSED_END=30002, 60 | DUSS_PIXFMT_DRAW_V1=30000, 61 | DUSS_PIXFMT_DUAL_PLANE_16BIT=40003, 62 | DUSS_PIXFMT_DUAL_PLANE_8BIT=40002, 63 | DUSS_PIXFMT_GENERAL_END=40004, 64 | DUSS_PIXFMT_JPEG_LS=30001, 65 | DUSS_PIXFMT_OPAQUE=0, 66 | DUSS_PIXFMT_RAW10_PACKED=10004, 67 | DUSS_PIXFMT_RAW12_PACKED=10005, 68 | DUSS_PIXFMT_RAW12_UNPACKED=10008, 69 | DUSS_PIXFMT_RAW14_PACKED=10006, 70 | DUSS_PIXFMT_RAW14_UNPACKED=10009, 71 | DUSS_PIXFMT_RAW16_OPAQUE=10001, 72 | DUSS_PIXFMT_RAW16_UNPACKED=10010, 73 | DUSS_PIXFMT_RAW18_PACKED=10007, 74 | DUSS_PIXFMT_RAW18_UNPACKED=10011, 75 | DUSS_PIXFMT_RAW24_OPAQUE=10002, 76 | DUSS_PIXFMT_RAW24_UNPACKED=10012, 77 | DUSS_PIXFMT_RAW32_OPAQUE=10003, 78 | DUSS_PIXFMT_RAW32_UNPACKED=10013, 79 | DUSS_PIXFMT_RAW8_OPAQUE=10000, 80 | DUSS_PIXFMT_RAW_END=10014, 81 | DUSS_PIXFMT_RGB101010=20003, 82 | DUSS_PIXFMT_RGB565=20001, 83 | DUSS_PIXFMT_RGB888=20000, 84 | DUSS_PIXFMT_RGBA8888=20002, 85 | DUSS_PIXFMT_RGBA8888_GOGGLES_V2=20012, 86 | DUSS_PIXFMT_RGB_END=20004, 87 | DUSS_PIXFMT_SINGLE_PLANE_16BIT=40001, 88 | DUSS_PIXFMT_SINGLE_PLANE_8BIT=40000, 89 | DUSS_PIXFMT_YUV10_420_END=1005, 90 | DUSS_PIXFMT_YUV10_422_END=1105, 91 | DUSS_PIXFMT_YUV10_444_END=1203, 92 | DUSS_PIXFMT_YUV10_P444_YUV=1202, 93 | DUSS_PIXFMT_YUV10_SP420_YUV=1003, 94 | DUSS_PIXFMT_YUV10_SP420_YUV16=1001, 95 | DUSS_PIXFMT_YUV10_SP420_YVU=1004, 96 | DUSS_PIXFMT_YUV10_SP420_YVU16=1002, 97 | DUSS_PIXFMT_YUV10_SP422_YUV=1103, 98 | DUSS_PIXFMT_YUV10_SP422_YUV16=1101, 99 | DUSS_PIXFMT_YUV10_SP422_YVU=1104, 100 | DUSS_PIXFMT_YUV10_Y422_YUYV16=1102, 101 | DUSS_PIXFMT_YUV10_Y444_UYVA=1201, 102 | DUSS_PIXFMT_YUV8_420_END=4, 103 | DUSS_PIXFMT_YUV8_422_END=106, 104 | DUSS_PIXFMT_YUV8_444_END=203, 105 | DUSS_PIXFMT_YUV8_P420_YUV=1, 106 | DUSS_PIXFMT_YUV8_P422_YUV=105, 107 | DUSS_PIXFMT_YUV8_P444_YUV=202, 108 | DUSS_PIXFMT_YUV8_SP420_YUV=2, 109 | DUSS_PIXFMT_YUV8_SP420_YVU=3, 110 | DUSS_PIXFMT_YUV8_SP422_YUV=103, 111 | DUSS_PIXFMT_YUV8_SP422_YVU=104, 112 | DUSS_PIXFMT_YUV8_Y422_UYVY=101, 113 | DUSS_PIXFMT_YUV8_Y422_YUYV=102, 114 | DUSS_PIXFMT_YUV8_Y444_UYV=201, 115 | DUSS_PIXFMT_YUV_END=1204 116 | } duss_pixel_format; 117 | 118 | typedef enum duss_pixel_format duss_pixel_format_t; 119 | 120 | struct duss_object { 121 | int ref_count; 122 | struct duss_osal_mutex_handle_t * lock; 123 | void (* on_released)(struct duss_object *); 124 | }; 125 | 126 | typedef struct duss_object duss_object_t; 127 | 128 | typedef struct duss_hal_mem_buf * duss_hal_mem_handle_t; 129 | 130 | typedef struct duss_frame_plane duss_frame_plane, *Pduss_frame_plane; 131 | 132 | typedef struct duss_frame_plane duss_frame_plane_t; 133 | 134 | struct duss_frame_plane { 135 | int32_t bytes_per_line; 136 | int32_t offset; 137 | int32_t plane_height; 138 | int32_t bytes_written; 139 | }; 140 | 141 | struct __attribute__((__packed__)) duss_frame_buffer { 142 | duss_object_t obj; 143 | duss_hal_mem_handle_t buffer; 144 | duss_pixel_format_t pixel_format; 145 | uint32_t unknown; 146 | int64_t time_stamp; 147 | int32_t width; 148 | int32_t height; 149 | duss_frame_plane_t planes[4]; 150 | int32_t plane_count; 151 | uint32_t frame_id; 152 | }; 153 | 154 | typedef struct duss_frame_buffer duss_frame_buffer_t; 155 | 156 | typedef struct duss_hal_device_desc_t duss_hal_device_desc_t, *Pduss_hal_device_desc_t; 157 | 158 | typedef struct duss_hal_obj duss_hal_obj, *Pduss_hal_obj; 159 | 160 | typedef struct duss_disp_instance_handle_t duss_disp_instance_handle_t, *Pduss_disp_instance_handle_t; 161 | 162 | struct __attribute__((__packed__)) duss_disp_instance_handle_t { 163 | uint8_t is_init; 164 | uint8_t field_0x1; 165 | uint8_t field_0x2; 166 | uint8_t field_0x3; 167 | uint32_t current_vop_index; 168 | uint32_t ref_cnt; 169 | duss_hal_obj_handle_t obj; 170 | void * instance_private_data; 171 | void * global_disp_info; 172 | }; 173 | 174 | typedef uint8_t duss_disp_vop_id_t; 175 | 176 | typedef uint8_t duss_disp_plane_type_t; 177 | 178 | typedef uint8_t duss_disp_port_id_t; 179 | 180 | typedef struct duss_disp_timing_detail_t duss_disp_timing_detail_t, *Pduss_disp_timing_detail_t; 181 | 182 | typedef enum duss_disp_timing_bitdepth_t { 183 | DUSS_DISP_BITDEPTH_10BIT=1, 184 | DUSS_DISP_BITDEPTH_12BIT=2, 185 | DUSS_DISP_BITDEPTH_8BIT=0 186 | } duss_disp_timing_bitdepth_t; 187 | 188 | typedef enum duss_disp_timing_fmt_t { 189 | DUSS_DISP_FMT_RGB=0, 190 | DUSS_DISP_FMT_YUV420=3, 191 | DUSS_DISP_FMT_YUV422=2, 192 | DUSS_DISP_FMT_YUV444=1 193 | } duss_disp_timing_fmt_t; 194 | 195 | struct duss_disp_timing_detail_t { 196 | int32_t clock; 197 | int32_t hdisplay; 198 | int32_t hsync_start; 199 | int32_t hsync_end; 200 | int32_t htotal; 201 | int32_t hskew; 202 | int32_t vdisplay; 203 | int32_t vsync_start; 204 | int32_t vsync_end; 205 | int32_t vtotal; 206 | int32_t vscan; 207 | enum duss_disp_timing_fmt_t disp_fmt; 208 | enum duss_disp_timing_bitdepth_t disp_bitdepth; 209 | }; 210 | 211 | typedef struct duss_disp_plane_blending_t duss_disp_plane_blending_t, *Pduss_disp_plane_blending_t; 212 | 213 | typedef uint8_t duss_disp_plane_alpha_alg_t; 214 | typedef int8_t duss_disp_plane_id_t; 215 | 216 | struct __attribute__((__packed__)) duss_disp_plane_blending_t { 217 | uint8_t is_enable; 218 | uint8_t glb_alpha_en; 219 | uint8_t field_0x2; 220 | uint8_t field_0x3; 221 | int32_t glb_alpha_val; 222 | duss_disp_plane_alpha_alg_t blending_alg; 223 | duss_disp_plane_id_t order; 224 | uint8_t field_0xa; 225 | uint8_t field_0xb; 226 | int32_t hoffset; 227 | int32_t voffset; 228 | }; 229 | 230 | typedef duss_result_t (frame_pop_handler)(duss_disp_instance_handle_t * , duss_disp_plane_id_t , duss_frame_buffer_t * , void * ); 231 | 232 | duss_result_t duss_hal_initialize(duss_hal_device_desc_t * ); 233 | duss_result_t duss_hal_deinitialize(); 234 | duss_result_t duss_hal_device_open(char *device_name, void *unknown, duss_hal_obj_handle_t * ); 235 | duss_result_t duss_hal_device_start(duss_hal_obj_handle_t , void * ); 236 | duss_result_t duss_hal_device_close(duss_hal_obj_handle_t ); 237 | duss_result_t duss_hal_device_stop(duss_hal_obj_handle_t ); 238 | 239 | duss_result_t duss_hal_mem_alloc(duss_hal_obj_handle_t , duss_hal_mem_handle_t * , uint32_t size, uint32_t , uint32_t , uint32_t ); 240 | duss_result_t duss_hal_mem_get_phys_addr(duss_hal_mem_handle_t , void * * ); 241 | duss_result_t duss_hal_mem_map(duss_hal_mem_handle_t , void * * ); 242 | duss_result_t duss_hal_mem_free(duss_hal_mem_handle_t ); 243 | duss_result_t duss_hal_mem_sync(duss_hal_mem_handle_t , uint32_t ); 244 | 245 | duss_result_t duss_hal_display_open(duss_hal_obj_handle_t , duss_disp_instance_handle_t * * , duss_disp_vop_id_t ); 246 | duss_result_t duss_hal_display_close(duss_hal_obj_handle_t , duss_disp_instance_handle_t * *); 247 | // sic 248 | duss_result_t duss_hal_display_aquire_plane(duss_disp_instance_handle_t * , duss_disp_plane_type_t , duss_disp_plane_id_t * ); 249 | duss_result_t duss_hal_display_reset(duss_disp_instance_handle_t *); 250 | duss_result_t duss_hal_display_register_frame_cycle_callback(duss_disp_instance_handle_t * , duss_disp_plane_id_t , frame_pop_handler * , void * ); 251 | duss_result_t duss_hal_display_timing_detail_get(duss_disp_instance_handle_t * , duss_disp_timing_detail_t * ); 252 | duss_result_t duss_hal_display_port_enable(duss_disp_instance_handle_t * , duss_disp_port_id_t , uint8_t ); 253 | duss_result_t duss_hal_display_plane_blending_set(duss_disp_instance_handle_t * , duss_disp_plane_id_t , duss_disp_plane_blending_t * ); 254 | duss_result_t duss_hal_display_release_plane(duss_disp_instance_handle_t * , duss_disp_plane_id_t ); 255 | duss_result_t duss_hal_display_push_frame(duss_disp_instance_handle_t * , duss_disp_plane_id_t , duss_frame_buffer_t * ); 256 | 257 | duss_result_t duss_hal_attach_disp(char *param_1,duss_hal_obj **param_2); 258 | duss_result_t duss_hal_attach_ion_mem(char *param_1,duss_hal_obj **param_2); 259 | duss_result_t duss_hal_detach_ion_mem(); 260 | duss_result_t duss_hal_detach_disp(); 261 | -------------------------------------------------------------------------------- /jni/json/osd_config.c: -------------------------------------------------------------------------------- 1 | #include "parson.h" 2 | #define JSON_CONFIG_PATH "/opt/etc/package-config/msp-osd/config.json" 3 | static JSON_Object *root_object = NULL; 4 | static void load_config() { 5 | JSON_Value *root_value = NULL; 6 | if(root_object == NULL) { 7 | root_value = json_parse_file(JSON_CONFIG_PATH); 8 | if (json_value_get_type(root_value) != JSONObject) { 9 | return; 10 | } 11 | root_object = json_value_get_object(root_value); 12 | } 13 | } 14 | 15 | int get_boolean_config_value(const char* key) { 16 | load_config(); 17 | if (root_object != NULL) { 18 | // parson returns -1 for undefined keys, which can happen when 19 | // new keys are defined in the schema - ensure they come back as false 20 | return json_object_get_boolean(root_object, key) > 0; 21 | } else { 22 | return 0; 23 | } 24 | } 25 | 26 | const char * get_string_config_value(const char *key) 27 | { 28 | load_config(); 29 | if (root_object != NULL) 30 | { 31 | return json_object_get_string(root_object, key); 32 | } 33 | else 34 | { 35 | return NULL; 36 | } 37 | } 38 | 39 | int get_integer_config_value(const char *key) 40 | { 41 | load_config(); 42 | if (root_object != NULL) 43 | { 44 | return (int)json_object_get_number(root_object, key); 45 | } 46 | else 47 | { 48 | return 0; 49 | } 50 | } -------------------------------------------------------------------------------- /jni/json/osd_config.h: -------------------------------------------------------------------------------- 1 | int get_boolean_config_value(const char* key); 2 | const char *get_string_config_value(const char *key); 3 | int get_integer_config_value(const char *key); -------------------------------------------------------------------------------- /jni/json/parson.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: MIT 3 | 4 | Parson 1.4.0 (https://github.com/kgabis/parson) 5 | Copyright (c) 2012 - 2022 Krzysztof Gabis 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | 26 | #ifndef parson_parson_h 27 | #define parson_parson_h 28 | 29 | #ifdef __cplusplus 30 | extern "C" 31 | { 32 | #endif 33 | #if 0 34 | } /* unconfuse xcode */ 35 | #endif 36 | 37 | #define PARSON_VERSION_MAJOR 1 38 | #define PARSON_VERSION_MINOR 4 39 | #define PARSON_VERSION_PATCH 0 40 | 41 | #define PARSON_VERSION_STRING "1.4.0" 42 | 43 | #include /* size_t */ 44 | 45 | /* Types and enums */ 46 | typedef struct json_object_t JSON_Object; 47 | typedef struct json_array_t JSON_Array; 48 | typedef struct json_value_t JSON_Value; 49 | 50 | enum json_value_type { 51 | JSONError = -1, 52 | JSONNull = 1, 53 | JSONString = 2, 54 | JSONNumber = 3, 55 | JSONObject = 4, 56 | JSONArray = 5, 57 | JSONBoolean = 6 58 | }; 59 | typedef int JSON_Value_Type; 60 | 61 | enum json_result_t { 62 | JSONSuccess = 0, 63 | JSONFailure = -1 64 | }; 65 | typedef int JSON_Status; 66 | 67 | typedef void * (*JSON_Malloc_Function)(size_t); 68 | typedef void (*JSON_Free_Function)(void *); 69 | 70 | /* Call only once, before calling any other function from parson API. If not called, malloc and free 71 | from stdlib will be used for all allocations */ 72 | void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun); 73 | 74 | /* Sets if slashes should be escaped or not when serializing JSON. By default slashes are escaped. 75 | This function sets a global setting and is not thread safe. */ 76 | void json_set_escape_slashes(int escape_slashes); 77 | 78 | /* Sets float format used for serialization of numbers. 79 | Make sure it can't serialize to a string longer than PARSON_NUM_BUF_SIZE. 80 | If format is null then the default format is used. */ 81 | void json_set_float_serialization_format(const char *format); 82 | 83 | /* Parses first JSON value in a file, returns NULL in case of error */ 84 | JSON_Value * json_parse_file(const char *filename); 85 | 86 | /* Parses first JSON value in a file and ignores comments (/ * * / and //), 87 | returns NULL in case of error */ 88 | JSON_Value * json_parse_file_with_comments(const char *filename); 89 | 90 | /* Parses first JSON value in a string, returns NULL in case of error */ 91 | JSON_Value * json_parse_string(const char *string); 92 | 93 | /* Parses first JSON value in a string and ignores comments (/ * * / and //), 94 | returns NULL in case of error */ 95 | JSON_Value * json_parse_string_with_comments(const char *string); 96 | 97 | /* Serialization */ 98 | size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */ 99 | JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); 100 | JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename); 101 | char * json_serialize_to_string(const JSON_Value *value); 102 | 103 | /* Pretty serialization */ 104 | size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */ 105 | JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); 106 | JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename); 107 | char * json_serialize_to_string_pretty(const JSON_Value *value); 108 | 109 | void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */ 110 | 111 | /* Comparing */ 112 | int json_value_equals(const JSON_Value *a, const JSON_Value *b); 113 | 114 | /* Validation 115 | This is *NOT* JSON Schema. It validates json by checking if object have identically 116 | named fields with matching types. 117 | For example schema {"name":"", "age":0} will validate 118 | {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"}, 119 | but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}. 120 | In case of arrays, only first value in schema is checked against all values in tested array. 121 | Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays, 122 | null validates values of every type. 123 | */ 124 | JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value); 125 | 126 | /* 127 | * JSON Object 128 | */ 129 | JSON_Value * json_object_get_value (const JSON_Object *object, const char *name); 130 | const char * json_object_get_string (const JSON_Object *object, const char *name); 131 | size_t json_object_get_string_len(const JSON_Object *object, const char *name); /* doesn't account for last null character */ 132 | JSON_Object * json_object_get_object (const JSON_Object *object, const char *name); 133 | JSON_Array * json_object_get_array (const JSON_Object *object, const char *name); 134 | double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ 135 | int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ 136 | 137 | /* dotget functions enable addressing values with dot notation in nested objects, 138 | just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). 139 | Because valid names in JSON can contain dots, some values may be inaccessible 140 | this way. */ 141 | JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name); 142 | const char * json_object_dotget_string (const JSON_Object *object, const char *name); 143 | size_t json_object_dotget_string_len(const JSON_Object *object, const char *name); /* doesn't account for last null character */ 144 | JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name); 145 | JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name); 146 | double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ 147 | int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ 148 | 149 | /* Functions to get available names */ 150 | size_t json_object_get_count (const JSON_Object *object); 151 | const char * json_object_get_name (const JSON_Object *object, size_t index); 152 | JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index); 153 | JSON_Value * json_object_get_wrapping_value(const JSON_Object *object); 154 | 155 | /* Functions to check if object has a value with a specific name. Returned value is 1 if object has 156 | * a value and 0 if it doesn't. dothas functions behave exactly like dotget functions. */ 157 | int json_object_has_value (const JSON_Object *object, const char *name); 158 | int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); 159 | 160 | int json_object_dothas_value (const JSON_Object *object, const char *name); 161 | int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); 162 | 163 | /* Creates new name-value pair or frees and replaces old value with a new one. 164 | * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */ 165 | JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value); 166 | JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string); 167 | JSON_Status json_object_set_string_with_len(JSON_Object *object, const char *name, const char *string, size_t len); /* length shouldn't include last null character */ 168 | JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number); 169 | JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean); 170 | JSON_Status json_object_set_null(JSON_Object *object, const char *name); 171 | 172 | /* Works like dotget functions, but creates whole hierarchy if necessary. 173 | * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */ 174 | JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value); 175 | JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string); 176 | JSON_Status json_object_dotset_string_with_len(JSON_Object *object, const char *name, const char *string, size_t len); /* length shouldn't include last null character */ 177 | JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number); 178 | JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean); 179 | JSON_Status json_object_dotset_null(JSON_Object *object, const char *name); 180 | 181 | /* Frees and removes name-value pair */ 182 | JSON_Status json_object_remove(JSON_Object *object, const char *name); 183 | 184 | /* Works like dotget function, but removes name-value pair only on exact match. */ 185 | JSON_Status json_object_dotremove(JSON_Object *object, const char *key); 186 | 187 | /* Removes all name-value pairs in object */ 188 | JSON_Status json_object_clear(JSON_Object *object); 189 | 190 | /* 191 | *JSON Array 192 | */ 193 | JSON_Value * json_array_get_value (const JSON_Array *array, size_t index); 194 | const char * json_array_get_string (const JSON_Array *array, size_t index); 195 | size_t json_array_get_string_len(const JSON_Array *array, size_t index); /* doesn't account for last null character */ 196 | JSON_Object * json_array_get_object (const JSON_Array *array, size_t index); 197 | JSON_Array * json_array_get_array (const JSON_Array *array, size_t index); 198 | double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */ 199 | int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */ 200 | size_t json_array_get_count (const JSON_Array *array); 201 | JSON_Value * json_array_get_wrapping_value(const JSON_Array *array); 202 | 203 | /* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist. 204 | * Order of values in array may change during execution. */ 205 | JSON_Status json_array_remove(JSON_Array *array, size_t i); 206 | 207 | /* Frees and removes from array value at given index and replaces it with given one. 208 | * Does nothing and returns JSONFailure if index doesn't exist. 209 | * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */ 210 | JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value); 211 | JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string); 212 | JSON_Status json_array_replace_string_with_len(JSON_Array *array, size_t i, const char *string, size_t len); /* length shouldn't include last null character */ 213 | JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number); 214 | JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean); 215 | JSON_Status json_array_replace_null(JSON_Array *array, size_t i); 216 | 217 | /* Frees and removes all values from array */ 218 | JSON_Status json_array_clear(JSON_Array *array); 219 | 220 | /* Appends new value at the end of array. 221 | * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */ 222 | JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value); 223 | JSON_Status json_array_append_string(JSON_Array *array, const char *string); 224 | JSON_Status json_array_append_string_with_len(JSON_Array *array, const char *string, size_t len); /* length shouldn't include last null character */ 225 | JSON_Status json_array_append_number(JSON_Array *array, double number); 226 | JSON_Status json_array_append_boolean(JSON_Array *array, int boolean); 227 | JSON_Status json_array_append_null(JSON_Array *array); 228 | 229 | /* 230 | *JSON Value 231 | */ 232 | JSON_Value * json_value_init_object (void); 233 | JSON_Value * json_value_init_array (void); 234 | JSON_Value * json_value_init_string (const char *string); /* copies passed string */ 235 | JSON_Value * json_value_init_string_with_len(const char *string, size_t length); /* copies passed string, length shouldn't include last null character */ 236 | JSON_Value * json_value_init_number (double number); 237 | JSON_Value * json_value_init_boolean(int boolean); 238 | JSON_Value * json_value_init_null (void); 239 | JSON_Value * json_value_deep_copy (const JSON_Value *value); 240 | void json_value_free (JSON_Value *value); 241 | 242 | JSON_Value_Type json_value_get_type (const JSON_Value *value); 243 | JSON_Object * json_value_get_object (const JSON_Value *value); 244 | JSON_Array * json_value_get_array (const JSON_Value *value); 245 | const char * json_value_get_string (const JSON_Value *value); 246 | size_t json_value_get_string_len(const JSON_Value *value); /* doesn't account for last null character */ 247 | double json_value_get_number (const JSON_Value *value); 248 | int json_value_get_boolean(const JSON_Value *value); 249 | JSON_Value * json_value_get_parent (const JSON_Value *value); 250 | 251 | /* Same as above, but shorter */ 252 | JSON_Value_Type json_type (const JSON_Value *value); 253 | JSON_Object * json_object (const JSON_Value *value); 254 | JSON_Array * json_array (const JSON_Value *value); 255 | const char * json_string (const JSON_Value *value); 256 | size_t json_string_len(const JSON_Value *value); /* doesn't account for last null character */ 257 | double json_number (const JSON_Value *value); 258 | int json_boolean(const JSON_Value *value); 259 | 260 | #ifdef __cplusplus 261 | } 262 | #endif 263 | 264 | #endif 265 | -------------------------------------------------------------------------------- /jni/libduml_hal.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpv-wtf/msp-osd/ef9d906cf43ac939799ef04287b19bfbd1bd968f/jni/libduml_hal.so -------------------------------------------------------------------------------- /jni/libspng/spng.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-2-Clause */ 2 | #ifndef SPNG_H 3 | #define SPNG_H 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(SPNG_STATIC) 10 | #if defined(SPNG__BUILD) 11 | #define SPNG_API __declspec(dllexport) 12 | #else 13 | #define SPNG_API __declspec(dllimport) 14 | #endif 15 | #else 16 | #define SPNG_API 17 | #endif 18 | 19 | #if defined(_MSC_VER) 20 | #define SPNG_CDECL __cdecl 21 | #else 22 | #define SPNG_CDECL 23 | #endif 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #define SPNG_VERSION_MAJOR 0 30 | #define SPNG_VERSION_MINOR 7 31 | #define SPNG_VERSION_PATCH 3 32 | 33 | enum spng_errno 34 | { 35 | SPNG_IO_ERROR = -2, 36 | SPNG_IO_EOF = -1, 37 | SPNG_OK = 0, 38 | SPNG_EINVAL, 39 | SPNG_EMEM, 40 | SPNG_EOVERFLOW, 41 | SPNG_ESIGNATURE, 42 | SPNG_EWIDTH, 43 | SPNG_EHEIGHT, 44 | SPNG_EUSER_WIDTH, 45 | SPNG_EUSER_HEIGHT, 46 | SPNG_EBIT_DEPTH, 47 | SPNG_ECOLOR_TYPE, 48 | SPNG_ECOMPRESSION_METHOD, 49 | SPNG_EFILTER_METHOD, 50 | SPNG_EINTERLACE_METHOD, 51 | SPNG_EIHDR_SIZE, 52 | SPNG_ENOIHDR, 53 | SPNG_ECHUNK_POS, 54 | SPNG_ECHUNK_SIZE, 55 | SPNG_ECHUNK_CRC, 56 | SPNG_ECHUNK_TYPE, 57 | SPNG_ECHUNK_UNKNOWN_CRITICAL, 58 | SPNG_EDUP_PLTE, 59 | SPNG_EDUP_CHRM, 60 | SPNG_EDUP_GAMA, 61 | SPNG_EDUP_ICCP, 62 | SPNG_EDUP_SBIT, 63 | SPNG_EDUP_SRGB, 64 | SPNG_EDUP_BKGD, 65 | SPNG_EDUP_HIST, 66 | SPNG_EDUP_TRNS, 67 | SPNG_EDUP_PHYS, 68 | SPNG_EDUP_TIME, 69 | SPNG_EDUP_OFFS, 70 | SPNG_EDUP_EXIF, 71 | SPNG_ECHRM, 72 | SPNG_EPLTE_IDX, 73 | SPNG_ETRNS_COLOR_TYPE, 74 | SPNG_ETRNS_NO_PLTE, 75 | SPNG_EGAMA, 76 | SPNG_EICCP_NAME, 77 | SPNG_EICCP_COMPRESSION_METHOD, 78 | SPNG_ESBIT, 79 | SPNG_ESRGB, 80 | SPNG_ETEXT, 81 | SPNG_ETEXT_KEYWORD, 82 | SPNG_EZTXT, 83 | SPNG_EZTXT_COMPRESSION_METHOD, 84 | SPNG_EITXT, 85 | SPNG_EITXT_COMPRESSION_FLAG, 86 | SPNG_EITXT_COMPRESSION_METHOD, 87 | SPNG_EITXT_LANG_TAG, 88 | SPNG_EITXT_TRANSLATED_KEY, 89 | SPNG_EBKGD_NO_PLTE, 90 | SPNG_EBKGD_PLTE_IDX, 91 | SPNG_EHIST_NO_PLTE, 92 | SPNG_EPHYS, 93 | SPNG_ESPLT_NAME, 94 | SPNG_ESPLT_DUP_NAME, 95 | SPNG_ESPLT_DEPTH, 96 | SPNG_ETIME, 97 | SPNG_EOFFS, 98 | SPNG_EEXIF, 99 | SPNG_EIDAT_TOO_SHORT, 100 | SPNG_EIDAT_STREAM, 101 | SPNG_EZLIB, 102 | SPNG_EFILTER, 103 | SPNG_EBUFSIZ, 104 | SPNG_EIO, 105 | SPNG_EOF, 106 | SPNG_EBUF_SET, 107 | SPNG_EBADSTATE, 108 | SPNG_EFMT, 109 | SPNG_EFLAGS, 110 | SPNG_ECHUNKAVAIL, 111 | SPNG_ENCODE_ONLY, 112 | SPNG_EOI, 113 | SPNG_ENOPLTE, 114 | SPNG_ECHUNK_LIMITS, 115 | SPNG_EZLIB_INIT, 116 | SPNG_ECHUNK_STDLEN, 117 | SPNG_EINTERNAL, 118 | SPNG_ECTXTYPE, 119 | SPNG_ENOSRC, 120 | SPNG_ENODST, 121 | SPNG_EOPSTATE, 122 | SPNG_ENOTFINAL, 123 | }; 124 | 125 | enum spng_text_type 126 | { 127 | SPNG_TEXT = 1, 128 | SPNG_ZTXT = 2, 129 | SPNG_ITXT = 3 130 | }; 131 | 132 | enum spng_color_type 133 | { 134 | SPNG_COLOR_TYPE_GRAYSCALE = 0, 135 | SPNG_COLOR_TYPE_TRUECOLOR = 2, 136 | SPNG_COLOR_TYPE_INDEXED = 3, 137 | SPNG_COLOR_TYPE_GRAYSCALE_ALPHA = 4, 138 | SPNG_COLOR_TYPE_TRUECOLOR_ALPHA = 6 139 | }; 140 | 141 | enum spng_filter 142 | { 143 | SPNG_FILTER_NONE = 0, 144 | SPNG_FILTER_SUB = 1, 145 | SPNG_FILTER_UP = 2, 146 | SPNG_FILTER_AVERAGE = 3, 147 | SPNG_FILTER_PAETH = 4 148 | }; 149 | 150 | enum spng_filter_choice 151 | { 152 | SPNG_DISABLE_FILTERING = 0, 153 | SPNG_FILTER_CHOICE_NONE = 8, 154 | SPNG_FILTER_CHOICE_SUB = 16, 155 | SPNG_FILTER_CHOICE_UP = 32, 156 | SPNG_FILTER_CHOICE_AVG = 64, 157 | SPNG_FILTER_CHOICE_PAETH = 128, 158 | SPNG_FILTER_CHOICE_ALL = (8|16|32|64|128) 159 | }; 160 | 161 | enum spng_interlace_method 162 | { 163 | SPNG_INTERLACE_NONE = 0, 164 | SPNG_INTERLACE_ADAM7 = 1 165 | }; 166 | 167 | /* Channels are always in byte-order */ 168 | enum spng_format 169 | { 170 | SPNG_FMT_RGBA8 = 1, 171 | SPNG_FMT_RGBA16 = 2, 172 | SPNG_FMT_RGB8 = 4, 173 | 174 | /* Partially implemented, see documentation */ 175 | SPNG_FMT_GA8 = 16, 176 | SPNG_FMT_GA16 = 32, 177 | SPNG_FMT_G8 = 64, 178 | 179 | /* No conversion or scaling */ 180 | SPNG_FMT_PNG = 256, 181 | SPNG_FMT_RAW = 512 /* big-endian (everything else is host-endian) */ 182 | }; 183 | 184 | enum spng_ctx_flags 185 | { 186 | SPNG_CTX_IGNORE_ADLER32 = 1, /* Ignore checksum in DEFLATE streams */ 187 | SPNG_CTX_ENCODER = 2 /* Create an encoder context */ 188 | }; 189 | 190 | enum spng_decode_flags 191 | { 192 | SPNG_DECODE_USE_TRNS = 1, /* Deprecated */ 193 | SPNG_DECODE_USE_GAMA = 2, /* Deprecated */ 194 | SPNG_DECODE_USE_SBIT = 8, /* Undocumented */ 195 | 196 | SPNG_DECODE_TRNS = 1, /* Apply transparency */ 197 | SPNG_DECODE_GAMMA = 2, /* Apply gamma correction */ 198 | SPNG_DECODE_PROGRESSIVE = 256 /* Initialize for progressive reads */ 199 | }; 200 | 201 | enum spng_crc_action 202 | { 203 | /* Default for critical chunks */ 204 | SPNG_CRC_ERROR = 0, 205 | 206 | /* Discard chunk, invalid for critical chunks. 207 | Since v0.6.2: default for ancillary chunks */ 208 | SPNG_CRC_DISCARD = 1, 209 | 210 | /* Ignore and don't calculate checksum. 211 | Since v0.6.2: also ignores checksums in DEFLATE streams */ 212 | SPNG_CRC_USE = 2 213 | }; 214 | 215 | enum spng_encode_flags 216 | { 217 | SPNG_ENCODE_PROGRESSIVE = 1, /* Initialize for progressive writes */ 218 | SPNG_ENCODE_FINALIZE = 2, /* Finalize PNG after encoding image */ 219 | }; 220 | 221 | struct spng_ihdr 222 | { 223 | uint32_t width; 224 | uint32_t height; 225 | uint8_t bit_depth; 226 | uint8_t color_type; 227 | uint8_t compression_method; 228 | uint8_t filter_method; 229 | uint8_t interlace_method; 230 | }; 231 | 232 | struct spng_plte_entry 233 | { 234 | uint8_t red; 235 | uint8_t green; 236 | uint8_t blue; 237 | 238 | uint8_t alpha; /* Reserved for internal use */ 239 | }; 240 | 241 | struct spng_plte 242 | { 243 | uint32_t n_entries; 244 | struct spng_plte_entry entries[256]; 245 | }; 246 | 247 | struct spng_trns 248 | { 249 | uint16_t gray; 250 | 251 | uint16_t red; 252 | uint16_t green; 253 | uint16_t blue; 254 | 255 | uint32_t n_type3_entries; 256 | uint8_t type3_alpha[256]; 257 | }; 258 | 259 | struct spng_chrm_int 260 | { 261 | uint32_t white_point_x; 262 | uint32_t white_point_y; 263 | uint32_t red_x; 264 | uint32_t red_y; 265 | uint32_t green_x; 266 | uint32_t green_y; 267 | uint32_t blue_x; 268 | uint32_t blue_y; 269 | }; 270 | 271 | struct spng_chrm 272 | { 273 | double white_point_x; 274 | double white_point_y; 275 | double red_x; 276 | double red_y; 277 | double green_x; 278 | double green_y; 279 | double blue_x; 280 | double blue_y; 281 | }; 282 | 283 | struct spng_iccp 284 | { 285 | char profile_name[80]; 286 | size_t profile_len; 287 | char *profile; 288 | }; 289 | 290 | struct spng_sbit 291 | { 292 | uint8_t grayscale_bits; 293 | uint8_t red_bits; 294 | uint8_t green_bits; 295 | uint8_t blue_bits; 296 | uint8_t alpha_bits; 297 | }; 298 | 299 | struct spng_text 300 | { 301 | char keyword[80]; 302 | int type; 303 | 304 | size_t length; 305 | char *text; 306 | 307 | uint8_t compression_flag; /* iTXt only */ 308 | uint8_t compression_method; /* iTXt, ztXt only */ 309 | char *language_tag; /* iTXt only */ 310 | char *translated_keyword; /* iTXt only */ 311 | }; 312 | 313 | struct spng_bkgd 314 | { 315 | uint16_t gray; /* Only for gray/gray alpha */ 316 | uint16_t red; 317 | uint16_t green; 318 | uint16_t blue; 319 | uint16_t plte_index; /* Only for indexed color */ 320 | }; 321 | 322 | struct spng_hist 323 | { 324 | uint16_t frequency[256]; 325 | }; 326 | 327 | struct spng_phys 328 | { 329 | uint32_t ppu_x, ppu_y; 330 | uint8_t unit_specifier; 331 | }; 332 | 333 | struct spng_splt_entry 334 | { 335 | uint16_t red; 336 | uint16_t green; 337 | uint16_t blue; 338 | uint16_t alpha; 339 | uint16_t frequency; 340 | }; 341 | 342 | struct spng_splt 343 | { 344 | char name[80]; 345 | uint8_t sample_depth; 346 | uint32_t n_entries; 347 | struct spng_splt_entry *entries; 348 | }; 349 | 350 | struct spng_time 351 | { 352 | uint16_t year; 353 | uint8_t month; 354 | uint8_t day; 355 | uint8_t hour; 356 | uint8_t minute; 357 | uint8_t second; 358 | }; 359 | 360 | struct spng_offs 361 | { 362 | int32_t x, y; 363 | uint8_t unit_specifier; 364 | }; 365 | 366 | struct spng_exif 367 | { 368 | size_t length; 369 | char *data; 370 | }; 371 | 372 | struct spng_chunk 373 | { 374 | size_t offset; 375 | uint32_t length; 376 | uint8_t type[4]; 377 | uint32_t crc; 378 | }; 379 | 380 | enum spng_location 381 | { 382 | SPNG_AFTER_IHDR = 1, 383 | SPNG_AFTER_PLTE = 2, 384 | SPNG_AFTER_IDAT = 8, 385 | }; 386 | 387 | struct spng_unknown_chunk 388 | { 389 | uint8_t type[4]; 390 | size_t length; 391 | void *data; 392 | enum spng_location location; 393 | }; 394 | 395 | enum spng_option 396 | { 397 | SPNG_KEEP_UNKNOWN_CHUNKS = 1, 398 | 399 | SPNG_IMG_COMPRESSION_LEVEL, 400 | SPNG_IMG_WINDOW_BITS, 401 | SPNG_IMG_MEM_LEVEL, 402 | SPNG_IMG_COMPRESSION_STRATEGY, 403 | 404 | SPNG_TEXT_COMPRESSION_LEVEL, 405 | SPNG_TEXT_WINDOW_BITS, 406 | SPNG_TEXT_MEM_LEVEL, 407 | SPNG_TEXT_COMPRESSION_STRATEGY, 408 | 409 | SPNG_FILTER_CHOICE, 410 | SPNG_CHUNK_COUNT_LIMIT, 411 | SPNG_ENCODE_TO_BUFFER, 412 | }; 413 | 414 | typedef void* SPNG_CDECL spng_malloc_fn(size_t size); 415 | typedef void* SPNG_CDECL spng_realloc_fn(void* ptr, size_t size); 416 | typedef void* SPNG_CDECL spng_calloc_fn(size_t count, size_t size); 417 | typedef void SPNG_CDECL spng_free_fn(void* ptr); 418 | 419 | struct spng_alloc 420 | { 421 | spng_malloc_fn *malloc_fn; 422 | spng_realloc_fn *realloc_fn; 423 | spng_calloc_fn *calloc_fn; 424 | spng_free_fn *free_fn; 425 | }; 426 | 427 | struct spng_row_info 428 | { 429 | uint32_t scanline_idx; 430 | uint32_t row_num; /* deinterlaced row index */ 431 | int pass; 432 | uint8_t filter; 433 | }; 434 | 435 | typedef struct spng_ctx spng_ctx; 436 | 437 | typedef int spng_read_fn(spng_ctx *ctx, void *user, void *dest, size_t length); 438 | typedef int spng_write_fn(spng_ctx *ctx, void *user, void *src, size_t length); 439 | 440 | typedef int spng_rw_fn(spng_ctx *ctx, void *user, void *dst_src, size_t length); 441 | 442 | SPNG_API spng_ctx *spng_ctx_new(int flags); 443 | SPNG_API spng_ctx *spng_ctx_new2(struct spng_alloc *alloc, int flags); 444 | SPNG_API void spng_ctx_free(spng_ctx *ctx); 445 | 446 | SPNG_API int spng_set_png_buffer(spng_ctx *ctx, const void *buf, size_t size); 447 | SPNG_API int spng_set_png_stream(spng_ctx *ctx, spng_rw_fn *rw_func, void *user); 448 | SPNG_API int spng_set_png_file(spng_ctx *ctx, FILE *file); 449 | 450 | SPNG_API void *spng_get_png_buffer(spng_ctx *ctx, size_t *len, int *error); 451 | 452 | SPNG_API int spng_set_image_limits(spng_ctx *ctx, uint32_t width, uint32_t height); 453 | SPNG_API int spng_get_image_limits(spng_ctx *ctx, uint32_t *width, uint32_t *height); 454 | 455 | SPNG_API int spng_set_chunk_limits(spng_ctx *ctx, size_t chunk_size, size_t cache_size); 456 | SPNG_API int spng_get_chunk_limits(spng_ctx *ctx, size_t *chunk_size, size_t *cache_size); 457 | 458 | SPNG_API int spng_set_crc_action(spng_ctx *ctx, int critical, int ancillary); 459 | 460 | SPNG_API int spng_set_option(spng_ctx *ctx, enum spng_option option, int value); 461 | SPNG_API int spng_get_option(spng_ctx *ctx, enum spng_option option, int *value); 462 | 463 | SPNG_API int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *len); 464 | 465 | /* Decode */ 466 | SPNG_API int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags); 467 | 468 | /* Progressive decode */ 469 | SPNG_API int spng_decode_scanline(spng_ctx *ctx, void *out, size_t len); 470 | SPNG_API int spng_decode_row(spng_ctx *ctx, void *out, size_t len); 471 | SPNG_API int spng_decode_chunks(spng_ctx *ctx); 472 | 473 | /* Encode/decode */ 474 | SPNG_API int spng_get_row_info(spng_ctx *ctx, struct spng_row_info *row_info); 475 | 476 | /* Encode */ 477 | SPNG_API int spng_encode_image(spng_ctx *ctx, const void *img, size_t len, int fmt, int flags); 478 | 479 | /* Progressive encode */ 480 | SPNG_API int spng_encode_scanline(spng_ctx *ctx, const void *scanline, size_t len); 481 | SPNG_API int spng_encode_row(spng_ctx *ctx, const void *row, size_t len); 482 | SPNG_API int spng_encode_chunks(spng_ctx *ctx); 483 | 484 | SPNG_API int spng_get_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr); 485 | SPNG_API int spng_get_plte(spng_ctx *ctx, struct spng_plte *plte); 486 | SPNG_API int spng_get_trns(spng_ctx *ctx, struct spng_trns *trns); 487 | SPNG_API int spng_get_chrm(spng_ctx *ctx, struct spng_chrm *chrm); 488 | SPNG_API int spng_get_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int); 489 | SPNG_API int spng_get_gama(spng_ctx *ctx, double *gamma); 490 | SPNG_API int spng_get_gama_int(spng_ctx *ctx, uint32_t *gama_int); 491 | SPNG_API int spng_get_iccp(spng_ctx *ctx, struct spng_iccp *iccp); 492 | SPNG_API int spng_get_sbit(spng_ctx *ctx, struct spng_sbit *sbit); 493 | SPNG_API int spng_get_srgb(spng_ctx *ctx, uint8_t *rendering_intent); 494 | SPNG_API int spng_get_text(spng_ctx *ctx, struct spng_text *text, uint32_t *n_text); 495 | SPNG_API int spng_get_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd); 496 | SPNG_API int spng_get_hist(spng_ctx *ctx, struct spng_hist *hist); 497 | SPNG_API int spng_get_phys(spng_ctx *ctx, struct spng_phys *phys); 498 | SPNG_API int spng_get_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t *n_splt); 499 | SPNG_API int spng_get_time(spng_ctx *ctx, struct spng_time *time); 500 | SPNG_API int spng_get_unknown_chunks(spng_ctx *ctx, struct spng_unknown_chunk *chunks, uint32_t *n_chunks); 501 | 502 | /* Official extensions */ 503 | SPNG_API int spng_get_offs(spng_ctx *ctx, struct spng_offs *offs); 504 | SPNG_API int spng_get_exif(spng_ctx *ctx, struct spng_exif *exif); 505 | 506 | 507 | SPNG_API int spng_set_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr); 508 | SPNG_API int spng_set_plte(spng_ctx *ctx, struct spng_plte *plte); 509 | SPNG_API int spng_set_trns(spng_ctx *ctx, struct spng_trns *trns); 510 | SPNG_API int spng_set_chrm(spng_ctx *ctx, struct spng_chrm *chrm); 511 | SPNG_API int spng_set_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int); 512 | SPNG_API int spng_set_gama(spng_ctx *ctx, double gamma); 513 | SPNG_API int spng_set_gama_int(spng_ctx *ctx, uint32_t gamma); 514 | SPNG_API int spng_set_iccp(spng_ctx *ctx, struct spng_iccp *iccp); 515 | SPNG_API int spng_set_sbit(spng_ctx *ctx, struct spng_sbit *sbit); 516 | SPNG_API int spng_set_srgb(spng_ctx *ctx, uint8_t rendering_intent); 517 | SPNG_API int spng_set_text(spng_ctx *ctx, struct spng_text *text, uint32_t n_text); 518 | SPNG_API int spng_set_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd); 519 | SPNG_API int spng_set_hist(spng_ctx *ctx, struct spng_hist *hist); 520 | SPNG_API int spng_set_phys(spng_ctx *ctx, struct spng_phys *phys); 521 | SPNG_API int spng_set_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t n_splt); 522 | SPNG_API int spng_set_time(spng_ctx *ctx, struct spng_time *time); 523 | SPNG_API int spng_set_unknown_chunks(spng_ctx *ctx, struct spng_unknown_chunk *chunks, uint32_t n_chunks); 524 | 525 | /* Official extensions */ 526 | SPNG_API int spng_set_offs(spng_ctx *ctx, struct spng_offs *offs); 527 | SPNG_API int spng_set_exif(spng_ctx *ctx, struct spng_exif *exif); 528 | 529 | 530 | SPNG_API const char *spng_strerror(int err); 531 | SPNG_API const char *spng_version_string(void); 532 | 533 | #ifdef __cplusplus 534 | } 535 | #endif 536 | 537 | #endif /* SPNG_H */ 538 | -------------------------------------------------------------------------------- /jni/msp/msp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "msp.h" 5 | 6 | uint16_t msp_data_from_msg(uint8_t message_buffer[], msp_msg_t *msg) { 7 | // return size 8 | construct_msp_command(message_buffer, msg->cmd, msg->payload, msg->size, msg->direction); 9 | return msg->size + 6; 10 | } 11 | 12 | msp_error_e construct_msp_command(uint8_t message_buffer[], uint8_t command, uint8_t payload[], uint8_t size, msp_direction_e direction) { 13 | uint8_t checksum; 14 | message_buffer[0] = '$'; // Header 15 | message_buffer[1] = 'M'; // MSP V1 16 | if (direction == MSP_OUTBOUND) { 17 | message_buffer[2] = '<'; 18 | } else { 19 | message_buffer[2] = '>'; 20 | } 21 | message_buffer[3] = size; // Payload Size 22 | checksum = size; 23 | message_buffer[4] = command; // Command 24 | checksum ^= command; 25 | for(uint8_t i = 0; i < size; i++) { 26 | message_buffer[5 + i] = payload[i]; 27 | checksum ^= message_buffer[5 + i]; 28 | } 29 | message_buffer[5 + size] = checksum; 30 | return 0; 31 | } 32 | 33 | msp_error_e msp_process_data(msp_state_t *msp_state, uint8_t dat) 34 | { 35 | switch (msp_state->state) 36 | { 37 | default: 38 | case MSP_IDLE: // look for begin 39 | if (dat == '$') 40 | { 41 | msp_state->state = MSP_VERSION; 42 | } 43 | else 44 | { 45 | return MSP_ERR_HDR; 46 | } 47 | break; 48 | case MSP_VERSION: // Look for 'M' (MSP V1, we do not support V2 at this time) 49 | if (dat == 'M') 50 | { 51 | msp_state->state = MSP_DIRECTION; 52 | } 53 | else 54 | { // Got garbage instead, try again 55 | msp_state->state = MSP_IDLE; 56 | return MSP_ERR_HDR; 57 | } 58 | break; 59 | case MSP_DIRECTION: // < for command, > for reply 60 | msp_state->state = MSP_SIZE; 61 | switch (dat) 62 | { 63 | case '<': 64 | msp_state->message.direction = MSP_OUTBOUND; 65 | break; 66 | case '>': 67 | msp_state->message.direction = MSP_INBOUND; 68 | break; 69 | default: // garbage, try again 70 | msp_state->state = MSP_IDLE; 71 | return MSP_ERR_HDR; 72 | break; 73 | } 74 | break; 75 | case MSP_SIZE: // next up is supposed to be size 76 | msp_state->message.checksum = dat; 77 | msp_state->message.size = dat; 78 | msp_state->state = MSP_CMD; 79 | if (msp_state->message.size > 256) 80 | { // bogus message, too big. this can't actually happen but good to check 81 | msp_state->state = MSP_IDLE; 82 | return MSP_ERR_LEN; 83 | break; 84 | } 85 | break; 86 | case MSP_CMD: // followed by command 87 | msp_state->message.cmd = dat; 88 | msp_state->message.checksum ^= dat; 89 | msp_state->buf_ptr = 0; 90 | if (msp_state->message.size > 0) 91 | { 92 | msp_state->state = MSP_PAYLOAD; 93 | } 94 | else 95 | { 96 | msp_state->state = MSP_CHECKSUM; 97 | } 98 | break; 99 | case MSP_PAYLOAD: // if we had a payload, keep going 100 | msp_state->message.payload[msp_state->buf_ptr] = dat; 101 | msp_state->message.checksum ^= dat; 102 | msp_state->buf_ptr++; 103 | if (msp_state->buf_ptr == msp_state->message.size) 104 | { 105 | msp_state->buf_ptr = 0; 106 | msp_state->state = MSP_CHECKSUM; 107 | } 108 | break; 109 | case MSP_CHECKSUM: 110 | if (msp_state->message.checksum == dat) 111 | { 112 | if (msp_state->cb != 0) 113 | { 114 | msp_state->cb(&msp_state->message); 115 | } 116 | memset(&msp_state->message, 0, sizeof(msp_msg_t)); 117 | msp_state->state = MSP_IDLE; 118 | break; 119 | } 120 | else 121 | { 122 | msp_state->state = MSP_IDLE; 123 | return MSP_ERR_CKS; 124 | } 125 | break; 126 | } 127 | return MSP_ERR_NONE; 128 | } -------------------------------------------------------------------------------- /jni/msp/msp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #define MSP_CMD_API_VERSION 1 5 | #define MSP_CMD_FC_VARIANT 2 6 | #define MSP_CMD_FC_VERSION 3 7 | #define MSP_CMD_NAME 10 8 | #define MSP_CMD_FILTER_CONFIG 92 9 | #define MSP_CMD_PID_ADVANCED 94 10 | #define MSP_CMD_STATUS 101 11 | #define MSP_CMD_RC 105 12 | #define MSP_CMD_ANALOG 110 13 | #define MSP_CMD_RC_TUNING 111 14 | #define MSP_CMD_PID 112 15 | #define MSP_CMD_BATTERY_STATE 130 16 | #define MSP_CMD_STATUS_EX 150 17 | #define MSP_CMD_DISPLAYPORT 182 18 | #define MSP_CMD_SET_OSD_CANVAS 188 19 | 20 | typedef enum { 21 | MSP_ERR_NONE, 22 | MSP_ERR_HDR, 23 | MSP_ERR_LEN, 24 | MSP_ERR_CKS 25 | } msp_error_e; 26 | 27 | typedef enum { 28 | MSP_IDLE, 29 | MSP_VERSION, 30 | MSP_DIRECTION, 31 | MSP_SIZE, 32 | MSP_CMD, 33 | MSP_PAYLOAD, 34 | MSP_CHECKSUM, 35 | } msp_state_machine_e; 36 | 37 | typedef enum { 38 | MSP_INBOUND, 39 | MSP_OUTBOUND 40 | } msp_direction_e; 41 | 42 | typedef struct msp_msg_s { 43 | uint8_t checksum; 44 | uint8_t cmd; 45 | uint8_t size; 46 | msp_direction_e direction; 47 | uint8_t payload[256]; 48 | } msp_msg_t; 49 | 50 | typedef void (*msp_msg_callback)(msp_msg_t *); 51 | 52 | typedef struct msp_state_s { 53 | msp_msg_callback cb; 54 | msp_state_machine_e state; 55 | uint8_t buf_ptr; 56 | msp_msg_t message; 57 | } msp_state_t; 58 | 59 | uint16_t msp_data_from_msg(uint8_t message_buffer[], msp_msg_t *msg); 60 | msp_error_e construct_msp_command(uint8_t message_buffer[], uint8_t command, uint8_t payload[], uint8_t size, msp_direction_e direction); 61 | msp_error_e msp_process_data(msp_state_t *msp_state, uint8_t dat); -------------------------------------------------------------------------------- /jni/msp/msp_displayport.c: -------------------------------------------------------------------------------- 1 | #include "msp.h" 2 | #include "msp_displayport.h" 3 | 4 | static void process_draw_string(displayport_vtable_t *display_driver, uint8_t *payload) { 5 | if(!display_driver || !display_driver->draw_character) return; 6 | uint8_t row = payload[0]; 7 | uint8_t col = payload[1]; 8 | uint8_t attrs = payload[2]; // INAV and Betaflight use this to specify a higher page number. 9 | uint8_t str_len; 10 | for(str_len = 1; str_len < 255; str_len++) { 11 | if(payload[2 + str_len] == '\0') { 12 | break; 13 | } 14 | } 15 | for(uint8_t idx = 0; idx < (str_len - 1); idx++) { 16 | uint16_t character = payload[3 + idx]; 17 | if(attrs & 0x3) { 18 | // shift over by the page number if they were specified 19 | character |= ((attrs & 0x3) * 0x100); 20 | } 21 | display_driver->draw_character(col, row, character); 22 | col++; 23 | } 24 | } 25 | 26 | static void process_clear_screen(displayport_vtable_t *display_driver) { 27 | if(!display_driver || !display_driver->clear_screen) return; 28 | display_driver->clear_screen(); 29 | } 30 | 31 | static void process_draw_complete(displayport_vtable_t *display_driver) { 32 | if(!display_driver || !display_driver->draw_complete) return; 33 | display_driver->draw_complete(); 34 | } 35 | 36 | static void process_set_options(displayport_vtable_t *display_driver, uint8_t *payload) { 37 | if(!display_driver || !display_driver->set_options) return; 38 | uint8_t font = payload[0]; 39 | msp_hd_options_e is_hd = payload[1]; 40 | display_driver->set_options(font, is_hd); 41 | } 42 | 43 | static void process_open(displayport_vtable_t *display_driver) { 44 | 45 | } 46 | 47 | static void process_close(displayport_vtable_t *display_driver) { 48 | process_clear_screen(display_driver); 49 | } 50 | 51 | int displayport_process_message(displayport_vtable_t *display_driver, msp_msg_t *msg) { 52 | if (msg->direction != MSP_INBOUND) { 53 | return 1; 54 | } 55 | if (msg->cmd != MSP_CMD_DISPLAYPORT) { 56 | return 1; 57 | } 58 | msp_displayport_cmd_e sub_cmd = msg->payload[0]; 59 | switch(sub_cmd) { 60 | case MSP_DISPLAYPORT_KEEPALIVE: // 0 -> Open/Keep-Alive DisplayPort 61 | process_open(display_driver); 62 | break; 63 | case MSP_DISPLAYPORT_CLOSE: // 1 -> Close DisplayPort 64 | process_close(display_driver); 65 | break; 66 | case MSP_DISPLAYPORT_CLEAR: // 2 -> Clear Screen 67 | process_clear_screen(display_driver); 68 | break; 69 | case MSP_DISPLAYPORT_DRAW_STRING: // 3 -> Draw String 70 | process_draw_string(display_driver, &msg->payload[1]); 71 | break; 72 | case MSP_DISPLAYPORT_DRAW_SCREEN: // 4 -> Draw Screen 73 | process_draw_complete(display_driver); 74 | break; 75 | case MSP_DISPLAYPORT_SET_OPTIONS: // 5 -> Set Options (HDZero/iNav) 76 | process_set_options(display_driver, &msg->payload[1]); 77 | break; 78 | default: 79 | break; 80 | } 81 | return 0; 82 | } 83 | 84 | -------------------------------------------------------------------------------- /jni/msp/msp_displayport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "msp.h" 4 | 5 | typedef enum { 6 | MSP_DISPLAYPORT_KEEPALIVE, 7 | MSP_DISPLAYPORT_CLOSE, 8 | MSP_DISPLAYPORT_CLEAR, 9 | MSP_DISPLAYPORT_DRAW_STRING, 10 | MSP_DISPLAYPORT_DRAW_SCREEN, 11 | MSP_DISPLAYPORT_SET_OPTIONS, 12 | MSP_DISPLAYPORT_DRAW_SYSTEM 13 | } msp_displayport_cmd_e; 14 | 15 | typedef enum { 16 | MSP_SD_OPTION_30_16, 17 | MSP_HD_OPTION_50_18, 18 | MSP_HD_OPTION_30_16, 19 | MSP_HD_OPTION_60_22 20 | } msp_hd_options_e; 21 | 22 | typedef void (*draw_character_func)(uint32_t x, uint32_t y, uint16_t c); 23 | typedef void (*set_options_func)(uint8_t font, msp_hd_options_e is_hd); 24 | typedef void (*clear_screen_func)(); 25 | typedef void (*draw_complete_func)(); 26 | 27 | typedef struct displayport_vtable_s { 28 | draw_character_func draw_character; 29 | clear_screen_func clear_screen; 30 | draw_complete_func draw_complete; 31 | set_options_func set_options; 32 | } displayport_vtable_t; 33 | 34 | int displayport_process_message(displayport_vtable_t *display_driver, msp_msg_t *msg); -------------------------------------------------------------------------------- /jni/msp_displayport_mux.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "hw/dji_radio_shm.h" 9 | #include "json/osd_config.h" 10 | #include "lz4/lz4.h" 11 | #include "net/data_protocol.h" 12 | #include "net/network.h" 13 | #include "net/serial.h" 14 | #include "msp/msp.h" 15 | #include "msp/msp_displayport.h" 16 | #include "util/debug.h" 17 | #include "util/time_util.h" 18 | #include "util/fs_util.h" 19 | 20 | #define CPU_TEMP_PATH "/sys/devices/platform/soc/f0a00000.apb/f0a71000.omc/temp1" 21 | #define AU_VOLTAGE_PATH "/sys/devices/platform/soc/f0a00000.apb/f0a71000.omc/voltage4" 22 | 23 | #define FAST_SERIAL_KEY "fast_serial" 24 | #define CACHE_SERIAL_KEY "cache_serial" 25 | #define COMPRESS_KEY "compress_osd" 26 | #define UPDATE_RATE_KEY "osd_update_rate_hz" 27 | #define NO_BTFL_HD_KEY "disable_betaflight_hd" 28 | 29 | // The MSP_PORT is used to send MSP passthrough messages. 30 | // The DATA_PORT is used to send arbitrary data - for example, bitrate and temperature data. 31 | 32 | #define MSP_PORT 7654 33 | #define DATA_PORT 7655 34 | #define COMPRESSED_DATA_PORT 7656 35 | 36 | #define COMPRESSED_DATA_VERSION 1 37 | 38 | enum { 39 | MAX_DISPLAY_X = 60, 40 | MAX_DISPLAY_Y = 22 41 | }; 42 | 43 | // The Betaflight MSP minor version in which MSP DisplayPort sizing is supported. 44 | #define MSP_DISPLAY_SIZE_VERSION 45 45 | 46 | typedef struct msp_cache_entry_s { 47 | struct timespec time; 48 | msp_msg_t message; 49 | } msp_cache_entry_t; 50 | 51 | static msp_cache_entry_t *msp_message_cache[256]; // make a slot for all possible messages 52 | 53 | static uint8_t frame_buffer[8192]; // buffer a whole frame of MSP commands until we get a draw command 54 | static uint32_t fb_cursor = 0; 55 | 56 | static uint8_t message_buffer[256]; // only needs to be the maximum size of an MSP packet, we only care to fwd MSP 57 | static char current_fc_identifier[4]; 58 | 59 | /* For compressed full-frame transmission */ 60 | static uint16_t msp_character_map_buffer[MAX_DISPLAY_X][MAX_DISPLAY_Y]; 61 | static uint16_t msp_character_map_draw[MAX_DISPLAY_X][MAX_DISPLAY_Y]; 62 | static msp_hd_options_e msp_hd_option = 0; 63 | static displayport_vtable_t *display_driver = NULL; 64 | LZ4_stream_t *lz4_ref_ctx = NULL; 65 | uint8_t update_rate_hz = 2; 66 | 67 | int pty_fd; 68 | int serial_fd; 69 | int socket_fd; 70 | int compressed_fd; 71 | 72 | static volatile sig_atomic_t quit = 0; 73 | static uint8_t serial_passthrough = 1; 74 | static uint8_t compress = 0; 75 | static uint8_t no_btfl_hd = 0; 76 | 77 | static void sig_handler(int _) 78 | { 79 | quit = 1; 80 | } 81 | 82 | static uint8_t cache_msp_message(msp_msg_t *msp_message) { 83 | // 0 -> cache overwritten 84 | // 1 -> freshly cached 85 | uint8_t retval = 0; 86 | msp_cache_entry_t *cache_message = msp_message_cache[msp_message->cmd]; 87 | if (cache_message == NULL) { 88 | DEBUG_PRINT("FC -> AU CACHE: no entry for msg %d, allocating\n", msp_message->cmd); 89 | cache_message = calloc(1, sizeof(msp_cache_entry_t)); 90 | msp_message_cache[msp_message->cmd] = cache_message; 91 | retval = 1; 92 | } 93 | DEBUG_PRINT ("FC -> AU CACHE: refreshing %d\n", msp_message->cmd); 94 | memcpy(&cache_message->message, msp_message, sizeof(msp_msg_t)); 95 | clock_gettime(CLOCK_MONOTONIC, &cache_message->time); 96 | return retval; 97 | } 98 | 99 | static int16_t msp_msg_from_cache(uint8_t msg_buffer[], uint8_t cmd_id) { 100 | // returns size of message or -1 101 | msp_cache_entry_t *cache_message = msp_message_cache[cmd_id]; 102 | if (cache_message == NULL) { 103 | // cache missed, return -1 to trigger a serial send 104 | return -1; 105 | } else { 106 | // cache hit, let's see if it's too old 107 | // messages we care to expire after second boundary 108 | if(cmd_id == MSP_CMD_ANALOG 109 | || cmd_id == MSP_CMD_STATUS 110 | || cmd_id == MSP_CMD_BATTERY_STATE) { 111 | struct timespec now; 112 | clock_gettime(CLOCK_MONOTONIC, &now); 113 | if(timespec_subtract_ns(&now, &cache_message->time) > NSEC_PER_SEC) { 114 | // message is too old, invalidate cache and force a resend 115 | DEBUG_PRINT("MSP cache EXPIRED %d\n", cmd_id); 116 | free(cache_message); 117 | msp_message_cache[cmd_id] = 0; 118 | return -1; 119 | } 120 | } 121 | // message existed and was not stale, send it back 122 | return msp_data_from_msg(msg_buffer, &cache_message->message); 123 | } 124 | } 125 | 126 | static void send_display_size(int serial_fd) { 127 | uint8_t buffer[8]; 128 | uint8_t payload[2] = {MAX_DISPLAY_X, MAX_DISPLAY_Y}; 129 | construct_msp_command(buffer, MSP_CMD_SET_OSD_CANVAS, payload, 2, MSP_OUTBOUND); 130 | write(serial_fd, &buffer, sizeof(buffer)); 131 | } 132 | 133 | static void send_variant_request(int serial_fd) { 134 | uint8_t buffer[6]; 135 | construct_msp_command(buffer, MSP_CMD_FC_VARIANT, NULL, 0, MSP_OUTBOUND); 136 | write(serial_fd, &buffer, sizeof(buffer)); 137 | } 138 | 139 | static void send_version_request(int serial_fd) { 140 | uint8_t buffer[6]; 141 | construct_msp_command(buffer, MSP_CMD_API_VERSION, NULL, 0, MSP_OUTBOUND); 142 | write(serial_fd, &buffer, sizeof(buffer)); 143 | } 144 | 145 | static void copy_to_msp_frame_buffer(void *buffer, uint16_t size) { 146 | memcpy(&frame_buffer[fb_cursor], buffer, size); 147 | fb_cursor += size; 148 | } 149 | 150 | static void rx_msp_callback(msp_msg_t *msp_message) 151 | { 152 | // Process a received MSP message from FC and decide whether to send it to the PTY (DJI) or UDP port (MSP-OSD on Goggles) 153 | DEBUG_PRINT("FC->AU MSP msg %d with data len %d \n", msp_message->cmd, msp_message->size); 154 | switch(msp_message->cmd) { 155 | case MSP_CMD_DISPLAYPORT: { 156 | if (compress) { 157 | // This was an MSP DisplayPort message and we're in compressed mode, so pass it off to the DisplayPort handlers. 158 | displayport_process_message(display_driver, msp_message); 159 | } else { 160 | // This was an MSP DisplayPort message and we're in legacy mode, so buffer it until we get a whole frame. 161 | if(fb_cursor > sizeof(frame_buffer)) { 162 | printf("Exhausted frame buffer! Resetting...\n"); 163 | fb_cursor = 0; 164 | return; 165 | } 166 | uint16_t size = msp_data_from_msg(message_buffer, msp_message); 167 | copy_to_msp_frame_buffer(message_buffer, size); 168 | if(msp_message->payload[0] == MSP_DISPLAYPORT_DRAW_SCREEN) { 169 | // Once we have a whole frame of data, send it to the goggles. 170 | write(socket_fd, frame_buffer, fb_cursor); 171 | DEBUG_PRINT("DRAW! wrote %d bytes\n", fb_cursor); 172 | fb_cursor = 0; 173 | } 174 | } 175 | break; 176 | } 177 | case MSP_CMD_FC_VARIANT: { 178 | // This is an FC Variant response, so we want to use it to set our FC variant. 179 | DEBUG_PRINT("Got FC Variant response!\n"); 180 | if(strncmp(current_fc_identifier, msp_message->payload, 4) != 0) { 181 | // FC variant changed or was updated. Update the current FC identifier and send an MSP version request. 182 | memcpy(current_fc_identifier, msp_message->payload, 4); 183 | send_version_request(serial_fd); 184 | } 185 | break; 186 | } 187 | case MSP_CMD_API_VERSION: { 188 | // Got an MSP API version response. Compare the version if we have Betaflight in order to see if we should send the new display size message. 189 | if(strncmp(current_fc_identifier, "BTFL", 4) == 0) { 190 | uint8_t msp_minor_version = msp_message->payload[2]; 191 | DEBUG_PRINT("Got Betaflight minor MSP version %d\n", msp_minor_version); 192 | if(msp_minor_version >= MSP_DISPLAY_SIZE_VERSION) { 193 | if(!no_btfl_hd) { 194 | if(!compress) { 195 | // If compression is disabled, we need to manually inject a canvas-change command into the command stream. 196 | uint8_t displayport_set_size[3] = {MSP_DISPLAYPORT_SET_OPTIONS, 0, MSP_HD_OPTION_60_22}; 197 | construct_msp_command(message_buffer, MSP_CMD_DISPLAYPORT, displayport_set_size, 3, MSP_INBOUND); 198 | copy_to_msp_frame_buffer(message_buffer, 9); 199 | DEBUG_PRINT("Sent display size to goggles\n"); 200 | 201 | } 202 | // Betaflight with HD support. Send our display size and set 60x22. 203 | send_display_size(serial_fd); 204 | msp_hd_option = MSP_HD_OPTION_60_22; 205 | DEBUG_PRINT("Sent display size to FC\n"); 206 | } 207 | } 208 | } 209 | break; 210 | } 211 | default: { 212 | uint16_t size = msp_data_from_msg(message_buffer, msp_message); 213 | if(serial_passthrough || cache_msp_message(msp_message)) { 214 | // Either serial passthrough was on, or the cache was enabled but missed (a response was not available). 215 | // Either way, this means we need to send the message through to DJI. 216 | write(pty_fd, message_buffer, size); 217 | } 218 | break; 219 | } 220 | } 221 | } 222 | 223 | static void tx_msp_callback(msp_msg_t *msp_message) 224 | { 225 | // We got a valid message from DJI asking for something. See if there's a response in the cache or not. 226 | // We can only get here if serial passthrough is off and caching is on, so no need to check again. 227 | DEBUG_PRINT("DJI->FC MSP msg %d with request len %d \n", msp_message->cmd, msp_message->size); 228 | uint8_t send_buffer[256]; 229 | int16_t size; 230 | if(0 < (size = msp_msg_from_cache(send_buffer, msp_message->cmd))) { 231 | // cache hit, so write the cached message straight back to DJI 232 | DEBUG_PRINT("DJI->FC MSP CACHE HIT msg %d with response len %d \n", msp_message->cmd, size); 233 | for(int i = 0; i < size; i++) { 234 | DEBUG_PRINT("%02X ", send_buffer[i]); 235 | } 236 | DEBUG_PRINT("\n"); 237 | write(pty_fd, send_buffer, size); 238 | } else { 239 | // cache miss, so write the DJI request to serial and wait for the FC to come back. 240 | DEBUG_PRINT("DJI->FC MSP CACHE MISS msg %d\n",msp_message->cmd); 241 | uint16_t size = msp_data_from_msg(message_buffer, msp_message); 242 | write(serial_fd, message_buffer, size); 243 | } 244 | } 245 | 246 | static void send_data_packet(int data_socket_fd, dji_shm_state_t *dji_shm) { 247 | packet_data_t data; 248 | memset(&data, 0, sizeof(data)); 249 | data.version_specifier = 0xFFFF; 250 | data.tx_temperature = get_int_from_fs(CPU_TEMP_PATH); 251 | data.tx_voltage = get_int_from_fs(AU_VOLTAGE_PATH); 252 | memcpy(data.fc_variant, current_fc_identifier, sizeof(current_fc_identifier)); 253 | DEBUG_PRINT("got voltage %f V temp %d C variant %.4s\n", (float)(data.tx_voltage / 64.0f), data.tx_temperature, data.fc_variant); 254 | write(data_socket_fd, &data, sizeof(data)); 255 | } 256 | 257 | /* MSP DisplayPort handlers for compressed mode */ 258 | 259 | static void msp_draw_character(uint32_t x, uint32_t y, uint16_t c) { 260 | DEBUG_PRINT("drawing char %d at x %d y %d\n", c, x, y); 261 | msp_character_map_buffer[x][y] = c; 262 | } 263 | 264 | static void msp_clear_screen() { 265 | memset(msp_character_map_buffer, 0, sizeof(msp_character_map_buffer)); 266 | } 267 | 268 | static void msp_draw_complete() { 269 | memcpy(msp_character_map_draw, msp_character_map_buffer, sizeof(msp_character_map_buffer)); 270 | } 271 | 272 | static void msp_set_options(uint8_t font_num, msp_hd_options_e is_hd) { 273 | DEBUG_PRINT("Got options!\n"); 274 | msp_clear_screen(); 275 | msp_hd_option = is_hd; 276 | } 277 | 278 | static void send_compressed_screen(int compressed_fd) { 279 | LZ4_stream_t current_stream_state; 280 | uint8_t dest_buf[sizeof(compressed_data_header_t) + LZ4_COMPRESSBOUND(sizeof(msp_character_map_draw))]; 281 | void *dest = &dest_buf; 282 | memcpy(¤t_stream_state, lz4_ref_ctx, sizeof(LZ4_stream_t)); 283 | int size = LZ4_compress_fast_extState_fastReset(¤t_stream_state, msp_character_map_draw, (dest + sizeof(compressed_data_header_t)), sizeof(msp_character_map_draw), LZ4_compressBound(sizeof(msp_character_map_draw)), 1); 284 | compressed_data_header_t *dest_header = (compressed_data_header_t *)dest; 285 | dest_header->hd_options =(uint16_t)msp_hd_option; 286 | dest_header->version = COMPRESSED_DATA_VERSION; 287 | write(compressed_fd, dest, size + sizeof(compressed_data_header_t)); 288 | DEBUG_PRINT("COMPRESSED: Sent %d bytes!\n", size); 289 | } 290 | 291 | int main(int argc, char *argv[]) { 292 | memset(current_fc_identifier, 0, sizeof(current_fc_identifier)); 293 | memset(msp_character_map_buffer, 0, sizeof(msp_character_map_buffer)); 294 | memset(msp_character_map_draw, 0, sizeof(msp_character_map_draw)); 295 | 296 | int compression_dict_size = 0; 297 | void *compression_dict = open_dict(COMPRESSED_DATA_VERSION, &compression_dict_size); 298 | 299 | int opt; 300 | uint8_t fast_serial = 0; 301 | uint8_t msp_command_number = 0; 302 | while((opt = getopt(argc, argv, "fsp")) != -1){ 303 | switch(opt){ 304 | case 'f': 305 | fast_serial = 1; 306 | break; 307 | case 's': 308 | serial_passthrough = 0; 309 | break; 310 | case '?': 311 | printf("unknown option: %c\n", optopt); 312 | break; 313 | } 314 | } 315 | 316 | if((argc - optind) < 2) { 317 | printf("usage: msp_displayport_mux [-f] [-s] ipaddr serial_port [pty_target]\n-s : enable serial caching\n-f : 230400 baud serial\n"); 318 | return 0; 319 | } 320 | 321 | if(get_boolean_config_value(FAST_SERIAL_KEY)) { 322 | fast_serial = 1; 323 | } 324 | 325 | if(get_boolean_config_value(CACHE_SERIAL_KEY)) { 326 | serial_passthrough = 0; 327 | } 328 | 329 | if(get_boolean_config_value(COMPRESS_KEY)) { 330 | compress = 1; 331 | } 332 | 333 | if(get_boolean_config_value(NO_BTFL_HD_KEY)) { 334 | no_btfl_hd = 1; 335 | } 336 | 337 | if(fast_serial == 1) { 338 | printf("Configured to use 230400 baud rate. \n"); 339 | } 340 | 341 | if(serial_passthrough == 0) { 342 | printf("Configured to use serial caching. \n"); 343 | } 344 | 345 | dji_shm_state_t dji_radio; 346 | memset(&dji_radio, 0, sizeof(dji_radio)); 347 | open_dji_radio_shm(&dji_radio); 348 | 349 | char *ip_address = argv[optind]; 350 | char *serial_port = argv[optind + 1]; 351 | signal(SIGINT, sig_handler); 352 | struct pollfd poll_fds[2]; 353 | const char *pty_name_ptr; 354 | msp_state_t *rx_msp_state = calloc(1, sizeof(msp_state_t)); 355 | msp_state_t *tx_msp_state = calloc(1, sizeof(msp_state_t)); 356 | rx_msp_state->cb = &rx_msp_callback; 357 | tx_msp_state->cb = &tx_msp_callback; 358 | serial_fd = open_serial_port(serial_port, fast_serial ? B230400 : B115200); 359 | if (serial_fd <= 0) { 360 | printf("Failed to open serial port!\n"); 361 | return 1; 362 | } 363 | pty_fd = open_pty(&pty_name_ptr); 364 | printf("Allocated PTY %s\n", pty_name_ptr); 365 | if ((argc - optind) > 2) { 366 | unlink(argv[optind + 2]); 367 | symlink(pty_name_ptr, argv[optind + 2]); 368 | printf("Relinked %s to %s\n", argv[optind + 2], pty_name_ptr); 369 | } 370 | socket_fd = connect_to_server(ip_address, MSP_PORT); 371 | compressed_fd = connect_to_server(ip_address, COMPRESSED_DATA_PORT); 372 | int data_fd = connect_to_server(ip_address, DATA_PORT); 373 | 374 | if (compress) { 375 | update_rate_hz = get_integer_config_value(UPDATE_RATE_KEY); 376 | display_driver = calloc(1, sizeof(displayport_vtable_t)); 377 | display_driver->draw_character = &msp_draw_character; 378 | display_driver->clear_screen = &msp_clear_screen; 379 | display_driver->draw_complete = &msp_draw_complete; 380 | display_driver->set_options = &msp_set_options; 381 | 382 | lz4_ref_ctx = LZ4_createStream(); 383 | LZ4_loadDict(lz4_ref_ctx, compression_dict, compression_dict_size); 384 | } 385 | 386 | uint8_t serial_data[256]; 387 | ssize_t serial_data_size; 388 | struct timespec now, last_data, last_frame; 389 | clock_gettime(CLOCK_MONOTONIC, &now); 390 | clock_gettime(CLOCK_MONOTONIC, &last_data); 391 | clock_gettime(CLOCK_MONOTONIC, &last_frame); 392 | 393 | while (!quit) { 394 | poll_fds[0].fd = serial_fd; 395 | poll_fds[1].fd = pty_fd; 396 | poll_fds[0].events = POLLIN; 397 | poll_fds[1].events = POLLIN; 398 | 399 | poll(poll_fds, 2, ((MSEC_PER_SEC / update_rate_hz) / 2)); 400 | 401 | // We got inbound serial data, process it as MSP data. 402 | if (0 < (serial_data_size = read(serial_fd, serial_data, sizeof(serial_data)))) { 403 | DEBUG_PRINT("RECEIVED data! length %d\n", serial_data_size); 404 | for (ssize_t i = 0; i < serial_data_size; i++) { 405 | msp_process_data(rx_msp_state, serial_data[i]); 406 | } 407 | } 408 | // We got data from DJI (the pty), so see what to do next: 409 | if(0 < (serial_data_size = read(pty_fd, serial_data, sizeof(serial_data)))) { 410 | if (serial_passthrough) { 411 | // If serial passthrough is enabled, send the message through verbatim. 412 | DEBUG_PRINT("SEND data! length %d\n", serial_data_size); 413 | for (ssize_t i= 0; i < serial_data_size; i++) { 414 | DEBUG_PRINT("%02X ", serial_data[i]); 415 | } 416 | DEBUG_PRINT("\n"); 417 | write(serial_fd, &serial_data, serial_data_size); 418 | } else { 419 | // Otherwise, queue it up for processing by the MSP layer. 420 | DEBUG_PRINT("SEND data to MSP buffer! length %d\n", serial_data_size); 421 | for (ssize_t i = 0; i < serial_data_size; i++) { 422 | msp_process_data(tx_msp_state, serial_data[i]); 423 | } 424 | } 425 | } 426 | clock_gettime(CLOCK_MONOTONIC, &now); 427 | if(timespec_subtract_ns(&now, &last_data) > (NSEC_PER_SEC / 2)) { 428 | // More than 500ms have elapsed, let's go ahead and send a data frame 429 | clock_gettime(CLOCK_MONOTONIC, &last_data); 430 | send_data_packet(data_fd, &dji_radio); 431 | if(current_fc_identifier[0] == 0) { 432 | send_variant_request(serial_fd); 433 | } 434 | } 435 | if(compress && (timespec_subtract_ns(&now, &last_frame) > (NSEC_PER_SEC / update_rate_hz))) { 436 | send_compressed_screen(compressed_fd); 437 | clock_gettime(CLOCK_MONOTONIC, &last_frame); 438 | } 439 | } 440 | close_dji_radio_shm(&dji_radio); 441 | close(serial_fd); 442 | close(pty_fd); 443 | close(socket_fd); 444 | close(data_fd); 445 | close(compressed_fd); 446 | if (display_driver != NULL) { 447 | free(display_driver); 448 | } 449 | if (lz4_ref_ctx != NULL) { 450 | free(lz4_ref_ctx); 451 | } 452 | free(rx_msp_state); 453 | free(tx_msp_state); 454 | } 455 | -------------------------------------------------------------------------------- /jni/net/data_protocol.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct packet_data_s { 4 | uint16_t tx_temperature; 5 | uint16_t version_specifier; // Used to be bitrate! Danger! 0xFFFF means V2 (no bitrate) for now. 6 | uint16_t tx_voltage; 7 | char fc_variant[4]; 8 | } __attribute__((packed)) packet_data_t; 9 | 10 | typedef struct compressed_data_header_s { 11 | uint16_t version; 12 | uint16_t hd_options; 13 | } __attribute__((packed)) compressed_data_header_t; -------------------------------------------------------------------------------- /jni/net/network.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "network.h" 9 | 10 | int connect_to_server(char *address, int port) 11 | { 12 | int sockfd; 13 | struct sockaddr_in servaddr; 14 | 15 | // socket create and verification 16 | sockfd = socket(AF_INET, SOCK_DGRAM, 0); 17 | if (sockfd == -1) 18 | { 19 | printf("socket failed!\n"); 20 | return -1; 21 | } 22 | 23 | memset(&servaddr, 0, sizeof(servaddr)); 24 | 25 | // assign IP, PORT 26 | servaddr.sin_family = AF_INET; 27 | servaddr.sin_addr.s_addr = inet_addr(address); 28 | servaddr.sin_port = htons(port); 29 | 30 | if (connect(sockfd, &servaddr, sizeof(servaddr)) != 0) 31 | { 32 | printf("connection failed!\n"); 33 | return -1; 34 | } 35 | 36 | fcntl(sockfd, F_SETFL, O_NONBLOCK); 37 | return sockfd; 38 | } 39 | 40 | int bind_socket(int port) 41 | { 42 | struct sockaddr_in si_me; 43 | int s; 44 | if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) 45 | { 46 | printf("Failed to get socket!\n"); 47 | return -1; 48 | } 49 | 50 | memset((char *)&si_me, 0, sizeof(si_me)); 51 | 52 | si_me.sin_family = AF_INET; 53 | si_me.sin_port = htons(port); 54 | si_me.sin_addr.s_addr = htonl(INADDR_ANY); 55 | 56 | // bind socket to port 57 | if (bind(s, (struct sockaddr *)&si_me, sizeof(si_me)) == -1) 58 | { 59 | printf("Failed to get bound!\n"); 60 | return -1; 61 | } 62 | return s; 63 | } -------------------------------------------------------------------------------- /jni/net/network.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int connect_to_server(char *address, int port); 4 | int bind_socket(int port); -------------------------------------------------------------------------------- /jni/net/serial.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #ifdef __APPLE__ 11 | #include 12 | #endif 13 | #include "serial.h" 14 | 15 | int open_serial_port(const char *device, speed_t baudrate) 16 | { 17 | struct termios tio; 18 | int tty_fd; 19 | 20 | memset(&tio, 0, sizeof(tio)); 21 | tio.c_iflag = 0; 22 | tio.c_oflag = 0; 23 | tio.c_cflag = CS8 | CREAD | CLOCAL; 24 | tio.c_lflag = 0; 25 | tio.c_cc[VMIN] = 1; 26 | tio.c_cc[VTIME] = 0; 27 | 28 | tty_fd = open(device, O_RDWR | O_NONBLOCK); 29 | cfsetospeed(&tio, baudrate); 30 | cfsetispeed(&tio, baudrate); 31 | tcsetattr(tty_fd, TCSANOW, &tio); 32 | return tty_fd; 33 | } 34 | 35 | int open_pty(const char **pty_name) 36 | { 37 | int app_pty; 38 | int virtual_serial_pty; 39 | 40 | // openpty makes a connection between the first argument, a parent FD which acts as the "receiving" end of the virtual PTY 41 | // and the second argument, a child FD which represents a "virtual serial port." 42 | // using ttyname on the second argument will return a /dev which can be opened as a serial port 43 | pid_t pid = openpty(&app_pty, &virtual_serial_pty, NULL, NULL, NULL); 44 | 45 | if (pid != 0) 46 | { 47 | printf("Could not get PTY!"); 48 | exit(1); 49 | return pid; 50 | } 51 | 52 | (*pty_name) = ttyname(virtual_serial_pty); 53 | struct termios orig_termios; 54 | if (tcgetattr(app_pty, &orig_termios) < 0) 55 | { 56 | printf("Could not get termio for current FD"); 57 | return -1; 58 | } 59 | 60 | orig_termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 61 | orig_termios.c_oflag &= ~(OPOST); 62 | orig_termios.c_cflag |= (CS8); 63 | orig_termios.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 64 | orig_termios.c_cc[VMIN] = 0; 65 | orig_termios.c_cc[VTIME] = 1; 66 | 67 | if (tcsetattr(app_pty, TCSANOW, &orig_termios) < 0) 68 | { 69 | perror("Could not set termio for current FD"); 70 | return -1; 71 | } 72 | 73 | if (tcgetattr(virtual_serial_pty, &orig_termios) < 0) 74 | { 75 | printf("Could not get termio for current FD"); 76 | return -1; 77 | } 78 | 79 | orig_termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 80 | orig_termios.c_oflag &= ~(OPOST); 81 | orig_termios.c_cflag |= (CS8); 82 | orig_termios.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 83 | orig_termios.c_cc[VMIN] = 0; 84 | orig_termios.c_cc[VTIME] = 1; 85 | 86 | if (tcsetattr(virtual_serial_pty, TCSANOW, &orig_termios) < 0) 87 | { 88 | perror("Could not set termio for current FD"); 89 | return -1; 90 | } 91 | fcntl(app_pty, F_SETFL, O_NONBLOCK); 92 | return app_pty; // Return the file descriptor 93 | } 94 | 95 | #ifdef DEBUG_TRACE 96 | void _trace(const char* format, ...); 97 | #endif 98 | 99 | #ifdef DEBUG_TRACE 100 | #define TRACE(X) _trace X; 101 | #else /*DEBUG_TRACE*/ 102 | #define TRACE(X) 103 | #endif /*DEBUG_TRACE*/ 104 | #ifdef __ANDROID__ 105 | int 106 | openpty (int *amaster, int *aslave, char *name, struct termios *termp, 107 | struct winsize *winp) 108 | { 109 | int master, slave; 110 | char *name_slave; 111 | 112 | master = open("/dev/ptmx", O_RDWR | O_NONBLOCK); 113 | if (master == -1) { 114 | TRACE(("Fail to open master")) 115 | return -1; 116 | } 117 | 118 | if (grantpt(master)) 119 | goto fail; 120 | 121 | if (unlockpt(master)) 122 | goto fail; 123 | 124 | name_slave = ptsname(master); 125 | TRACE(("openpty: slave name %s", name_slave)) 126 | slave = open(name_slave, O_RDWR | O_NOCTTY); 127 | if (slave == -1) 128 | { 129 | goto fail; 130 | } 131 | 132 | if(termp) 133 | tcsetattr(slave, TCSAFLUSH, termp); 134 | if (winp) 135 | ioctl (slave, TIOCSWINSZ, winp); 136 | 137 | *amaster = master; 138 | *aslave = slave; 139 | if (name != NULL) 140 | strcpy(name, name_slave); 141 | 142 | return 0; 143 | 144 | fail: 145 | close (master); 146 | return -1; 147 | } 148 | #endif -------------------------------------------------------------------------------- /jni/net/serial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | int open_serial_port(const char *device, speed_t baudrate); 6 | int open_pty(const char **pty_name); 7 | #ifdef __ANDROID__ 8 | int 9 | openpty (int *amaster, int *aslave, char *name, struct termios *termp, 10 | struct winsize *winp); 11 | #endif -------------------------------------------------------------------------------- /jni/osd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hw/dji_display.h" 4 | 5 | void osd_directfb(duss_disp_instance_handle_t *disp, duss_hal_obj_handle_t ion_handle); 6 | void osd_disable(); 7 | void osd_enable(); -------------------------------------------------------------------------------- /jni/osd_dji_udp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "hw/dji_display.h" 19 | #include "hw/dji_radio_shm.h" 20 | #include "hw/dji_services.h" 21 | #include "net/network.h" 22 | #include "net/data_protocol.h" 23 | #include "msp/msp.h" 24 | #include "msp/msp_displayport.h" 25 | #include "util/debug.h" 26 | #include "util/fs_util.h" 27 | 28 | #define MSP_PORT 7654 29 | #define DATA_PORT 7655 30 | 31 | #define WIDTH 1440 32 | #define HEIGHT 810 33 | #define BYTES_PER_PIXEL 4 34 | #define PLANE_ID 6 35 | 36 | #define NUM_CHARS 256 37 | 38 | #define INPUT_FILENAME "/dev/input/event0" 39 | #define SPLASH_STRING "OSD WAITING..." 40 | #define SHUTDOWN_STRING "SHUTTING DOWN..." 41 | 42 | #define FALLBACK_FONT_PATH "/blackbox/font" 43 | #define ENTWARE_FONT_PATH "/opt/fonts/font" 44 | #define SDCARD_FONT_PATH "/storage/sdcard0/font" 45 | 46 | #define GOGGLES_VOLTAGE_PATH "/sys/devices/platform/soc/f0a00000.apb/f0a71000.omc/voltage5" 47 | 48 | #define EV_CODE_BACK 0xc9 49 | 50 | #define BACK_BUTTON_DELAY 4 51 | 52 | #define SWAP32(data) \ 53 | ( (((data) >> 24) & 0x000000FF) | (((data) >> 8) & 0x0000FF00) | \ 54 | (((data) << 8) & 0x00FF0000) | (((data) << 24) & 0xFF000000) ) 55 | 56 | #define MAX_DISPLAY_X 50 57 | #define MAX_DISPLAY_Y 18 58 | 59 | typedef struct display_info_s { 60 | uint8_t char_width; 61 | uint8_t char_height; 62 | uint8_t font_width; 63 | uint8_t font_height; 64 | uint16_t x_offset; 65 | uint16_t y_offset; 66 | void *font_page_1; 67 | void *font_page_2; 68 | } display_info_t; 69 | 70 | static volatile sig_atomic_t quit = 0; 71 | static dji_display_state_t *dji_display; 72 | static uint16_t msp_character_map[MAX_DISPLAY_X][MAX_DISPLAY_Y]; 73 | static uint16_t overlay_character_map[MAX_DISPLAY_X][MAX_DISPLAY_Y]; 74 | static displayport_vtable_t *display_driver; 75 | static uint8_t which_fb = 0; 76 | 77 | static display_info_t sd_display_info = { 78 | .char_width = 31, 79 | .char_height = 15, 80 | .font_width = 36, 81 | .font_height = 54, 82 | .x_offset = 180, 83 | .y_offset = 0, 84 | .font_page_1 = NULL, 85 | .font_page_2 = NULL, 86 | }; 87 | 88 | static display_info_t hd_display_info = { 89 | .char_width = 50, 90 | .char_height = 18, 91 | .font_width = 24, 92 | .font_height = 36, 93 | .x_offset = 120, 94 | .y_offset = 80, 95 | .font_page_1 = NULL, 96 | .font_page_2 = NULL, 97 | }; 98 | 99 | static display_info_t overlay_display_info = { 100 | .char_width = 20, 101 | .char_height = 10, 102 | .font_width = 24, 103 | .font_height = 36, 104 | .x_offset = 960, 105 | .y_offset = 450, 106 | .font_page_1 = NULL, 107 | .font_page_2 = NULL, 108 | }; 109 | 110 | static display_info_t *current_display_info; 111 | 112 | static void sig_handler(int _) 113 | { 114 | quit = 1; 115 | } 116 | 117 | static void draw_character(display_info_t *display_info, uint16_t character_map[MAX_DISPLAY_X][MAX_DISPLAY_Y], uint32_t x, uint32_t y, uint16_t c) 118 | { 119 | if ((x > (display_info->char_width - 1)) || (y > (display_info->char_height - 1))) { 120 | return; 121 | } 122 | character_map[x][y] = c; 123 | } 124 | 125 | static void msp_draw_character(uint32_t x, uint32_t y, uint16_t c) { 126 | draw_character(current_display_info, msp_character_map, x, y, c); 127 | } 128 | 129 | static void draw_character_map(display_info_t *display_info, void *fb_addr, uint16_t character_map[MAX_DISPLAY_X][MAX_DISPLAY_Y]) { 130 | if (display_info->font_page_1 == NULL) { 131 | // give up if we don't have a font loaded 132 | return; 133 | } 134 | void *font; 135 | for(int y = 0; y < display_info->char_height; y++) { 136 | for(int x = 0; x < display_info->char_width; x++) { 137 | uint16_t c = character_map[x][y]; 138 | if (c != 0) { 139 | font = display_info->font_page_1; 140 | if (c > 255) { 141 | c = c & 0xFF; 142 | if (display_info->font_page_2 != NULL) { 143 | // fall back to writing page 1 chars if we don't have a page 2 font 144 | font = display_info->font_page_2; 145 | } 146 | } 147 | uint32_t pixel_x = (x * display_info->font_width) + display_info->x_offset; 148 | uint32_t pixel_y = (y * display_info->font_height) + display_info->y_offset; 149 | uint32_t character_offset = (((display_info->font_height * display_info->font_width) * BYTES_PER_PIXEL) * c); 150 | for(uint8_t gx = 0; gx < display_info->font_width; gx++) { 151 | for(uint8_t gy = 0; gy < display_info->font_height; gy++) { 152 | uint32_t font_offset = character_offset + (gy * display_info->font_width * BYTES_PER_PIXEL) + (gx * BYTES_PER_PIXEL); 153 | uint32_t target_offset = ((((pixel_x + gx) * BYTES_PER_PIXEL) + ((pixel_y + gy) * WIDTH * BYTES_PER_PIXEL))); 154 | *((uint8_t *)fb_addr + target_offset) = *(uint8_t *)((uint8_t *)font + font_offset + 2); 155 | *((uint8_t *)fb_addr + target_offset + 1) = *(uint8_t *)((uint8_t *)font + font_offset + 1); 156 | *((uint8_t *)fb_addr + target_offset + 2) = *(uint8_t *)((uint8_t *)font + font_offset); 157 | *((uint8_t *)fb_addr + target_offset + 3) = ~*(uint8_t *)((uint8_t *)font + font_offset + 3); 158 | } 159 | } 160 | DEBUG_PRINT("%c", c > 31 ? c : 20); 161 | } 162 | DEBUG_PRINT(" "); 163 | } 164 | DEBUG_PRINT("\n"); 165 | } 166 | } 167 | 168 | static void draw_screen() { 169 | void *fb_addr = dji_display_get_fb_address(dji_display, which_fb); 170 | // DJI has a backwards alpha channel - FF is transparent, 00 is opaque. 171 | memset(fb_addr, 0x000000FF, WIDTH * HEIGHT * BYTES_PER_PIXEL); 172 | 173 | draw_character_map(current_display_info, fb_addr, msp_character_map); 174 | draw_character_map(&overlay_display_info, fb_addr, overlay_character_map); 175 | } 176 | 177 | static void msp_clear_screen() 178 | { 179 | memset(msp_character_map, 0, sizeof(msp_character_map)); 180 | } 181 | 182 | static void msp_draw_complete() { 183 | draw_screen(); 184 | dji_display_push_frame(dji_display, which_fb); 185 | which_fb = !which_fb; 186 | DEBUG_PRINT("drew a frame\n"); 187 | } 188 | 189 | static void msp_callback(msp_msg_t *msp_message) 190 | { 191 | displayport_process_message(display_driver, msp_message); 192 | } 193 | 194 | static void get_font_path_with_prefix(char *font_path_dest, const char *font_path, uint8_t len, uint8_t is_hd, uint8_t page) { 195 | char name_buf[len]; 196 | if (is_hd) { 197 | snprintf(name_buf, len, "%s_hd", font_path); 198 | } else { 199 | snprintf(name_buf, len, "%s", font_path); 200 | } 201 | if (page > 0) { 202 | snprintf(font_path_dest, len, "%s_%d.bin", name_buf, page + 1); 203 | } else { 204 | snprintf(font_path_dest, len, "%s.bin", name_buf); 205 | } 206 | } 207 | 208 | static int open_font(const char *filename, void** font, uint8_t page, uint8_t is_hd) { 209 | char file_path[255]; 210 | get_font_path_with_prefix(file_path, filename, 255, is_hd, page); 211 | printf("Opening font: %s\n", file_path); 212 | struct stat st; 213 | memset(&st, 0, sizeof(st)); 214 | stat(file_path, &st); 215 | size_t filesize = st.st_size; 216 | display_info_t display_info = is_hd ? hd_display_info : sd_display_info; 217 | size_t desired_filesize = display_info.font_height * display_info.font_width * NUM_CHARS * BYTES_PER_PIXEL; 218 | if(filesize != desired_filesize) { 219 | if (filesize != 0) { 220 | printf("Font was wrong size: %s %d != %d\n", file_path, filesize, desired_filesize); 221 | } 222 | return -1; 223 | } 224 | int fd = open(file_path, O_RDONLY, 0); 225 | if (!fd) { 226 | printf("Could not open file %s\n", file_path); 227 | return -1; 228 | } 229 | void* mmappedData = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0); 230 | // there is no need to keep an FD open after mmap 231 | close(fd); 232 | if (mmappedData != MAP_FAILED) { 233 | *font = mmappedData; 234 | } else { 235 | printf("Could not map font %s\n", file_path); 236 | *font = 0; 237 | } 238 | return 0; 239 | } 240 | 241 | static void load_font() { 242 | if (open_font(SDCARD_FONT_PATH, &sd_display_info.font_page_1, 0, 0) < 0) { 243 | if (open_font(ENTWARE_FONT_PATH, &sd_display_info.font_page_1, 0, 0) < 0) { 244 | open_font(FALLBACK_FONT_PATH, &sd_display_info.font_page_1, 0, 0); 245 | } 246 | } 247 | if (open_font(SDCARD_FONT_PATH, &sd_display_info.font_page_2, 1, 0) < 0) { 248 | if (open_font(ENTWARE_FONT_PATH, &sd_display_info.font_page_2, 1, 0) < 0) { 249 | open_font(FALLBACK_FONT_PATH, &sd_display_info.font_page_2, 1, 0); 250 | } 251 | } 252 | if (open_font(SDCARD_FONT_PATH, &hd_display_info.font_page_1, 0, 1) < 0) { 253 | if (open_font(ENTWARE_FONT_PATH, &hd_display_info.font_page_1, 0, 1) < 0) { 254 | open_font(FALLBACK_FONT_PATH, &hd_display_info.font_page_1, 0, 1); 255 | } 256 | } 257 | if (open_font(SDCARD_FONT_PATH, &hd_display_info.font_page_2, 1, 1) < 0) { 258 | if (open_font(ENTWARE_FONT_PATH, &hd_display_info.font_page_2, 1, 1) < 0) { 259 | open_font(FALLBACK_FONT_PATH, &hd_display_info.font_page_2, 1, 1); 260 | } 261 | } 262 | if (open_font(SDCARD_FONT_PATH, &overlay_display_info.font_page_1, 0, 1) < 0) { 263 | if (open_font(ENTWARE_FONT_PATH, &overlay_display_info.font_page_1, 0, 1) < 0) { 264 | open_font(FALLBACK_FONT_PATH, &overlay_display_info.font_page_1, 0, 1); 265 | } 266 | } 267 | if (open_font(SDCARD_FONT_PATH, &overlay_display_info.font_page_2, 1, 1) < 0) { 268 | if (open_font(ENTWARE_FONT_PATH, &overlay_display_info.font_page_2, 1, 1) < 0) { 269 | open_font(FALLBACK_FONT_PATH, &overlay_display_info.font_page_2, 1, 1); 270 | } 271 | } 272 | } 273 | 274 | static void close_fonts(display_info_t *display_info) { 275 | if (display_info->font_page_1 != NULL) 276 | { 277 | munmap(display_info->font_page_1, display_info->font_height * display_info->font_width * NUM_CHARS * 4); 278 | } 279 | if (display_info->font_page_2 != NULL) 280 | { 281 | munmap(display_info->font_page_2, display_info->font_height * display_info->font_width * NUM_CHARS * 4); 282 | } 283 | } 284 | 285 | static void msp_set_options(uint8_t font_num, uint8_t is_hd) { 286 | msp_clear_screen(); 287 | if(is_hd) { 288 | current_display_info = &hd_display_info; 289 | } else { 290 | current_display_info = &sd_display_info; 291 | } 292 | } 293 | 294 | static void display_print_string(uint8_t init_x, uint8_t y, const char *string, uint8_t len) { 295 | for(uint8_t x = 0; x < len; x++) { 296 | draw_character(&overlay_display_info, overlay_character_map, x + init_x, y, string[x]); 297 | } 298 | } 299 | 300 | static void start_display(uint8_t is_v2_goggles) { 301 | memset(msp_character_map, 0, sizeof(msp_character_map)); 302 | memset(overlay_character_map, 0, sizeof(overlay_character_map)); 303 | 304 | dji_display = dji_display_state_alloc(is_v2_goggles); 305 | dji_display_open_framebuffer(dji_display, PLANE_ID); 306 | display_print_string(0, overlay_display_info.char_height -1, SPLASH_STRING, sizeof(SPLASH_STRING)); 307 | msp_draw_complete(); 308 | } 309 | 310 | static void stop_display() { 311 | display_print_string(0, overlay_display_info.char_height -1, SHUTDOWN_STRING, sizeof(SHUTDOWN_STRING)); 312 | dji_display_close_framebuffer(dji_display); 313 | dji_display_state_free(dji_display); 314 | } 315 | 316 | static void process_data_packet(uint8_t *buf, int len, dji_shm_state_t *radio_shm) { 317 | packet_data_t *packet = (packet_data_t *)buf; 318 | DEBUG_PRINT("got data %f mbit %d C %f V\n", packet->tx_bitrate / 1000.0f, packet->tx_temperature, packet->tx_voltage / 64.0f); 319 | memset(overlay_character_map, 0, sizeof(overlay_character_map)); 320 | char str[8]; 321 | snprintf(str, 8, "%2.1fMB ", packet->tx_bitrate / 1000.0f); 322 | display_print_string(overlay_display_info.char_width - 6, overlay_display_info.char_height - 5, str, 6); 323 | uint16_t latency = dji_radio_latency_ms(radio_shm); 324 | snprintf(str, 8, "%d MS", latency); 325 | display_print_string(overlay_display_info.char_width - 6, overlay_display_info.char_height - 4, str, 6); 326 | snprintf(str, 8, "%d C", packet->tx_temperature); 327 | display_print_string(overlay_display_info.char_width - 5, overlay_display_info.char_height - 3, str, 5); 328 | snprintf(str, 8, "A %2.1fV", packet->tx_voltage / 64.0f); 329 | display_print_string(overlay_display_info.char_width - 7, overlay_display_info.char_height - 2, str, 7); 330 | uint16_t goggle_voltage = get_int_from_fs(GOGGLES_VOLTAGE_PATH); 331 | snprintf(str, 8, "G %2.1fV", (goggle_voltage / 45.0f) - 0.65f); 332 | display_print_string(overlay_display_info.char_width - 7, overlay_display_info.char_height - 1, str, 7); 333 | } 334 | 335 | int main(int argc, char *argv[]) 336 | { 337 | signal(SIGINT, sig_handler); 338 | 339 | current_display_info = &sd_display_info; 340 | 341 | uint8_t is_v2_goggles = dji_goggles_are_v2(); 342 | printf("Detected DJI goggles %s\n", is_v2_goggles ? "V2" : "V1"); 343 | 344 | display_driver = calloc(1, sizeof(displayport_vtable_t)); 345 | display_driver->draw_character = &msp_draw_character; 346 | display_driver->clear_screen = &msp_clear_screen; 347 | display_driver->draw_complete = &msp_draw_complete; 348 | display_driver->set_options = &msp_set_options; 349 | 350 | msp_state_t *msp_state = calloc(1, sizeof(msp_state_t)); 351 | msp_state->cb = &msp_callback; 352 | 353 | int event_fd = open(INPUT_FILENAME, O_RDONLY); 354 | assert(event_fd > 0); 355 | 356 | dji_shm_state_t radio_shm; 357 | memset(&radio_shm, 0, sizeof(radio_shm)); 358 | 359 | int msp_socket_fd = bind_socket(MSP_PORT); 360 | int data_socket_fd = bind_socket(DATA_PORT); 361 | printf("started up, listening on port %d\n", MSP_PORT); 362 | 363 | 364 | struct pollfd poll_fds[3]; 365 | int recv_len = 0; 366 | uint8_t byte = 0; 367 | uint8_t buffer[4096]; 368 | struct sockaddr_storage src_addr; 369 | socklen_t src_addr_len=sizeof(src_addr); 370 | struct input_event ev; 371 | struct timespec button_start, display_start, now; 372 | memset(&display_start, 0, sizeof(display_start)); 373 | memset(&button_start, 0, sizeof(button_start)); 374 | 375 | enum display_mode_s { 376 | DISPLAY_DISABLED = 0, 377 | DISPLAY_RUNNING = 1, 378 | DISPLAY_WAITING = 2 379 | } display_mode = DISPLAY_DISABLED; 380 | 381 | while (!quit) 382 | { 383 | clock_gettime(CLOCK_MONOTONIC, &now); 384 | if(display_mode == DISPLAY_WAITING && display_start.tv_sec > 0 && ((now.tv_sec - display_start.tv_sec) > 1)) { 385 | // Wait 1 second between stopping Glasses service and trying to start OSD. 386 | memset(&display_start, 0, sizeof(display_start)); 387 | load_font(); 388 | open_dji_radio_shm(&radio_shm); 389 | start_display(is_v2_goggles); 390 | display_mode = DISPLAY_RUNNING; 391 | } 392 | if(button_start.tv_sec > 0 && ((now.tv_sec - button_start.tv_sec) > BACK_BUTTON_DELAY)) { 393 | // We held the back button down for 5 seconds. 394 | memset(&button_start, 0, sizeof(button_start)); 395 | if (display_mode == DISPLAY_DISABLED) { 396 | printf("Switching Disabled -> Enabled!\n"); 397 | dji_stop_goggles(is_v2_goggles); 398 | clock_gettime(CLOCK_MONOTONIC, &display_start); 399 | display_mode = DISPLAY_WAITING; 400 | } else { 401 | printf("Switching Enabled/Waiting -> Disabled!\n"); 402 | if(display_mode == DISPLAY_RUNNING) { 403 | stop_display(); 404 | close_dji_radio_shm(&radio_shm); 405 | } 406 | close_fonts(&sd_display_info); 407 | close_fonts(&hd_display_info); 408 | close_fonts(&overlay_display_info); 409 | display_mode = DISPLAY_DISABLED; 410 | dji_start_goggles(is_v2_goggles); 411 | } 412 | } 413 | 414 | poll_fds[0].fd = msp_socket_fd; 415 | poll_fds[0].events = POLLIN; 416 | poll_fds[1].fd = event_fd; 417 | poll_fds[1].events = POLLIN; 418 | poll_fds[2].fd = data_socket_fd; 419 | poll_fds[2].events = POLLIN; 420 | poll(poll_fds, 3, 100); 421 | 422 | if(poll_fds[1].revents) { 423 | read(event_fd, &ev, sizeof(struct input_event)); 424 | if(ev.code == EV_CODE_BACK) { 425 | if(ev.value == 1) { 426 | clock_gettime(CLOCK_MONOTONIC, &button_start); 427 | } else { 428 | memset(&button_start, 0, sizeof(button_start)); 429 | } 430 | } 431 | DEBUG_PRINT("input type: %i, code: %i, value: %i\n", ev.type, ev.code, ev.value); 432 | } 433 | if(poll_fds[0].revents) { 434 | // Got MSP UDP packet 435 | if (0 < (recv_len = recvfrom(msp_socket_fd,&buffer,sizeof(buffer),0,(struct sockaddr*)&src_addr,&src_addr_len))) 436 | { 437 | DEBUG_PRINT("got MSP packet len %d\n", recv_len); 438 | if(display_mode == DISPLAY_RUNNING) { 439 | for (int i=0; i 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "msp/msp.h" 16 | #include "msp/msp_displayport.h" 17 | #include "net/serial.h" 18 | #include "net/network.h" 19 | #include "util/debug.h" 20 | 21 | #define X_OFFSET 120 22 | 23 | #define PORT 7654 24 | 25 | #define WIDTH 1440 26 | #define HEIGHT 810 27 | 28 | typedef struct display_info_s { 29 | uint8_t char_width; 30 | uint8_t char_height; 31 | uint8_t font_width; 32 | uint8_t font_height; 33 | uint16_t num_chars; 34 | } display_info_t; 35 | 36 | #define SD_DISPLAY_INFO {.char_width = 31, .char_height = 15, .font_width = 36, .font_height = 54, .num_chars = 256} 37 | 38 | static const display_info_t sd_display_info = SD_DISPLAY_INFO; 39 | 40 | static const display_info_t hd_display_info = { 41 | .char_width = 50, 42 | .char_height = 18, 43 | .font_width = 24, 44 | .font_height = 36, 45 | .num_chars = 512, 46 | }; 47 | 48 | #define MAX_OSD_WIDTH 50 49 | #define MAX_OSD_HEIGHT 18 50 | 51 | static display_info_t current_display_info = SD_DISPLAY_INFO; 52 | 53 | static volatile sig_atomic_t quit = 0; 54 | sfTexture *font_1; 55 | sfTexture *font_2; 56 | sfSprite *font_sprite_1; 57 | sfSprite *font_sprite_2; 58 | sfRenderWindow *window; 59 | uint16_t character_map[MAX_OSD_WIDTH][MAX_OSD_HEIGHT]; 60 | displayport_vtable_t *display_driver; 61 | 62 | static void sig_handler(int _) 63 | { 64 | quit = 1; 65 | } 66 | 67 | static void draw_character(uint32_t x, uint32_t y, uint16_t c) 68 | { 69 | if (x > current_display_info.char_width - 1 || y > current_display_info.char_height - 1) 70 | { 71 | return; 72 | } 73 | character_map[x][y] = c; 74 | } 75 | 76 | static void draw_screen() 77 | { 78 | sfRenderWindow_clear(window, sfColor_fromRGB(55, 55, 55)); 79 | for (int y = 0; y < current_display_info.char_height; y++) 80 | { 81 | for (int x = 0; x < current_display_info.char_width; x++) 82 | { 83 | uint16_t c = character_map[x][y]; 84 | if (c != 0) 85 | { 86 | uint8_t page = 0; 87 | if (c > 255) { 88 | page = 1; 89 | c = c & 0xFF; 90 | } 91 | DEBUG_PRINT("%02X", c); 92 | sfIntRect r = {0, current_display_info.font_height * c, current_display_info.font_width, current_display_info.font_height}; 93 | sfVector2f dest = {(x * current_display_info.font_width) + X_OFFSET, y * current_display_info.font_height}; 94 | sfSprite *font_sprite = page ? font_sprite_2 : font_sprite_1; 95 | sfSprite_setTextureRect(font_sprite, r); 96 | sfSprite_setPosition(font_sprite, dest); 97 | sfRenderWindow_drawSprite(window, font_sprite, NULL); 98 | } 99 | DEBUG_PRINT(" "); 100 | } 101 | DEBUG_PRINT("\n"); 102 | } 103 | } 104 | 105 | static void clear_screen() 106 | { 107 | DEBUG_PRINT("clear\n"); 108 | memset(character_map, 0, sizeof(character_map)); 109 | } 110 | 111 | static void draw_complete() 112 | { 113 | draw_screen(); 114 | sfRenderWindow_display(window); 115 | DEBUG_PRINT("draw complete!\n"); 116 | } 117 | 118 | static void msp_callback(msp_msg_t *msp_message) 119 | { 120 | displayport_process_message(display_driver, msp_message); 121 | } 122 | 123 | static void set_options(uint8_t font, uint8_t is_hd) { 124 | if(is_hd) { 125 | current_display_info = hd_display_info; 126 | } else { 127 | current_display_info = sd_display_info; 128 | } 129 | } 130 | 131 | int main(int argc, char *argv[]) 132 | { 133 | struct pollfd poll_fds[1]; 134 | signal(SIGINT, sig_handler); 135 | memset(character_map, 0, sizeof(character_map)); 136 | sfVideoMode videoMode = {1440, 810, 32}; 137 | window = sfRenderWindow_create(videoMode, "MSP OSD", 0, NULL); 138 | sfRenderWindow_display(window); 139 | char *font_name; 140 | if (argc > 1) { 141 | font_name = argv[1]; 142 | } else { 143 | font_name = "bold.png"; 144 | } 145 | char font_load_name[255]; 146 | snprintf(font_load_name, 255, "%s.png", font_name); 147 | font_1 = sfTexture_createFromFile(font_name, NULL); 148 | font_sprite_1 = sfSprite_create(); 149 | sfSprite_setTexture(font_sprite_1, font_1, 0); 150 | snprintf(font_2_name, 255, "%s_2.png", font_name); 151 | font_2 = sfTexture_createFromFile(font_2_name, NULL); 152 | font_sprite_2 = sfSprite_create(); 153 | sfSprite_setTexture(font_sprite_2, font_2, 0); 154 | 155 | display_driver = calloc(1, sizeof(displayport_vtable_t)); 156 | display_driver->draw_character = &draw_character; 157 | display_driver->clear_screen = &clear_screen; 158 | display_driver->draw_complete = &draw_complete; 159 | display_driver->set_options = &set_options; 160 | 161 | msp_state_t *msp_state = calloc(1, sizeof(msp_state_t)); 162 | msp_state->cb = &msp_callback; 163 | 164 | int socket_fd = bind_socket(PORT); 165 | int recv_len = 0; 166 | uint8_t buffer[4096]; 167 | 168 | struct timespec fps_start, now; 169 | uint32_t message_counter = 0; 170 | clock_gettime(CLOCK_MONOTONIC, &fps_start); 171 | 172 | printf("started up, listening on port %d\n", PORT); 173 | 174 | while (!quit) 175 | { 176 | clock_gettime(CLOCK_MONOTONIC, &now); 177 | if(now.tv_sec > fps_start.tv_sec) { 178 | clock_gettime(CLOCK_MONOTONIC, &fps_start); 179 | printf("Got %d messages in the last second\n", message_counter); 180 | message_counter = 0; 181 | } 182 | 183 | sfEvent event; 184 | sfRenderWindow_pollEvent(window, &event); 185 | 186 | // Close window: exit 187 | if (event.type == sfEvtMouseButtonReleased) 188 | { 189 | sfRenderWindow_close(window); 190 | quit = 1; 191 | } 192 | 193 | poll_fds[0].fd = socket_fd; 194 | poll_fds[0].events = POLLIN; 195 | if(poll(poll_fds, 1, 50) > 0) // poll every 50ms so we also go back through the SFML loop 196 | { 197 | struct sockaddr_storage src_addr; 198 | socklen_t src_addr_len=sizeof(src_addr); 199 | if (0 < (recv_len = recvfrom(socket_fd,&buffer,sizeof(buffer),0,(struct sockaddr*)&src_addr,&src_addr_len))) 200 | { 201 | message_counter++; 202 | for (int i=0; i 2 | #include 3 | #include 4 | 5 | #include "../json/osd_config.h" 6 | 7 | #include "rec.h" 8 | #include "rec_shim.h" 9 | #include "rec_util.h" 10 | 11 | #define REC_CONFIG_ENABLED_KEY "rec_enabled" 12 | 13 | #ifdef DEBUG 14 | #define DEBUG_PRINT(fmt, args...) fprintf(stderr, "msp_osd.rec: " fmt "\n", ##args) 15 | #else 16 | #define DEBUG_PRINT(fmt, args...) 17 | #endif 18 | 19 | static bool rec_is_ready(); 20 | static uint32_t rec_get_frame_idx(); 21 | 22 | static bool rec_enabled = false; 23 | gs_lv_transcode_t *rec_lv_transcode = NULL; 24 | static FILE *rec_fd = NULL; 25 | static bool rec_recording = false; 26 | 27 | void rec_start(rec_config_t *config) 28 | { 29 | if (!rec_is_ready()) 30 | return; 31 | 32 | if (rec_fd != NULL) 33 | { 34 | fflush(rec_fd); 35 | fclose(rec_fd); 36 | } 37 | 38 | char rec_file_name[256]; 39 | rec_util_osd_path_from_video_path( 40 | rec_lv_transcode->file_name, 41 | rec_file_name, 42 | sizeof(rec_file_name)); 43 | DEBUG_PRINT("rec_file_name: %s", rec_file_name); 44 | 45 | DEBUG_PRINT("Config:\n"); 46 | DEBUG_PRINT(" Char Width: %u\n", config->char_width); 47 | DEBUG_PRINT(" Char Height: %u\n", config->char_height); 48 | DEBUG_PRINT(" Font Width: %u\n", config->font_width); 49 | DEBUG_PRINT(" Font Height: %u\n", config->font_height); 50 | DEBUG_PRINT(" X Offset: %u\n", config->x_offset); 51 | DEBUG_PRINT(" Y Offset: %u\n", config->y_offset); 52 | DEBUG_PRINT(" Font Variant: %.5s\n", config->font_variant); 53 | 54 | rec_fd = fopen(rec_file_name, "wb"); 55 | if (rec_fd == NULL) 56 | { 57 | DEBUG_PRINT("Failed to open file: %s", rec_file_name); 58 | return; 59 | } 60 | 61 | rec_file_header_t file_header = { 62 | .magic = REC_MAGIC, 63 | .version = REC_VERSION, 64 | }; 65 | memcpy(&file_header.config, config, sizeof(rec_config_t)); 66 | fwrite(&file_header, sizeof(rec_file_header_t), 1, rec_fd); 67 | 68 | rec_recording = true; 69 | } 70 | 71 | void rec_stop() 72 | { 73 | if (rec_fd != NULL) 74 | { 75 | fflush(rec_fd); 76 | fclose(rec_fd); 77 | rec_fd = NULL; 78 | } 79 | 80 | rec_recording = false; 81 | } 82 | 83 | void rec_write_frame(uint16_t *frame_data, size_t frame_size) 84 | { 85 | if (rec_fd == NULL) 86 | return; 87 | 88 | rec_frame_header_t frame_header = { 89 | .frame_idx = rec_get_frame_idx(), 90 | .size = frame_size, 91 | }; 92 | 93 | fwrite(&frame_header, sizeof(frame_header), 1, rec_fd); 94 | fwrite(frame_data, sizeof(uint16_t), frame_size, rec_fd); 95 | } 96 | 97 | void rec_load_config() 98 | { 99 | rec_enabled = get_boolean_config_value(REC_CONFIG_ENABLED_KEY); 100 | 101 | DEBUG_PRINT("rec_enabled: %d", rec_enabled); 102 | } 103 | 104 | bool rec_is_enabled() 105 | { 106 | return rec_enabled; 107 | } 108 | 109 | bool rec_is_osd_recording() 110 | { 111 | return rec_recording; 112 | } 113 | 114 | bool rec_is_gls_recording() 115 | { 116 | if (rec_is_ready() == false) 117 | return false; 118 | 119 | return rec_lv_transcode->cur_state == RECORD_STATE_RECORDING; 120 | } 121 | 122 | static bool rec_is_ready() 123 | { 124 | return rec_lv_transcode != NULL; 125 | } 126 | 127 | static uint32_t rec_get_frame_idx() 128 | { 129 | 130 | return rec_lv_transcode->last_frame_idx; 131 | } 132 | -------------------------------------------------------------------------------- /jni/rec/rec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define REC_MAGIC "MSPOSD" 7 | #define REC_VERSION 2 8 | 9 | typedef struct rec_config_t 10 | { 11 | uint8_t char_width; 12 | uint8_t char_height; 13 | uint8_t font_width; 14 | uint8_t font_height; 15 | uint16_t x_offset; 16 | uint16_t y_offset; 17 | char font_variant[5]; 18 | } __attribute__((packed)) rec_config_t; 19 | 20 | typedef struct rec_file_header_t 21 | { 22 | char magic[7]; 23 | uint16_t version; 24 | rec_config_t config; 25 | } __attribute__((packed)) rec_file_header_t; 26 | 27 | typedef struct rec_config_v1_t 28 | { 29 | uint8_t char_width; 30 | uint8_t char_height; 31 | uint8_t font_width; 32 | uint8_t font_height; 33 | uint16_t x_offset; 34 | uint16_t y_offset; 35 | uint8_t font_variant; 36 | } __attribute__((packed)) rec_config_v1_t; 37 | 38 | typedef struct rec_file_header_v1_t 39 | { 40 | char magic[7]; 41 | uint16_t version; 42 | rec_config_v1_t config; 43 | } __attribute__((packed)) rec_file_header_v1_t; 44 | 45 | typedef struct rec_frame_header_t 46 | { 47 | uint32_t frame_idx; 48 | uint32_t size; 49 | } __attribute__((packed)) rec_frame_header_t; 50 | 51 | void rec_start(); 52 | void rec_stop(); 53 | void rec_load_config(); 54 | void rec_write_frame(uint16_t *frame_data, size_t frame_size); 55 | bool rec_is_enabled(); 56 | bool rec_is_osd_recording(); 57 | bool rec_is_gls_recording(); 58 | 59 | -------------------------------------------------------------------------------- /jni/rec/rec_pb.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../json/osd_config.h" 7 | 8 | #include "rec.h" 9 | #include "rec_shim.h" 10 | #include "rec_util.h" 11 | 12 | #include "rec_pb.h" 13 | 14 | #include "../font/font.h" 15 | 16 | #define REC_PB_CONFIG_ENABLED_KEY "rec_pb_enabled" 17 | 18 | #define MAX_X 60 19 | #define MAX_Y 22 20 | #define MAX_T (MAX_X * MAX_Y) 21 | 22 | #define FRAME_SIZE (sizeof(rec_frame_header_t) + (sizeof(uint16_t) * MAX_T)) 23 | 24 | #ifdef DEBUG 25 | #define DEBUG_PRINT(fmt, args...) fprintf(stderr, "msp_osd.rec_pb: " fmt "\n", ##args) 26 | #else 27 | #define DEBUG_PRINT(fmt, args...) 28 | #endif 29 | 30 | cp_vdec_t *rec_pb_cp_vdec = NULL; 31 | vdec_local_player_t *rec_pb_vdec_local_player = NULL; 32 | uint8_t rec_pb_start_attempted = false; 33 | 34 | static bool rec_pb_enabled = false; 35 | 36 | static FILE *osd_fd = NULL; 37 | static rec_config_t osd_config = {0}; 38 | 39 | static uint32_t header_size = 0; 40 | static int64_t frame_counter = 0; 41 | 42 | static uint32_t *frame_idxs; 43 | static uint32_t frame_idx_len = 0; 44 | static uint32_t current_frame_idx = 0; 45 | 46 | void rec_pb_load_config() 47 | { 48 | rec_pb_enabled = get_boolean_config_value(REC_PB_CONFIG_ENABLED_KEY); 49 | 50 | DEBUG_PRINT("rec_pb_enabled: %d", rec_pb_enabled); 51 | } 52 | 53 | bool rec_pb_is_enabled() 54 | { 55 | return rec_pb_enabled; 56 | } 57 | 58 | int rec_pb_start() 59 | { 60 | if (rec_pb_is_ready() == false) 61 | { 62 | return 1; 63 | } 64 | 65 | if (osd_fd != NULL) 66 | { 67 | return 1; 68 | } 69 | 70 | // TODO: Mutex needed here? Can cause a crash if placback stops as we're trying to read info. 71 | DEBUG_PRINT("playback on: %s", rec_pb_vdec_local_player->fmt_ctx->filename); 72 | 73 | char osd_path[256]; 74 | rec_util_osd_path_from_video_path(rec_pb_vdec_local_player->fmt_ctx->filename, osd_path, sizeof(osd_path)); 75 | 76 | DEBUG_PRINT("osd path: %s", osd_path); 77 | osd_fd = fopen(osd_path, "rb"); 78 | if (osd_fd == NULL) 79 | { 80 | DEBUG_PRINT("osd file not found"); 81 | return 1; 82 | } 83 | 84 | DEBUG_PRINT("osd file found"); 85 | rec_file_header_t file_header; 86 | fread(&file_header, sizeof(rec_file_header_t), 1, osd_fd); 87 | 88 | if (strncmp(file_header.magic, REC_MAGIC, sizeof(REC_MAGIC)) != 0) 89 | { 90 | DEBUG_PRINT("invalid osd file"); 91 | fclose(osd_fd); 92 | osd_fd = NULL; 93 | return 1; 94 | } 95 | 96 | if (file_header.version == REC_VERSION) 97 | { 98 | DEBUG_PRINT("header ok!"); 99 | memcpy(&osd_config, &file_header.config, sizeof(rec_config_t)); 100 | header_size = sizeof(rec_file_header_t); 101 | } 102 | else if (file_header.version == 1) 103 | { 104 | DEBUG_PRINT("header is v1"); 105 | rec_file_header_v1_t file_header_v1; 106 | fseek(osd_fd, 0, SEEK_SET); 107 | 108 | fread(&file_header_v1, sizeof(rec_file_header_v1_t), 1, osd_fd); 109 | if (strncmp(file_header_v1.magic, REC_MAGIC, sizeof(REC_MAGIC)) != 0) 110 | { 111 | DEBUG_PRINT("invalid osd file"); 112 | fclose(osd_fd); 113 | osd_fd = NULL; 114 | return 1; 115 | } 116 | 117 | switch (file_header_v1.config.font_variant) 118 | { 119 | case FONT_VARIANT_BETAFLIGHT: 120 | strcpy(file_header.config.font_variant, "BTFL"); 121 | break; 122 | case FONT_VARIANT_INAV: 123 | strcpy(file_header.config.font_variant, "INAV"); 124 | break; 125 | case FONT_VARIANT_ARDUPILOT: 126 | strcpy(file_header.config.font_variant, "ARDU"); 127 | break; 128 | case FONT_VARIANT_KISS_ULTRA: 129 | strcpy(file_header.config.font_variant, "ULTR"); 130 | break; 131 | case FONT_VARIANT_QUICKSILVER: 132 | strcpy(file_header.config.font_variant, "QUIC"); 133 | break; 134 | default: 135 | file_header.config.font_variant[0] = '\0'; // Empty string 136 | } 137 | 138 | memcpy(&osd_config, &file_header.config, sizeof(rec_config_t)); 139 | header_size = sizeof(rec_file_header_v1_t); 140 | } 141 | else 142 | { 143 | DEBUG_PRINT("invalid osd file version! expected: %d, got: %d", REC_VERSION, file_header.version); 144 | fclose(osd_fd); 145 | osd_fd = NULL; 146 | return 1; 147 | } 148 | 149 | DEBUG_PRINT("loading frame indexes"); 150 | 151 | fseek(osd_fd, 0, SEEK_END); 152 | uint32_t file_size = ftell(osd_fd); 153 | fseek(osd_fd, header_size, SEEK_SET); 154 | DEBUG_PRINT("file size: %d", file_size); 155 | 156 | frame_idx_len = file_size / FRAME_SIZE; 157 | DEBUG_PRINT("frame_idx_len: %d", frame_idx_len); 158 | frame_idxs = malloc(sizeof(uint32_t) * frame_idx_len); 159 | 160 | for (uint32_t i = 0; i < frame_idx_len; i++) 161 | { 162 | rec_frame_header_t frame_header; 163 | fread(&frame_header, sizeof(rec_frame_header_t), 1, osd_fd); 164 | frame_idxs[i] = frame_header.frame_idx; 165 | DEBUG_PRINT("frame_idx: %d = %d", i, frame_idxs[i]); 166 | fseek(osd_fd, sizeof(uint16_t) * MAX_T, SEEK_CUR); 167 | } 168 | 169 | fseek(osd_fd, header_size, SEEK_SET); 170 | 171 | current_frame_idx = 0; 172 | frame_counter = rec_pb_cp_vdec->frames_sent; 173 | 174 | return 0; 175 | } 176 | 177 | void rec_pb_stop() 178 | { 179 | if (osd_fd != NULL) 180 | { 181 | fclose(osd_fd); 182 | osd_fd = NULL; 183 | } 184 | 185 | rec_pb_cp_vdec = NULL; 186 | rec_pb_vdec_local_player = NULL; 187 | 188 | free(frame_idxs); 189 | frame_idxs = NULL; 190 | 191 | DEBUG_PRINT("playback stopped"); 192 | } 193 | 194 | rec_config_t *rec_pb_get_config() 195 | { 196 | if (rec_pb_is_ready() == false) 197 | { 198 | return NULL; 199 | } 200 | 201 | return &osd_config; 202 | } 203 | 204 | int rec_pb_do_next_frame(uint16_t *map_out) 205 | { 206 | // -45 is absolutely a magic number based on testing, seems play_tm_ms lags by about that much. 207 | uint64_t frame_counter = ((rec_pb_cp_vdec->play_tm_ms) * 60 / 1000) - 45; 208 | 209 | uint32_t closest_frame_idx = 0; 210 | for (uint32_t i = 0; i < frame_idx_len; i++) 211 | { 212 | if (frame_idxs[i] > frame_counter) 213 | { 214 | break; 215 | } 216 | 217 | closest_frame_idx = i; 218 | } 219 | 220 | if (closest_frame_idx == current_frame_idx && current_frame_idx != 0) 221 | { 222 | return 0; 223 | } 224 | 225 | fseek( 226 | osd_fd, 227 | header_size + (closest_frame_idx * FRAME_SIZE) + sizeof(rec_frame_header_t), 228 | SEEK_SET); 229 | fread(map_out, sizeof(uint16_t), MAX_T, osd_fd); 230 | 231 | current_frame_idx = closest_frame_idx; 232 | 233 | return 0; 234 | } 235 | 236 | bool rec_pb_gls_is_playing() 237 | { 238 | if (rec_pb_is_ready() == false) 239 | { 240 | return false; 241 | } 242 | 243 | // state == 5 is stopped (i.e., when gs_player_stop is called) 244 | return rec_pb_vdec_local_player->b_running && !rec_pb_vdec_local_player->b_v_eos && rec_pb_vdec_local_player->state != 5; 245 | } 246 | 247 | bool rec_pb_is_ready() 248 | { 249 | return rec_pb_cp_vdec != NULL && rec_pb_vdec_local_player != NULL; 250 | } 251 | 252 | -------------------------------------------------------------------------------- /jni/rec/rec_pb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | extern uint8_t rec_pb_start_attempted; 8 | 9 | void rec_pb_load_config(); 10 | bool rec_pb_is_enabled(); 11 | 12 | int rec_pb_start(); 13 | void rec_pb_stop(); 14 | 15 | int rec_pb_do_next_frame(uint16_t *map_out); 16 | 17 | rec_config_t *rec_pb_get_config(); 18 | 19 | bool rec_pb_is_ready(); 20 | bool rec_pb_gls_is_playing(); 21 | -------------------------------------------------------------------------------- /jni/rec/rec_shim.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../hw/dji_services.h" 6 | #include "rec_shim.h" 7 | 8 | #ifdef DEBUG 9 | #define DEBUG_PRINT(fmt, args...) fprintf(stderr, "msp_osd.rec_shim: " fmt "\n", ##args) 10 | #else 11 | #define DEBUG_PRINT(fmt, args...) 12 | #endif 13 | 14 | duss_osal_priority_t (*duss_osal_task_create_orig)( 15 | duss_osal_task_attrib_t *task_attrib, 16 | duss_osal_task_handle_t **task_handle) = NULL; 17 | 18 | extern cp_vdec_t *rec_pb_cp_vdec; 19 | extern vdec_local_player_t *rec_pb_vdec_local_player; 20 | 21 | extern gs_lv_transcode_t *rec_lv_transcode; 22 | 23 | duss_osal_priority_t duss_osal_task_create( 24 | duss_osal_task_attrib_t *task_attrib, 25 | duss_osal_task_handle_t **task_handle) 26 | { 27 | if (duss_osal_task_create_orig == NULL) 28 | { 29 | duss_osal_task_create_orig = dlsym(RTLD_NEXT, "duss_osal_task_create"); 30 | } 31 | 32 | if (strcmp(task_attrib->name, "record_thread") == 0) 33 | { 34 | rec_lv_transcode = task_attrib->param; 35 | DEBUG_PRINT("got lv_transcode_t from record_thread: %p", rec_lv_transcode); 36 | } 37 | else if (strcmp(task_attrib->name, "player_thread") == 0) 38 | { 39 | gs_info_t *ctx = task_attrib->param; 40 | 41 | if (dji_goggles_are_v2()) 42 | { 43 | rec_pb_vdec_local_player = *(vdec_local_player_t **)((void *)ctx + 0x8b8); 44 | } 45 | else 46 | { 47 | rec_pb_vdec_local_player = *(vdec_local_player_t **)((void *)ctx + 0x8b4); 48 | } 49 | 50 | DEBUG_PRINT("got vdec_local_player_t from player_thread: %p", rec_pb_vdec_local_player); 51 | } 52 | else if (strcmp(task_attrib->name, "vdec_thread") == 0) 53 | { 54 | rec_pb_cp_vdec = task_attrib->param; 55 | DEBUG_PRINT("got cp_vdec_t from vdec_thread: %p", rec_pb_cp_vdec); 56 | } 57 | 58 | return duss_osal_task_create_orig(task_attrib, task_handle); 59 | } 60 | -------------------------------------------------------------------------------- /jni/rec/rec_util.c: -------------------------------------------------------------------------------- 1 | #include "rec_util.h" 2 | #include 3 | 4 | void rec_util_osd_path_from_video_path(const char *video_path, char *osd_path, size_t osd_path_size) 5 | { 6 | char *file_basename = strrchr(video_path, '/') + 1; 7 | char *file_ext = strrchr(file_basename, '.'); 8 | uint8_t file_dir_len = file_basename - video_path - 1; 9 | uint8_t file_stem_len = file_ext - file_basename; 10 | 11 | snprintf( 12 | osd_path, 13 | osd_path_size, 14 | "%.*s/%.*s.osd", 15 | file_dir_len, 16 | video_path, 17 | file_stem_len, 18 | file_basename); 19 | } 20 | -------------------------------------------------------------------------------- /jni/rec/rec_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void rec_util_osd_path_from_video_path(const char *video_path, char *osd_path, size_t osd_path_size); 6 | -------------------------------------------------------------------------------- /jni/toast/toast.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "json/osd_config.h" 9 | #include "util/time_util.h" 10 | 11 | #define DATASIZE 20 12 | #define TOP_ROW 5 13 | #define BOTTOM_ROW 8 14 | 15 | #define TOAST_DISABLE_KEY "hide_diagnostics" 16 | 17 | #include 18 | 19 | int toast_enabled = 1; 20 | typedef struct ToastItem 21 | { 22 | char data[DATASIZE]; 23 | struct timespec shown; 24 | struct ToastItem *nextNode; 25 | } ToastItem; 26 | 27 | ToastItem *bottomStackPointer = NULL; 28 | ToastItem *topStackPointer = NULL; 29 | 30 | struct timespec now, lasttoastremoval; 31 | 32 | void toast_load_config() 33 | { 34 | if (get_boolean_config_value(TOAST_DISABLE_KEY)) 35 | { 36 | toast_enabled = 0; 37 | } 38 | } 39 | 40 | int toast_pop() 41 | { 42 | if (bottomStackPointer == NULL) 43 | return 0; 44 | 45 | char data[DATASIZE]; 46 | strcpy(data, bottomStackPointer->data); 47 | bottomStackPointer->shown = (struct timespec){0, 0}; 48 | ToastItem *TempPointer = bottomStackPointer; 49 | bottomStackPointer = bottomStackPointer->nextNode; 50 | free(TempPointer); 51 | if (bottomStackPointer == NULL) 52 | topStackPointer = NULL; 53 | return 1; 54 | } 55 | 56 | int toast(char *data, ...) 57 | { 58 | if (!toast_enabled) { 59 | return 0; 60 | } 61 | ToastItem *TempPointer = malloc(sizeof(ToastItem)); 62 | if (TempPointer == NULL) 63 | return 0; 64 | 65 | // printf 66 | va_list va; 67 | va_start(va, data); 68 | char copy[DATASIZE]; 69 | vsnprintf(copy, DATASIZE, data, va); 70 | 71 | // our fonts are caps only.... 72 | for (int i = 0; copy[i]; i++) 73 | { 74 | copy[i] = toupper(copy[i]); 75 | } 76 | 77 | strncpy(TempPointer->data, copy, DATASIZE); 78 | TempPointer->shown = (struct timespec){0, 0}; 79 | TempPointer->nextNode = NULL; 80 | if (bottomStackPointer == NULL) 81 | bottomStackPointer = TempPointer; 82 | else 83 | topStackPointer->nextNode = TempPointer; 84 | topStackPointer = TempPointer; 85 | return 1; 86 | } 87 | 88 | void do_toast(void (*display_print_string)(uint8_t init_x, uint8_t y, const char *string, uint8_t len)) 89 | { 90 | if (!toast_enabled) { 91 | return; 92 | } 93 | 94 | int numberoffNodes = 0; 95 | 96 | clock_gettime(CLOCK_MONOTONIC, &now); 97 | 98 | // if the last item has been up 3 seconds; chop it 99 | // but only remove 1 per second 100 | ToastItem *TempPointer = bottomStackPointer; 101 | if ( 102 | timespec_subtract_ns(&now, &lasttoastremoval) > NSEC_PER_SEC // 1ce per second limit 103 | && TempPointer != NULL && TempPointer->shown.tv_sec > 0 // guards 104 | && timespec_subtract_ns(&now, &TempPointer->shown) > ((uint64_t)NSEC_PER_SEC * 3) // item has been on screen for long enough 105 | ) 106 | { 107 | clock_gettime(CLOCK_MONOTONIC, &lasttoastremoval); 108 | TempPointer = TempPointer->nextNode; 109 | toast_pop(); 110 | } 111 | 112 | // loop and display 113 | for (int row = BOTTOM_ROW; row >= TOP_ROW; row--) 114 | { 115 | // if we ran out of items blank out the rest of our rows 116 | if (TempPointer == NULL) { 117 | char empty[DATASIZE] = {0}; 118 | display_print_string(0, row, empty, DATASIZE); 119 | } else { 120 | 121 | // set shown time if not set (ie: item not yet shown) 122 | if (TempPointer->shown.tv_sec == 0) 123 | { 124 | clock_gettime(CLOCK_MONOTONIC, &TempPointer->shown); 125 | } 126 | 127 | // draw the current item in the current row 128 | display_print_string(0, row, TempPointer->data, DATASIZE); 129 | TempPointer = TempPointer->nextNode; 130 | } 131 | }; 132 | 133 | return; 134 | } 135 | -------------------------------------------------------------------------------- /jni/toast/toast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int toast(char*, ...); 4 | void toast_load_config(); 5 | void do_toast(void (*display_print_string)(uint8_t init_x, uint8_t y, const char *string, uint8_t len)); -------------------------------------------------------------------------------- /jni/util/debug.h: -------------------------------------------------------------------------------- 1 | // #pragma once 2 | 3 | #ifdef DEBUG 4 | #define DEBUG_PRINT(fmt, args...) fprintf(stderr, fmt, ## args) 5 | #else 6 | #define DEBUG_PRINT(fmt, args...) 7 | #endif -------------------------------------------------------------------------------- /jni/util/display_info.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define NUM_CHARS 256 6 | #define NUM_FONT_PAGES 4 7 | 8 | typedef struct display_info_s { 9 | uint8_t char_width; 10 | uint8_t char_height; 11 | uint8_t font_width; 12 | uint8_t font_height; 13 | uint16_t x_offset; 14 | uint16_t y_offset; 15 | void *fonts[NUM_FONT_PAGES]; 16 | } display_info_t; -------------------------------------------------------------------------------- /jni/util/fs_util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "fs_util.h" 12 | 13 | #define DICTIONARY_PATH "/opt/mspdictionaries" 14 | 15 | int32_t get_int_from_fs(char* path) { 16 | int32_t val; 17 | char read_buffer[32]; 18 | memset(read_buffer, 0, 32); 19 | int fd = open(path, O_RDONLY, 0); 20 | if(fd < 0) { 21 | return -1; 22 | } 23 | int read_count = read(fd, read_buffer, 32); 24 | if(read_count > 0) { 25 | val = atoi(read_buffer); 26 | } 27 | close(fd); 28 | return val; 29 | } 30 | 31 | void *open_dict(int dict_version, int *size) { 32 | char file_path[255]; 33 | snprintf(file_path, 255, "%s/dictionary_%d.bin", DICTIONARY_PATH, dict_version); 34 | struct stat st; 35 | memset(&st, 0, sizeof(st)); 36 | stat(file_path, &st); 37 | size_t filesize = st.st_size; 38 | int fd = open(file_path, O_RDONLY, 0); 39 | if (!fd) { 40 | return NULL; 41 | } 42 | void* dict = malloc(filesize); 43 | void* mmappedData = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0); 44 | *size = filesize; 45 | memcpy(dict, mmappedData, filesize); 46 | close(fd); 47 | munmap(mmappedData, filesize); 48 | return dict; 49 | } 50 | -------------------------------------------------------------------------------- /jni/util/fs_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | int32_t get_int_from_fs(char*); 6 | void *open_dict(int, int *); -------------------------------------------------------------------------------- /jni/util/time_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define NSEC_PER_SEC 1000000000 7 | #define MSEC_PER_SEC 1000 8 | 9 | static inline void timespec_subtract(struct timespec *res, const struct timespec *a, const struct timespec *b) 10 | { 11 | res->tv_sec = a->tv_sec - b->tv_sec; 12 | res->tv_nsec = a->tv_nsec - b->tv_nsec; 13 | if (res->tv_nsec < 0) { 14 | res->tv_sec--; 15 | res->tv_nsec += NSEC_PER_SEC; 16 | } 17 | } 18 | 19 | static inline int64_t timespec_subtract_ns(const struct timespec *a, const struct timespec *b) 20 | { 21 | struct timespec res; 22 | timespec_subtract(&res, a, b); 23 | return (int64_t)res.tv_sec * NSEC_PER_SEC + res.tv_nsec; 24 | } -------------------------------------------------------------------------------- /libshims/duml_hal.c: -------------------------------------------------------------------------------- 1 | #include "jni/hw/duml_hal.h" 2 | 3 | duss_result_t duss_hal_initialize(duss_hal_device_desc_t *) 4 | { 5 | duss_result_t dummy; 6 | 7 | return dummy; 8 | } 9 | 10 | 11 | duss_result_t duss_hal_deinitialize() 12 | { 13 | duss_result_t dummy; 14 | 15 | return dummy; 16 | } 17 | 18 | 19 | duss_result_t duss_hal_device_open(char *device_name, void *unknown, duss_hal_obj_handle_t *) 20 | { 21 | duss_result_t dummy; 22 | 23 | return dummy; 24 | } 25 | 26 | 27 | duss_result_t duss_hal_device_start(duss_hal_obj_handle_t, void *) 28 | { 29 | duss_result_t dummy; 30 | 31 | return dummy; 32 | } 33 | 34 | 35 | duss_result_t duss_hal_device_close(duss_hal_obj_handle_t) 36 | { 37 | duss_result_t dummy; 38 | 39 | return dummy; 40 | } 41 | 42 | 43 | duss_result_t duss_hal_device_stop(duss_hal_obj_handle_t) 44 | { 45 | duss_result_t dummy; 46 | 47 | return dummy; 48 | } 49 | 50 | 51 | duss_result_t duss_hal_mem_alloc(duss_hal_obj_handle_t, duss_hal_mem_handle_t *, uint32_t size, uint32_t, uint32_t, uint32_t) 52 | { 53 | duss_result_t dummy; 54 | 55 | return dummy; 56 | } 57 | 58 | 59 | duss_result_t duss_hal_mem_get_phys_addr(duss_hal_mem_handle_t, void **) 60 | { 61 | duss_result_t dummy; 62 | 63 | return dummy; 64 | } 65 | 66 | 67 | duss_result_t duss_hal_mem_map(duss_hal_mem_handle_t, void **) 68 | { 69 | duss_result_t dummy; 70 | 71 | return dummy; 72 | } 73 | 74 | 75 | duss_result_t duss_hal_mem_free(duss_hal_mem_handle_t) 76 | { 77 | duss_result_t dummy; 78 | 79 | return dummy; 80 | } 81 | 82 | 83 | duss_result_t duss_hal_mem_sync(duss_hal_mem_handle_t, uint32_t) 84 | { 85 | duss_result_t dummy; 86 | 87 | return dummy; 88 | } 89 | 90 | 91 | duss_result_t duss_hal_display_open(duss_hal_obj_handle_t, duss_disp_instance_handle_t **, duss_disp_vop_id_t) 92 | { 93 | duss_result_t dummy; 94 | 95 | return dummy; 96 | } 97 | 98 | 99 | duss_result_t duss_hal_display_close(duss_hal_obj_handle_t, duss_disp_instance_handle_t **) 100 | { 101 | duss_result_t dummy; 102 | 103 | return dummy; 104 | } 105 | 106 | 107 | duss_result_t duss_hal_display_aquire_plane(duss_disp_instance_handle_t *, duss_disp_plane_type_t, duss_disp_plane_id_t *) 108 | { 109 | duss_result_t dummy; 110 | 111 | return dummy; 112 | } 113 | 114 | 115 | duss_result_t duss_hal_display_reset(duss_disp_instance_handle_t *) 116 | { 117 | duss_result_t dummy; 118 | 119 | return dummy; 120 | } 121 | 122 | 123 | duss_result_t duss_hal_display_register_frame_cycle_callback(duss_disp_instance_handle_t *, duss_disp_plane_id_t, frame_pop_handler *, void *) 124 | { 125 | duss_result_t dummy; 126 | 127 | return dummy; 128 | } 129 | 130 | 131 | duss_result_t duss_hal_display_timing_detail_get(duss_disp_instance_handle_t *, duss_disp_timing_detail_t *) 132 | { 133 | duss_result_t dummy; 134 | 135 | return dummy; 136 | } 137 | 138 | 139 | duss_result_t duss_hal_display_port_enable(duss_disp_instance_handle_t *, duss_disp_port_id_t, uint8_t) 140 | { 141 | duss_result_t dummy; 142 | 143 | return dummy; 144 | } 145 | 146 | 147 | duss_result_t duss_hal_display_plane_blending_set(duss_disp_instance_handle_t *, duss_disp_plane_id_t, duss_disp_plane_blending_t *) 148 | { 149 | duss_result_t dummy; 150 | 151 | return dummy; 152 | } 153 | 154 | 155 | duss_result_t duss_hal_display_release_plane(duss_disp_instance_handle_t *, duss_disp_plane_id_t) 156 | { 157 | duss_result_t dummy; 158 | 159 | return dummy; 160 | } 161 | 162 | 163 | duss_result_t duss_hal_display_push_frame(duss_disp_instance_handle_t *, duss_disp_plane_id_t, duss_frame_buffer_t *) 164 | { 165 | duss_result_t dummy; 166 | 167 | return dummy; 168 | } 169 | 170 | 171 | duss_result_t duss_hal_attach_disp(char *param_1, duss_hal_obj **param_2) 172 | { 173 | duss_result_t dummy; 174 | 175 | return dummy; 176 | } 177 | 178 | 179 | duss_result_t duss_hal_attach_ion_mem(char *param_1, duss_hal_obj **param_2) 180 | { 181 | duss_result_t dummy; 182 | 183 | return dummy; 184 | } 185 | 186 | 187 | duss_result_t duss_hal_detach_ion_mem() 188 | { 189 | duss_result_t dummy; 190 | 191 | return dummy; 192 | } 193 | 194 | 195 | duss_result_t duss_hal_detach_disp() 196 | { 197 | duss_result_t dummy; 198 | 199 | return dummy; 200 | } 201 | 202 | 203 | --------------------------------------------------------------------------------