diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 8232f2875..98aa82426 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -33,17 +33,14 @@ jobs: with: submodules: 'recursive' - - name: Install Qt - uses: jurplel/install-qt-action@v4 + - name: Install Java + uses: actions/setup-java@v5 with: - aqtversion: '==3.3.*' - version: '5.14.2' - host: 'linux' - target: 'android' - arch: 'android' + distribution: 'jetbrains' + java-version: '25' - name: Install SDK - run: ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install 'platforms;android-31' 'build-tools;31.0.0' 'ndk;21.3.6528147' + run: ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install 'platforms;android-36' 'build-tools;36.1.0' 'ndk;28.2.13676358' # Secrets should be the base64 portion of the PEM format # (i.e. the stuff between the BEGIN/END lines): @@ -71,36 +68,21 @@ jobs: # be signed with debug keys. - name: Build Android package - working-directory: Desktop_Interface + working-directory: Android_App run: | - export ANDROID_NDK_ROOT="${ANDROID_SDK_ROOT}/ndk/21.3.6528147" APK_BASENAME="Labrador-$(git rev-parse --short HEAD)" if [ -e cert.cer ] && [ -e cert.p8 ]; then - qmake -config release - make -j$(nproc) - make INSTALL_ROOT=android-build install - JAVA_HOME=${JAVA_HOME_8_X64} androiddeployqt --input android-Labrador-deployment-settings.json --output android-build --aab --android-platform android-31 --verbose --gradle --release - ${ANDROID_SDK_ROOT}/build-tools/31.0.0/apksigner sign --in android-build/build/outputs/apk/release/android-build-release-unsigned.apk --out ${APK_BASENAME}-release-signed.apk --key cert.p8 --cert cert.cer --verbose + ./gradlew assembleRelease + ${ANDROID_SDK_ROOT}/build-tools/36.1.0/apksigner sign --in app/build/outputs/apk/release/app-release-unsigned.apk --out ${APK_BASENAME}-release-signed.apk --key cert.p8 --cert cert.cer --verbose else - qmake -config debug - make -j$(nproc) - make INSTALL_ROOT=android-build install - JAVA_HOME=${JAVA_HOME_8_X64} androiddeployqt --input android-Labrador-deployment-settings.json --output android-build --aab --android-platform android-31 --verbose --gradle - cp android-build/build/outputs/apk/debug/android-build-debug.apk ${APK_BASENAME}-debug.apk + ./gradlew assembleDebug + cp app/build/outputs/apk/debug/app-debug.apk ${APK_BASENAME}-debug.apk fi - cp android-build/build/outputs/bundle/release/android-build-release.aab ${APK_BASENAME}-release-unsigned.aab - name: Upload apk artifacts uses: actions/upload-artifact@v4 with: name: asset-apk - path: Desktop_Interface/Labrador*.apk - compression-level: 0 - if-no-files-found: error - - name: Upload aab artifacts - uses: actions/upload-artifact@v4 - with: - name: asset-aab - path: Desktop_Interface/Labrador*.aab + path: Android_App/Labrador*.apk compression-level: 0 if-no-files-found: error diff --git a/.gitmodules b/.gitmodules index 75de64e96..9807e5983 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,15 @@ [submodule "libdfuprog/dfu-programmer"] path = libdfuprog/dfu-programmer url = https://github.com/dfu-programmer/dfu-programmer +[submodule "Android_App/app/src/main/cpp/deps/implot"] + path = Android_App/app/src/main/cpp/deps/implot + url = https://github.com/brentfpage/implot.git + branch = for_labrador_android_app +[submodule "Android_App/app/src/main/cpp/deps/imgui"] + path = Android_App/app/src/main/cpp/deps/imgui + url = https://github.com/brentfpage/imgui.git + branch = for_labrador_android_app +[submodule "Android_App/app/src/main/cpp/deps/SDL"] + path = Android_App/app/src/main/cpp/deps/SDL + url = https://github.com/brentfpage/SDL.git + branch = for_labrador_app diff --git a/Android_App/.unpackaged_assets/labrador_icon.png b/Android_App/.unpackaged_assets/labrador_icon.png new file mode 100644 index 000000000..7b788e3b7 Binary files /dev/null and b/Android_App/.unpackaged_assets/labrador_icon.png differ diff --git a/Android_App/.unpackaged_assets/labrador_icon_background.png b/Android_App/.unpackaged_assets/labrador_icon_background.png new file mode 100644 index 000000000..33a37933c Binary files /dev/null and b/Android_App/.unpackaged_assets/labrador_icon_background.png differ diff --git a/Android_App/app/build.gradle.kts b/Android_App/app/build.gradle.kts new file mode 100644 index 000000000..be964e94c --- /dev/null +++ b/Android_App/app/build.gradle.kts @@ -0,0 +1,56 @@ +plugins { + alias(libs.plugins.android.application) +} + +android { + namespace = "com.EspoTek.Labrador" + compileSdk { + version = release(36) + } + + defaultConfig { + applicationId = "com.EspoTek.Labrador" + minSdk = 25 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + ndk { + abiFilters.add("arm64-v8a") + abiFilters.add("armeabi-v7a") + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + externalNativeBuild { + cmake { + path = file("src/main/cpp/CMakeLists.txt") + version = "3.22.1" + } + } + sourceSets { + getByName("main") { + java { + directories.add("src/main/cpp/deps/SDL/android-project/app/src/main/java") + } + } + } + aaptOptions{ + ignoreAssetsPattern = "!pulse.svg:!svg(2)-converted.svg:!settings-gear-svgrepo-com.svg:!mm.svg:!Greek_uc_delta.svg:!Readme.txt"; + } +} + +dependencies { + implementation("com.google.android.material:material:1.2.1") + implementation("com.android.support.constraint:constraint-layout:1.0.2") + implementation("androidx.appcompat:appcompat:1.7.1") +// implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) + //implementation("androidx.appcompat:appcompat:1.0.2") +} diff --git a/Android_App/app/proguard-rules.pro b/Android_App/app/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/Android_App/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/Android_App/app/src/main/AndroidManifest.xml b/Android_App/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..f8f3c033b --- /dev/null +++ b/Android_App/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Android_App/app/src/main/assets/firmware/labrafirm_0007_02.hex b/Android_App/app/src/main/assets/firmware/labrafirm_0007_02.hex new file mode 100644 index 000000000..ad704b552 --- /dev/null +++ b/Android_App/app/src/main/assets/firmware/labrafirm_0007_02.hex @@ -0,0 +1,753 @@ +:1000000028C1000042C1000040C100003EC1000004 +:100010003CC100003AC100000C94340A0C94640AFC +:1000200034C1000032C1000030C100002EC1000008 +:100030002CC100002AC1000028C1000026C1000018 +:1000400024C1000022C1000020C100001EC1000028 +:100050001CC100001AC1000018C1000016C1000038 +:100060000C94610B0C946C0B10C100000EC10000CD +:100070000CC100000AC1000008C1000006C1000058 +:1000800004C1000002C1000000C10000FEC0000069 +:10009000FCC00000FAC00000F8C00000F6C000007C +:1000A000F4C00000F2C00000F0C00000EEC000008C +:1000B000ECC00000EAC00000E8C00000E6C000009C +:1000C000E4C00000E2C00000E0C00000DEC00000AC +:1000D000DCC00000DAC00000D8C00000D6C00000BC +:1000E000D4C00000D2C00000D0C00000CEC00000CC +:1000F000CCC00000CAC00000C8C00000C6C00000DC +:10010000C4C00000C2C00000C0C00000BEC00000EB +:10011000BCC00000BAC00000B8C000007EC400002F +:10012000B4C00000B2C00000B0C00000AEC000000B +:10013000ACC00000AAC00000A8C00000A6C000001B +:10014000A4C00000A2C00000A0C000000C94D60A09 +:100150009CC000009AC0000098C0000096C000003B +:1001600094C0000092C0000090C000008EC000004B +:100170008CC000008AC0000088C0000086C000005B +:1001800084C0000082C0000080C000007EC000006B +:100190007CC000007AC0000078C0000076C000007B +:1001A00074C0000072C0000070C000006EC000008B +:1001B0006CC000006AC0000068C0000066C000009B +:1001C00064C0000062C0000060C000005EC00000AB +:1001D0005CC000005AC0000058C0000056C00000BB +:1001E00054C0000052C0000050C000004EC00000CB +:1001F0004CC000000C946E150C941C1606040804E8 +:100200000A040C040E041004120414042604280426 +:100210002A042C042E04330430043204380D5E0DFD +:10022000850DCD0DD60DDB0D2E0E330E520E590E53 +:10023000610E650E650E650E650E650E650E650E2A +:10024000AA0DE70DF10DFB0D090E0B0E0D0E170E8D +:10025000210E11241FBECFEFCDBFDFE2DEBF10E2C3 +:10026000A0E0B0E2EAE5FEE202C005900D92AA39F4 +:10027000B107D9F72BE2AAE9B0E201C01D92A03E76 +:10028000B207E1F779D20C942B17BACE84E08093B1 +:1002900056008CE18AD3809363008BEB80936600D9 +:1002A00080E88093650081E08093600088ED84BFE2 +:1002B0001092410083E08093500080915100833080 +:1002C000E1F78091500081608093500088E1809335 +:1002D00055008091510080FFFCCF809150008061DB +:1002E000809350008091510084FFFCCF88ED84BF43 +:1002F00084E0809340000895E0E7F0E085818E7F00 +:100300008583E0E0FAE08FEB9DE586A397A380EE7E +:100310009EE280A391A382E0808308958091200AC9 +:100320009091210A813E2EE2920718F020918221BD +:1003300006C0803E2EE2920721F020917F2120937B +:100340006A0020910520222321F080910520815010 +:1003500033C0885F9A42813D974080F110929A2085 +:1003600080919B2090919C20A0919D20B0919E20F7 +:100370000196A11DB11D80939B2090939C20A0937A +:100380009D20B0939E2080919B2090919C20A091D5 +:100390009D20B0919E20892F9A2FAB2FBB27809351 +:1003A000A22180919B2090919C20A0919D20B091B2 +:1003B0009E208093A12186E08093052080910720D4 +:1003C000909108200196B1F480910A208F3F91F01E +:1003D00080910620811106C0809106208150809373 +:1003E00006200895809114019091150180930720B3 +:1003F000909308200895009709F45EC00CF457C04C +:1004000040E020916B0030E02093802130938121E7 +:1004100020918021309181213695322F22273795E6 +:100420002795209380213093812150916A0020915B +:10043000802130918121250F311D2093802130931F +:1004400081210000209180213091812128173907D6 +:1004500028F410928021109281210895209180210A +:1004600030918121280F391F2115304238F08FEF4C +:100470009FE1809380219093812108952091802194 +:1004800030918121442329F0A901481B590BCA014D +:1004900002C0820F931F80938021909381218091CD +:1004A0008021909181218F7780936A0008959195A2 +:1004B0008195910941E0A5CF0895CF93DF93C09135 +:1004C000200AD091210A80910320909104209E015E +:1004D000281B390B209383213093842180919A200B +:1004E00081114BC080918321909184218E3C9F4F3C +:1004F000D4F480918321909184218B369F4F9CF07E +:1005000080910220882321F080910220815008C030 +:1005100080916A008093822181E080939A208CE010 +:10052000809302202AC08CE0809302208091832156 +:10053000909184218A369F4F1CF08FEF9FEF02C06D +:1005400081E090E080937D2190937E2180918321B2 +:100550009091842120917D2130917E2160E370E093 +:100560000E940217261B370B20937D2130937E219A +:1005700080917D2190917E213EDF80919A20813073 +:1005800029F58091832190918421C39794F08091E3 +:100590000220882331F08091022081508093022034 +:1005A00015C080916A0080937F2183E080939A2018 +:1005B0000DC08091832190918421873991051CF48D +:1005C00081E090E002C08FEF9FEF15DFC093032022 +:1005D000D0930420DF91CF9108958617970779F083 +:1005E0006817790728F49C01261B370BB90109C04D +:1005F0008617970718F4681B790B03C0089560E00D +:1006000070E0CB010895CF93DF938091002090910B +:100610000120892B51F080910020909101200197B9 +:10062000809300209093012043C080E490E0809369 +:10063000002090930120809107209091082001963E +:10064000B9F1809114019091150120910A20263072 +:1006500018F467E771E002C06EEE72E00E94EE16D9 +:10066000EC0180910720909108208C179D0758F489 +:100670006091072070910820CE01AFDF029718F03B +:1006800080EC9DE511C08091072090910820C8174B +:10069000D90770F46091072070910820CE019DDF8A +:1006A000029730F08FEB9DE58093360A9093370ADE +:1006B000DF91CF910895AC01460F571F861B970B12 +:1006C0002091200A3091210A24173507C8F720917C +:1006D000200A3091210A8217930790F70895E0E8E5 +:1006E000F6E08FE0808385E0848308951F93CF93A5 +:1006F000DF93C0E4D0E0198180E4898340E250E0D8 +:1007000068EB7BE280E290E00E94C8101983DF91E1 +:10071000CF911F9108951F93CF93DF93C0E4D0E052 +:10072000198180E489838BEB95E20E94FE1081E0C1 +:100730000E9410111983DF91CF911F9108950895A0 +:100740000F931F932091A4208EEE92E0289FA0018A +:10075000299F500D112444525A4D00EA13E02EEE09 +:1007600032E060E081E80E94AB141F910F91089580 +:100770001CBEEEEFF0E40994B9DF8091B82B88231A +:1007800069F080E2E8EBFBE2ABEBB5E201900D92A1 +:100790008A95E1F71092BB25BEDFEADF87E0809300 +:1007A000A200789473DDCBDF0E94C70C49D16CD1D5 +:1007B00060E080E003D1EFD08CE0E3D0B5D659D72C +:1007C00072D78DDF60E870E28EE891E20E94241714 +:1007D0000000000000000000000000000000000019 +:1007E00000000000000000000000000080919F2039 +:1007F000882371F3E091BA238E2F90E08830910521 +:10080000A0F4FC01E250FF4F0C94151768D10DC005 +:10081000FCD10BC0B9D209C06ED307C02DD405C01E +:10082000EDD403C0ECD401C084D510929F20D0CF6A +:1008300008950895E0910A208E2F90E08830910568 +:1008400090F4FC01EA5FFE4F0C941517DDD10BC04C +:100850009AD209C04FD307C00ED405C0CED403C06E +:1008600067D501C000D680911401809397218091B3 +:100870001501809398218091240180939921809182 +:10088000250180939A218091200A8093A321809151 +:10089000210A8093A421809114019091150186343E +:1008A000914080F08091140190911501893A914016 +:1008B00048F480911401909115018093D82B909366 +:1008C000D92B00008091A220882339F015DD1092E9 +:1008D000A22081E08093A12008958091A120882307 +:1008E00041F080919A20833019F418DD8CDE01C02C +:1008F000E4DD8091A020882351F080911401909133 +:1009000015018093D82B9093D92B1092A020909111 +:10091000A42081E0911180E08093A42008950F939A +:100920001F9381E08093A7208093A22000EA13E028 +:100930002DE730E04CED55E260E081E80E94AB1419 +:1009400081E01F910F9108951092A720089581E0F2 +:10095000089581E0089510922002982F9C71936869 +:100960009093200287FF03C080E192E002C080E004 +:1009700090E0892B8093210210922202109226028D +:100980000895E0E0F2E010AA836880AB80E181ABDB +:1009900012AA16AA0895E0E0F2E010A683E880A764 +:1009A0008FE281A712A616A60895AAECB1E092E004 +:1009B0009C93E82FF0E084911C920895909171002F +:1009C0009D7F9093710010920002623011F09CE1C3 +:1009D00001C09EE19093010290E490930202811184 +:1009E00002C080E401C080EC80930302613011F00A +:1009F00084E001C083E08093040280E2D6DF80932C +:100A00000C0281E2D2DF80930D0210921802109244 +:100A1000190281E080930002BECF1F920F920FB6A1 +:100A20000F9211248F93000000000000000081E06D +:100A3000809323028F910F900FBE0F901F901895F7 +:100A4000CF93DF93E0E7F0E082818B7F8283C0E287 +:100A5000D3E08DE0888380E4898389E08A8383E31F +:100A6000A4DF888782E3A1DF898787E39EDF8A8707 +:100A700086E39BDF8B87E0E2F6E080818360808302 +:100A80001482DF91CF910895E0E7F0E080818E7FBE +:100A9000808380E8809300010895E0E0F1E0108A0F +:100AA00080E4808B10A280A310AA80ABE0E4F1E088 +:100AB000108280831092A6201092A5201092A4206C +:100AC00010928C2110928D2110928A2110928B21EC +:100AD00008958093BA2381E080939F200895109217 +:100AE0000A20DBDF80910920811105C081E080931D +:100AF000360184E003C01092360184E28093300115 +:100B00001092310149E54093320122E020933301F4 +:100B100080910C2090910D208093340190933501A9 +:100B20008BEB93E2809338019093390110923A0154 +:100B30008BE380933C0183E080933D0110923E0162 +:100B4000909130019068909330011092460134E208 +:100B50003093400110924101409342018093430140 +:100B600040910E2050910F20409344015093450135 +:100B70004AEA51E2409348015093490110924A01D8 +:100B800099E390934C0180934D0110924E01909106 +:100B900040019068909340011092100190E490936E +:100BA000100194E0909310018093110185E98093E6 +:100BB000120180E18093130187E791E08093140193 +:100BC00090931501309318012093190110921A0186 +:100BD0008CED95E280931C0190931D0110921E01F3 +:100BE00068EC70E084EF91E066DD88EC90E0809343 +:100BF00007209093082081E08093062080911001C7 +:100C000080688093100108950895CF93C1E0C09348 +:100C10000A2043DF1092300180E48093300194E099 +:100C20009093300110923101109232018BE4809345 +:100C300033011092340110923501109236012BE0ED +:100C400030E2209338013093390110923A0180EA62 +:100C500080933C0188E080933D0110923E01809199 +:100C60003001806A8093300185E58093A0088093ED +:100C7000A00880910920811105C0C093460190937E +:100C8000400105C01092460184E280934001109219 +:100C9000410189E58093420122E020934301809144 +:100CA0000C2090910D2080934401909345018BEB93 +:100CB00093E2809348019093490110924A018BE39B +:100CC00080934C0183E080934D0110924E019091EE +:100CD000400190689093400134E03093200180936C +:100CE000210195E9909322014BE44093230147E7CA +:100CF00051E0409324015093250160EA609328015C +:100D000068E06093290110922A016AEC78E260930E +:100D10002C0170932D0110922E01309310018093BD +:100D200011019093120180E180931301409314010B +:100D30005093150184E280931801209319011092B9 +:100D40001A018CED95E280931C0190931D01109285 +:100D50001E0168EC70E084EF91E0ADDC88EC90E07F +:100D6000809307209093082081E080930620809153 +:100D70002001806880932001809110018068809319 +:100D80001001CF910895089582E080930A2085DEB6 +:100D900080910920811105C081E08093360184E0B3 +:100DA00003C01092360184E2809330011092310129 +:100DB00039E53093320192E09093330140910C2059 +:100DC00050910D2040933401509335014BEB53E289 +:100DD000409338015093390110923A018BE380938C +:100DE0003C0183E080933D0110923E01209130014F +:100DF0002068209330011092460174E27093400104 +:100E000010924101309342018093430120910E20C2 +:100E100030910F2020934401309345012AEA31E2BA +:100E2000209348013093490110924A0129E320930D +:100E30004C0180934D0110924E0120914001206899 +:100E4000209340011092100120E42093100164E0EF +:100E5000609310018093110155E95093120140E114 +:100E60004093130127E731E02093140130931501DB +:100E7000709318019093190110921A01ECEDF5E2AC +:100E8000E0931C01F0931D0110921E01609320015C +:100E90008093210150932201409323012093240148 +:100EA0003093250184E380932801909329011092C7 +:100EB0002A018AEC98E280932C0190932D011092E4 +:100EC0002E0168EC70E084EF91E0F5DB88EC90E0B7 +:100ED000809307209093082081E0809306208091E2 +:100EE00010018068809310018091200180688093B8 +:100EF00020010895089583E080930A20CEDD80913B +:100F00000920811105C081E08093460184E003C07F +:100F10001092460184E2809340011092410129E53C +:100F20002093420182E08093430180910C209091B4 +:100F30000D2080934401909345018BEB93E28093C5 +:100F400048019093490110924A018BE380934C0130 +:100F500083E080934D0110924E0190914001906882 +:100F6000909340011092360194E2909330011092D8 +:100F70003101209332018093330120910E20309172 +:100F80000F2020933401309335012AEA31E2209377 +:100F900038013093390110923A0199E390933C0162 +:100FA00080933D0110923E019091300190689093A2 +:100FB00030011092200160E46093200154E05093CE +:100FC000200110922101109222014BE44093230151 +:100FD0001092240110922501109226012BE030E29C +:100FE000209328013093290110922A0130EA30938E +:100FF0002C0128E020932D0110922E0190912001C8 +:10100000906A9093200195E59093A00810921001AA +:1010100060931001509310018093110185E9809332 +:1010200012014093130187E791E08093140190939C +:101030001501309318012093190110921A018CEDBB +:1010400095E280931C0190931D0110921E0168ECA3 +:1010500070E084EF91E02FDB88EC90E08093072034 +:101060009093082081E08093062080911001806891 +:101070008093100108950895CF93C4E0C0930A208F +:101080000CDD1092300180E480933001C093300178 +:1010900010923101109232018BE48093330110924F +:1010A000340110923501109236018BE090E280936A +:1010B00038019093390110923A0180EA80933C0103 +:1010C00088E080933D0110923E0180913001806A5A +:1010D0008093300185E58093A008809109208111DB +:1010E00006C081E080934601C093400105C0109284 +:1010F000460184E2809340011092410189E580938A +:10110000420182E08093430180910C2090910D2058 +:1011100080934401909345018BEB93E280934801C7 +:101120009093490110924A018BE380934C0183E034 +:1011300080934D0110924E019091400190689093E0 +:10114000400154E0509310018093110145E9409310 +:1011500012019BE49093130127E731E020931401DF +:101160003093150190EA9093180198E0909319013B +:1011700010921A016CED75E260931C0170931D01D1 +:1011800010921E015093200180932101409322016F +:101190008AE480932301209324013093250183EC7A +:1011A000809328019093290110922A018AEC98E2F9 +:1011B00080932C0190932D0110922E0168EC70E029 +:1011C00084EF91E078DA88EC90E0809307209093A8 +:1011D000082081E080930620809110018068809330 +:1011E000100180912001806880932001809110017E +:1011F000806180931001CF9108950895FFCF86E01C +:1012000080930A204ADC1092360194E290933001D8 +:101210001092310189E58093320183E0809333019C +:1012200020910E2030910F2020933401309335010E +:101230002AEA31E2209338013093390110923A01C1 +:1012400029E320933C0180933D0110923E0180915F +:10125000300180688093300180910920811107C09E +:1012600081E08093460184E08093400104C01092A5 +:101270004601909340011092410189E5809342011B +:1012800092E09093430120910C2030910D20209307 +:101290004401309345012BEB33E220934801309316 +:1012A000490110924A018BE380934C0183E08093C3 +:1012B0004D0110924E018091400180688093400161 +:1012C0001092100180E48093100184E28093100159 +:1012D0001092110125E92093120120E120931301BE +:1012E0002CED35E020931401309315018093180103 +:1012F0009093190110921A018CED95E280931C01D4 +:1013000090931D0110921E0168EC70E084EF91E053 +:10131000D2D980E991E0809307209093082081E062 +:1013200080930620809110018068809310010895B9 +:10133000089587E080930A20B0DB1092360194E292 +:10134000909330011092310189E58093320183E05E +:101350008093330120910E2030910F20209334018F +:10136000309335012AEA31E2209338013093390174 +:1013700010923A0129E320933C0180933D011092A1 +:101380003E01809130018068809330018091092076 +:10139000811107C081E08093460184E08093400181 +:1013A00004C010924601909340011092410189E5DA +:1013B0008093420192E09093430120910C20309160 +:1013C0000D2020934401309345012BEB33E2209311 +:1013D00048013093490110924A018BE380934C01FC +:1013E00083E080934D0110924E018091400180680E +:1013F000809340011092100180E48093100185E2F7 +:10140000809310011092110185E98093120180E10F +:10141000809313012CED35E02093140130931501D6 +:1014200084E2809318019093190110921A018CEDB7 +:1014300095E280931C0190931D0110921E0168ECAF +:1014400070E084EF91E037D980E991E08093072044 +:101450009093082081E0809306208091100180689D +:1014600080931001089508951F920F920FB60F9266 +:1014700011248F939F9381E08093030110921001B8 +:1014800084E08093100187E791E0809314019093AA +:1014900015018091A420811103C08EEE92E002C05C +:1014A00080E090E084529A4D80931C0190931D013E +:1014B000809110018068809310019F918F910F900F +:1014C0000FBE0F901F9018951F920F920FB60F929C +:1014D00011248F939F9382E0809303011092200147 +:1014E00084E08093200187E791E08093240190932A +:1014F00025018091A420811103C085E694E002C0FB +:1015000087E791E084529A4D80932C0190932D01AE +:10151000809120018068809320019F918F910F908E +:101520000FBE0F901F901895E0E7F0E083818C7F4D +:10153000838384818C7F848380818D7F8083E0E8B6 +:10154000F1E080EC8283128688EC81831186A0E032 +:10155000B8E034E03C9323E011962C931197149655 +:101560001C92149785ED91E096968D939C93979796 +:1015700016961C921697E0E4F8E0308321831482DB +:1015800086A397A31682E0E6F6E0808180618083DF +:10159000E0E4F9E083E18183148282E08683A6A3FC +:1015A000B7A310A611A681E0808308951F920F9221 +:1015B0000FB60F9211242F933F934F935F938F9306 +:1015C0009F93CF93DF931F92CDB7DEB78FEF8093BA +:1015D0004C0980910A20873031F480912C02898354 +:1015E00090912D0202C090912C028091A320891B22 +:1015F000209168093091690982308CF02A3F374088 +:1016000070F4209168093091690989301CF481E0F7 +:1016100090E002C086E090E0820F931F18C02091F6 +:101620006809309169098F3FB4F42730310598F08B +:101630002091680930916909883F1CF081E090E0B1 +:1016400002C086E090E0A901481B590BCA018093B3 +:101650007809909379090F90DF91CF919F918F91A5 +:101660005F914F913F912F910F900FBE0F901F9060 +:101670001895E0E7F0E083818F7E8383E0E4F6E075 +:1016800080818A6A80838FEF848388E5828BE0EA99 +:10169000F8E080E3838384EC858387E08683178288 +:1016A00088E184830895E0E7F0E08381877F838386 +:1016B000E0ECF8E0118288E58093550680E68083AF +:1016C00008951F920F920FB60F92112400000F90F1 +:1016D0000FBE0F901F9018951F920F920FB60F928A +:1016E00011248F93EF93FF93E0EAF8E080818083E9 +:1016F000FF91EF918F910F900FBE0F901F90189553 +:101700009FB7F894A0E5B0E016968C911697897F64 +:1017100016968C931697E0E6F0E080E885838BEBD5 +:10172000868316968C911697846016968C9380818A +:10173000816080839FBF08959FB7F894E82FF0E001 +:10174000E059FF4F60958081682360839FBF0895B3 +:10175000CF93863011F0C0E001C0C8E18091510004 +:1017600081FD0DC09FB7F8948091500082608093F6 +:1017700050009FBF8091510081FFFCCFC1DF6C2FD3 +:10178000636084E490E0E9D460E480E0CF91D4CF5A +:101790008091A82008958091A820813009F4D4C8B0 +:1017A0000895F0D0FC0183818093A820811101C0AD +:1017B000B6C881E008959091AA23892F807697FF7B +:1017C00007C0803491F48091AB2381110EC0C1C851 +:1017D000803459F48091AB23811107C08091B023EC +:1017E0009091B123892B09F0B2C880E008958091CF +:1017F000AC238F770C9435149091B020911102C0D6 +:1018000080E00895E091AD20F091AE200190F0814C +:10181000E02D94818917A0F7E093AB20F093AC20E2 +:10182000228133812E0F3F1FE217F30798F4918135 +:10183000943061F49281891309C09381691306C0C1 +:10184000E093AB20F093AC2081E008959081E90F04 +:10185000F11DEACFE093AB20F093AC20D1CFE09123 +:10186000AD20F091AE200190F081E02D22813381F6 +:101870002E0F3F1FFC014081E40FF11DE217F3071B +:1018800048F48181843031F0853039F08081E80F6F +:10189000F11DF4CF80E090E00895CF0108951F93EB +:1018A000CF93DF93182FA8DF882339F1C091AB20A5 +:1018B000D091AC20CE01D3DFEC01892B41F04C81DB +:1018C0005D816B818A81DED78111F4CF16C0E091F2 +:1018D000AD20F091AE2092818381E92FF82FE10FA6 +:1018E000F11DE10FF11D0190F081E02D0190F081DB +:1018F000E02DDF91CF911F91099480E0DF91CF918E +:101900001F910895EF92FF921F93CF93DF93C82FFB +:1019100060E072DF811102C010E02DC0E091AD20C7 +:10192000F091AE2092818381E92FF82FEC0FF11D09 +:10193000EC0FF11DE080F180D7011696ED91FC913E +:1019400017970995682F8C2F57DF182F882321F3BD +:10195000C091AB20D091AC20CE0181DFEC01892B6E +:1019600021F08A810E945515F7CFD7011296ED918B +:10197000FC9113970995812FDF91CF911F91FF90D3 +:10198000EF9008958091AB209091AC2008950FC7FF +:10199000CF938091B020882311F0C0E008C010924E +:1019A000B0201092B4201092B520CF910895E0910C +:1019B000AD20F091AE200190F081E02D8481C81718 +:1019C00070F78C2F9FDFCF5FF2CFCF938091B02045 +:1019D0008823F1F0C0E0E091AD20F091AE20A0812D +:1019E000B18114968C91C81798F492818381E92F64 +:1019F000F82FEC0FF11DEC0FF11D0190F081E02D9F +:101A00000084F185E02D309709F00995CF5FE3CF91 +:101A1000CF9108950F931F93CF93DF931092B42328 +:101A20001092B5231092B6231092B7231092B823C8 +:101A30001092B9238091AA2387FF07C02091B02379 +:101A40003091B123232B09F482C38076803409F0CE +:101A50003CC1E091AB238E2F90E0FC01E05AF109EC +:101A6000E331F10508F031C1E25FFE4F0C94151728 +:101A700081E08093A020809107209091082080939E +:101A80009B21809107209091082090939C21809128 +:101A9000822180939D2180917F2180939E2180913E +:101AA0006A0080939F2180916B008093A0216091B8 +:101AB000B0237091B1238EE891E201C11092000829 +:101AC0008091AC239091AD23809336089093370892 +:101AD0008091AE238F70809300086091B023709145 +:101AE000B1238AEA91E2C7D68091B0239091B123C5 +:101AF00020910E2030910F208217930709F4F2C233 +:101B000080930E2090930F2045C081E080930920A0 +:101B1000109240088091AC239091AD238093760879 +:101B2000909377088091AE238F70809340086091E6 +:101B3000B0237091B1238BEB93E29DD68091B023BB +:101B40009091B12320910C2030910D2082179307A2 +:101B5000E9F4C8C210920920109240088091AC2389 +:101B60009091AD2380937608909377088091AE236F +:101B70008F70809340086091B0237091B1238BEBFC +:101B800093E279D68091B0239091B12380930C2079 +:101B900090930D2080910A205EC0109268091092E7 +:101BA00069098091AC238093A3209CC28091AC23CF +:101BB0008093240697C2E091AC23F091AD23E830E6 +:101BC000F10508F082C0EF5DFE4F0C94151760E040 +:101BD00080E00E94DE048091AE230E94AB0480E08E +:101BE0003AC060E080E00E94DE048091AE230E9453 +:101BF000AB0481E030C061E081E00E94DE048091AE +:101C0000AE230E94AB048091AF230E94C10482E006 +:101C100022C083E020C084E01EC060E080E00E941B +:101C2000DE048091AE230E94AB0485E014C061E025 +:101C300080E00E94DE048091AE230E94AB0486E027 +:101C40000AC062E080E00E94DE048091AE238068DA +:101C50000E94AB0487E00E94690544C28091AC23D6 +:101C6000809384063FC28091AC239091AD23892B51 +:101C700091F00E94760380E2E8EBFBE2ABEBB5E289 +:101C800001900D928A95E1F781E08093BB250E9437 +:101C90008B030E947603E9E7F0E091E088EDF89489 +:101CA00084BF90836091B0237091B1238AE890E261 +:101CB00006C06091B0237091B12389E890E2DBD532 +:101CC00011C20E948F0481110DC28091AA23982F06 +:101CD000907609F0C9C187FFFBC02091B0233091F5 +:101CE000B1232115310509F4BFC18F7109F09EC0E0 +:101CF0008091AB23863071F0883009F48EC0811159 +:101D0000B3C12230310509F0AFC162E070E084EB6D +:101D100090E2D5CF8091AC239091AD23292F33272A +:101D200022303105A1F060F42130310509F09CC169 +:101D300080911A2090911B20DC016C9170E05BC0B7 +:101D40002330310589F12F30310509F18DC1E09142 +:101D50001A20F0911B202189821708F085C199274C +:101D6000880F991F880F991FE0911C20F0911D206A +:101D7000E80FF91F80819181FC01628173817BD51D +:101D8000E091B223F091B32382E0818335C080914A +:101D90001E2090911F20009709F466C1DC01129665 +:101DA0006D917C91139727C099278130910569F037 +:101DB00038F0029709F058C168E08AE690E208C05E +:101DC00064E070E08CE790E216C067E083E790E2A1 +:101DD000282FAAE5B0E2FC01819190E08D939D93BC +:101DE0008E2F821B8617C0F3660F6E5F609358209C +:101DF00070E088E590E23FD58091B0239091B123C7 +:101E00002091B4233091B5238217930708F06AC15B +:101E10008093B4239093B52365C12130310509F037 +:101E200023C161E070E080EB90E249CF8130C1F5E1 +:101E30008091AB238A3009F017C12130310509F0B8 +:101E400013C18091B020882309F40EC1C091AE2344 +:101E5000D091AF230091AD201091AE20D801ED912B +:101E6000FC918481C81708F0FFC060E08C2FC4DCAF +:101E7000882309F4F9C0DD27CC0FDD1FF80182812A +:101E80009381C80FD91FE881F9810680F781E02D81 +:101E900009958093B22061E070E082EB90E20FCF71 +:101EA000823009F0E1C08091AB238111DDC0223086 +:101EB000310509F0D9C08091AE232DD590E08093F3 +:101EC000A9209093AA2062E070E089EA90E2F7CE20 +:101ED0008F7109F07DC08091AB23833009F4C4C0B9 +:101EE00018F48130A1F0C0C0853019F0893039F183 +:101EF000BBC08091B0239091B123892B09F0B4C06D +:101F000087EF9BE08093B6239093B723EBC080913B +:101F1000B0239091B123892B09F0A6C08091AC2306 +:101F20009091AD23019709F09FC08091B4209091CA +:101F3000B5208D7F8093B4209093B520D3C080913D +:101F4000B0239091B123892B09F08EC091D48823BE +:101F500009F48AC02091AC233091AD233327E0915E +:101F60001A20F0911B20818990E08217930708F4D2 +:101F70007BC00EDD8091AC239091AD238093B02087 +:101F8000882309F4AFC099278150904C880F991F7E +:101F9000880F991F20911C2030911D20820F931FC4 +:101FA0008093AD209093AE20C0E0E091AD20F09101 +:101FB000AE200190F081E02D8481C81708F092C016 +:101FC00060E08C2F6CDC882309F44EC0CF5FEDCF2E +:101FD0008130E9F48091AB238B3009F045C08091CA +:101FE000B0239091B123892B09F03EC08091B0209D +:101FF000882309F439C0C091AE23D091AC238C2F33 +:1020000081DC882389F16D2F8C2F49DC2BC0823035 +:1020100059F58091AB23813019F0833089F024C0C9 +:102020008091B0239091B123892BF1F48091AC235E +:102030009091AD23892BC1F48091AE2373D412C04B +:102040008091B0239091B123892B71F48091AC23BE +:102050009091AD23892B41F48091AE23F6D48091E9 +:10206000AE232CD581113EC08091AA238F7181307F +:10207000D9F58091B020882309F469C0C091AE23BE +:10208000D091AF230091AD201091AE20D801ED91F9 +:10209000FC918481C81708F05AC060E08C2FACDB3B +:1020A000882311F1FE01FF27EE0FFF1FD8011296C2 +:1020B0008D919C911397E80FF91F00811181D80130 +:1020C0001696ED91FC9117970995682F8C2F94DB4C +:1020D000882351F0D8011496ED91FC91159709953C +:1020E000882311F081E034C08091AA238F7182305F +:1020F00071F58091B020882351F1C0E0E091AD20CE +:10210000F091AE20A081B18114968C91C817F8F49B +:1021100092818381E92FF82FEC0FF11DEC0FF11D57 +:1021200000811181D8011696ED91FC9117970995C0 +:10213000682F8C2F61DB882351F0D8011496ED9124 +:10214000FC91159709958111CDCFCF5FD7CF80E056 +:10215000DF91CF911F910F9108951BBEFC0128EDD7 +:1021600024BF608308958091CF0187FDFCCF08953F +:102170001F93CF93DF93C0ECD1E01A858A8761E08B +:102180008BEC91E0EADF1A87DF91CF911F910895E0 +:10219000CF92DF92EF92FF920F931F93CF93DF9333 +:1021A0008C017B016A01DFDFCCECD1E088818860A3 +:1021B0008883B801705FA601C7013DD68881877FFB +:1021C0008883DF91CF911F910F91FF90EF90DF9067 +:1021D000CF900895CF93DF93C82FD62FC4DFACECF8 +:1021E000B1E08C9188608C93EC2FF0E0F05FD083AD +:1021F0008C91877F8C93DF91CF9108950F931F93DC +:10220000CF938C01B0DFC0E0F80161918F018C2F7A +:10221000E1DFCF5FC032C1F7CF911F910F910895D9 +:10222000CF93C82FA0DF20E2C29FC0011124E0ECB1 +:10223000F1E012829183808385E3CF9199CF409121 +:10224000CA01E62FF72F8093CA0184914093CA01F7 +:102250000895E82FF0E0E652F44D80818F3F09F4B5 +:10226000FFCF9FB7F89480818F5F80839FBF0895D1 +:10227000E82FF0E0E652F44D8081811101C0FFCFDC +:102280009FB7F8948081815080839FBF0895CF933A +:10229000C82F80917C21C11105C0882339F081E0CD +:1022A000E7DF04C0811102C081E0D3DFC0937C214D +:1022B000CF910895282F2F70082E000C990B392FDD +:1022C000331F3327331F832F90E0820F911D820F1E +:1022D000911D23E0880F991F2A95E1F7885A9E4D9A +:1022E00008950F93E8ECF4E080818F7D80838081F6 +:1022F0008F7D8083E0E6F1E202E005931092622197 +:102300001092632100E2069300E40693E8E5F1E20F +:1023100000E2069300E40693EAEAF3E214861586E7 +:10232000168617861286138610924D210F910895F6 +:102330000F9385E080934D21E1E6F1E204E00593FF +:10234000E9E5F1E204E005930F9108950F9383E02E +:1023500080934D211092622110926321E0E6F1E218 +:1023600002E006930F910895E091B623F091B72310 +:10237000309709F009940895282F2F70082E000C2B +:10238000990B392F331F3327331F832F90E0820F90 +:10239000911D820F911D029749E0489F9001499F2E +:1023A000300D1124C90189509F4D0895AF92BF92FD +:1023B000DF92EF92FF920F931F93CF93DF93D82E6C +:1023C000DBDFEC018D2D76DF7C01DC011196EC91D9 +:1023D000E770E150E73040F4F0E0EE0FFF1FE45704 +:1023E000FF4D0081118102C008E010E02D813E8187 +:1023F0008B819C814881D7FE4AC0F7016681778135 +:10240000260F371F2D833E8328173907A1F1BC0102 +:10241000621B730B9B01211574E0370738F02FEF17 +:1024200033E0C901B801DAD4281B390B41FF0AC0D7 +:10243000C901B801D3D4AC0191E0452B09F090E07B +:10244000892F01C080E0988180FB91F99883D701A2 +:1024500016961D921C92179712962D933C931397E4 +:1024600029813A818D819E81820F931F14968D93CD +:102470009C93159708C041FF78C04D7F4883168212 +:10248000178212821382F70102E0069385C0D701FA +:102490001296AD90BC90139742FF0FC0E981FA816C +:1024A000B8019CD4B0E4DB9EB001112469587F4D83 +:1024B000AC01CF01820F931FBED42D813E812A0D26 +:1024C0003B1D2D833E838B819C818217930710F4E3 +:1024D0008D839E83F70126813781A216B30609F00A +:1024E00044C02D813E818217930709F43EC0AC01A0 +:1024F000421B530B9A01211554E0350750F02FEF82 +:1025000033E0C901B8016AD4B901681B790BCB016A +:1025100007C0C901B80162D4D901A81BB90BCD010C +:10252000F701128213828017910798F488818460E2 +:102530008883F0E4DF9EC001112489589F4DD701A4 +:1025400014968D939C93159716960D931C9317973D +:102550009ACF49815A812D813E81240F351FF70181 +:1025600024833583868397838ECF888180FF14C030 +:102570008E7F8883EF81F885309771F06D817E8141 +:102580004D2D80E0DF91CF911F910F91FF90EF9043 +:10259000DF90BF90AF900994DF91CF911F910F9181 +:1025A000FF90EF90DF90BF90AF9008950F93CF937F +:1025B000DF9380914D21833029F4D6DEDF91CF91D6 +:1025C0000F918FCE2091492130914A21C091B4239F +:1025D000D091B523C21BD30B61F580914B21909113 +:1025E0004C21820F931F80934B2190934C2120917B +:1025F000B0233091B1238217930721F08091B62048 +:10260000882331F084E080934D21E8E5F1E236C083 +:10261000E091B823F091B923309799F00995882378 +:1026200081F01092492110924A21C091B423D09197 +:10263000B523C034D10528F01092B620C0E4D0E014 +:1026400003C081E08093B620C0936221D0936321C0 +:102650008091492190914A212091B2233091B32356 +:10266000280F391F2093642130936521C80FD91F8B +:10267000C0934921D0934A21E0E6F1E202E00693BB +:10268000DF91CF910F9108950F93CF938091CC0458 +:1026900080FF63C021E02093CC0488E591E2FC0137 +:1026A00000E80693E0E6F1E200E806932093CA040E +:1026B000FC0100E1069380914D21882329F083508D +:1026C000823008F451DE0DDE80915A2190915B2119 +:1026D000089709F044C088E0E9E0F1E2AAEAB3E231 +:1026E00001900D928A95E1F78091C80480628093F1 +:1026F000C8048091C80480628093C8048BD9C82F15 +:10270000811102C015DE2BC08091AA2387FF0DC066 +:1027100010924B2110924C211092492110924A2183 +:1027200082E080934D2142DF1BC08091B0239091C5 +:10273000B123892B11F40ADE13C010924B211092A1 +:102740004C211092492110924A2181E080934D2121 +:10275000E8E5F1E202E0069303C0C0E001C0C1E099 +:102760008C2FCF910F910895CF93CFB7F89481E03C +:102770008EDDEAECF4E080E4808380E28083E1ECAB +:10278000F4E0808181608083A9ECB4E08C91826068 +:102790008C93E8ECF4E08081806480838C9181608C +:1027A0008C93808180688083CFBFCF910895CF9331 +:1027B0001092600080E30E94A80B8091C0048064A6 +:1027C0008093C00481E080936000CFB7F8941092AA +:1027D00059211092612110926921109271218091EA +:1027E000F7208E7F8093F720809100218E7F809349 +:1027F00000216AE170E082E022DD8F3F09F48FE181 +:102800008093FA046BE170E082E019DD8F3F09F4F8 +:102810008FE18093FB048091C00481608093C004A9 +:102820008091C00480688093C0048091C00480615E +:102830008093C00488E591E28093C6049093C70416 +:102840008091C00480628093C0048FEF8093C504A0 +:102850008091C80482608093C80410927C2185E036 +:10286000F8DC82DFCFBFCF9108958093C304089531 +:102870008091C3040895EAEAF3E280879187628732 +:10288000738708951F93CF93DF93162FEA0112DD0C +:10289000FC018181807CD1F5612F6370613019F07A +:1028A00020F080E803C080EC01C080E4C038D1058E +:1028B000E1F050F4C032D105A1F0C034D10599F057 +:1028C0006097C9F491E018C0C11592E0D90779F07A +:1028D000CF3F93E0D90769F0C115D14061F495E08D +:1028E0000BC092E009C093E007C094E005C096E0F9 +:1028F00003C097E001C090E0118226E02083982B6E +:10290000918381818062818381E001C080E0DF91D9 +:10291000CF911F910895CEDCFC01818182FB882735 +:1029200080F90895CF93C82FC5DCFC01818182FF17 +:102930000FC081818B7F81838C2F1EDDFC019081F4 +:1029400090FF06C09E7F90830780F085E02D09955B +:1029500081E0CF910895AF92BF92CF92DF92EF9234 +:10296000FF920F931F93CF93DF93D82EC62E5A0159 +:10297000790102DDEC018D2D9DDC9C01DC011196BD +:102980008C911197807C09F454C011968C91119709 +:10299000807C803C21F011968C9182FD4AC09FB7CB +:1029A000F894888180FF02C09FBF43C08160888304 +:1029B0009FBFA982BA82EB82FC821D821E820F8396 +:1029C000188791E0C11004C0E114F10409F090E00F +:1029D000888190FB81F98B7F8883D7FE02C0F90143 +:1029E00022C0D90111968C911197807C803CC1F452 +:1029F0001196EC91E770E150E73040F4F0E0EE0F13 +:102A0000FF1FE457FF4D6081718102C068E070E0F4 +:102A1000C701E4D1892B21F088818E7F888309C08A +:102A2000F90112821382168217828D2DBFDC81E09C +:102A300001C080E0DF91CF911F910F91FF90EF9047 +:102A4000DF90CF90BF90AF900895EF92FF920F93D9 +:102A5000CF93C82F2FDC7C018C2F8EDCDC01F7019B +:102A600002E005939C9190FF1BC09E7F9C9317965C +:102A7000ED91FC911897309799F0D701C7FF05C0E9 +:102A800016966D917C91179704C012966D917C916A +:102A900013974C2F81E0CF910F91FF90EF90099405 +:102AA000CF910F91FF90EF900895CF93C82FCDDF76 +:102AB0008C2F00DCFC011182CF9108950F93CF93EE +:102AC000C82FF8DBFC0181818460818301E00693DB +:102AD0008C2FBBDF81E0CF910F9108951F920F9251 +:102AE0000FB60F9211240F932F933F934F935F9341 +:102AF0006F937F938F939F93AF93BF93EF93FF93C6 +:102B00008091CB0487FF08C080E88093CA040E94AC +:102B1000E50C0E941A047EC08091CB0482FF18C08D +:102B200084E08093CA048091602186FF73C0809105 +:102B3000CC0481FD6FC0A8DD81116CC080914D2156 +:102B4000813011F403DC66C0843009F063C021C019 +:102B50008091CB0481FF26C082E08093CA048091DB +:102B6000582186FF57C08091CC0481FD53C08CDD75 +:102B7000811150C080914D21823041F484E08093D6 +:102B80004D21E8E5F1E202E0069344C0833009F00C +:102B900041C0E1E6F1E204E00593E9E5F1E204E099 +:102BA000059338C08091CB0484FF1DC080E18093E1 +:102BB000CA040E94C80C1092C3041092592196E0D6 +:102BC0009093582183E4809359211092612190932E +:102BD00060218093612189E091E280935C21909350 +:102BE0005D217FDB17C08091CB0486FF08C080E4A5 +:102BF0008093CA0480E04BDB0E9418040BC08091D4 +:102C0000CB0485FF07C080E28093CA0481E03FDBEC +:102C10000E941904FF91EF91BF91AF919F918F9105 +:102C20007F916F915F914F913F912F910F910F90F5 +:102C30000FBE0F901F9018951F920F920FB60F9214 +:102C400011240F931F932F933F934F935F936F9391 +:102C50007F938F939F93AF93BF93CF93DF93EF9324 +:102C6000FF938091CC0481FF1AC082E08093CC0452 +:102C70008091C5048195880FE8E5F1E2E81BF10930 +:102C8000208131812855314283E0369527958A95F8 +:102C9000E1F7822F869520FD06C0C0E005C0F4DC78 +:102CA000882319F386C0C0E8C80F8C2F03DBFC0112 +:102CB000208125FF7EC000E20693C11174C080917F +:102CC0004D21843019F450DB0CDB73C000915A2184 +:102CD00010915B218091B4239091B523C09149213B +:102CE000D0914A2198012C0F3D1F8217930718F4A9 +:102CF0008C010C1B1D0B8091B2239091B323A80172 +:102D000069E071E28C0F9D1F96D0C00FD11FC09358 +:102D10004921D0934A210034110569F480914B2157 +:102D200090914C218C0F9D1F2091B0233091B123A5 +:102D30008217930788F0C093B423D093B523E09112 +:102D4000B823F091B923309729F00995811102C079 +:102D5000EFDA2FC0FBDA2DC08091B4239091B52318 +:102D6000C817D907D9F4E091B823F091B923309767 +:102D700079F30995882361F320914B2130914C21FF +:102D80008091492190914A21820F931F80934B217A +:102D900090934C211092492110924A21E8E5F1E2EA +:102DA00002E0069306C0C03811F400DC02C08C2F8C +:102DB000FDDAFF91EF91DF91CF91BF91AF919F919C +:102DC0008F917F916F915F914F913F912F911F91C3 +:102DD0000F910F900FBE0F901F901895AA1BBB1B51 +:102DE00051E107C0AA1FBB1FA617B70710F0A61B0B +:102DF000B70B881F991F5A95A9F780959095BC012C +:102E0000CD01089597FB072E16F4009406D077FDA8 +:102E100008D0E4DF07FC05D03EF4909581959F4FE4 +:102E20000895709561957F4F0895EE0FFF1F0590EF +:102E3000F491E02D0994FB01DC0102C001900D9298 +:102E400041505040D8F70895FB01DC0101900D92EC +:0A2E50000020E1F70895F894FFCF89 +:102E5A0040000CE02E0AFFFFFF01FF5580008000B2 +:102E6A00D10BCB0BDB0BC80B000044202020000049 +:102E7A0028202420102000000902190001010080E6 +:102E8A00FA0904000001FFFFFF0007058101FF03A3 +:102E9A00010000001201000200000040EB0394BA96 +:102EAA00000201020001000000030000000000000F +:102EBA00000000000000000000004C6162726164C2 +:102ECA006F72004573706F54656B00000403090448 +:102EDA006465627567313233000207001000200012 +:0A2EEA004000800000010002FF0319 +:00000001FF diff --git a/Android_App/app/src/main/assets/font/Greek_uc_delta.svg b/Android_App/app/src/main/assets/font/Greek_uc_delta.svg new file mode 100644 index 000000000..1c6850e5a --- /dev/null +++ b/Android_App/app/src/main/assets/font/Greek_uc_delta.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Android_App/app/src/main/assets/font/Readme.txt b/Android_App/app/src/main/assets/font/Readme.txt new file mode 100644 index 000000000..7d479d2da --- /dev/null +++ b/Android_App/app/src/main/assets/font/Readme.txt @@ -0,0 +1,12 @@ +Note: this readme and the .svg files in this directory are excluded from the assets that are included in the app. This is done via the aaptOptions in /app/build.gradle.kts + +waveform-glyphs3 resources: + +https://www.sinwaver.com/ (have to hack it a bit to get a wider stroke) +https://boxy-svg.com/app/ (handy online svg editor) +https://10015.io/tools/svg-stroke-to-fill-converter ( apply to the waveform from sinwaver) + +greek delta from wiki commons https://commons.wikimedia.org/wiki/File:Greek_uc_delta.svg + +load the .svg's into https://icomoon.io/app/#/select to generate the font + diff --git a/Android_App/app/src/main/assets/font/greek_delta.ttf b/Android_App/app/src/main/assets/font/greek_delta.ttf new file mode 100644 index 000000000..0dbb66ae8 Binary files /dev/null and b/Android_App/app/src/main/assets/font/greek_delta.ttf differ diff --git a/Android_App/app/src/main/assets/font/mm.svg b/Android_App/app/src/main/assets/font/mm.svg new file mode 100644 index 000000000..c53dc66d3 --- /dev/null +++ b/Android_App/app/src/main/assets/font/mm.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Android_App/app/src/main/assets/font/pulse.svg b/Android_App/app/src/main/assets/font/pulse.svg new file mode 100644 index 000000000..cb2de128c --- /dev/null +++ b/Android_App/app/src/main/assets/font/pulse.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Android_App/app/src/main/assets/font/settings-gear-svgrepo-com.svg b/Android_App/app/src/main/assets/font/settings-gear-svgrepo-com.svg new file mode 100644 index 000000000..7d6026be4 --- /dev/null +++ b/Android_App/app/src/main/assets/font/settings-gear-svgrepo-com.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Android_App/app/src/main/assets/font/svg(2)-converted.svg b/Android_App/app/src/main/assets/font/svg(2)-converted.svg new file mode 100644 index 000000000..105fb5a34 --- /dev/null +++ b/Android_App/app/src/main/assets/font/svg(2)-converted.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Android_App/app/src/main/assets/font/waveform-glyphs3.ttf b/Android_App/app/src/main/assets/font/waveform-glyphs3.ttf new file mode 100644 index 000000000..092b537f6 Binary files /dev/null and b/Android_App/app/src/main/assets/font/waveform-glyphs3.ttf differ diff --git a/Android_App/app/src/main/cpp/CMakeLists.txt b/Android_App/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000..d51a73a17 --- /dev/null +++ b/Android_App/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,99 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html. +# For more examples on how to use CMake, see https://github.com/android/ndk-samples. + +# Sets the minimum CMake version required for this project. +cmake_minimum_required(VERSION 3.22.1) + +# Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, +# Since this is the top level CMakeLists.txt, the project name is also accessible +# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level +# build script scope). +project("labrador-imgui-android") + +add_compile_options(-fsigned-char) # saw same symptom as shown here: https://github.com/espotek-org/Labrador/issues/231 ; adding this flag fixes it, and seemingly doesn't break imgui, SDL, etc. +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +find_package(SDL3 REQUIRED) + +# add_subdirectory(deps/librador) + +file(GLOB SOURCES "*.cpp") +file(GLOB IMGUI_SOURCES "deps/imgui/*.cpp") +file(GLOB IMPLOT_SOURCES "deps/implot/*.cpp") +file(GLOB LIBRADOR_SOURCES "deps/librador/*.cpp") + +# now build app's shared lib +add_library( + labrador-imgui-android + SHARED + ${SOURCES} + ${IMGUI_SOURCES} + deps/imgui/backends/imgui_impl_sdl3.cpp + deps/imgui/backends/imgui_impl_opengl3.cpp + ${IMPLOT_SOURCES} + ${LIBRADOR_SOURCES} + deps/SDL/src/core/android/SDL_android.c + deps/SDL/src +) + +target_include_directories(labrador-imgui-android PRIVATE + deps/imgui + deps/imgui/backends + deps/implot + deps/librador + ../../../../../libdfuprog + deps/librador/librador_deps/libusb + ${SDL3_INCLUDE_DIR} + # for SDL_android.h + deps/SDL/src/core/android + # for SDL_internal.h + deps/SDL/src + # for SDL_build_config.h + deps/SDL/include/build_config + ) + +target_compile_definitions(labrador-imgui-android PRIVATE + IMGUI_IMPL_OPENGL_ES3 + PLATFORM_ANDROID) + +add_library(libusb SHARED IMPORTED) +set_target_properties(libusb PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/deps/librador/librador_deps/libusb/android/${ANDROID_ABI}/libusb1.0.so) + +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../../../../libdfuprog ${CMAKE_CURRENT_LIST_DIR}/../../../../../libdfuprog/build) + +# add lib dependencies +target_link_libraries(labrador-imgui-android + android + log + EGL + libdfuprog + libusb + GLESv3 + SDL3-static + ) + + +## Creates and names a library, sets it as either STATIC +## or SHARED, and provides the relative paths to its source code. +## You can define multiple libraries, and CMake builds them for you. +## Gradle automatically packages shared libraries with your APK. +## +## In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define +## the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} +## is preferred for the same purpose. +## +## In order to load a library into your app from Java/Kotlin, you must call +## System.loadLibrary() and pass the name of the library defined here; +## for GameActivity/NativeActivity derived applications, the same library name must be +## used in the AndroidManifest.xml file. +#add_library(${CMAKE_PROJECT_NAME} SHARED +# # List C/C++ source files with relative paths to this CMakeLists.txt. +# native-lib.cpp) +# +## Specifies libraries CMake should link to your target library. You +## can link libraries from various origins, such as libraries defined in this +## build script, prebuilt third-party libraries, or Android system libraries. +#target_link_libraries(${CMAKE_PROJECT_NAME} +# # List libraries link to the target library +# android +# log) diff --git a/Android_App/app/src/main/cpp/cmake/FindSDL3.cmake b/Android_App/app/src/main/cpp/cmake/FindSDL3.cmake new file mode 100644 index 000000000..1539d2cc1 --- /dev/null +++ b/Android_App/app/src/main/cpp/cmake/FindSDL3.cmake @@ -0,0 +1,220 @@ +# Locate SDL3 library +# This module defines +# SDL3_LIBRARY, the name of the library to link against +# SDL3_FOUND, if false, do not try to link to SDL3 +# SDL3_INCLUDE_DIR, where to find SDL.h +# +# This module responds to the the flag: +# SDL3_BUILDING_LIBRARY +# If this is defined, then no SDL3main will be linked in because +# only applications need main(). +# Otherwise, it is assumed you are building an application and this +# module will attempt to locate and set the the proper link flags +# as part of the returned SDL3_LIBRARY variable. +# +# Don't forget to include SDLmain.h and SDLmain.m your project for the +# OS X framework based version. (Other versions link to -lSDL3main which +# this module will try to find on your behalf.) Also for OS X, this +# module will automatically add the -framework Cocoa on your behalf. +# +# +# Additional Note: If you see an empty SDL3_LIBRARY_TEMP in your configuration +# and no SDL3_LIBRARY, it means CMake did not find your SDL3 library +# (SDL3.dll, libsdl3.so, SDL3.framework, etc). +# Set SDL3_LIBRARY_TEMP to point to your SDL3 library, and configure again. +# Similarly, if you see an empty SDL3MAIN_LIBRARY, you should set this value +# as appropriate. These values are used to generate the final SDL3_LIBRARY +# variable, but when these values are unset, SDL3_LIBRARY does not get created. +# +# +# $SDL3DIR is an environment variable that would +# correspond to the ./configure --prefix=$SDL3DIR +# used in building SDL3. +# l.e.galup 9-20-02 +# +# Modified by Eric Wing. +# Added code to assist with automated building by using environmental variables +# and providing a more controlled/consistent search behavior. +# Added new modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). +# Also corrected the header search path to follow "proper" SDL guidelines. +# Added a search for SDL3main which is needed by some platforms. +# Added a search for threads which is needed by some platforms. +# Added needed compile switches for MinGW. +# +# On OSX, this will prefer the Framework version (if found) over others. +# People will have to manually change the cache values of +# SDL3_LIBRARY to override this selection or set the CMake environment +# CMAKE_INCLUDE_PATH to modify the search paths. +# +# Note that the header path has changed from SDL3/SDL.h to just SDL.h +# This needed to change because "proper" SDL convention +# is #include "SDL.h", not . This is done for portability +# reasons because not all systems place things in SDL3/ (see FreeBSD). + +#============================================================================= +# Copyright 2003-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +# Modified by sfalexrog to accomodate Android builds. SDL3_LIBRARY +# will be a target which you'll want to link against. It will be built +# statically, mostly because that would save you from needing to +# also add SDL3MAIN_LIBRARY to your target, and will not require you to +# add SDL_android_main.c to your source sets. +# You can override this behavior by changing this file, but as far as +# I know having multiple shared libraries don't really give you any +# benefit on Android. +# +# Modified by brentfpage: SDL2->SDL3 +# specified set(SDL_STATIC ON) in the if(ANDROID) block +# also, in app/src/main/cpp/CMakeLists.txt, instead of using the convenience +# variable ${SDL3_LIBRARY} in target_link_libraries(native-plasma ... +# ${SDL3_LIBRARY}$ ) I hard-coded the value SDL3-static, as in +# target_link_libraries(native-plasma ... SDL3-static ) + +if (ANDROID) + set(_SDL_SOURCE_SEARCH_PATHS + SDL + deps/SDL + external/SDL + libs/SDL + ) + + # This is a hack to allow find_path to actually search for headers and sources + set(_SDL_CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ${CMAKE_FIND_ROOT_PATH_MODE_INCLUDE}) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE "NEVER") + set(SDL_STATIC ON) + + find_path(_SDL3_SOURCE_DIR SDL.c + HINTS + ${_SDL_SOURCE_SEARCH_PATHS} + PATH_SUFFIXES src + PATHS ${_SDL_SOURCE_SEARCH_PATHS} + ) + + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ${_SDL_CMAKE_FIND_ROOT_PATH_MODE_INCLUDE}) + + if (${_SDL3_SOURCE_DIR} STREQUAL "_SDL3_SOURCE_DIR-NOTFOUND") + message(FATAL_ERROR "Could not find SDL3 source directory, cannot continue") + endif() + + get_filename_component(_SDL3_PROJECT_DIR ${_SDL3_SOURCE_DIR} DIRECTORY) + + add_subdirectory(${_SDL3_PROJECT_DIR}) + + # You can't directly specify a target in target_link_libraries + # in ${}, but you can't specify a variable name without ${}, + # so here's a little hack to make ${target_name} behave like + # it should. + set(SDL3_LIBRARY SDL3 SDL3main) + set(SDL3_INCLUDE_DIR ${_SDL3_PROJECT_DIR}/include) + + +else() + + SET(SDL3_SEARCH_PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + ) + + FIND_PATH(SDL3_INCLUDE_DIR SDL.h + HINTS + $ENV{SDL3DIR} + PATH_SUFFIXES include/SDL3 include + PATHS ${SDL3_SEARCH_PATHS} + ) + + FIND_LIBRARY(SDL3_LIBRARY_TEMP + NAMES SDL3 + HINTS + $ENV{SDL3DIR} + PATH_SUFFIXES lib64 lib + PATHS ${SDL3_SEARCH_PATHS} + ) + + IF(NOT SDL3_BUILDING_LIBRARY) + IF(NOT ${SDL3_INCLUDE_DIR} MATCHES ".framework") + # Non-OS X framework versions expect you to also dynamically link to + # SDL3main. This is mainly for Windows and OS X. Other (Unix) platforms + # seem to provide SDL3main for compatibility even though they don't + # necessarily need it. + FIND_LIBRARY(SDL3MAIN_LIBRARY + NAMES SDL3main + HINTS + $ENV{SDL3DIR} + PATH_SUFFIXES lib64 lib + PATHS ${SDL3_SEARCH_PATHS} + ) + ENDIF(NOT ${SDL3_INCLUDE_DIR} MATCHES ".framework") + ENDIF(NOT SDL3_BUILDING_LIBRARY) + + # SDL3 may require threads on your system. + # The Apple build may not need an explicit flag because one of the + # frameworks may already provide it. + # But for non-OSX systems, I will use the CMake Threads package. + IF(NOT APPLE) + FIND_PACKAGE(Threads) + ENDIF(NOT APPLE) + + # MinGW needs an additional library, mwindows + # It's total link flags should look like -lmingw32 -lSDL3main -lSDL3 -lmwindows + # (Actually on second look, I think it only needs one of the m* libraries.) + IF(MINGW) + SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") + ENDIF(MINGW) + + IF(SDL3_LIBRARY_TEMP) + # For SDL3main + IF(NOT SDL3_BUILDING_LIBRARY) + IF(SDL3MAIN_LIBRARY) + SET(SDL3_LIBRARY_TEMP ${SDL3MAIN_LIBRARY} ${SDL3_LIBRARY_TEMP}) + ENDIF(SDL3MAIN_LIBRARY) + ENDIF(NOT SDL3_BUILDING_LIBRARY) + + # For OS X, SDL3 uses Cocoa as a backend so it must link to Cocoa. + # CMake doesn't display the -framework Cocoa string in the UI even + # though it actually is there if I modify a pre-used variable. + # I think it has something to do with the CACHE STRING. + # So I use a temporary variable until the end so I can set the + # "real" variable in one-shot. + IF(APPLE) + SET(SDL3_LIBRARY_TEMP ${SDL3_LIBRARY_TEMP} "-framework Cocoa") + ENDIF(APPLE) + + # For threads, as mentioned Apple doesn't need this. + # In fact, there seems to be a problem if I used the Threads package + # and try using this line, so I'm just skipping it entirely for OS X. + IF(NOT APPLE) + SET(SDL3_LIBRARY_TEMP ${SDL3_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) + ENDIF(NOT APPLE) + + # For MinGW library + IF(MINGW) + SET(SDL3_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL3_LIBRARY_TEMP}) + ENDIF(MINGW) + + # Set the final string here so the GUI reflects the final state. + SET(SDL3_LIBRARY ${SDL3_LIBRARY_TEMP} CACHE STRING "Where the SDL3 Library can be found") + # Set the temp variable to INTERNAL so it is not seen in the CMake GUI + SET(SDL3_LIBRARY_TEMP "${SDL3_LIBRARY_TEMP}" CACHE INTERNAL "") + ENDIF(SDL3_LIBRARY_TEMP) + + INCLUDE(FindPackageHandleStandardArgs) + + FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL3 REQUIRED_VARS SDL3_LIBRARY SDL3_INCLUDE_DIR) +endif() diff --git a/Android_App/app/src/main/cpp/deps/SDL b/Android_App/app/src/main/cpp/deps/SDL new file mode 160000 index 000000000..1726b34de --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/SDL @@ -0,0 +1 @@ +Subproject commit 1726b34deaa73d2952cf74801f23337ac9da7317 diff --git a/Android_App/app/src/main/cpp/deps/imgui b/Android_App/app/src/main/cpp/deps/imgui new file mode 160000 index 000000000..f1a3d6826 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/imgui @@ -0,0 +1 @@ +Subproject commit f1a3d68266807d597cc35a17a628a7952d7ac579 diff --git a/Android_App/app/src/main/cpp/deps/implot b/Android_App/app/src/main/cpp/deps/implot new file mode 160000 index 000000000..9c620603f --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/implot @@ -0,0 +1 @@ +Subproject commit 9c620603fd5610c0ee8fc8a71b13ce7268947e79 diff --git a/Android_App/app/src/main/cpp/deps/librador/i2cdecoder.cpp b/Android_App/app/src/main/cpp/deps/librador/i2cdecoder.cpp new file mode 100644 index 000000000..0d9dac433 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/i2cdecoder.cpp @@ -0,0 +1,214 @@ +#include "i2cdecoder.h" + +using namespace i2c; + +i2cDecoder::i2cDecoder(o1buffer* sda_in, o1buffer* scl_in) : + m_serialBuffer{I2C_BUFFER_LENGTH}, + sda(sda_in), + scl(scl_in) +{ +} + +void i2cDecoder::reset() +{ + LIBRADOR_LOG(LOG_DEBUG, "Resetting I2C"); + + if (sda->mostRecentAddress != scl->mostRecentAddress) + { + // Perhaps the data could be saved, but just resetting them seems much safer + sda->reset(true); + scl->reset(true); + } + + serialPtr_bit = sda->mostRecentAddress * 8; + + { + std::lock_guard lock(mutex); + + m_serialBuffer.clear(); + } +} + + +void i2cDecoder::run() +{ + if(!m_decode_on) + return; + + while (serialDistance(sda) > SERIAL_DELAY * sda->m_samples_per_second * 8) + { + updateBitValues(); + runStateMachine(); + serialPtr_bit ++; + if (serialPtr_bit >= (sda->m_bufferLen * 8)) + serialPtr_bit -= (sda->m_bufferLen * 8); + } +} + +int i2cDecoder::serialDistance(o1buffer* buffer) +{ + int back_bit = buffer->mostRecentAddress * 8; + int bufferEnd_bit = buffer->m_bufferLen * 8; + if (back_bit >= serialPtr_bit) + return back_bit - serialPtr_bit; + else + return bufferEnd_bit - serialPtr_bit + back_bit; +} + +void i2cDecoder::updateBitValues(){ + previousSdaValue = currentSdaValue; + previousSclValue = currentSclValue; + + int coord_byte = serialPtr_bit/8; + int coord_bit = serialPtr_bit - (8*coord_byte); + unsigned char dataByteSda = sda->get(coord_byte); + unsigned char dataByteScl = scl->get(coord_byte); + unsigned char mask = (0x01 << coord_bit); + currentSdaValue = dataByteSda & mask; + currentSclValue = dataByteScl & mask; +} + +void i2cDecoder::runStateMachine() +{ + edge sdaEdge = edgeDetection(currentSdaValue, previousSdaValue); + edge sclEdge = edgeDetection(currentSclValue, previousSclValue); + + if ((sdaEdge == edge::rising) && (sclEdge == edge::falling)) // INVALID STATE TRANSITION + { + state = transmissionState::unknown; + LIBRADOR_LOG(LOG_WARNING, "Dumping I2C state and aborting..."); + for (int i=31; i>=0; i--) + LIBRADOR_LOG(LOG_DEBUG, "%02x\t%02x", sda->get(serialPtr_bit/8 - i) & 0xFF, scl->get(serialPtr_bit/8 - i) & 0xFF); + throw std::runtime_error("unknown i2c transmission state"); + return; + } + + if ((sdaEdge == edge::rising) && (sclEdge == edge::held_high)) // START + { + stopCondition(); + return; + } + + if ((sdaEdge == edge::falling) && (sclEdge == edge::held_high)) // STOP + { + startCondition(); + return; + } + + switch (state) + { + case transmissionState::idle: + return; + case transmissionState::address: + decodeAddress(sdaEdge, sclEdge); + break; + case transmissionState::data: + decodeData(sdaEdge, sclEdge); + break; + } +} + +edge i2cDecoder::edgeDetection(uint8_t current, uint8_t prev) +{ + if (current && prev) + return edge::held_high; + if (!current && !prev) + return edge::held_low; + if (current && !prev) + return edge::rising; + if (!current && prev) + return edge::falling; + + throw std::runtime_error("i2c Edge Detection critical failure"); +} + +void i2cDecoder::decodeAddress(edge sdaEdge, edge sclEdge) +{ + // Read in the next bit. + if (sclEdge == edge::rising && sdaEdge == edge::held_high && currentBitIndex++ < addressBitStreamLength) + currentBitStream = (currentBitStream << 1) | 0x0001; + else if (sclEdge == edge::rising && sdaEdge == edge::held_low && currentBitIndex++ < addressBitStreamLength) + currentBitStream = (currentBitStream << 1) & 0xFFFE; + else + return; + + if (currentBitIndex == addressBitStreamLength) + { + LIBRADOR_LOG(LOG_DEBUG, "Finished Address Decode"); + if (currentBitStream & 0b0000000000000010) + m_serialBuffer.insert("READ: "); + else + m_serialBuffer.insert("WRITE: "); + + m_serialBuffer.insert_hex((uint8_t)((currentBitStream & 0b0000000111111100) >> 2)); + m_serialBuffer.insert(' '); + + if (currentBitStream & 0b0000000000000001) + m_serialBuffer.insert("(NACK)"); + + consoleStateInvalid = true; + + // Prepare for next bit + currentBitIndex = 0; + currentBitStream = 0x0000; + state = transmissionState::data; + } +} + +void i2cDecoder::decodeData(edge sdaEdge, edge sclEdge) +{ + // Read in the next bit. + if (sclEdge == edge::rising && sdaEdge == edge::held_high && currentBitIndex++ < dataBitStreamLength) + currentBitStream = (currentBitStream << 1) | 0x0001; + else if (sclEdge == edge::rising && sdaEdge == edge::held_low && currentBitIndex++ < dataBitStreamLength) + currentBitStream = (currentBitStream << 1) & 0xFFFE; + else + return; + + if (currentBitIndex == dataBitStreamLength) + { + LIBRADOR_LOG(LOG_DEBUG, "Finished Data byte Decode"); + + m_serialBuffer.insert_hex((uint8_t)((currentBitStream & 0b0000000111111110) >> 1)); + m_serialBuffer.insert(' '); + + if (currentBitStream & 0b0000000000000001) + m_serialBuffer.insert("(NACK)"); + + consoleStateInvalid = true; + + // Prepare for next bit + currentBitIndex = 0; + currentBitStream = 0x0000; + } +} + +void i2cDecoder::startCondition() +{ + currentBitIndex = 0; + currentBitStream = 0x0000; + state = transmissionState::address; + LIBRADOR_LOG(LOG_DEBUG, "I2C START"); +} + +void i2cDecoder::stopCondition() +{ + state = transmissionState::idle; + m_serialBuffer.insert('\n'); + LIBRADOR_LOG(LOG_DEBUG, "I2C STOP"); +} + +char * i2cDecoder::getString() +{ + memcpy(convertedStream_string, m_serialBuffer.begin(), sizeof(char) * m_serialBuffer.size()); + convertedStream_string[m_serialBuffer.size()] = '\0'; + return convertedStream_string; +} + +void i2cDecoder::setIsDecoding(bool new_decode_on) +{ + if(new_decode_on && !m_decode_on) + m_serialBuffer.clear(); + reset(); + m_decode_on = new_decode_on; +} diff --git a/Android_App/app/src/main/cpp/deps/librador/i2cdecoder.h b/Android_App/app/src/main/cpp/deps/librador/i2cdecoder.h new file mode 100644 index 000000000..7e96f88b3 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/i2cdecoder.h @@ -0,0 +1,82 @@ +#ifndef I2CDECODER_H +#define I2CDECODER_H + +#include "o1buffer.h" +#include "isobufferbuffer.h" +#include "logging_internal.h" + +#include + +#define SERIAL_BUFFER_LENGTH 8192 + + +namespace i2c +{ + +enum class transmissionState: uint8_t +{ + unknown, + idle, + address, + data +}; + +enum class edge: uint8_t +{ + rising, + falling, + held_high, + held_low +}; + +constexpr uint8_t addressBitStreamLength = 9; +constexpr uint8_t dataBitStreamLength = 9; +constexpr uint32_t I2C_BUFFER_LENGTH = 4096; +constexpr double SERIAL_DELAY = 0.01; + +class i2cDecoder +{ + char convertedStream_string[SERIAL_BUFFER_LENGTH + 1]; + +public: + explicit i2cDecoder(o1buffer* sda_in, o1buffer* scl_in); + // misc + o1buffer* sda; + o1buffer* scl; + isoBufferBuffer m_serialBuffer; + std::mutex mutex; + + // State vars + uint8_t currentSdaValue = 0; + uint8_t previousSdaValue = 0; + uint8_t currentSclValue = 0; + uint8_t previousSclValue = 0; + uint64_t serialPtr_bit = 0; + transmissionState state = transmissionState::unknown; + bool consoleStateInvalid; + + // Data Transmission + uint8_t currentBitIndex = 0; + uint16_t currentBitStream; + + // Member functions + void updateBitValues(); + void runStateMachine(); + void run(); + int serialDistance(o1buffer* buffer); + edge edgeDetection(uint8_t current, uint8_t prev); + void decodeAddress(edge sdaEdge, edge sclEdge); + void decodeData(edge sdaEdge, edge sclEdge); + void startCondition(); + void stopCondition(); + void reset(); + + void setIsDecoding(bool new_decode_on); + char * getString(); + + bool m_decode_on = false; +}; + +} // Namespace i2c + +#endif // UARTSTYLEDECODER_H diff --git a/Android_App/app/src/main/cpp/deps/librador/isobufferbuffer.cpp b/Android_App/app/src/main/cpp/deps/librador/isobufferbuffer.cpp new file mode 100644 index 000000000..542b5afd9 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/isobufferbuffer.cpp @@ -0,0 +1,101 @@ +#include "isobufferbuffer.h" +#include +#define LOG_TAG "librador" +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) + +/* isoBufferBuffer is implemented as two consecutive, duplicate, + * ring buffers. the effect of this is that we are able to hand + * out pointers to pieces of contiguous memory representing the + * last N inserted elements (for some N <= capacity()) even + * when we looped back to the begining of the ring buffer less + * than N insertions ago + * + * There are some differences with the original implementation + * functionality-wise. + * Where the original implementation allowed queries half as + * long as the length passed in, and allocated a buffer 1.5 times + * the length passed in, this version allows queries of length up + * to the length passed into the constructor and allocates a + * buffer 2 times the length passed in. + * Overall, this means that in contrast to the original + * implementation which only allowed queries up to a third as + * long as the allocated buffer, we can now do queries as long as + * half of the allocated buffer, which is a notable improvement. + */ + +isoBufferBuffer::isoBufferBuffer(uint32_t length) + : m_data(std::make_unique(length*2)) + , m_capacity(length) +{ +} + +// Adds a character to the end of the buffer +void isoBufferBuffer::insert(char c) +{ + char* dataPtr = m_data.get(); + + // Add character to first half of the buffer + dataPtr[m_top] = c; + // Then to the second + dataPtr[m_top+m_capacity] = c; + + // Loop the buffer index if necessary and update size accordingly + m_top = (m_top + 1) % m_capacity; + m_size = std::min(m_size + 1, m_capacity); +} + +void isoBufferBuffer::insert(char const * s) +{ + while (*s != '\0') + insert(*s++); +} + +void isoBufferBuffer::insert(std::string const & s) +{ + for (char c : s) + insert(c); +} + +void isoBufferBuffer::insert_hex(uint8_t x) +{ + char str[5]; + snprintf(str, sizeof str, "0x%02hhx", x); + insert((char const *)str); +} + +char const* isoBufferBuffer::query(uint32_t count) const +{ + if (count > m_capacity) + LOGF("isoBufferBuffer::query : you may not request more items than the capacity of the buffer"); + + if (count > m_size) + LOGF("isoBufferBuffer::query : you may not request more items than inserted"); + + return end() - count; +} + +void isoBufferBuffer::clear() +{ + m_top = 0; + m_size = 0; +} + +char const * isoBufferBuffer::begin() const +{ + return m_data.get() + m_top - m_size + m_capacity; +} + +char const * isoBufferBuffer::end() const +{ + return m_data.get() + m_top + m_capacity; +} + +uint32_t isoBufferBuffer::size() const +{ + return m_size; +} + +uint32_t isoBufferBuffer::capacity() const +{ + return m_capacity; +} diff --git a/Android_App/app/src/main/cpp/deps/librador/isobufferbuffer.h b/Android_App/app/src/main/cpp/deps/librador/isobufferbuffer.h new file mode 100644 index 000000000..2d3bf4b3e --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/isobufferbuffer.h @@ -0,0 +1,53 @@ +#ifndef ISOBUFFERBUFFER_H +#define ISOBUFFERBUFFER_H + +#include +#include +#include + +/** @file isobufferbuffer.h + * @brief This module implements a data structure that allows + * insertion of single characters and a view of the last N + * inserted characters in constant time. + * + * To obtain such complexity, a double ring buffer is used. + * That is, two identical ring buffers are mantained adjacent + * in memory. If we always return a pointer to the beginning of a + * range that ends in the second buffer, we will always return a + * valid address(*), even when the requested length is greater + * than the current position being inserted into in the buffer. + * + * (*) By valid address I mean that both the addresses that + * represent the beginning and end of the requested query result + * are within the allocated buffer. + */ +class isoBufferBuffer +{ +public: + isoBufferBuffer(uint32_t length); + ~isoBufferBuffer() = default; + + void insert(char c); + void insert(char const * s); + void insert(std::string const & s); + void insert_hex(uint8_t x); + + char const * query(uint32_t length) const; + // TODO?: add ability to get a copy of the content + // (e.g. return std::string or Qstring) + + void clear(); + + char const * begin() const; + char const * end() const; + + uint32_t size() const; + uint32_t capacity() const; +private: + std::unique_ptr m_data; + uint32_t m_capacity; + uint32_t m_size = 0; + uint32_t m_top = 0; +}; + +#endif // ISOBUFFERBUFFER_H diff --git a/Android_App/app/src/main/cpp/deps/librador/librador.cpp b/Android_App/app/src/main/cpp/deps/librador/librador.cpp new file mode 100644 index 000000000..4d511eed0 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/librador.cpp @@ -0,0 +1,408 @@ +#include "librador.h" +#include "librador_internal.h" +#include "logging_internal.h" + +#define _USE_MATH_DEFINES + +#include +#include +#include +#include + +#ifdef PLATFORM_ANDROID +JNIEXPORT void JNICALL Java_com_EspoTek_Labrador_MainActivity_nativeRespondToStartupOrUsbStateChange(JNIEnv *env, jobject thisobject, jboolean is_plugged_in, jint file_descriptor, jboolean bootloader_mode) +{ + if(!internal_librador_object) + { + librador_init(); + } + internal_librador_object->usb_driver->respondToStartupOrUsbStateChange((bool) is_plugged_in, (int) file_descriptor, (bool) bootloader_mode); + return; +} + +JNIEXPORT void JNICALL Java_com_EspoTek_Labrador_MainActivity_nativeInitiateFirmwareFlash(JNIEnv *env, jobject thisobject) +{ + internal_librador_object->usb_driver->initiateFirmwareFlash(); + return; +} +#endif + +Librador::Librador() +{ + usb_driver = new usbCallHandler(LABRADOR_VID, LABRADOR_PID); +} + +int librador_init(){ + if(internal_librador_object){ + //Object already initialised + return 1; + } + + internal_librador_object = new Librador(); + if(!internal_librador_object){ + //Object initialisation failed + return -1; + } else { + //good, fresh initialisation + return 0; + } +} + +int librador_exit(){ + CHECK_API_INITIALISED + if(!internal_librador_object){ + //Object not yet initialised + return 1; + } + + delete internal_librador_object; + internal_librador_object = nullptr; + //Object deleted + return 0; +} + +int librador_avr_debug(){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return internal_librador_object->usb_driver->avrDebug(); +} + +std::vector * librador_get_analog_data(int channel, double timeWindow_seconds, int numToGet, double delay_seconds, int filter_mode) +{ + VECTOR_API_INIT_CHECK + VECTOR_USB_INIT_CHECK + + double samples_per_second = internal_librador_object->usb_driver->get_samples_per_second(); + + if(samples_per_second == 0){ + return nullptr; + } + + double interval_samples = timeWindow_seconds * samples_per_second / (numToGet-1); + int delay_samples = round(delay_seconds * samples_per_second); +// int numToGet = round(timeWindow_seconds * samples_per_second)/interval_samples; + + return internal_librador_object->usb_driver->getMany_double(channel, numToGet, interval_samples, delay_samples, filter_mode); +} + +// int librador_setTriggerType(int channel, o1buffer::TriggerType trigger_type) +// { +// CHECK_API_INITIALISED +// return internal_librador_object->usb_driver->setTriggerType(channel, trigger_type); +// } + +std::vector librador_get_time_array(double delay, double timeWindow_seconds, int n_samples) { + std::vector time_array(n_samples,0); + double sample_period = timeWindow_seconds / (n_samples - 1); + double* data = time_array.data(); + for(int i=0; i < n_samples; i++) { + data[i] = -delay - i * sample_period; + } + return time_array; +} + + +std::vector * librador_get_digital_data(int channel, double timeWindow_seconds, int numToGet, double delay_seconds){ + VECTOR_API_INIT_CHECK + VECTOR_USB_INIT_CHECK + + double subsamples_per_second = internal_librador_object->usb_driver->get_samples_per_second() * 8; + + if(subsamples_per_second == 0){ + return nullptr; + } + + double interval_subsamples = timeWindow_seconds * subsamples_per_second / (numToGet-1); + int delay_subsamples = round(delay_seconds * subsamples_per_second); +// int numToGet = round(timeWindow_seconds * subsamples_per_second)/interval_subsamples; + + LIBRADOR_LOG(LOG_DEBUG, "interval_subsamples = %d\ndelay_subsamples = %d\nnumToGet=%d\n", interval_subsamples, delay_subsamples, numToGet); + + return internal_librador_object->usb_driver->getMany_singleBit(channel, numToGet, interval_subsamples, delay_subsamples); +} + + +std::vector * librador_get_analog_data_sincelast(int channel, double timeWindow_max_seconds, double sample_rate_hz, double delay_seconds, int filter_mode) +{ + VECTOR_API_INIT_CHECK + VECTOR_USB_INIT_CHECK + + double samples_per_second = internal_librador_object->usb_driver->get_samples_per_second(); + + if(samples_per_second == 0){ + return nullptr; + } + + int interval_samples = round(samples_per_second / sample_rate_hz); + int feasible_window_end = round(delay_seconds * samples_per_second); + int feasible_window_begin = round((delay_seconds + timeWindow_max_seconds) * samples_per_second); + + return internal_librador_object->usb_driver->getMany_sincelast(channel, feasible_window_begin, feasible_window_end, interval_samples, filter_mode); + +} + +int librador_update_signal_gen_settings(int channel, unsigned char *sampleBuffer, int numSamples, double usecs_between_samples, double amplitude_v, double offset_v){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + int error = internal_librador_object->usb_driver->update_function_gen_settings(channel, sampleBuffer, numSamples, usecs_between_samples, amplitude_v, offset_v); + if(error){ + return error-1000; + } else return internal_librador_object->usb_driver->send_function_gen_settings(channel); +} + +int librador_set_power_supply_voltage(double voltage){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return internal_librador_object->usb_driver->set_psu_voltage(voltage); +} + +int librador_set_device_mode(int mode){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return internal_librador_object->usb_driver->set_device_mode(mode); +} + +int librador_set_oscilloscope_gain(double gain){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return internal_librador_object->usb_driver->set_gain(gain); +} + +int librador_set_paused(int channel, bool is_paused){ + CHECK_API_INITIALISED + return internal_librador_object->usb_driver->setPaused(channel, is_paused); +} + +bool librador_get_paused(int channel){ + CHECK_API_INITIALISED + return internal_librador_object->usb_driver->getPaused(channel); +} + +int librador_set_digital_out(int channel, bool state_on){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + static uint8_t channelStates[4] = {0, 0, 0, 0}; + channel--; + if((channel < 0) || (channel > 3)){ + return -1000; //Invalid Channel + } + channelStates[channel] = state_on ? 1 : 0; + + return internal_librador_object->usb_driver->set_digital_state((channelStates [0] | channelStates[1] << 1 | channelStates[2] << 2 | channelStates[3] << 3)); +} + +int librador_reset_device(){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return internal_librador_object->usb_driver->reset_device(false); +} + +int librador_jump_to_bootloader(){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return internal_librador_object->usb_driver->reset_device(true); +} + +uint16_t librador_get_device_firmware_version(){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return internal_librador_object->usb_driver->get_firmware_version(); +} + +uint8_t librador_get_device_firmware_variant(){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return internal_librador_object->usb_driver->get_firmware_variant(); +} + +int round_to_log2(double in){ + //Round down to the nearest power of 2. + return round(pow(2, floor(log2(in)))); +} + +unsigned char generator_sin(double x) +{ + //Offset of 1 and divided by 2 shifts range from -1:1 to 0:1. We've got to return an unsigned char, after all! + return (unsigned char)round(255.0 * ((sin(x)+1)/2)); +} + +unsigned char generator_square(double x) +{ + return (x > M_PI) ? 255 : 0; +} + +unsigned char generator_sawtooth(double x) +{ + return round(255.0 * (x/(2.0*M_PI))); +} + +unsigned char generator_triangle(double x) +{ + if(x <= M_PI){ + return round(255.0 * (x/M_PI)); + } else { + return round(255.0 * (1 -((x - M_PI)/M_PI))); + } +} + +int send_convenience_waveform(int channel, double frequency_Hz, double amplitude_v, double offset_v, unsigned char (*sample_generator)(double)) +{ + if((amplitude_v + offset_v) > 9.6){ + return -1; + //Voltage range too high + } + if((amplitude_v < 0) | (offset_v < 0)){ + return -2; + //Negative voltage + } + + if((channel != 1) && (channel != 2)){ + return -3; + //Invalid channel + } + int num_samples = fmin(1000000.0/frequency_Hz, 512); + //The maximum number of samples that Labrador's buffer holds is 512. + //The minimum time between samples is 1us. Using T=1/f, this gives a maximum sample number of 10^6/f. + num_samples = 2*(num_samples / 2); + //Square waves need an even number. Others don't care. + double usecs_between_samples = 1000000.0/((double)num_samples * frequency_Hz); + //Again, from T=1/f. + unsigned char* sampleBuffer = (unsigned char*)malloc(num_samples); + + int i; + double x_temp; + for(i=0; i< num_samples; i++){ + x_temp = (double)i * (2.0*M_PI/(double)num_samples); + //Generate points at interval 2*pi/num_samples. + sampleBuffer[i] = sample_generator(x_temp); + } + + librador_update_signal_gen_settings(channel, sampleBuffer, num_samples, usecs_between_samples, amplitude_v, offset_v); + + free(sampleBuffer); + return 0; +} + +int librador_send_sin_wave(int channel, double frequency_Hz, double amplitude_v, double offset_v){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return send_convenience_waveform(channel, frequency_Hz, amplitude_v, offset_v, generator_sin); +} + +int librador_send_square_wave(int channel, double frequency_Hz, double amplitude_v, double offset_v){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return send_convenience_waveform(channel, frequency_Hz, amplitude_v, offset_v, generator_square); +} + +int librador_send_triangle_wave(int channel, double frequency_Hz, double amplitude_v, double offset_v){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return send_convenience_waveform(channel, frequency_Hz, amplitude_v, offset_v, generator_triangle); +} + +int librador_send_sawtooth_wave(int channel, double frequency_Hz, double amplitude_v, double offset_v){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + return send_convenience_waveform(channel, frequency_Hz, amplitude_v, offset_v, generator_sawtooth); +} + +int librador_send_wave(int wf, int channel, double frequency_Hz, double amplitude_v, double offset_v){ + CHECK_API_INITIALISED + CHECK_USB_INITIALISED + switch(wf) + { + case 0: + return send_convenience_waveform(channel, frequency_Hz, amplitude_v, offset_v, generator_sin); + case 1: + return send_convenience_waveform(channel, frequency_Hz, amplitude_v, offset_v, generator_square); + case 2: + return send_convenience_waveform(channel, frequency_Hz, amplitude_v, offset_v, generator_triangle); + case 3: + return send_convenience_waveform(channel, frequency_Hz, amplitude_v, offset_v, generator_sawtooth); + }; + return -4; +} + + + +/* +int librador_synchronise_begin(){ + CHECK_API_INITIALISED + return internal_librador_object->usb_driver->set_synchronous_pause_state(true); +} + +int librador_synchronise_end(){ + CHECK_API_INITIALISED + return internal_librador_object->usb_driver->set_synchronous_pause_state(false); +} +*/ + +static void std_logger(void * userdata, const int level, const char * format, va_list ap); +static librador_logger_p _librador_global_logger = std_logger; +static void * _librador_global_userdata = nullptr; + +void librador_global_logger(const int level, const char * format, ...){ + va_list args; + va_start(args, format); + if (_librador_global_logger) + _librador_global_logger(_librador_global_userdata, level, format, args); + va_end(args); +} + +void librador_logger_set(void * userdata, librador_logger_p logger){ + _librador_global_logger = logger ? logger : std_logger; + _librador_global_userdata = userdata; +} + +librador_logger_p librador_logger_get(void){ + return _librador_global_logger; +} + +void * librador_logger_get_userdata(void){ + return _librador_global_userdata; +} + +static void std_logger(void * userdata, const int level, const char * format, va_list ap){ + vfprintf((level > LOG_ERROR) ? stdout : stderr , format, ap); +} + +void librador_set_trigger_settings(int channel, o1buffer::trigger_settings new_trigger_settings) +{ + return internal_librador_object->usb_driver->setTriggerSettings(channel, new_trigger_settings); +} + +void librador_set_virtual_transform_settings(int channel, o1buffer::virtual_transform_settings new_virtual_transform_settings) +{ + return internal_librador_object->usb_driver->setVirtualTransformSettings(channel, new_virtual_transform_settings); +} + +char * librador_get_uart_string(int channel, bool* parity_check) +{ + return internal_librador_object->usb_driver->getUart_String(channel, parity_check); +} + +void librador_set_uart_decode_settings(int ch, UartSettings new_settings) { + internal_librador_object->usb_driver->setUartDecodeSettings(ch, new_settings); +} + +char * librador_get_i2c_string() +{ + return internal_librador_object->usb_driver->getI2c_String(); +} + +void librador_set_i2c_is_decoding(bool new_decode_on) +{ + internal_librador_object->usb_driver->setI2cIsDecoding(new_decode_on); +} + +bool librador_is_connected() +{ + return internal_librador_object->usb_driver->connected; +} + +bool librador_iso_thread_is_active() +{ + return internal_librador_object->usb_driver->isoThreadIsActive(); +} + diff --git a/Android_App/app/src/main/cpp/deps/librador/librador.h b/Android_App/app/src/main/cpp/deps/librador/librador.h new file mode 100644 index 000000000..27d8558d5 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/librador.h @@ -0,0 +1,91 @@ +#ifndef LIBRADOR_H +#define LIBRADOR_H + +#include "librador_global.h" +#include +#include "usbcallhandler.h" +#include "logging.h" +#include +#include +#include +#include + +#ifdef PLATFORM_ANDROID +#ifdef __cplusplus +extern "C" { +#endif +JNIEXPORT void JNICALL Java_com_EspoTek_Labrador_MainActivity_nativeRespondToStartupOrUsbStateChange(JNIEnv *, jobject, jboolean, jint, jboolean); +JNIEXPORT void JNICALL Java_com_EspoTek_Labrador_MainActivity_nativeInitiateFirmwareFlash(JNIEnv *, jobject); +#ifdef __cplusplus +} +#endif +#endif // PLATFORM_ANDROID + +LIBRADORSHARED_EXPORT int librador_init(); +LIBRADORSHARED_EXPORT int librador_exit(); +LIBRADORSHARED_EXPORT int librador_reset_usb(); +//Control +//a0 +LIBRADORSHARED_EXPORT int librador_avr_debug(); +//a1 +LIBRADORSHARED_EXPORT int librador_update_signal_gen_settings(int channel, unsigned char* sampleBuffer, int numSamples, double usecs_between_samples, double amplitude_v, double offset_v); +LIBRADORSHARED_EXPORT int librador_send_sin_wave(int channel, double frequency_Hz, double amplitude_v, double offset_v); +LIBRADORSHARED_EXPORT int librador_send_square_wave(int channel, double frequency_Hz, double amplitude_v, double offset_v); +LIBRADORSHARED_EXPORT int librador_send_sawtooth_wave(int channel, double frequency_Hz, double amplitude_v, double offset_v); +LIBRADORSHARED_EXPORT int librador_send_triangle_wave(int channel, double frequency_Hz, double amplitude_v, double offset_v); +LIBRADORSHARED_EXPORT int librador_send_wave(int wf, int channel, double frequency_Hz, double amplitude_v, double offset_v); +//a2 +////As above +//a3 +LIBRADORSHARED_EXPORT int librador_set_power_supply_voltage(double voltage); +//a4 +///As above, a1 and a2 +//a5 +LIBRADORSHARED_EXPORT int librador_set_device_mode(int mode); +LIBRADORSHARED_EXPORT int librador_set_oscilloscope_gain(double gain); +LIBRADORSHARED_EXPORT bool librador_get_paused(int channel); +LIBRADORSHARED_EXPORT int librador_set_paused(int channel, bool is_paused); +//a6 +LIBRADORSHARED_EXPORT int librador_set_digital_out(int channel, bool state_on); +//a7 +LIBRADORSHARED_EXPORT int librador_reset_device(); +LIBRADORSHARED_EXPORT int librador_jump_to_bootloader(); +//a8 +LIBRADORSHARED_EXPORT uint16_t librador_get_device_firmware_version(); +//a9 +LIBRADORSHARED_EXPORT uint8_t librador_get_device_firmware_variant(); +//aa +//LIBRADORSHARED_EXPORT int librador_kickstart_isochronous_loop(); + +LIBRADORSHARED_EXPORT std::vector * librador_get_analog_data(int channel, double timeWindow_seconds, int numToGet, double delay_seconds, int filter_mode); +LIBRADORSHARED_EXPORT std::vector * librador_get_analog_data_sincelast(int channel, double timeWindow_max_seconds, double sample_rate_hz, double delay_seconds, int filter_mode); +LIBRADORSHARED_EXPORT std::vector * librador_get_digital_data(int channel, double timeWindow_seconds, int numToGet, double delay_seconds); + +LIBRADORSHARED_EXPORT std::vector librador_get_time_array(double delay, double timeWindow_seconds, int n_samples); + +//TODO: flashFirmware(); + + +/* + * Should never be unsynchronised... Hide these ones +LIBRADORSHARED_EXPORT int librador_synchronise_begin(); +LIBRADORSHARED_EXPORT int librador_synchronise_end(); +*/ + +typedef void (*librador_logger_p)(void * userdata, const int level, const char * format, va_list); + +LIBRADORSHARED_EXPORT void librador_logger_set(void * userdata, librador_logger_p logger); +LIBRADORSHARED_EXPORT librador_logger_p librador_logger_get(void); +LIBRADORSHARED_EXPORT void * librador_logger_get_userdata(void); + +LIBRADORSHARED_EXPORT void librador_set_trigger_settings(int ch, o1buffer::trigger_settings new_trigger_settings); +LIBRADORSHARED_EXPORT void librador_set_virtual_transform_settings(int ch, o1buffer::virtual_transform_settings new_virtual_transform_settings); + +LIBRADORSHARED_EXPORT void librador_set_uart_decode_settings(int ch, UartSettings new_settings); +LIBRADORSHARED_EXPORT void librador_set_i2c_is_decoding(bool new_decode_on); +LIBRADORSHARED_EXPORT char * librador_get_uart_string(int ch, bool* parity_check); +LIBRADORSHARED_EXPORT char * librador_get_i2c_string(); + +LIBRADORSHARED_EXPORT int librador_init_libusb(); +LIBRADORSHARED_EXPORT bool librador_iso_thread_is_active(); +#endif // LIBRADOR_H diff --git a/Android_App/app/src/main/cpp/deps/librador/librador.pro b/Android_App/app/src/main/cpp/deps/librador/librador.pro new file mode 100644 index 000000000..ef993fb52 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/librador.pro @@ -0,0 +1,62 @@ +QT += widgets +QT -= gui + +win32: TARGET = librador +else: TARGET = rador +TEMPLATE = lib + +DEFINES += LIBRADOR_LIBRARY + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + librador.cpp \ + o1buffer.cpp \ + usbcallhandler.cpp + +HEADERS += \ + librador.h \ + librador_global.h \ + librador_internal.h \ + logging.h \ + logging_internal.h \ + o1buffer.h \ + usbcallhandler.h + +unix { + target.path = /usr/lib + INSTALLS += target +} + +unix:!android:!macx { + DEFINES += PLATFORM_LINUX + + #libusb include + CONFIG += link_pkgconfig + PKGCONFIG += libusb-1.0 +} + +macx { + DEFINES += PLATFORM_MAC + + #libusb include + INCLUDEPATH += $$system(brew --prefix)/include/libusb-1.0 + LIBS += -L$$system(brew --prefix)/lib -lusb-1.0 +} + +win32 { + DEFINES += PLATFORM_WINDOWS + + #libusb include + INCLUDEPATH += ../libusb + LIBS += -L$$PWD/../libusb -llibusb-1.0 +} diff --git a/Android_App/app/src/main/cpp/deps/librador/librador_deps/libusb/android/arm64-v8a/libusb1.0.so b/Android_App/app/src/main/cpp/deps/librador/librador_deps/libusb/android/arm64-v8a/libusb1.0.so new file mode 100644 index 000000000..f7c15b2c9 Binary files /dev/null and b/Android_App/app/src/main/cpp/deps/librador/librador_deps/libusb/android/arm64-v8a/libusb1.0.so differ diff --git a/Android_App/app/src/main/cpp/deps/librador/librador_deps/libusb/android/armeabi-v7a/libusb1.0.so b/Android_App/app/src/main/cpp/deps/librador/librador_deps/libusb/android/armeabi-v7a/libusb1.0.so new file mode 100755 index 000000000..dc3df24b0 Binary files /dev/null and b/Android_App/app/src/main/cpp/deps/librador/librador_deps/libusb/android/armeabi-v7a/libusb1.0.so differ diff --git a/Android_App/app/src/main/cpp/deps/librador/librador_deps/libusb/libusb.h b/Android_App/app/src/main/cpp/deps/librador/librador_deps/libusb/libusb.h new file mode 100644 index 000000000..8542c6ac7 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/librador_deps/libusb/libusb.h @@ -0,0 +1,2453 @@ +/* + * Public libusb header file + * Copyright © 2001 Johannes Erdfelt + * Copyright © 2007-2008 Daniel Drake + * Copyright © 2012 Pete Batard + * Copyright © 2012-2023 Nathan Hjelm + * Copyright © 2014-2020 Chris Dickens + * For more information, please visit: https://libusb.info + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBUSB_H +#define LIBUSB_H + +#if defined(_MSC_VER) +#pragma warning(push) +/* Disable: warning C4200: nonstandard extension used : zero-sized array in struct/union */ +#pragma warning(disable:4200) +/* on MS environments, the inline keyword is available in C++ only */ +#if !defined(__cplusplus) +#define inline __inline +#endif +/* ssize_t is also not available */ +#ifndef _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED +#include +typedef SSIZE_T ssize_t; +#endif /* _SSIZE_T_DEFINED */ +#endif /* _MSC_VER */ + +#include +#include +#include +#if !defined(_MSC_VER) +#include +#endif +#include + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#define LIBUSB_FLEXIBLE_ARRAY /* [] - valid C99 code */ +#else +#define LIBUSB_FLEXIBLE_ARRAY 0 /* [0] - non-standard, but usually working code */ +#endif /* __STDC_VERSION__ */ + +/* 'interface' might be defined as a macro on Windows, so we need to + * undefine it so as not to break the current libusb API, because + * libusb_config_descriptor has an 'interface' member + * As this can be problematic if you include windows.h after libusb.h + * in your sources, we force windows.h to be included first. */ +#if defined(_WIN32) || defined(__CYGWIN__) +#include +#if defined(interface) +#undef interface +#endif +#endif /* _WIN32 || __CYGWIN__ */ + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define LIBUSB_DEPRECATED_FOR(f) __attribute__ ((deprecated ("Use " #f " instead"))) +#elif defined(__GNUC__) && (__GNUC__ >= 3) +#define LIBUSB_DEPRECATED_FOR(f) __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define LIBUSB_DEPRECATED_FOR(f) __declspec(deprecated("Use " #f " instead")) +#else +#define LIBUSB_DEPRECATED_FOR(f) +#endif /* __GNUC__ */ + +#if defined(__GNUC__) +#define LIBUSB_PACKED __attribute__ ((packed)) +#else +#define LIBUSB_PACKED +#endif /* __GNUC__ */ + +/** \def LIBUSB_CALL + * \ingroup libusb_misc + * libusb's Windows calling convention. + * + * Under Windows, the selection of available compilers and configurations + * means that, unlike other platforms, there is not one true calling + * convention (calling convention: the manner in which parameters are + * passed to functions in the generated assembly code). + * + * Matching the Windows API itself, libusb uses the WINAPI convention (which + * translates to the stdcall convention) and guarantees that the + * library is compiled in this way. The public header file also includes + * appropriate annotations so that your own software will use the right + * convention, even if another convention is being used by default within + * your codebase. + * + * The one consideration that you must apply in your software is to mark + * all functions which you use as libusb callbacks with this LIBUSB_CALL + * annotation, so that they too get compiled for the correct calling + * convention. + * + * On non-Windows operating systems, this macro is defined as nothing. This + * means that you can apply it to your code without worrying about + * cross-platform compatibility. + */ +/* LIBUSB_CALL must be defined on both definition and declaration of libusb + * functions. You'd think that declaration would be enough, but cygwin will + * complain about conflicting types unless both are marked this way. + * The placement of this macro is important too; it must appear after the + * return type, before the function name. See internal documentation for + * API_EXPORTED. + */ +#if defined(_WIN32) || defined(__CYGWIN__) +#define LIBUSB_CALL WINAPI +#define LIBUSB_CALLV WINAPIV +#else +#define LIBUSB_CALL +#define LIBUSB_CALLV +#endif /* _WIN32 || __CYGWIN__ */ + +/** \def LIBUSB_API_VERSION + * \ingroup libusb_misc + * libusb's API version. + * + * Since version 1.0.18, to help with feature detection, libusb defines + * a LIBUSB_API_VERSION macro that gets increased every time there is a + * significant change to the API, such as the introduction of a new call, + * the definition of a new macro/enum member, or any other element that + * libusb applications may want to detect at compilation time. + * + * Between versions 1.0.13 and 1.0.17 (inclusive) the older spelling of + * LIBUSBX_API_VERSION was used. + * + * The macro is typically used in an application as follows: + * \code + * #if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01001234) + * // Use one of the newer features from the libusb API + * #endif + * \endcode + * + * Internally, LIBUSB_API_VERSION is defined as follows: + * (libusb major << 24) | (libusb minor << 16) | (16 bit incremental) + * + * The incremental component has changed as follows: + *
    + *
  • libusbx version 1.0.13: LIBUSBX_API_VERSION = 0x01000100 + *
  • libusbx version 1.0.14: LIBUSBX_API_VERSION = 0x010000FF + *
  • libusbx version 1.0.15: LIBUSBX_API_VERSION = 0x01000101 + *
  • libusbx version 1.0.16: LIBUSBX_API_VERSION = 0x01000102 + *
  • libusbx version 1.0.17: LIBUSBX_API_VERSION = 0x01000102 + *
  • libusb version 1.0.18: LIBUSB_API_VERSION = 0x01000102 + *
  • libusb version 1.0.19: LIBUSB_API_VERSION = 0x01000103 + *
  • libusb version 1.0.20: LIBUSB_API_VERSION = 0x01000104 + *
  • libusb version 1.0.21: LIBUSB_API_VERSION = 0x01000105 + *
  • libusb version 1.0.22: LIBUSB_API_VERSION = 0x01000106 + *
  • libusb version 1.0.23: LIBUSB_API_VERSION = 0x01000107 + *
  • libusb version 1.0.24: LIBUSB_API_VERSION = 0x01000108 + *
  • libusb version 1.0.25: LIBUSB_API_VERSION = 0x01000109 + *
  • libusb version 1.0.26: LIBUSB_API_VERSION = 0x01000109 + *
  • libusb version 1.0.27: LIBUSB_API_VERSION = 0x0100010A + *
  • libusb version 1.0.28: LIBUSB_API_VERSION = 0x0100010A + *
  • libusb version 1.0.29: LIBUSB_API_VERSION = 0x0100010B + *
  • libusb version 1.0.30: LIBUSB_API_VERSION = 0x0100010C + *
+ */ +#define LIBUSB_API_VERSION 0x0100010C + +/** \def LIBUSBX_API_VERSION + * \ingroup libusb_misc + * + * This is the older spelling, kept for backwards compatibility of code + * needing to test for older library versions where the newer spelling + * did not exist. */ +#define LIBUSBX_API_VERSION LIBUSB_API_VERSION + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * \ingroup libusb_misc + * Convert a 16-bit value from host-endian to little-endian format. On + * little endian systems, this function does nothing. On big endian systems, + * the bytes are swapped. + * \param x the host-endian value to convert + * \returns the value in little-endian byte order + */ +static inline uint16_t libusb_cpu_to_le16(uint16_t x) +{ + union { + uint8_t b8[2]; + uint16_t b16; + } _tmp; + _tmp.b8[1] = (uint8_t) (x >> 8); + _tmp.b8[0] = (uint8_t) (x & 0xff); + return _tmp.b16; +} + +/** \def libusb_le16_to_cpu + * \ingroup libusb_misc + * Convert a 16-bit value from little-endian to host-endian format. On + * little endian systems, this function does nothing. On big endian systems, + * the bytes are swapped. + * \param x the little-endian value to convert + * \returns the value in host-endian byte order + */ +#define libusb_le16_to_cpu libusb_cpu_to_le16 + +/* standard USB stuff */ + +/** \ingroup libusb_desc + * Device and/or Interface Class codes */ +enum libusb_class_code { + /** In the context of a \ref libusb_device_descriptor "device descriptor", + * this bDeviceClass value indicates that each interface specifies its + * own class information and all interfaces operate independently. + */ + LIBUSB_CLASS_PER_INTERFACE = 0x00, + + /** Audio class */ + LIBUSB_CLASS_AUDIO = 0x01, + + /** Communications class */ + LIBUSB_CLASS_COMM = 0x02, + + /** Human Interface Device class */ + LIBUSB_CLASS_HID = 0x03, + + /** Physical */ + LIBUSB_CLASS_PHYSICAL = 0x05, + + /** Image class */ + LIBUSB_CLASS_IMAGE = 0x06, + LIBUSB_CLASS_PTP = 0x06, /* legacy name from libusb-0.1 usb.h */ + + /** Printer class */ + LIBUSB_CLASS_PRINTER = 0x07, + + /** Mass storage class */ + LIBUSB_CLASS_MASS_STORAGE = 0x08, + + /** Hub class */ + LIBUSB_CLASS_HUB = 0x09, + + /** Data class */ + LIBUSB_CLASS_DATA = 0x0a, + + /** Smart Card */ + LIBUSB_CLASS_SMART_CARD = 0x0b, + + /** Content Security */ + LIBUSB_CLASS_CONTENT_SECURITY = 0x0d, + + /** Video */ + LIBUSB_CLASS_VIDEO = 0x0e, + + /** Personal Healthcare */ + LIBUSB_CLASS_PERSONAL_HEALTHCARE = 0x0f, + + /** Diagnostic Device */ + LIBUSB_CLASS_DIAGNOSTIC_DEVICE = 0xdc, + + /** Wireless class */ + LIBUSB_CLASS_WIRELESS = 0xe0, + + /** Miscellaneous class */ + LIBUSB_CLASS_MISCELLANEOUS = 0xef, + + /** Application class */ + LIBUSB_CLASS_APPLICATION = 0xfe, + + /** Class is vendor-specific */ + LIBUSB_CLASS_VENDOR_SPEC = 0xff +}; + +/** \ingroup libusb_desc + * Descriptor types as defined by the USB specification. */ +enum libusb_descriptor_type { + /** Device descriptor. See libusb_device_descriptor. */ + LIBUSB_DT_DEVICE = 0x01, + + /** Configuration descriptor. See libusb_config_descriptor. */ + LIBUSB_DT_CONFIG = 0x02, + + /** String descriptor */ + LIBUSB_DT_STRING = 0x03, + + /** Interface descriptor. See libusb_interface_descriptor. */ + LIBUSB_DT_INTERFACE = 0x04, + + /** Endpoint descriptor. See libusb_endpoint_descriptor. */ + LIBUSB_DT_ENDPOINT = 0x05, + + /** Interface Association Descriptor. + * See libusb_interface_association_descriptor */ + LIBUSB_DT_INTERFACE_ASSOCIATION = 0x0b, + + /** BOS descriptor */ + LIBUSB_DT_BOS = 0x0f, + + /** Device Capability descriptor */ + LIBUSB_DT_DEVICE_CAPABILITY = 0x10, + + /** HID descriptor */ + LIBUSB_DT_HID = 0x21, + + /** HID report descriptor */ + LIBUSB_DT_REPORT = 0x22, + + /** Physical descriptor */ + LIBUSB_DT_PHYSICAL = 0x23, + + /** Hub descriptor */ + LIBUSB_DT_HUB = 0x29, + + /** SuperSpeed Hub descriptor */ + LIBUSB_DT_SUPERSPEED_HUB = 0x2a, + + /** SuperSpeed Endpoint Companion descriptor */ + LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30 +}; + +/* Descriptor sizes per descriptor type */ +#define LIBUSB_DT_DEVICE_SIZE 18 +#define LIBUSB_DT_CONFIG_SIZE 9 +#define LIBUSB_DT_INTERFACE_SIZE 9 +#define LIBUSB_DT_ENDPOINT_SIZE 7 +#define LIBUSB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ +#define LIBUSB_DT_HUB_NONVAR_SIZE 7 +#define LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE 6 +#define LIBUSB_DT_BOS_SIZE 5 +#define LIBUSB_DT_DEVICE_CAPABILITY_SIZE 3 +#define LIBUSB_DT_INTERFACE_ASSOCIATION_SIZE 8 + +/* BOS descriptor sizes */ +#define LIBUSB_BT_USB_2_0_EXTENSION_SIZE 7 +#define LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE 10 +#define LIBUSB_BT_SSPLUS_USB_DEVICE_CAPABILITY_SIZE 12 +#define LIBUSB_BT_CONTAINER_ID_SIZE 20 +#define LIBUSB_BT_PLATFORM_DESCRIPTOR_MIN_SIZE 20 + +/* We unwrap the BOS => define its max size */ +#define LIBUSB_DT_BOS_MAX_SIZE \ + (LIBUSB_DT_BOS_SIZE + \ + LIBUSB_BT_USB_2_0_EXTENSION_SIZE + \ + LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE + \ + LIBUSB_BT_CONTAINER_ID_SIZE) + +#define LIBUSB_ENDPOINT_ADDRESS_MASK 0x0f /* in bEndpointAddress */ +#define LIBUSB_ENDPOINT_DIR_MASK 0x80 + +/** \ingroup libusb_desc + * Endpoint direction. Values for bit 7 of the + * \ref libusb_endpoint_descriptor::bEndpointAddress "endpoint address" scheme. + */ +enum libusb_endpoint_direction { + /** Out: host-to-device */ + LIBUSB_ENDPOINT_OUT = 0x00, + + /** In: device-to-host */ + LIBUSB_ENDPOINT_IN = 0x80 +}; + +#define LIBUSB_TRANSFER_TYPE_MASK 0x03 /* in bmAttributes */ + +/** \ingroup libusb_desc + * Endpoint transfer type. Values for bits 0:1 of the + * \ref libusb_endpoint_descriptor::bmAttributes "endpoint attributes" field. + */ +enum libusb_endpoint_transfer_type { + /** Control endpoint */ + LIBUSB_ENDPOINT_TRANSFER_TYPE_CONTROL = 0x0, + + /** Isochronous endpoint */ + LIBUSB_ENDPOINT_TRANSFER_TYPE_ISOCHRONOUS = 0x1, + + /** Bulk endpoint */ + LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK = 0x2, + + /** Interrupt endpoint */ + LIBUSB_ENDPOINT_TRANSFER_TYPE_INTERRUPT = 0x3 +}; + +/** \ingroup libusb_misc + * Standard requests, as defined in table 9-5 of the USB 3.0 specifications */ +enum libusb_standard_request { + /** Request status of the specific recipient */ + LIBUSB_REQUEST_GET_STATUS = 0x00, + + /** Clear or disable a specific feature */ + LIBUSB_REQUEST_CLEAR_FEATURE = 0x01, + + /* 0x02 is reserved */ + + /** Set or enable a specific feature */ + LIBUSB_REQUEST_SET_FEATURE = 0x03, + + /* 0x04 is reserved */ + + /** Set device address for all future accesses */ + LIBUSB_REQUEST_SET_ADDRESS = 0x05, + + /** Get the specified descriptor */ + LIBUSB_REQUEST_GET_DESCRIPTOR = 0x06, + + /** Used to update existing descriptors or add new descriptors */ + LIBUSB_REQUEST_SET_DESCRIPTOR = 0x07, + + /** Get the current device configuration value */ + LIBUSB_REQUEST_GET_CONFIGURATION = 0x08, + + /** Set device configuration */ + LIBUSB_REQUEST_SET_CONFIGURATION = 0x09, + + /** Return the selected alternate setting for the specified interface */ + LIBUSB_REQUEST_GET_INTERFACE = 0x0a, + + /** Select an alternate interface for the specified interface */ + LIBUSB_REQUEST_SET_INTERFACE = 0x0b, + + /** Set then report an endpoint's synchronization frame */ + LIBUSB_REQUEST_SYNCH_FRAME = 0x0c, + + /** Sets both the U1 and U2 Exit Latency */ + LIBUSB_REQUEST_SET_SEL = 0x30, + + /** Delay from the time a host transmits a packet to the time it is + * received by the device. */ + LIBUSB_SET_ISOCH_DELAY = 0x31 +}; + +/** \ingroup libusb_misc + * Request type bits of the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control + * transfers. */ +enum libusb_request_type { + /** Standard */ + LIBUSB_REQUEST_TYPE_STANDARD = (0x00 << 5), + + /** Class */ + LIBUSB_REQUEST_TYPE_CLASS = (0x01 << 5), + + /** Vendor */ + LIBUSB_REQUEST_TYPE_VENDOR = (0x02 << 5), + + /** Reserved */ + LIBUSB_REQUEST_TYPE_RESERVED = (0x03 << 5) +}; + +/** \ingroup libusb_misc + * Recipient bits of the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control + * transfers. Values 4 through 31 are reserved. */ +enum libusb_request_recipient { + /** Device */ + LIBUSB_RECIPIENT_DEVICE = 0x00, + + /** Interface */ + LIBUSB_RECIPIENT_INTERFACE = 0x01, + + /** Endpoint */ + LIBUSB_RECIPIENT_ENDPOINT = 0x02, + + /** Other */ + LIBUSB_RECIPIENT_OTHER = 0x03 +}; + +#define LIBUSB_ISO_SYNC_TYPE_MASK 0x0c + +/** \ingroup libusb_desc + * Synchronization type for isochronous endpoints. Values for bits 2:3 of the + * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in + * libusb_endpoint_descriptor. + */ +enum libusb_iso_sync_type { + /** No synchronization */ + LIBUSB_ISO_SYNC_TYPE_NONE = 0x0, + + /** Asynchronous */ + LIBUSB_ISO_SYNC_TYPE_ASYNC = 0x1, + + /** Adaptive */ + LIBUSB_ISO_SYNC_TYPE_ADAPTIVE = 0x2, + + /** Synchronous */ + LIBUSB_ISO_SYNC_TYPE_SYNC = 0x3 +}; + +#define LIBUSB_ISO_USAGE_TYPE_MASK 0x30 + +/** \ingroup libusb_desc + * Usage type for isochronous endpoints. Values for bits 4:5 of the + * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in + * libusb_endpoint_descriptor. + */ +enum libusb_iso_usage_type { + /** Data endpoint */ + LIBUSB_ISO_USAGE_TYPE_DATA = 0x0, + + /** Feedback endpoint */ + LIBUSB_ISO_USAGE_TYPE_FEEDBACK = 0x1, + + /** Implicit feedback Data endpoint */ + LIBUSB_ISO_USAGE_TYPE_IMPLICIT = 0x2 +}; + +/** \ingroup libusb_desc + * Supported speeds (wSpeedSupported) bitfield. Indicates what + * speeds the device supports. + */ +enum libusb_supported_speed { + /** Low speed operation supported (1.5MBit/s). */ + LIBUSB_LOW_SPEED_OPERATION = (1 << 0), + + /** Full speed operation supported (12MBit/s). */ + LIBUSB_FULL_SPEED_OPERATION = (1 << 1), + + /** High speed operation supported (480MBit/s). */ + LIBUSB_HIGH_SPEED_OPERATION = (1 << 2), + + /** Superspeed operation supported (5000MBit/s). */ + LIBUSB_SUPER_SPEED_OPERATION = (1 << 3) +}; + +/** \ingroup libusb_desc + * Masks for the bits of the + * \ref libusb_usb_2_0_extension_descriptor::bmAttributes "bmAttributes" field + * of the USB 2.0 Extension descriptor. + */ +enum libusb_usb_2_0_extension_attributes { + /** Supports Link Power Management (LPM) */ + LIBUSB_BM_LPM_SUPPORT = (1 << 1) +}; + +/** \ingroup libusb_desc + * Masks for the bits of the + * \ref libusb_ss_usb_device_capability_descriptor::bmAttributes "bmAttributes" field + * field of the SuperSpeed USB Device Capability descriptor. + */ +enum libusb_ss_usb_device_capability_attributes { + /** Supports Latency Tolerance Messages (LTM) */ + LIBUSB_BM_LTM_SUPPORT = (1 << 1) +}; + +/** \ingroup libusb_desc + * USB capability types + */ +enum libusb_bos_type { + /** Wireless USB device capability */ + LIBUSB_BT_WIRELESS_USB_DEVICE_CAPABILITY = 0x01, + + /** USB 2.0 extensions */ + LIBUSB_BT_USB_2_0_EXTENSION = 0x02, + + /** SuperSpeed USB device capability */ + LIBUSB_BT_SS_USB_DEVICE_CAPABILITY = 0x03, + + /** Container ID type */ + LIBUSB_BT_CONTAINER_ID = 0x04, + + /** Platform descriptor */ + LIBUSB_BT_PLATFORM_DESCRIPTOR = 0x05, + + /** SuperSpeedPlus device capability */ + LIBUSB_BT_SUPERSPEED_PLUS_CAPABILITY = 0x0A, +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB device descriptor. This + * descriptor is documented in section 9.6.1 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_device_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE LIBUSB_DT_DEVICE in this + * context. */ + uint8_t bDescriptorType; + + /** USB specification release number in binary-coded decimal. A value of + * 0x0200 indicates USB 2.0, 0x0110 indicates USB 1.1, etc. */ + uint16_t bcdUSB; + + /** USB-IF class code for the device. See \ref libusb_class_code. */ + uint8_t bDeviceClass; + + /** USB-IF subclass code for the device, qualified by the bDeviceClass + * value */ + uint8_t bDeviceSubClass; + + /** USB-IF protocol code for the device, qualified by the bDeviceClass and + * bDeviceSubClass values */ + uint8_t bDeviceProtocol; + + /** Maximum packet size for endpoint 0 */ + uint8_t bMaxPacketSize0; + + /** USB-IF vendor ID */ + uint16_t idVendor; + + /** USB-IF product ID */ + uint16_t idProduct; + + /** Device release number in binary-coded decimal */ + uint16_t bcdDevice; + + /** Index of string descriptor describing manufacturer */ + uint8_t iManufacturer; + + /** Index of string descriptor describing product */ + uint8_t iProduct; + + /** Index of string descriptor containing device serial number */ + uint8_t iSerialNumber; + + /** Number of possible configurations */ + uint8_t bNumConfigurations; +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB endpoint descriptor. This + * descriptor is documented in section 9.6.6 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_endpoint_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_ENDPOINT LIBUSB_DT_ENDPOINT in + * this context. */ + uint8_t bDescriptorType; + + /** The address of the endpoint described by this descriptor. Bits 0:3 are + * the endpoint number. Bits 4:6 are reserved. Bit 7 indicates direction, + * see \ref libusb_endpoint_direction. */ + uint8_t bEndpointAddress; + + /** Attributes which apply to the endpoint when it is configured using + * the bConfigurationValue. Bits 0:1 determine the transfer type and + * correspond to \ref libusb_endpoint_transfer_type. Bits 2:3 are only used + * for isochronous endpoints and correspond to \ref libusb_iso_sync_type. + * Bits 4:5 are also only used for isochronous endpoints and correspond to + * \ref libusb_iso_usage_type. Bits 6:7 are reserved. */ + uint8_t bmAttributes; + + /** Maximum packet size this endpoint is capable of sending/receiving. */ + uint16_t wMaxPacketSize; + + /** Interval for polling endpoint for data transfers. */ + uint8_t bInterval; + + /** For audio devices only: the rate at which synchronization feedback + * is provided. */ + uint8_t bRefresh; + + /** For audio devices only: the address if the synch endpoint */ + uint8_t bSynchAddress; + + /** Extra descriptors. If libusb encounters unknown endpoint descriptors, + * it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. Must be non-negative. */ + int extra_length; +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB interface association descriptor. + * This descriptor is documented in section 9.6.4 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_interface_association_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_INTERFACE_ASSOCIATION + * LIBUSB_DT_INTERFACE_ASSOCIATION in this context. */ + uint8_t bDescriptorType; + + /** Interface number of the first interface that is associated + * with this function */ + uint8_t bFirstInterface; + + /** Number of contiguous interfaces that are associated with + * this function */ + uint8_t bInterfaceCount; + + /** USB-IF class code for this function. + * A value of zero is not allowed in this descriptor. + * If this field is 0xff, the function class is vendor-specific. + * All other values are reserved for assignment by the USB-IF. + */ + uint8_t bFunctionClass; + + /** USB-IF subclass code for this function. + * If this field is not set to 0xff, all values are reserved + * for assignment by the USB-IF + */ + uint8_t bFunctionSubClass; + + /** USB-IF protocol code for this function. + * These codes are qualified by the values of the bFunctionClass + * and bFunctionSubClass fields. + */ + uint8_t bFunctionProtocol; + + /** Index of string descriptor describing this function */ + uint8_t iFunction; +}; + +/** \ingroup libusb_desc + * Structure containing an array of 0 or more interface association + * descriptors + */ +struct libusb_interface_association_descriptor_array { + /** Array of interface association descriptors. The size of this array + * is determined by the length field. + */ + const struct libusb_interface_association_descriptor *iad; + + /** Number of interface association descriptors contained. Read-only. */ + int length; +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB interface descriptor. This + * descriptor is documented in section 9.6.5 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_interface_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_INTERFACE LIBUSB_DT_INTERFACE + * in this context. */ + uint8_t bDescriptorType; + + /** Number of this interface */ + uint8_t bInterfaceNumber; + + /** Value used to select this alternate setting for this interface */ + uint8_t bAlternateSetting; + + /** Number of endpoints used by this interface (excluding the control + * endpoint). */ + uint8_t bNumEndpoints; + + /** USB-IF class code for this interface. See \ref libusb_class_code. */ + uint8_t bInterfaceClass; + + /** USB-IF subclass code for this interface, qualified by the + * bInterfaceClass value */ + uint8_t bInterfaceSubClass; + + /** USB-IF protocol code for this interface, qualified by the + * bInterfaceClass and bInterfaceSubClass values */ + uint8_t bInterfaceProtocol; + + /** Index of string descriptor describing this interface */ + uint8_t iInterface; + + /** Array of endpoint descriptors. This length of this array is determined + * by the bNumEndpoints field. */ + const struct libusb_endpoint_descriptor *endpoint; + + /** Extra descriptors. If libusb encounters unknown interface descriptors, + * it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. Must be non-negative. */ + int extra_length; +}; + +/** \ingroup libusb_desc + * A collection of alternate settings for a particular USB interface. + */ +struct libusb_interface { + /** Array of interface descriptors. The length of this array is determined + * by the num_altsetting field. */ + const struct libusb_interface_descriptor *altsetting; + + /** The number of alternate settings that belong to this interface. + * Must be non-negative. */ + int num_altsetting; +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB configuration descriptor. This + * descriptor is documented in section 9.6.3 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_config_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_CONFIG LIBUSB_DT_CONFIG + * in this context. */ + uint8_t bDescriptorType; + + /** Total length of data returned for this configuration */ + uint16_t wTotalLength; + + /** Number of interfaces supported by this configuration */ + uint8_t bNumInterfaces; + + /** Identifier value for this configuration */ + uint8_t bConfigurationValue; + + /** Index of string descriptor describing this configuration */ + uint8_t iConfiguration; + + /** Configuration characteristics */ + uint8_t bmAttributes; + + /** Maximum power consumption of the USB device from this bus in this + * configuration when the device is fully operation. Expressed in units + * of 2 mA when the device is operating in high-speed mode and in units + * of 8 mA when the device is operating in super-speed mode. */ + uint8_t MaxPower; + + /** Array of interfaces supported by this configuration. The length of + * this array is determined by the bNumInterfaces field. */ + const struct libusb_interface *interface; + + /** Extra descriptors. If libusb encounters unknown configuration + * descriptors, it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. Must be non-negative. */ + int extra_length; +}; + +/** \ingroup libusb_desc + * A structure representing the superspeed endpoint companion + * descriptor. This descriptor is documented in section 9.6.7 of + * the USB 3.0 specification. All multiple-byte fields are represented in + * host-endian format. + */ +struct libusb_ss_endpoint_companion_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_SS_ENDPOINT_COMPANION in + * this context. */ + uint8_t bDescriptorType; + + /** The maximum number of packets the endpoint can send or + * receive as part of a burst. */ + uint8_t bMaxBurst; + + /** In bulk EP: bits 4:0 represents the maximum number of + * streams the EP supports. In isochronous EP: bits 1:0 + * represents the Mult - a zero based value that determines + * the maximum number of packets within a service interval */ + uint8_t bmAttributes; + + /** The total number of bytes this EP will transfer every + * service interval. Valid only for periodic EPs. */ + uint16_t wBytesPerInterval; +}; + +/** \ingroup libusb_desc + * A generic representation of a BOS Device Capability descriptor. It is + * advised to check bDevCapabilityType and call the matching + * libusb_get_*_descriptor function to get a structure fully matching the type. + */ +struct libusb_bos_dev_capability_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Device Capability type */ + uint8_t bDevCapabilityType; + + /** Device Capability data (bLength - 3 bytes) */ + uint8_t dev_capability_data[LIBUSB_FLEXIBLE_ARRAY]; +}; + +/** \ingroup libusb_desc + * A structure representing the Binary Device Object Store (BOS) descriptor. + * This descriptor is documented in section 9.6.2 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_bos_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_BOS LIBUSB_DT_BOS + * in this context. */ + uint8_t bDescriptorType; + + /** Length of this descriptor and all of its sub descriptors */ + uint16_t wTotalLength; + + /** The number of separate device capability descriptors in + * the BOS */ + uint8_t bNumDeviceCaps; + + /** bNumDeviceCap Device Capability Descriptors */ + struct libusb_bos_dev_capability_descriptor *dev_capability[LIBUSB_FLEXIBLE_ARRAY]; +}; + +/** \ingroup libusb_desc + * A structure representing the USB 2.0 Extension descriptor + * This descriptor is documented in section 9.6.2.1 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_usb_2_0_extension_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_bos_type::LIBUSB_BT_USB_2_0_EXTENSION + * LIBUSB_BT_USB_2_0_EXTENSION in this context. */ + uint8_t bDevCapabilityType; + + /** Bitmap encoding of supported device level features. + * A value of one in a bit location indicates a feature is + * supported; a value of zero indicates it is not supported. + * See \ref libusb_usb_2_0_extension_attributes. */ + uint32_t bmAttributes; +}; + +/** \ingroup libusb_desc + * A structure representing the SuperSpeed USB Device Capability descriptor + * This descriptor is documented in section 9.6.2.2 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_ss_usb_device_capability_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_bos_type::LIBUSB_BT_SS_USB_DEVICE_CAPABILITY + * LIBUSB_BT_SS_USB_DEVICE_CAPABILITY in this context. */ + uint8_t bDevCapabilityType; + + /** Bitmap encoding of supported device level features. + * A value of one in a bit location indicates a feature is + * supported; a value of zero indicates it is not supported. + * See \ref libusb_ss_usb_device_capability_attributes. */ + uint8_t bmAttributes; + + /** Bitmap encoding of the speed supported by this device when + * operating in SuperSpeed mode. See \ref libusb_supported_speed. */ + uint16_t wSpeedSupported; + + /** The lowest speed at which all the functionality supported + * by the device is available to the user. For example if the + * device supports all its functionality when connected at + * full speed and above then it sets this value to 1. */ + uint8_t bFunctionalitySupport; + + /** U1 Device Exit Latency. */ + uint8_t bU1DevExitLat; + + /** U2 Device Exit Latency. */ + uint16_t bU2DevExitLat; +}; + +/** \ingroup libusb_desc + * enum used in \ref libusb_ssplus_sublink_attribute + */ +enum libusb_superspeedplus_sublink_attribute_sublink_type { + LIBUSB_SSPLUS_ATTR_TYPE_SYM = 0, + LIBUSB_SSPLUS_ATTR_TYPE_ASYM = 1, +}; + +/** \ingroup libusb_desc + * enum used in \ref libusb_ssplus_sublink_attribute + */ +enum libusb_superspeedplus_sublink_attribute_sublink_direction { + LIBUSB_SSPLUS_ATTR_DIR_RX = 0, + LIBUSB_SSPLUS_ATTR_DIR_TX = 1, +}; + +/** \ingroup libusb_desc + * enum used in \ref libusb_ssplus_sublink_attribute + * Bit = Bits per second + * Kb = Kbps + * Mb = Mbps + * Gb = Gbps + */ +enum libusb_superspeedplus_sublink_attribute_exponent { + LIBUSB_SSPLUS_ATTR_EXP_BPS = 0, + LIBUSB_SSPLUS_ATTR_EXP_KBS = 1, + LIBUSB_SSPLUS_ATTR_EXP_MBS = 2, + LIBUSB_SSPLUS_ATTR_EXP_GBS = 3, +}; + +/** \ingroup libusb_desc + * enum used in \ref libusb_ssplus_sublink_attribute + */ +enum libusb_superspeedplus_sublink_attribute_link_protocol { + LIBUSB_SSPLUS_ATTR_PROT_SS = 0, + LIBUSB_SSPLUS_ATTR_PROT_SSPLUS = 1, +}; + +/** \ingroup libusb_desc + * Expose \ref libusb_ssplus_usb_device_capability_descriptor.sublinkSpeedAttributes + */ +struct libusb_ssplus_sublink_attribute { + /** Sublink Speed Attribute ID (SSID). + This field is an ID that uniquely identifies the speed of this sublink */ + uint8_t ssid; + + /** This field defines the + base 10 exponent times 3, that shall be applied to the + mantissa. */ + enum libusb_superspeedplus_sublink_attribute_exponent exponent; + + /** This field identifies whether the + Sublink Speed Attribute defines a symmetric or + asymmetric bit rate.*/ + enum libusb_superspeedplus_sublink_attribute_sublink_type type; + + /** This field indicates if this + Sublink Speed Attribute defines the receive or + transmit bit rate. */ + enum libusb_superspeedplus_sublink_attribute_sublink_direction direction; + + /** This field identifies the protocol + supported by the link. */ + enum libusb_superspeedplus_sublink_attribute_link_protocol protocol; + + /** This field defines the mantissa that shall be applied to the exponent when + calculating the maximum bit rate. */ + uint16_t mantissa; +}; + +/** \ingroup libusb_desc + * A structure representing the SuperSpeedPlus descriptor + * This descriptor is documented in section 9.6.2.5 of the USB 3.1 specification. + */ +struct libusb_ssplus_usb_device_capability_descriptor { + /** Sublink Speed Attribute Count */ + uint8_t numSublinkSpeedAttributes; + + /** Sublink Speed ID Count */ + uint8_t numSublinkSpeedIDs; + + /** Unique ID to indicates the minimum lane speed */ + uint8_t ssid; + + /** This field indicates the minimum receive lane count.*/ + uint8_t minRxLaneCount; + + /** This field indicates the minimum transmit lane count*/ + uint8_t minTxLaneCount; + + /** Array size is \ref libusb_ssplus_usb_device_capability_descriptor.numSublinkSpeedAttributes */ + struct libusb_ssplus_sublink_attribute sublinkSpeedAttributes[]; +}; + +/** \ingroup libusb_desc + * A structure representing the Container ID descriptor. + * This descriptor is documented in section 9.6.2.3 of the USB 3.0 specification. + * All multiple-byte fields, except UUIDs, are represented in host-endian format. + */ +struct libusb_container_id_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_bos_type::LIBUSB_BT_CONTAINER_ID + * LIBUSB_BT_CONTAINER_ID in this context. */ + uint8_t bDevCapabilityType; + + /** Reserved field */ + uint8_t bReserved; + + /** 128 bit UUID */ + uint8_t ContainerID[16]; +}; + +/** \ingroup libusb_desc + * A structure representing a Platform descriptor. + * This descriptor is documented in section 9.6.2.4 of the USB 3.2 specification. + */ +struct libusb_platform_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_bos_type::LIBUSB_BT_PLATFORM_DESCRIPTOR + * LIBUSB_BT_CONTAINER_ID in this context. */ + uint8_t bDevCapabilityType; + + /** Reserved field */ + uint8_t bReserved; + + /** 128 bit UUID */ + uint8_t PlatformCapabilityUUID[16]; + + /** Capability data (bLength - 20) */ + uint8_t CapabilityData[LIBUSB_FLEXIBLE_ARRAY]; +}; + +/** \ingroup libusb_asyncio + * Setup packet for control transfers. */ +#if defined(_MSC_VER) || defined(__WATCOMC__) +#pragma pack(push, 1) +#endif +struct libusb_control_setup { + /** Request type. Bits 0:4 determine recipient, see + * \ref libusb_request_recipient. Bits 5:6 determine type, see + * \ref libusb_request_type. Bit 7 determines data transfer direction, see + * \ref libusb_endpoint_direction. + */ + uint8_t bmRequestType; + + /** Request. If the type bits of bmRequestType are equal to + * \ref libusb_request_type::LIBUSB_REQUEST_TYPE_STANDARD + * "LIBUSB_REQUEST_TYPE_STANDARD" then this field refers to + * \ref libusb_standard_request. For other cases, use of this field is + * application-specific. */ + uint8_t bRequest; + + /** Value. Varies according to request */ + uint16_t wValue; + + /** Index. Varies according to request, typically used to pass an index + * or offset */ + uint16_t wIndex; + + /** Number of bytes to transfer */ + uint16_t wLength; +} LIBUSB_PACKED; +#if defined(_MSC_VER) || defined(__WATCOMC__) +#pragma pack(pop) +#endif + +#define LIBUSB_CONTROL_SETUP_SIZE (sizeof(struct libusb_control_setup)) + +/* libusb */ + +struct libusb_context; +struct libusb_device; +struct libusb_device_handle; + +/** \ingroup libusb_lib + * Structure providing the version of the libusb runtime + */ +struct libusb_version { + /** Library major version. */ + uint16_t major; + + /** Library minor version. */ + uint16_t minor; + + /** Library micro version. */ + uint16_t micro; + + /** Library nano version. */ + uint16_t nano; + + /** Library release candidate suffix string, e.g. "-rc4". */ + const char *rc; + + /** For ABI compatibility only. */ + const char *describe; +}; + +/** \ingroup libusb_lib + * Structure representing a libusb session. The concept of individual libusb + * sessions allows for your program to use two libraries (or dynamically + * load two modules) which both independently use libusb. This will prevent + * interference between the individual libusb users - for example + * libusb_set_option() will not affect the other user of the library, and + * libusb_exit() will not destroy resources that the other user is still + * using. + * + * Sessions are created by libusb_init_context() and destroyed through libusb_exit(). + * If your application is guaranteed to only ever include a single libusb + * user (i.e. you), you do not have to worry about contexts: pass NULL in + * every function call where a context is required, and the default context + * will be used. Note that libusb_set_option(NULL, ...) is special, and adds + * an option to a list of default options for new contexts. + * + * For more information, see \ref libusb_contexts. + */ +typedef struct libusb_context libusb_context; + +/** \ingroup libusb_dev + * Structure representing a USB device detected on the system. This is an + * opaque type for which you are only ever provided with a pointer, usually + * originating from libusb_get_device_list() or libusb_hotplug_register_callback(). + * + * Certain operations can be performed on a device, but in order to do any + * I/O you will have to first obtain a device handle using libusb_open(). + * + * Devices are reference counted with libusb_ref_device() and + * libusb_unref_device(), and are freed when the reference count reaches 0. + * New devices presented by libusb_get_device_list() have a reference count of + * 1, and libusb_free_device_list() can optionally decrease the reference count + * on all devices in the list. libusb_open() adds another reference which is + * later destroyed by libusb_close(). + */ +typedef struct libusb_device libusb_device; + + +/** \ingroup libusb_dev + * Structure representing a handle on a USB device. This is an opaque type for + * which you are only ever provided with a pointer, usually originating from + * libusb_open(). + * + * A device handle is used to perform I/O and other operations. When finished + * with a device handle, you should call libusb_close(). + */ +typedef struct libusb_device_handle libusb_device_handle; + +/** \ingroup libusb_dev + * Speed codes. Indicates the speed at which the device is operating. + */ +enum libusb_speed { + /** The OS doesn't report or know the device speed. */ + LIBUSB_SPEED_UNKNOWN = 0, + + /** The device is operating at low speed (1.5MBit/s). */ + LIBUSB_SPEED_LOW = 1, + + /** The device is operating at full speed (12MBit/s). */ + LIBUSB_SPEED_FULL = 2, + + /** The device is operating at high speed (480MBit/s). */ + LIBUSB_SPEED_HIGH = 3, + + /** The device is operating at super speed (5000MBit/s). */ + LIBUSB_SPEED_SUPER = 4, + + /** The device is operating at super speed plus (10000MBit/s). */ + LIBUSB_SPEED_SUPER_PLUS = 5, + + /** The device is operating at super speed plus x2 (20000MBit/s). */ + LIBUSB_SPEED_SUPER_PLUS_X2 = 6, +}; + +/** \ingroup libusb_misc + * Error codes. Most libusb functions return 0 on success or one of these + * codes on failure. + * You can call libusb_error_name() to retrieve a string representation of an + * error code or libusb_strerror() to get an end-user suitable description of + * an error code. + */ +enum libusb_error { + /** Success (no error) */ + LIBUSB_SUCCESS = 0, + + /** Input/output error */ + LIBUSB_ERROR_IO = -1, + + /** Invalid parameter */ + LIBUSB_ERROR_INVALID_PARAM = -2, + + /** Access denied (insufficient permissions) */ + LIBUSB_ERROR_ACCESS = -3, + + /** No such device (it may have been disconnected) */ + LIBUSB_ERROR_NO_DEVICE = -4, + + /** Entity not found */ + LIBUSB_ERROR_NOT_FOUND = -5, + + /** Resource busy */ + LIBUSB_ERROR_BUSY = -6, + + /** Operation timed out */ + LIBUSB_ERROR_TIMEOUT = -7, + + /** Overflow */ + LIBUSB_ERROR_OVERFLOW = -8, + + /** Pipe error */ + LIBUSB_ERROR_PIPE = -9, + + /** System call interrupted (perhaps due to signal) */ + LIBUSB_ERROR_INTERRUPTED = -10, + + /** Insufficient memory */ + LIBUSB_ERROR_NO_MEM = -11, + + /** Operation not supported or unimplemented on this platform */ + LIBUSB_ERROR_NOT_SUPPORTED = -12, + + /* NB: Remember to update LIBUSB_ERROR_COUNT below as well as the + message strings in strerror.c when adding new error codes here. */ + + /** Other error */ + LIBUSB_ERROR_OTHER = -99 +}; + +/* Total number of error codes in enum libusb_error */ +#define LIBUSB_ERROR_COUNT 14 + +/** \ingroup libusb_asyncio + * Transfer type */ +enum libusb_transfer_type { + /** Control transfer */ + LIBUSB_TRANSFER_TYPE_CONTROL = 0U, + + /** Isochronous transfer */ + LIBUSB_TRANSFER_TYPE_ISOCHRONOUS = 1U, + + /** Bulk transfer */ + LIBUSB_TRANSFER_TYPE_BULK = 2U, + + /** Interrupt transfer */ + LIBUSB_TRANSFER_TYPE_INTERRUPT = 3U, + + /** Bulk stream transfer */ + LIBUSB_TRANSFER_TYPE_BULK_STREAM = 4U +}; + +/** \ingroup libusb_asyncio + * Transfer status codes */ +enum libusb_transfer_status { + /** Transfer completed without error. Note that this does not indicate + * that the entire amount of requested data was transferred. */ + LIBUSB_TRANSFER_COMPLETED, + + /** Transfer failed */ + LIBUSB_TRANSFER_ERROR, + + /** Transfer timed out */ + LIBUSB_TRANSFER_TIMED_OUT, + + /** Transfer was cancelled */ + LIBUSB_TRANSFER_CANCELLED, + + /** For bulk/interrupt endpoints: halt condition detected (endpoint + * stalled). For control endpoints: control request not supported. */ + LIBUSB_TRANSFER_STALL, + + /** Device was disconnected */ + LIBUSB_TRANSFER_NO_DEVICE, + + /** Device sent more data than requested */ + LIBUSB_TRANSFER_OVERFLOW + + /* NB! Remember to update libusb_error_name() + when adding new status codes here. */ +}; + +/** \ingroup libusb_asyncio + * libusb_transfer.flags values */ +enum libusb_transfer_flags { + /** Report short frames as errors */ + LIBUSB_TRANSFER_SHORT_NOT_OK = (1U << 0), + + /** Automatically free() transfer buffer during libusb_free_transfer(). + * Note that buffers allocated with libusb_dev_mem_alloc() should not + * be attempted freed in this way, since free() is not an appropriate + * way to release such memory. */ + LIBUSB_TRANSFER_FREE_BUFFER = (1U << 1), + + /** Automatically call libusb_free_transfer() after callback returns. + * If this flag is set, it is illegal to call libusb_free_transfer() + * from your transfer callback, as this will result in a double-free + * when this flag is acted upon. */ + LIBUSB_TRANSFER_FREE_TRANSFER = (1U << 2), + + /** Terminate transfers that are a multiple of the endpoint's + * wMaxPacketSize with an extra zero length packet. This is useful + * when a device protocol mandates that each logical request is + * terminated by an incomplete packet (i.e. the logical requests are + * not separated by other means). + * + * This flag only affects host-to-device transfers to bulk and interrupt + * endpoints. In other situations, it is ignored. + * + * This flag only affects transfers with a length that is a multiple of + * the endpoint's wMaxPacketSize. On transfers of other lengths, this + * flag has no effect. Therefore, if you are working with a device that + * needs a ZLP whenever the end of the logical request falls on a packet + * boundary, then it is sensible to set this flag on every + * transfer (you do not have to worry about only setting it on transfers + * that end on the boundary). + * + * This flag is currently only supported on Linux. + * On other systems, libusb_submit_transfer() will return + * \ref LIBUSB_ERROR_NOT_SUPPORTED for every transfer where this + * flag is set. + * + * Available since libusb-1.0.9. + */ + LIBUSB_TRANSFER_ADD_ZERO_PACKET = (1U << 3) +}; + +/** \ingroup libusb_asyncio + * Isochronous packet descriptor. */ +struct libusb_iso_packet_descriptor { + /** Length of data to request in this packet */ + unsigned int length; + + /** Amount of data that was actually transferred */ + unsigned int actual_length; + + /** Status code for this packet */ + enum libusb_transfer_status status; +}; + +struct libusb_transfer; + +/** \ingroup libusb_asyncio + * Asynchronous transfer callback function type. When submitting asynchronous + * transfers, you pass a pointer to a callback function of this type via the + * \ref libusb_transfer::callback "callback" member of the libusb_transfer + * structure. libusb will call this function later, when the transfer has + * completed or failed. See \ref libusb_asyncio for more information. + * \param transfer The libusb_transfer struct the callback function is being + * notified about. + */ +typedef void (LIBUSB_CALL *libusb_transfer_cb_fn)(struct libusb_transfer *transfer); + +/** \ingroup libusb_asyncio + * The generic USB transfer structure. The user populates this structure and + * then submits it in order to request a transfer. After the transfer has + * completed, the library populates the transfer with the results and passes + * it back to the user. + */ +struct libusb_transfer { + /** Handle of the device that this transfer will be submitted to */ + libusb_device_handle *dev_handle; + + /** A bitwise OR combination of \ref libusb_transfer_flags. */ + uint8_t flags; + + /** Address of the endpoint where this transfer will be sent. */ + unsigned char endpoint; + + /** Type of the transfer from \ref libusb_transfer_type */ + unsigned char type; + + /** Timeout for this transfer in milliseconds. A value of 0 indicates no + * timeout. */ + unsigned int timeout; + + /** The status of the transfer. Read-only, and only for use within + * transfer callback function. + * + * If this is an isochronous transfer, this field may read COMPLETED even + * if there were errors in the frames. Use the + * \ref libusb_iso_packet_descriptor::status "status" field in each packet + * to determine if errors occurred. */ + enum libusb_transfer_status status; + + /** Length of the data buffer. Must be non-negative. */ + int length; + + /** Actual length of data that was transferred. Read-only, and only for + * use within transfer callback function. Not valid for isochronous + * endpoint transfers. */ + int actual_length; + + /** Callback function. This will be invoked when the transfer completes, + * fails, or is cancelled. */ + libusb_transfer_cb_fn callback; + + /** User context data. Useful for associating specific data to a transfer + * that can be accessed from within the callback function. + * + * This field may be set manually or is taken as the `user_data` parameter + * of the following functions: + * - libusb_fill_bulk_transfer() + * - libusb_fill_bulk_stream_transfer() + * - libusb_fill_control_transfer() + * - libusb_fill_interrupt_transfer() + * - libusb_fill_iso_transfer() */ + void *user_data; + + /** Data buffer */ + unsigned char *buffer; + + /** Number of isochronous packets. Only used for I/O with isochronous + * endpoints. Must be non-negative. */ + int num_iso_packets; + + /** Isochronous packet descriptors, for isochronous transfers only. */ + struct libusb_iso_packet_descriptor iso_packet_desc[LIBUSB_FLEXIBLE_ARRAY]; +}; + +/** \ingroup libusb_misc + * Capabilities supported by an instance of libusb on the current running + * platform. Test if the loaded library supports a given capability by calling + * \ref libusb_has_capability(). + */ +enum libusb_capability { + /** The libusb_has_capability() API is available. */ + LIBUSB_CAP_HAS_CAPABILITY = 0x0000U, + + /** Hotplug support is available on this platform. */ + LIBUSB_CAP_HAS_HOTPLUG = 0x0001U, + + /** The library can access HID devices without requiring user intervention. + * Note that before being able to actually access an HID device, you may + * still have to call additional libusb functions such as + * \ref libusb_detach_kernel_driver(). */ + LIBUSB_CAP_HAS_HID_ACCESS = 0x0100U, + + /** The library supports detaching of the default USB driver, using + * \ref libusb_detach_kernel_driver(), if one is set by the OS kernel */ + LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER = 0x0101U +}; + +/** \ingroup libusb_lib + * Log message levels. + */ +enum libusb_log_level { + /** (0) : No messages ever emitted by the library (default) */ + LIBUSB_LOG_LEVEL_NONE = 0, + + /** (1) : Error messages are emitted */ + LIBUSB_LOG_LEVEL_ERROR = 1, + + /** (2) : Warning and error messages are emitted */ + LIBUSB_LOG_LEVEL_WARNING = 2, + + /** (3) : Informational, warning and error messages are emitted */ + LIBUSB_LOG_LEVEL_INFO = 3, + + /** (4) : All messages are emitted */ + LIBUSB_LOG_LEVEL_DEBUG = 4 +}; + +/** \ingroup libusb_lib + * Log callback mode. + * + * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107 + * + * \see libusb_set_log_cb() + */ +enum libusb_log_cb_mode { + /** Callback function handling all log messages. */ + LIBUSB_LOG_CB_GLOBAL = (1 << 0), + + /** Callback function handling context related log messages. */ + LIBUSB_LOG_CB_CONTEXT = (1 << 1) +}; + +/** \ingroup libusb_lib + * Available option values for libusb_set_option() and libusb_init_context(). + */ +enum libusb_option { + /** Set the log message verbosity. + * + * This option must be provided an argument of type \ref libusb_log_level. + * The default level is LIBUSB_LOG_LEVEL_NONE, which means no messages are ever + * printed. If you choose to increase the message verbosity level, ensure + * that your application does not close the stderr file descriptor. + * + * You are advised to use level LIBUSB_LOG_LEVEL_WARNING. libusb is conservative + * with its message logging and most of the time, will only log messages that + * explain error conditions and other oddities. This will help you debug + * your software. + * + * If the LIBUSB_DEBUG environment variable was set when libusb was + * initialized, this option does nothing: the message verbosity is fixed + * to the value in the environment variable. + * + * If libusb was compiled without any message logging, this option does + * nothing: you'll never get any messages. + * + * If libusb was compiled with verbose debug message logging, this option + * does nothing: you'll always get messages from all levels. + */ + LIBUSB_OPTION_LOG_LEVEL = 0, + + /** Use the UsbDk backend for a specific context, if available. + * + * This option should be set at initialization with libusb_init_context() + * otherwise unspecified behavior may occur. + * + * Only valid on Windows. Ignored on all other platforms. + */ + LIBUSB_OPTION_USE_USBDK = 1, + + /** Do not scan for devices + * + * With this option set, libusb will skip scanning devices in + * libusb_init_context(). + * + * Hotplug functionality will also be deactivated. + * + * The option is useful in combination with libusb_wrap_sys_device(), + * which can access a device directly without prior device scanning. + * + * This is typically needed on Android, where access to USB devices + * is limited. + * + * This option should only be used with libusb_init_context() + * otherwise unspecified behavior may occur. + * + * Only valid on Linux. Ignored on all other platforms. + */ + LIBUSB_OPTION_NO_DEVICE_DISCOVERY = 2, + +#define LIBUSB_OPTION_WEAK_AUTHORITY LIBUSB_OPTION_NO_DEVICE_DISCOVERY + + /** Set the context log callback function. + * + * Set the log callback function either on a context or globally. This + * option must be provided an argument of type \ref libusb_log_cb. + * Using this option with a NULL context is equivalent to calling + * libusb_set_log_cb() with mode \ref LIBUSB_LOG_CB_GLOBAL. + * Using it with a non-NULL context is equivalent to calling + * libusb_set_log_cb() with mode \ref LIBUSB_LOG_CB_CONTEXT. + */ + LIBUSB_OPTION_LOG_CB = 3, + + LIBUSB_OPTION_MAX = 4 +}; + +/** \ingroup libusb_desc + * The device string type. + */ +enum libusb_device_string_type { + LIBUSB_DEVICE_STRING_MANUFACTURER, + LIBUSB_DEVICE_STRING_PRODUCT, + LIBUSB_DEVICE_STRING_SERIAL_NUMBER, + LIBUSB_DEVICE_STRING_COUNT /* The total number of string types. */ +}; + +/** \ingroup libusb_desc + * The maximum length for a device string descriptor in UTF-8. + * + * 255 max descriptor length with 2 byte header + * => 253 bytes UTF-16LE, no null termination (USB 2.0 9.6.7) + * => 126.5 codepoints + * => 126 * 3 + 1 + * => 382 bytes + * + * Stay with 256 * 2/3 = 384 to be safe. + */ +#define LIBUSB_DEVICE_STRING_BYTES_MAX (384U) + +/** \ingroup libusb_lib + * Callback function for handling log messages. + * \param ctx the context which is related to the log message, or NULL if it + * is a global log message + * \param level the log level, see \ref libusb_log_level for a description + * \param str the log message + * + * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107 + * + * \see libusb_set_log_cb() + */ +typedef void (LIBUSB_CALL *libusb_log_cb)(libusb_context *ctx, + enum libusb_log_level level, const char *str); + +/** \ingroup libusb_lib + * Structure used for setting options through \ref libusb_init_context. + * + */ +struct libusb_init_option { + /** Which option to set */ + enum libusb_option option; + /** An integer value used by the option (if applicable). */ + union { + int ival; + libusb_log_cb log_cbval; + } value; +}; + +int LIBUSB_CALL libusb_init(libusb_context **ctx); +int LIBUSB_CALL libusb_init_context(libusb_context **ctx, const struct libusb_init_option options[], int num_options); +void LIBUSB_CALL libusb_exit(libusb_context *ctx); +void LIBUSB_CALL libusb_set_debug(libusb_context *ctx, int level); +/* may be deprecated in the future in favor of lubusb_init_context()+libusb_set_option() */ +void LIBUSB_CALL libusb_set_log_cb(libusb_context *ctx, libusb_log_cb cb, int mode); +const struct libusb_version * LIBUSB_CALL libusb_get_version(void); +int LIBUSB_CALL libusb_has_capability(uint32_t capability); +const char * LIBUSB_CALL libusb_error_name(int error_code); +int LIBUSB_CALL libusb_setlocale(const char *locale); +const char * LIBUSB_CALL libusb_strerror(int errcode); + +ssize_t LIBUSB_CALL libusb_get_device_list(libusb_context *ctx, + libusb_device ***list); +void LIBUSB_CALL libusb_free_device_list(libusb_device **list, + int unref_devices); +libusb_device * LIBUSB_CALL libusb_ref_device(libusb_device *dev); +void LIBUSB_CALL libusb_unref_device(libusb_device *dev); + +int LIBUSB_CALL libusb_get_device_string(libusb_device *dev, + enum libusb_device_string_type string_type, char *data, int length); +int LIBUSB_CALL libusb_get_configuration(libusb_device_handle *dev, + int *config); +int LIBUSB_CALL libusb_get_device_descriptor(libusb_device *dev, + struct libusb_device_descriptor *desc); +int LIBUSB_CALL libusb_get_active_config_descriptor(libusb_device *dev, + struct libusb_config_descriptor **config); +int LIBUSB_CALL libusb_get_config_descriptor(libusb_device *dev, + uint8_t config_index, struct libusb_config_descriptor **config); +int LIBUSB_CALL libusb_get_config_descriptor_by_value(libusb_device *dev, + uint8_t bConfigurationValue, struct libusb_config_descriptor **config); +void LIBUSB_CALL libusb_free_config_descriptor( + struct libusb_config_descriptor *config); +int LIBUSB_CALL libusb_get_ss_endpoint_companion_descriptor( + libusb_context *ctx, + const struct libusb_endpoint_descriptor *endpoint, + struct libusb_ss_endpoint_companion_descriptor **ep_comp); +void LIBUSB_CALL libusb_free_ss_endpoint_companion_descriptor( + struct libusb_ss_endpoint_companion_descriptor *ep_comp); +int LIBUSB_CALL libusb_get_bos_descriptor(libusb_device_handle *dev_handle, + struct libusb_bos_descriptor **bos); +void LIBUSB_CALL libusb_free_bos_descriptor(struct libusb_bos_descriptor *bos); +int LIBUSB_CALL libusb_get_usb_2_0_extension_descriptor( + libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_usb_2_0_extension_descriptor **usb_2_0_extension); +void LIBUSB_CALL libusb_free_usb_2_0_extension_descriptor( + struct libusb_usb_2_0_extension_descriptor *usb_2_0_extension); +int LIBUSB_CALL libusb_get_ss_usb_device_capability_descriptor( + libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_ss_usb_device_capability_descriptor **ss_usb_device_cap); +void LIBUSB_CALL libusb_free_ss_usb_device_capability_descriptor( + struct libusb_ss_usb_device_capability_descriptor *ss_usb_device_cap); +int LIBUSB_CALL libusb_get_ssplus_usb_device_capability_descriptor( + libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_ssplus_usb_device_capability_descriptor **ssplus_usb_device_cap); +void LIBUSB_CALL libusb_free_ssplus_usb_device_capability_descriptor( + struct libusb_ssplus_usb_device_capability_descriptor *ssplus_usb_device_cap); +int LIBUSB_CALL libusb_get_container_id_descriptor(libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_container_id_descriptor **container_id); +void LIBUSB_CALL libusb_free_container_id_descriptor( + struct libusb_container_id_descriptor *container_id); +int LIBUSB_CALL libusb_get_platform_descriptor(libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_platform_descriptor **platform_descriptor); +void LIBUSB_CALL libusb_free_platform_descriptor( + struct libusb_platform_descriptor *platform_descriptor); +unsigned long LIBUSB_CALL libusb_get_session_data(libusb_device *dev); +uint8_t LIBUSB_CALL libusb_get_bus_number(libusb_device *dev); +uint8_t LIBUSB_CALL libusb_get_port_number(libusb_device *dev); +int LIBUSB_CALL libusb_get_port_numbers(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len); +LIBUSB_DEPRECATED_FOR(libusb_get_port_numbers) +int LIBUSB_CALL libusb_get_port_path(libusb_context *ctx, libusb_device *dev, uint8_t *path, uint8_t path_length); +libusb_device * LIBUSB_CALL libusb_get_parent(libusb_device *dev); +uint8_t LIBUSB_CALL libusb_get_device_address(libusb_device *dev); +int LIBUSB_CALL libusb_get_device_speed(libusb_device *dev); +int LIBUSB_CALL libusb_get_max_packet_size(libusb_device *dev, + unsigned char endpoint); +int LIBUSB_CALL libusb_get_max_iso_packet_size(libusb_device *dev, + unsigned char endpoint); +int LIBUSB_CALL libusb_get_max_alt_packet_size(libusb_device *dev, + int interface_number, int alternate_setting, unsigned char endpoint); + +int LIBUSB_CALL libusb_get_interface_association_descriptors(libusb_device *dev, + uint8_t config_index, struct libusb_interface_association_descriptor_array **iad_array); +int LIBUSB_CALL libusb_get_active_interface_association_descriptors(libusb_device *dev, + struct libusb_interface_association_descriptor_array **iad_array); +void LIBUSB_CALL libusb_free_interface_association_descriptors( + struct libusb_interface_association_descriptor_array *iad_array); + +int LIBUSB_CALL libusb_wrap_sys_device(libusb_context *ctx, intptr_t sys_dev, libusb_device_handle **dev_handle); +int LIBUSB_CALL libusb_open(libusb_device *dev, libusb_device_handle **dev_handle); +void LIBUSB_CALL libusb_close(libusb_device_handle *dev_handle); +libusb_device * LIBUSB_CALL libusb_get_device(libusb_device_handle *dev_handle); + +int LIBUSB_CALL libusb_set_configuration(libusb_device_handle *dev_handle, + int configuration); +int LIBUSB_CALL libusb_claim_interface(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_release_interface(libusb_device_handle *dev_handle, + int interface_number); + +libusb_device_handle * LIBUSB_CALL libusb_open_device_with_vid_pid( + libusb_context *ctx, uint16_t vendor_id, uint16_t product_id); + +int LIBUSB_CALL libusb_set_interface_alt_setting(libusb_device_handle *dev_handle, + int interface_number, int alternate_setting); +int LIBUSB_CALL libusb_clear_halt(libusb_device_handle *dev_handle, + unsigned char endpoint); +int LIBUSB_CALL libusb_reset_device(libusb_device_handle *dev_handle); + +int LIBUSB_CALL libusb_alloc_streams(libusb_device_handle *dev_handle, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints); +int LIBUSB_CALL libusb_free_streams(libusb_device_handle *dev_handle, + unsigned char *endpoints, int num_endpoints); + +unsigned char * LIBUSB_CALL libusb_dev_mem_alloc(libusb_device_handle *dev_handle, + size_t length); +int LIBUSB_CALL libusb_dev_mem_free(libusb_device_handle *dev_handle, + unsigned char *buffer, size_t length); + +int LIBUSB_CALL libusb_kernel_driver_active(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_detach_kernel_driver(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_attach_kernel_driver(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_set_auto_detach_kernel_driver( + libusb_device_handle *dev_handle, int enable); + +int LIBUSB_CALL libusb_endpoint_supports_raw_io(libusb_device_handle* dev_handle, + uint8_t endpoint); +int LIBUSB_CALL libusb_endpoint_set_raw_io(libusb_device_handle *dev_handle, + uint8_t endpoint, int enable); +int LIBUSB_CALL libusb_get_max_raw_io_transfer_size( + libusb_device_handle *dev_handle, + uint8_t endpoint); + +/* async I/O */ + +/** \ingroup libusb_asyncio + * Get the data section of a control transfer. This convenience function is here + * to remind you that the data does not start until 8 bytes into the actual + * buffer, as the setup packet comes first. + * + * Calling this function only makes sense from a transfer callback function, + * or situations where you have already allocated a suitably sized buffer at + * transfer->buffer. + * + * \param transfer a transfer + * \returns pointer to the first byte of the data section + */ +static inline unsigned char *libusb_control_transfer_get_data( + struct libusb_transfer *transfer) +{ + return transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; +} + +/** \ingroup libusb_asyncio + * Get the control setup packet of a control transfer. This convenience + * function is here to remind you that the control setup occupies the first + * 8 bytes of the transfer data buffer. + * + * Calling this function only makes sense from a transfer callback function, + * or situations where you have already allocated a suitably sized buffer at + * transfer->buffer. + * + * \param transfer a transfer + * \returns a casted pointer to the start of the transfer data buffer + */ +static inline struct libusb_control_setup *libusb_control_transfer_get_setup( + struct libusb_transfer *transfer) +{ + return (struct libusb_control_setup *)(void *)transfer->buffer; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the setup packet (first 8 bytes of the data + * buffer) for a control transfer. The wIndex, wValue and wLength values should + * be given in host-endian byte order. + * + * \param buffer buffer to output the setup packet into + * This pointer must be aligned to at least 2 bytes boundary. + * \param bmRequestType see the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field of + * \ref libusb_control_setup + * \param bRequest see the + * \ref libusb_control_setup::bRequest "bRequest" field of + * \ref libusb_control_setup + * \param wValue see the + * \ref libusb_control_setup::wValue "wValue" field of + * \ref libusb_control_setup + * \param wIndex see the + * \ref libusb_control_setup::wIndex "wIndex" field of + * \ref libusb_control_setup + * \param wLength see the + * \ref libusb_control_setup::wLength "wLength" field of + * \ref libusb_control_setup + */ +static inline void libusb_fill_control_setup(unsigned char *buffer, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + uint16_t wLength) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *)buffer; + setup->bmRequestType = bmRequestType; + setup->bRequest = bRequest; + setup->wValue = libusb_cpu_to_le16(wValue); + setup->wIndex = libusb_cpu_to_le16(wIndex); + setup->wLength = libusb_cpu_to_le16(wLength); +} + +struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer(int iso_packets); +int LIBUSB_CALL libusb_submit_transfer(struct libusb_transfer *transfer); +int LIBUSB_CALL libusb_cancel_transfer(struct libusb_transfer *transfer); +void LIBUSB_CALL libusb_free_transfer(struct libusb_transfer *transfer); +void LIBUSB_CALL libusb_transfer_set_stream_id( + struct libusb_transfer *transfer, uint32_t stream_id); +uint32_t LIBUSB_CALL libusb_transfer_get_stream_id( + struct libusb_transfer *transfer); + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a control transfer. + * + * If you pass a transfer buffer to this function, the first 8 bytes will + * be interpreted as a control setup packet, and the wLength field will be + * used to automatically populate the \ref libusb_transfer::length "length" + * field of the transfer. Therefore the recommended approach is: + * -# Allocate a suitably sized data buffer (including space for control setup) + * -# Call libusb_fill_control_setup() + * -# If this is a host-to-device transfer with a data stage, put the data + * in place after the setup packet + * -# Call this function + * -# Call libusb_submit_transfer() + * + * It is also legal to pass a NULL buffer to this function, in which case this + * function will not attempt to populate the length field. Remember that you + * must then populate the buffer and length fields later. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param buffer data buffer. If provided, this function will interpret the + * first 8 bytes as a setup packet and infer the transfer length from that. + * This pointer must be aligned to at least 2 bytes boundary. + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_control_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data, + unsigned int timeout) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *)buffer; + transfer->dev_handle = dev_handle; + transfer->endpoint = 0; + transfer->type = LIBUSB_TRANSFER_TYPE_CONTROL; + transfer->timeout = timeout; + transfer->buffer = buffer; + if (setup) + transfer->length = (int) (LIBUSB_CONTROL_SETUP_SIZE + + libusb_le16_to_cpu(setup->wLength)); + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a bulk transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_bulk_transfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *buffer, int length, libusb_transfer_cb_fn callback, + void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_BULK; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a bulk transfer using bulk streams. + * + * Since version 1.0.19, \ref LIBUSB_API_VERSION >= 0x01000103 + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param stream_id bulk stream id for this transfer + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_bulk_stream_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char endpoint, uint32_t stream_id, + unsigned char *buffer, int length, libusb_transfer_cb_fn callback, + void *user_data, unsigned int timeout) +{ + libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, + length, callback, user_data, timeout); + transfer->type = LIBUSB_TRANSFER_TYPE_BULK_STREAM; + libusb_transfer_set_stream_id(transfer, stream_id); +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for an interrupt transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_interrupt_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *buffer, int length, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for an isochronous transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param num_iso_packets the number of isochronous packets + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_iso_transfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *buffer, int length, int num_iso_packets, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->num_iso_packets = num_iso_packets; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Convenience function to set the length of all packets in an isochronous + * transfer, based on the num_iso_packets field in the transfer structure. + * + * \param transfer a transfer + * \param length the length to set in each isochronous packet descriptor + * \see libusb_get_max_packet_size() + */ +static inline void libusb_set_iso_packet_lengths( + struct libusb_transfer *transfer, unsigned int length) +{ + int i; + + for (i = 0; i < transfer->num_iso_packets; i++) + transfer->iso_packet_desc[i].length = length; +} + +/** \ingroup libusb_asyncio + * Convenience function to locate the position of an isochronous packet + * within the buffer of an isochronous transfer. + * + * This is a thorough function which loops through all preceding packets, + * accumulating their lengths to find the position of the specified packet. + * Typically you will assign equal lengths to each packet in the transfer, + * and hence the above method is sub-optimal. You may wish to use + * libusb_get_iso_packet_buffer_simple() instead. + * + * \param transfer a transfer + * \param packet the packet to return the address of + * \returns the base address of the packet buffer inside the transfer buffer, + * or NULL if the packet does not exist. + * \see libusb_get_iso_packet_buffer_simple() + */ +static inline unsigned char *libusb_get_iso_packet_buffer( + struct libusb_transfer *transfer, unsigned int packet) +{ + int i; + size_t offset = 0; + int _packet; + + /* oops..slight bug in the API. packet is an unsigned int, but we use + * signed integers almost everywhere else. range-check and convert to + * signed to avoid compiler warnings. FIXME for libusb-2. */ + if (packet > INT_MAX) + return NULL; + _packet = (int) packet; + + if (_packet >= transfer->num_iso_packets) + return NULL; + + for (i = 0; i < _packet; i++) + offset += transfer->iso_packet_desc[i].length; + + return transfer->buffer + offset; +} + +/** \ingroup libusb_asyncio + * Convenience function to locate the position of an isochronous packet + * within the buffer of an isochronous transfer, for transfers where each + * packet is of identical size. + * + * This function relies on the assumption that every packet within the transfer + * is of identical size to the first packet. Calculating the location of + * the packet buffer is then just a simple calculation: + * buffer + (packet_size * packet) + * + * Do not use this function on transfers other than those that have identical + * packet lengths for each packet. + * + * \param transfer a transfer + * \param packet the packet to return the address of + * \returns the base address of the packet buffer inside the transfer buffer, + * or NULL if the packet does not exist. + * \see libusb_get_iso_packet_buffer() + */ +static inline unsigned char *libusb_get_iso_packet_buffer_simple( + struct libusb_transfer *transfer, unsigned int packet) +{ + int _packet; + + /* oops..slight bug in the API. packet is an unsigned int, but we use + * signed integers almost everywhere else. range-check and convert to + * signed to avoid compiler warnings. FIXME for libusb-2. */ + if (packet > INT_MAX) + return NULL; + _packet = (int) packet; + + if (_packet >= transfer->num_iso_packets) + return NULL; + + return transfer->buffer + ((int) transfer->iso_packet_desc[0].length * _packet); +} + +/* sync I/O */ + +int LIBUSB_CALL libusb_control_transfer(libusb_device_handle *dev_handle, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + unsigned char *data, uint16_t wLength, unsigned int timeout); + +int LIBUSB_CALL libusb_bulk_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *transferred, unsigned int timeout); + +int LIBUSB_CALL libusb_interrupt_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *transferred, unsigned int timeout); + +/** \ingroup libusb_desc + * Retrieve a descriptor from the default control pipe. + * This is a convenience function which formulates the appropriate control + * message to retrieve the descriptor. + * + * \param dev_handle a device handle + * \param desc_type the descriptor type, see \ref libusb_descriptor_type + * \param desc_index the index of the descriptor to retrieve + * \param data output buffer for descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + */ +static inline int libusb_get_descriptor(libusb_device_handle *dev_handle, + uint8_t desc_type, uint8_t desc_index, unsigned char *data, int length) +{ + return libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, (uint16_t) ((desc_type << 8) | desc_index), + 0, data, (uint16_t) length, 1000); +} + +/** \ingroup libusb_desc + * Retrieve a descriptor from a device. + * This is a convenience function which formulates the appropriate control + * message to retrieve the descriptor. The string returned is Unicode, as + * detailed in the USB specifications. + * + * \param dev_handle a device handle + * \param desc_index the index of the descriptor to retrieve + * \param langid the language ID for the string descriptor + * \param data output buffer for descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + * \see libusb_get_string_descriptor_ascii() + */ +static inline int libusb_get_string_descriptor(libusb_device_handle *dev_handle, + uint8_t desc_index, uint16_t langid, unsigned char *data, int length) +{ + return libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, (uint16_t)((LIBUSB_DT_STRING << 8) | desc_index), + langid, data, (uint16_t) length, 1000); +} + +int LIBUSB_CALL libusb_get_string_descriptor_ascii(libusb_device_handle *dev_handle, + uint8_t desc_index, unsigned char *data, int length); + +/* polling and timeouts */ + +int LIBUSB_CALL libusb_try_lock_events(libusb_context *ctx); +void LIBUSB_CALL libusb_lock_events(libusb_context *ctx); +void LIBUSB_CALL libusb_unlock_events(libusb_context *ctx); +int LIBUSB_CALL libusb_event_handling_ok(libusb_context *ctx); +int LIBUSB_CALL libusb_event_handler_active(libusb_context *ctx); +void LIBUSB_CALL libusb_interrupt_event_handler(libusb_context *ctx); +void LIBUSB_CALL libusb_lock_event_waiters(libusb_context *ctx); +void LIBUSB_CALL libusb_unlock_event_waiters(libusb_context *ctx); +int LIBUSB_CALL libusb_wait_for_event(libusb_context *ctx, struct timeval *tv); + +int LIBUSB_CALL libusb_handle_events_timeout(libusb_context *ctx, + struct timeval *tv); +int LIBUSB_CALL libusb_handle_events_timeout_completed(libusb_context *ctx, + struct timeval *tv, int *completed); +int LIBUSB_CALL libusb_handle_events(libusb_context *ctx); +int LIBUSB_CALL libusb_handle_events_completed(libusb_context *ctx, int *completed); +int LIBUSB_CALL libusb_handle_events_locked(libusb_context *ctx, + struct timeval *tv); +int LIBUSB_CALL libusb_pollfds_handle_timeouts(libusb_context *ctx); +int LIBUSB_CALL libusb_get_next_timeout(libusb_context *ctx, + struct timeval *tv); + +/** \ingroup libusb_poll + * File descriptor for polling + */ +struct libusb_pollfd { + /** Numeric file descriptor */ + int fd; + + /** Event flags to poll for from . POLLIN indicates that you + * should monitor this file descriptor for becoming ready to read from, + * and POLLOUT indicates that you should monitor this file descriptor for + * nonblocking write readiness. */ + short events; +}; + +/** \ingroup libusb_poll + * Callback function, invoked when a new file descriptor should be added + * to the set of file descriptors monitored for events. + * \param fd the new file descriptor + * \param events events to monitor for, see \ref libusb_pollfd for a + * description + * \param user_data User data pointer specified in + * libusb_set_pollfd_notifiers() call + * \see libusb_set_pollfd_notifiers() + */ +typedef void (LIBUSB_CALL *libusb_pollfd_added_cb)(int fd, short events, + void *user_data); + +/** \ingroup libusb_poll + * Callback function, invoked when a file descriptor should be removed from + * the set of file descriptors being monitored for events. After returning + * from this callback, do not use that file descriptor again. + * \param fd the file descriptor to stop monitoring + * \param user_data User data pointer specified in + * libusb_set_pollfd_notifiers() call + * \see libusb_set_pollfd_notifiers() + */ +typedef void (LIBUSB_CALL *libusb_pollfd_removed_cb)(int fd, void *user_data); + +const struct libusb_pollfd ** LIBUSB_CALL libusb_get_pollfds( + libusb_context *ctx); +void LIBUSB_CALL libusb_free_pollfds(const struct libusb_pollfd **pollfds); +void LIBUSB_CALL libusb_set_pollfd_notifiers(libusb_context *ctx, + libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb, + void *user_data); + +/** \ingroup libusb_hotplug + * Callback handle. + * + * Callbacks handles are generated by libusb_hotplug_register_callback() + * and can be used to deregister callbacks. Callback handles are unique + * per libusb_context and it is safe to call libusb_hotplug_deregister_callback() + * on an already deregistered callback. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * For more information, see \ref libusb_hotplug. + */ +typedef int libusb_hotplug_callback_handle; + +/** \ingroup libusb_hotplug + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * Hotplug events */ +typedef enum { + /** A device has been plugged in and is ready to use */ + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED = (1 << 0), + + /** A device has left and is no longer available. + * It is the user's responsibility to call libusb_close on any handle associated with a disconnected device. + * It is safe to call libusb_get_device_descriptor on a device that has left */ + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT = (1 << 1) +} libusb_hotplug_event; + +/** \ingroup libusb_hotplug + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * Hotplug flags */ +typedef enum { + /** Arm the callback and fire it for all matching currently attached devices. */ + LIBUSB_HOTPLUG_ENUMERATE = (1 << 0) +} libusb_hotplug_flag; + +/** \ingroup libusb_hotplug + * Convenience macro when not using any flags */ +#define LIBUSB_HOTPLUG_NO_FLAGS 0 + +/** \ingroup libusb_hotplug + * Wildcard matching for hotplug events */ +#define LIBUSB_HOTPLUG_MATCH_ANY -1 + +/** \ingroup libusb_hotplug + * Hotplug callback function type. When requesting hotplug event notifications, + * you pass a pointer to a callback function of this type. + * + * This callback may be called by an internal event thread and as such it is + * recommended the callback do minimal processing before returning. + * + * libusb will call this function later, when a matching event had happened on + * a matching device. See \ref libusb_hotplug for more information. + * + * It is safe to call either libusb_hotplug_register_callback() or + * libusb_hotplug_deregister_callback() from within a callback function. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * \param ctx context of this notification + * \param device libusb_device this event occurred on + * \param event event that occurred + * \param user_data user data provided when this callback was registered + * \returns bool whether this callback is finished processing events. + * returning 1 will cause this callback to be deregistered + */ +typedef int (LIBUSB_CALL *libusb_hotplug_callback_fn)(libusb_context *ctx, + libusb_device *device, libusb_hotplug_event event, void *user_data); + +/** \ingroup libusb_hotplug + * Register a hotplug callback function + * + * Register a callback with the libusb_context. The callback will fire + * when a matching event occurs on a matching device. The callback is + * armed until either it is deregistered with libusb_hotplug_deregister_callback() + * or the supplied callback returns 1 to indicate it is finished processing events. + * + * If the \ref LIBUSB_HOTPLUG_ENUMERATE is passed the callback will be + * called with a \ref LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED for all devices + * already plugged into the machine. Note that libusb modifies its internal + * device list from a separate thread, while calling hotplug callbacks from + * libusb_handle_events(), so it is possible for a device to already be present + * on, or removed from, its internal device list, while the hotplug callbacks + * still need to be dispatched. This means that when using \ref + * LIBUSB_HOTPLUG_ENUMERATE, your callback may be called twice for the arrival + * of the same device, once from libusb_hotplug_register_callback() and once + * from libusb_handle_events(); and/or your callback may be called for the + * removal of a device for which an arrived call was never made. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * \param[in] ctx context to register this callback with + * \param[in] events bitwise or of hotplug events that will trigger this callback. + * See \ref libusb_hotplug_event + * \param[in] flags bitwise or of hotplug flags that affect registration. + * See \ref libusb_hotplug_flag + * \param[in] vendor_id the vendor id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] product_id the product id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] dev_class the device class to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] cb_fn the function to be invoked on a matching event/device + * \param[in] user_data user data to pass to the callback function + * \param[out] callback_handle pointer to store the handle of the allocated callback (can be NULL) + * \returns \ref LIBUSB_SUCCESS on success LIBUSB_ERROR code on failure + */ +int LIBUSB_CALL libusb_hotplug_register_callback(libusb_context *ctx, + int events, int flags, + int vendor_id, int product_id, int dev_class, + libusb_hotplug_callback_fn cb_fn, void *user_data, + libusb_hotplug_callback_handle *callback_handle); + +/** \ingroup libusb_hotplug + * Deregisters a hotplug callback. + * + * Deregister a callback from a libusb_context. This function is safe to call from within + * a hotplug callback. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * \param[in] ctx context this callback is registered with + * \param[in] callback_handle the handle of the callback to deregister + */ +void LIBUSB_CALL libusb_hotplug_deregister_callback(libusb_context *ctx, + libusb_hotplug_callback_handle callback_handle); + +/** \ingroup libusb_hotplug + * Gets the user_data associated with a hotplug callback. + * + * Since version v1.0.24 \ref LIBUSB_API_VERSION >= 0x01000108 + * + * \param[in] ctx context this callback is registered with + * \param[in] callback_handle the handle of the callback to get the user_data of + */ +void * LIBUSB_CALL libusb_hotplug_get_user_data(libusb_context *ctx, + libusb_hotplug_callback_handle callback_handle); + +int LIBUSB_CALLV libusb_set_option(libusb_context *ctx, enum libusb_option option, ...); + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/Android_App/app/src/main/cpp/deps/librador/librador_global.h b/Android_App/app/src/main/cpp/deps/librador/librador_global.h new file mode 100644 index 000000000..929e6c720 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/librador_global.h @@ -0,0 +1,22 @@ +#ifndef LIBRADOR_GLOBAL_H +#define LIBRADOR_GLOBAL_H + +#ifdef FORCE_COMPILATION_FOR_QT5 + #include +#else + #if defined(_WIN32) || defined(_WIN64) + #define Q_DECL_EXPORT __declspec(dllexport) + #define Q_DECL_IMPORT + #else + #define Q_DECL_EXPORT + #define Q_DECL_IMPORT + #endif +#endif + +#if defined(LIBRADOR_LIBRARY) +# define LIBRADORSHARED_EXPORT Q_DECL_EXPORT +#else +# define LIBRADORSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // LIBRADOR_GLOBAL_H diff --git a/Android_App/app/src/main/cpp/deps/librador/librador_internal.h b/Android_App/app/src/main/cpp/deps/librador/librador_internal.h new file mode 100644 index 000000000..29dad6741 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/librador_internal.h @@ -0,0 +1,26 @@ +#ifndef LIBRADOR_INTERNAL_H +#define LIBRADOR_INTERNAL_H + +#endif // LIBRADOR_INTERNAL_H + +#define LABRADOR_VID 0x03eb +#define LABRADOR_PID 0xba94 + +#define CHECK_API_INITIALISED if(!internal_librador_object) return -420; +#define CHECK_USB_INITIALISED if(!internal_librador_object->usb_driver->connected) return -421; + +#define VECTOR_API_INIT_CHECK if(!internal_librador_object) return nullptr; +#define VECTOR_USB_INIT_CHECK if(!internal_librador_object->usb_driver->connected) return nullptr; + + +class usbCallHandler; + +class Librador +{ + +public: + Librador(); + usbCallHandler *usb_driver = nullptr; +}; + +Librador *internal_librador_object = nullptr; diff --git a/Android_App/app/src/main/cpp/deps/librador/logging.h b/Android_App/app/src/main/cpp/deps/librador/logging.h new file mode 100644 index 000000000..b028bb0e6 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/logging.h @@ -0,0 +1,23 @@ +#ifndef LOGGING_H +#define LOGGING_H + +#ifdef PLATFORM_ANDROID +// sync to android_LogPriority +enum { + LOG_NONE = 0, + LOG_ERROR = 6, + LOG_WARNING = 5, + LOG_DEBUG = 3, +}; +#else +enum { + LOG_NONE = 0, + LOG_ERROR, + LOG_WARNING, + LOG_DEBUG, +}; +#endif //PLATFORM_ANDROID + + + +#endif // LOGGING_H diff --git a/Android_App/app/src/main/cpp/deps/librador/logging_internal.h b/Android_App/app/src/main/cpp/deps/librador/logging_internal.h new file mode 100644 index 000000000..bd7426199 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/logging_internal.h @@ -0,0 +1,32 @@ +#ifndef LOGGING_INTERNAL_H +#define LOGGING_INTERNAL_H + +#include "logging.h" +//TODO : maybe handle the logger switch for android by setting _librador_global_logger in librador.cpp instead? +#ifdef PLATFORM_ANDROID + +#include +#define LOG_TAG "librador" +#define LIBRADOR_LOG(level, ...) __android_log_print(level, LOG_TAG, __VA_ARGS__) + +#else + +#ifdef LIBRADOR_ENABLE_LOGGING + #define LIBRADOR_LOG(level, ...) \ + do \ + { \ + librador_global_logger(level, __VA_ARGS__); \ + } while (0) +#else + #define LIBRADOR_LOG(level, ...) \ + do \ + { \ + /* Logging is disabled */ \ + } while (0) +#endif // LIBRADOR_DISABLE_LOGGING + +#endif // PLATFORM_ANDROID + +void librador_global_logger(const int level, const char* format, ...); + +#endif // LOGGING_INTERNAL_H diff --git a/Android_App/app/src/main/cpp/deps/librador/o1buffer.cpp b/Android_App/app/src/main/cpp/deps/librador/o1buffer.cpp new file mode 100644 index 000000000..7f0c188b5 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/o1buffer.cpp @@ -0,0 +1,451 @@ +#include "o1buffer.h" +#include "logging_internal.h" +#include +#include +#include + +#include "uartstyledecoder.h" + + +//TODO: incorporate mostRecentAddressPaused into getSinceLast? +//TODO: triggering is finicky when the trigger level is ~0 b/c m_triggerSensitivity becomes too small? +std::mutex buffer_related_settings_mutex; + +//o1buffer is an object that has o(1) access times for its elements. +//At the moment it's basically an array, but I'm keeping it as an object so it can be changed to something more memory efficient later. +//See isobuffer in github.com/espotek-org/labrador for an example of a much more compact (RAM-wise) buffer. +o1buffer::o1buffer(double sps) +{ + buffer = (int *) (malloc(sizeof(int)*NUM_SAMPLES_PER_CHANNEL)); + buffer_paused = (int *) (malloc(sizeof(int)*NUM_SAMPLES_PER_CHANNEL)); + m_is_triggered = (bool *) (malloc(sizeof(bool)*NUM_SAMPLES_PER_CHANNEL)); + m_samples_per_second = sps; + m_uart_decoder = new uartStyleDecoder(this); +} + +o1buffer::~o1buffer(){ + free(buffer); + free(buffer_paused); + free(m_is_triggered); + delete m_uart_decoder; +} + +int o1buffer::reset(bool hard){ + mostRecentAddress = 0; + stream_index_at_last_call = 0; + if(hard){ + for (int i=0; i= NUM_SAMPLES_PER_CHANNEL){ + address = address % NUM_SAMPLES_PER_CHANNEL; + } + if(address<0){ + LIBRADOR_LOG(LOG_ERROR, "ERROR: o1buffer::add was given a negative address\n"); + } + //Assign the value + buffer[address] = value; + updateMostRecentAddress(address); +} + +int o1buffer::addVector(int *firstElement, int numElements){ + int currentAddress = mostRecentAddress; + + buffer_related_settings_mutex.lock(); + for(int i=0; i< numElements; i++){ + add(firstElement[i], currentAddress); + checkTriggered(currentAddress); + currentAddress = (currentAddress + 1) % NUM_SAMPLES_PER_CHANNEL; + } + buffer_related_settings_mutex.unlock(); + return 0; +} + +int o1buffer::addVector(char *firstElement, int numElements){ + int currentAddress = mostRecentAddress; + + buffer_related_settings_mutex.lock(); + for(int i=0; i< numElements; i++){ + add(firstElement[i], currentAddress); + checkTriggered(currentAddress); + currentAddress = (currentAddress + 1) % NUM_SAMPLES_PER_CHANNEL; + } + buffer_related_settings_mutex.unlock(); + + return 0; +} + +int o1buffer::addVector(unsigned char *firstElement, int numElements){ + int currentAddress = mostRecentAddress; + + buffer_related_settings_mutex.lock(); + for(int i=0; i< numElements; i++){ + add(firstElement[i], currentAddress); + checkTriggered(currentAddress); + currentAddress = (currentAddress + 1) % NUM_SAMPLES_PER_CHANNEL; + } + buffer_related_settings_mutex.unlock(); + return 0; +} + +int o1buffer::addVector(short *firstElement, int numElements){ + int currentAddress = mostRecentAddress; + + buffer_related_settings_mutex.lock(); + for(int i=0; i< numElements; i++){ + #ifdef MULTIMETER_INVERT + add(-firstElement[i] >> 4, currentAddress); + #else + add(firstElement[i] >> 4, currentAddress); + #endif + checkTriggered(currentAddress); + currentAddress = (currentAddress + 1) % NUM_SAMPLES_PER_CHANNEL; + } + buffer_related_settings_mutex.unlock(); + return 0; +} + + +int o1buffer::get(int address){ + int *read_buffer; + if(m_virtual_transform_settings.is_paused) { + read_buffer = buffer_paused; + } else { + read_buffer = buffer; + } + + //Ensure that the address is not too high. + if(address >= NUM_SAMPLES_PER_CHANNEL){ + address = address % NUM_SAMPLES_PER_CHANNEL; + } + if(address<0){ + LIBRADOR_LOG(LOG_ERROR, "ERROR: o1buffer::get was given a negative address\n"); + } + //Return the value + return read_buffer[address]; +} + +inline void o1buffer::updateMostRecentAddress(int newAddress){ + mostRecentAddress = newAddress; +} + +//This function places samples in a buffer than can be plotted on the streamingDisplay. +//A small delay, is added in case the packets arrive out of order. +std::vector *o1buffer::getMany_double(int numToGet, double interval_samples, int delay_samples, int filter_mode, double scope_gain, bool twelve_bit_multimeter){ + //Resize the vector + convertedStream_double.resize(numToGet); + + //Copy raw samples out. + int tempAddress; + double window_mean; + + if(m_virtual_transform_settings.is_ac) + { + tempAddress = (m_virtual_transform_settings.is_paused ? mostRecentAddressPaused : mostRecentAddress) - delay_samples - round(interval_samples*numToGet/2.); + if(tempAddress < 0) + tempAddress += NUM_SAMPLES_PER_CHANNEL; + window_mean = get_filtered_sample(tempAddress, 1, round(interval_samples * numToGet), scope_gain, twelve_bit_multimeter); + m_ac_offset_adc_units = inverseSampleConvert(window_mean + (twelve_bit_multimeter ? 0 : voltage_ref), scope_gain, twelve_bit_multimeter); + } else { + m_ac_offset_adc_units = 0; + } + + for(int i=0;i *o1buffer::getMany_singleBit(int numToGet, double interval_subsamples, int delay_subsamples){ + //Resize the vector + convertedStream_double.resize(numToGet); + + //Copy raw samples out. + int tempAddress; + int subsample_current_delay; + uint8_t mask; + double *data = convertedStream_double.data(); + int tempInt; + + for(int i=0;i *o1buffer::getSinceLast(int feasible_window_begin, int feasible_window_end, int interval_samples, int filter_mode, double scope_gain, bool twelve_bit_multimeter){ + + //Calculate what sample the feasible window begins at + //printf_debugging("o1buffer::getSinceLast()\n") + int feasible_start_point = mostRecentAddress - feasible_window_begin; + if(feasible_start_point < 0){ + feasible_start_point += NUM_SAMPLES_PER_CHANNEL; + } + + //Work out whether or not we're starting from the feasible window or the last point + int actual_start_point; + if(distanceFromMostRecentAddress(feasible_start_point) > distanceFromMostRecentAddress(stream_index_at_last_call + interval_samples)){ + actual_start_point = stream_index_at_last_call + interval_samples; + } else { + actual_start_point = feasible_start_point; + } + + //Work out how much we're copying + int actual_sample_distance = distanceFromMostRecentAddress(actual_start_point) - distanceFromMostRecentAddress(mostRecentAddress - feasible_window_end); + int numToGet = actual_sample_distance/interval_samples; + //printf_debugging("Fetching %d samples, starting at index %d with interval %d\n", numToGet, actual_start_point, interval_samples); + + //Set up the buffer + convertedStream_double.resize(numToGet); + + //Copy raw samples out. + int tempAddress = stream_index_at_last_call; + for(int i=0;i= NUM_SAMPLES_PER_CHANNEL){ + tempAddress -= NUM_SAMPLES_PER_CHANNEL; + } + double *data = convertedStream_double.data(); + if(m_virtual_transform_settings.is_ac) + data[numToGet-1-i] = m_virtual_transform_settings.gain * (get_filtered_sample(tempAddress, 0, interval_samples, scope_gain, twelve_bit_multimeter) - get_filtered_sample(tempAddress, 1, actual_sample_distance, scope_gain, twelve_bit_multimeter)) + m_virtual_transform_settings.offset; + else + data[numToGet-1-i] = m_virtual_transform_settings.gain * get_filtered_sample(tempAddress, filter_mode, interval_samples, scope_gain, twelve_bit_multimeter) + m_virtual_transform_settings.offset; + //convertedStream_double.replace(i, buffer[tempAddress]); + } + + //update stream_index_at_last_call for next call + stream_index_at_last_call = tempAddress; + + return &convertedStream_double; +} + +int o1buffer::distanceFromMostRecentAddress(int index){ + //Standard case. buffer[NUM_SAMPLES_PER_CHANNEL] not crossed between most recent and index's sample writes. + if(index < mostRecentAddress){ + return mostRecentAddress - index; + } + + //Corner case. buffer[NUM_SAMPLES_PER_CHANNEL] boundary has been crossed. + if(index > mostRecentAddress){ + //Two areas. 0 to mostRecentAddress, and index to the end of the buffer. + return mostRecentAddress + (NUM_SAMPLES_PER_CHANNEL - index); + } + + //I guess the other corner case is when the addresses are the same. + return 0; +} + +//replace with get_filtered_sample +double o1buffer::get_filtered_sample(int index, int filter_type, int filter_size, double scope_gain, bool twelve_bit_multimeter){ + double accum = 0; + int currentPos = index - (filter_size / 2); + int end = currentPos + filter_size; + int *read_buffer; + if(m_virtual_transform_settings.is_paused) { + read_buffer = buffer_paused; + } else { + read_buffer = buffer; + } + + switch(filter_type){ + case 0: //No filter +// buffer[index]; + return sampleConvert(read_buffer[index], scope_gain, twelve_bit_multimeter); + case 1: //Moving Average filter + if(currentPos < 0){ + currentPos += NUM_SAMPLES_PER_CHANNEL; + } + if(end >= NUM_SAMPLES_PER_CHANNEL){ + end -= NUM_SAMPLES_PER_CHANNEL; + } + while(currentPos != end){ + accum += read_buffer[currentPos]; + currentPos = (currentPos + 1) % NUM_SAMPLES_PER_CHANNEL; + } + return sampleConvert(accum/((double)filter_size), scope_gain, twelve_bit_multimeter); + break; + default: //Default to "no filter" + return read_buffer[index]; + } +} + +double o1buffer::sampleConvert(int sample, double scope_gain, bool twelve_bit_multimeter) const { + double voltageLevel; + double TOP; + + if(twelve_bit_multimeter){ + TOP = 2048; + } else TOP = 128; + + voltageLevel = ((double)sample * (vcc/2)) / (frontendGain * scope_gain * TOP); + if (!twelve_bit_multimeter) voltageLevel += voltage_ref; + + return voltageLevel; +} + +short o1buffer::inverseSampleConvert(double voltageLevel, double scope_gain, bool twelve_bit_multimeter) const { + double TOP; + + if(twelve_bit_multimeter){ + TOP = 2048; + } else TOP = 128; + + if (!twelve_bit_multimeter) voltageLevel -= voltage_ref; + + short sample = round( (voltageLevel * (frontendGain * scope_gain * TOP)) / (vcc/2) ); + + return sample; +} + +// gets called by setsettingsforchannel in usbcallhandler.h +void o1buffer::resetTrigger(double scope_gain, bool twelve_bit_multimeter) +{ + double TOP; + + if(twelve_bit_multimeter){ + TOP = 2048; + } else TOP = 128; + + // user sets m_trigger_settings.trigger_level based on what they see in the waveform that includes virtual transforms; these transforms are compensated for in actual_trigger_level + double actual_trigger_level = (m_trigger_settings.trigger_level - m_virtual_transform_settings.offset)/m_virtual_transform_settings.gain; + + short new_triggerLevelADC = inverseSampleConvert(actual_trigger_level, scope_gain, twelve_bit_multimeter); + buffer_related_settings_mutex.lock(); + memset(m_is_triggered, false, sizeof(bool) * NUM_SAMPLES_PER_CHANNEL); + m_triggerSeekState = TriggerSeekState::Invalid; + m_triggerLevelADC = new_triggerLevelADC; + m_triggerSensitivity = static_cast((1 + abs(actual_trigger_level * kTriggerSensitivityMultiplier )) * TOP / 128.); + buffer_related_settings_mutex.unlock(); + + LIBRADOR_LOG(LOG_DEBUG, "Trigger Level: %d", m_triggerLevelADC); + LIBRADOR_LOG(LOG_DEBUG, "Trigger sensitivity: %d", m_triggerSensitivity); +} + + +void o1buffer::checkTriggered(int mostRecentAddress) { + if (m_trigger_settings.trigger_type == TriggerType::Disabled) + return; + m_is_triggered[mostRecentAddress] = false; + + if (buffer[mostRecentAddress] >= ((m_triggerLevelADC + m_ac_offset_adc_units) + m_triggerSensitivity)) + { + // Rising Edge + if((m_triggerSeekState == TriggerSeekState::BelowTriggerLevel) && (m_trigger_settings.trigger_type == TriggerType::Rising)) + m_is_triggered[mostRecentAddress] = true; + m_triggerSeekState = TriggerSeekState::AboveTriggerLevel; + } + else if (buffer[mostRecentAddress] < ((m_triggerLevelADC + m_ac_offset_adc_units) - m_triggerSensitivity)) + { + // Falling Edge + if((m_triggerSeekState == TriggerSeekState::AboveTriggerLevel) && (m_trigger_settings.trigger_type == TriggerType::Falling)) + m_is_triggered[mostRecentAddress] = true; + m_triggerSeekState = TriggerSeekState::BelowTriggerLevel; + } +} + +int o1buffer::getDelayIncludingFromTrigger(int delay_samples, int window_samples, bool* single_shot_reached, int* trigger_delay_out) { + int tempAddress = mostRecentAddress - delay_samples; + if((m_trigger_settings.trigger_type == TriggerType::Disabled) || (m_virtual_transform_settings.is_paused)) + return delay_samples; +// for (int i=0; i<(NUM_SAMPLES_PER_CHANNEL - delay_samples - window_samples); i++) { + for (int trigger_delay=0; trigger_delayUartDecode(); +} + +void o1buffer::setUartDecodeSettings(UartSettings new_settings) +{ + m_uart_decoder->setSettings(new_settings); +} + +char * o1buffer::getUart_String(bool* parity_check) +{ + return m_uart_decoder->getString(parity_check); +} diff --git a/Android_App/app/src/main/cpp/deps/librador/o1buffer.h b/Android_App/app/src/main/cpp/deps/librador/o1buffer.h new file mode 100644 index 000000000..d84726725 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/o1buffer.h @@ -0,0 +1,90 @@ +#ifndef O1BUFFER_H +#define O1BUFFER_H + +#include +#include +#include +#include + +#define NUM_SAMPLES_PER_CHANNEL (375000 * 10) //10 seconds of samples at 375ksps! +#define MULTIMETER_INVERT + +class uartStyleDecoder; +struct UartSettings; + +class o1buffer +{ +public: + enum TriggerType {Disabled, Rising, Falling}; + struct trigger_settings { + TriggerType trigger_type = TriggerType::Disabled; + float trigger_level = 0.f; + bool is_single_shot = false; + bool operator==(const trigger_settings& other) const{ + return (trigger_type == other.trigger_type) && (trigger_level == other.trigger_level) && (is_single_shot == other.is_single_shot); + } + }; + struct virtual_transform_settings { + float offset = 0.f; + int gain = 1; + bool is_ac = false; + bool is_paused = false; + bool operator==(const virtual_transform_settings& other) const{ + return (offset == other.offset) && (gain == other.gain) && (is_ac == other.is_ac) && (is_paused == other.is_paused); + } + }; + o1buffer(double sps); + ~o1buffer(); + int reset(bool hard); + void add(int value, int address); + int addVector(int *firstElement, int numElements); + int addVector(char *firstElement, int numElements); + int addVector(unsigned char *firstElement, int numElements); + int addVector(short *firstElement, int numElements); + int get(int address); + int mostRecentAddress = 0; + int mostRecentAddressPaused = 0; + int stream_index_at_last_call = 0; + int distanceFromMostRecentAddress(int index); + void resetTrigger(double scope_gain, bool twelve_bit_multimeter); + std::vector *getMany_double(int numToGet, double interval_samples, int delay_sample, int filter_mode, double scope_gain, bool twelve_bit_multimeter); + std::vector *getMany_singleBit(int numToGet, double interval_subsamples, int delay_subsamples); + std::vector *getSinceLast(int feasible_window_begin, int feasible_window_end, int interval_samples, int filter_mode, double scope_gain, bool twelve_bit_multimeter); + double vcc = 3.3; + double frontendGain = (75.0/1075.0); + double voltage_ref = 1.65; + int setPaused(bool is_paused, int mostRecentAddressDelta = 0, bool hard = false); + bool getPaused(); + bool setTriggerSettings(trigger_settings new_trigger_settings); + bool setVirtualTransformSettings(virtual_transform_settings new_virtual_transform_settings); + void setUartDecodeSettings(UartSettings new_settings); + bool isTriggeringEnabled(); + int getDelayIncludingFromTrigger(int delay_samples, int window_samples, bool* single_shot_reached = NULL, int* trigger_delay_out = NULL); + double m_samples_per_second; + int m_bufferLen = NUM_SAMPLES_PER_CHANNEL; + void UartDecode(); + char * getUart_String(bool* parity_check); +private: + trigger_settings m_trigger_settings; + virtual_transform_settings m_virtual_transform_settings; + int *buffer; + int *buffer_paused; + bool *m_is_triggered; + std::vector convertedStream_double; + std::vector convertedStream_digital; + void updateMostRecentAddress(int newAddress); + double get_filtered_sample(int index, int filter_type, int filter_size, double scope_gain, bool twelve_bit_multimeter); + double sampleConvert(int sample, double scope_gain, bool twelve_bit_multimeter) const; + short inverseSampleConvert(double voltageLevel, double scope_gain, bool twelve_bit_multimeter) const; + enum TriggerSeekState {Invalid, AboveTriggerLevel, BelowTriggerLevel}; + TriggerSeekState m_triggerSeekState = TriggerSeekState::Invalid; + short m_triggerLevelADC; + short m_triggerSensitivity; + const double kTriggerSensitivityMultiplier = 4; + void checkTriggered(int mostRecentAddress); + int m_ac_offset_adc_units = 0; + void computeTriggerLevel(); + uartStyleDecoder* m_uart_decoder = NULL; +}; + +#endif // O1BUFFER_H diff --git a/Android_App/app/src/main/cpp/deps/librador/uartstyledecoder.cpp b/Android_App/app/src/main/cpp/deps/librador/uartstyledecoder.cpp new file mode 100644 index 000000000..959e083c9 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/uartstyledecoder.cpp @@ -0,0 +1,241 @@ +#include "uartstyledecoder.h" +#include + +uartStyleDecoder::uartStyleDecoder(o1buffer *parent) : + m_serialBuffer{SERIAL_BUFFER_LENGTH}, + m_parent(parent) +{ +} + + +void uartStyleDecoder::UartDecode() +{ + if(!m_settings.decode_on) + return; + if(switched_on) { + m_serialBuffer.clear(); + // Begin decoding SAMPLE_DELAY seconds in the past. + serialPtr_bit = (int)(m_parent->mostRecentAddress * 8 - SERIAL_DELAY * m_parent->m_samples_per_second * 8 + m_parent->m_bufferLen * 8) % (m_parent->m_bufferLen*8); + switched_on = false; + } + double dist_seconds = (double)serialDistance()/(8 * m_parent->m_samples_per_second); + double bitPeriod_seconds = 1.0 / m_settings.baudRate; + + // Used to check for wire disconnects. You should get at least one "1" for a stop bit. + allZeroes = true; + + while(dist_seconds > (bitPeriod_seconds + SERIAL_DELAY)) + { + // Read next uart bit + bool uart_bit = getNextUartBit(); + + if (uart_bit == 1) + allZeroes = false; + + // Process it + if (uartTransmitting) + { + decodeNextUartBit(uart_bit); + } + else + { + // Uart starts transmitting after start bit (logic low). + uartTransmitting = uart_bit == false; + jitterCompensationNeeded = true; + } + + // Update the pointer, accounting for jitter + updateSerialPtr(uart_bit); + // Calculate stopping condition + dist_seconds = (double)serialDistance()/(8*m_parent->m_samples_per_second); + } + + //Not a single stop bit, or idle bit, in the whole stream. Wire must be disconnected. + if (allZeroes) + { + LIBRADOR_LOG(LOG_WARNING, "Wire Disconnect detected!"); + } +} + +int uartStyleDecoder::serialDistance() const +{ + int back_bit = m_parent->mostRecentAddress * 8; + int bufferEnd_bit = (m_parent->m_bufferLen-1) * 8; + if (back_bit >= serialPtr_bit) + return back_bit - serialPtr_bit; + else + return bufferEnd_bit - serialPtr_bit + back_bit; +} + +void uartStyleDecoder::updateSerialPtr(bool current_bit) +{ + if (jitterCompensationNeeded && uartTransmitting) + jitterCompensationNeeded = jitterCompensationProcedure(current_bit); + + int distance_between_bits = (8*m_parent->m_samples_per_second)/ m_settings.baudRate; + if (uartTransmitting) + serialPtr_bit += distance_between_bits; + else + serialPtr_bit += (distance_between_bits - 1); //Less than one baud period so that it will always see that start bit. + + if (serialPtr_bit >= (m_parent->m_bufferLen * 8)) + serialPtr_bit -= (m_parent->m_bufferLen * 8); +} + +bool uartStyleDecoder::getNextUartBit() const +{ + int bitIndex = serialPtr_bit; + + int coord_byte = bitIndex/8; + int coord_bit = bitIndex - (8*coord_byte); + uint8_t dataByte = m_parent->get(coord_byte); + uint8_t mask = (0x01 << coord_bit); + return dataByte & mask; +} + +void uartStyleDecoder::decodeNextUartBit(bool bitValue) +{ + if (dataBit_current == parityIndex) + { + parityCheckFailed = !isParityCorrect(currentUartSymbol, bitValue); + dataBit_current++; + } + else if (dataBit_current < dataBit_max) + { + currentUartSymbol |= (bitValue << dataBit_current); + dataBit_current++; + } + else + { + char decodedDatabit = currentUartSymbol; + +// if (parityCheckFailed) +// { +// m_serialBuffer.insert("\n\n"); +// } + + // Start + body of escape code + if(decodedDatabit == 0x1b || (escape_code_started && !((decodedDatabit >= 'A' && decodedDatabit <= 'Z') || (decodedDatabit >= 'a' && decodedDatabit <= 'z')))) + { + escape_code_started = true; + } + // End of escape code + else if(escape_code_started && ((decodedDatabit >= 'A' && decodedDatabit <= 'Z') || (decodedDatabit >= 'a' && decodedDatabit <= 'z'))) + { + escape_code_started = false; + } + else + { + if (m_hexDisplay) + { + m_serialBuffer.insert_hex(decodedDatabit); + m_serialBuffer.insert(" "); + } + else + { + if(decodedDatabit=='\0') { + if(!allZeroes) + m_serialBuffer.insert("\\0"); //insert escaped null terminator in place of null terminator + } else { + m_serialBuffer.insert(decodedDatabit); + } + } + } + + currentUartSymbol = 0; + dataBit_current = 0; + uartTransmitting = false; + } +} + +//This function compensates for jitter by, when the current bit is a "1", and the last bit was a zero, setting the pointer +//to the sample at the midpoint between this bit and the last. +bool uartStyleDecoder::jitterCompensationProcedure(bool current_bit) +{ + + //We only run when the current bit is a "1", to prevent slowdown when there are long breaks between transmissions. + if (current_bit == false) + return true; + + //Can't be bothered dealing with the corner case where the serial pointer is at the very start of the buffer. + //Just return and try again next time. + int left_coord = serialPtr_bit - (8*m_parent->m_samples_per_second)/ m_settings.baudRate; + LIBRADOR_LOG(LOG_DEBUG, "left_coord = %d", left_coord); + if (left_coord < 0) + return true; //Don't want to read out of bounds!! + + //The usual case, when transmitting anyway. + uint8_t left_byte = (m_parent->get(left_coord/8) & 0xff); + //Only run when a zero is detected in the leftmost symbol. + if (left_byte != 0xff) + { + //Step back, one sample at a time, to the 0->1 transition point + bool temp_bit = 1; + while(temp_bit) + { + temp_bit = getNextUartBit(); + serialPtr_bit--; + } + //Jump the pointer forward by half a uart bit period, and return "done!". + serialPtr_bit += (8*m_parent->m_samples_per_second/ m_settings.baudRate)/2; + return false; + } + return true; +} + +char uartStyleDecoder::decodeBaudot(short symbol) const +{ + return 'a'; +} + +bool uartStyleDecoder::isParityCorrect(unsigned short bitField, bool bitValue) const +{ + assert(m_settings.parity != UartParity::None); + bitField |= (bitValue << parityIndex); + + return parityOf(bitField) == m_settings.parity; +} + +UartParity uartStyleDecoder::parityOf(unsigned short bitField) const +{ + bool result = false; + + for (uint32_t mask = 1 << dataBit_max; mask != 0; mask >>= 1) + result ^= static_cast(bitField & mask); + + return result ? UartParity::Odd : UartParity::Even; +} + +void uartStyleDecoder::setSettings(UartSettings new_settings) +{ + if(new_settings.decode_on && !m_settings.decode_on) { + switched_on = true; + } else if (!new_settings.decode_on) { + switched_on = false; + uartTransmitting = false; + escape_code_started = false; + } + + switch(new_settings.parity) + { + case UartParity::None: + parityIndex = UINT_MAX; + break; + case UartParity::Even: + case UartParity::Odd: + parityIndex = dataBit_max; + } + m_settings = new_settings; +} + +char * uartStyleDecoder::getString(bool* parity_check) +{ + if(m_settings.parity == UartParity::None) { + *parity_check = true; + } else { + *parity_check = !parityCheckFailed; + } + memcpy(convertedStream_string, m_serialBuffer.begin(), sizeof(char) * m_serialBuffer.size()); + convertedStream_string[m_serialBuffer.size()] = '\0'; + return convertedStream_string; +} diff --git a/Android_App/app/src/main/cpp/deps/librador/uartstyledecoder.h b/Android_App/app/src/main/cpp/deps/librador/uartstyledecoder.h new file mode 100644 index 000000000..b175c30f9 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/uartstyledecoder.h @@ -0,0 +1,86 @@ +#ifndef UARTSTYLEDECODER_H +#define UARTSTYLEDECODER_H + +#include "isobufferbuffer.h" +#include "logging_internal.h" +#include "o1buffer.h" +#include +#include +#include +#include + +#define SERIAL_BUFFER_LENGTH 8192 + +constexpr double SERIAL_DELAY = 0.01; //100 baud? + +enum class UartParity : uint8_t +{ + None, + Even, + Odd +}; +struct UartSettings { + bool decode_on = false; + double baudRate = 300; + UartParity parity = UartParity::None; +}; + +class uartStyleDecoder +{ +public: + explicit uartStyleDecoder(o1buffer *parent); +private: + o1buffer *m_parent; + + // Indicates the current bit being decoded. + int serialPtr_bit; + + bool allZeroes = false; + bool uartTransmitting = false; + bool newUartSymbol = false; + uint32_t dataBit_current = 0; + uint32_t parityIndex = UINT_MAX; + uint32_t dataBit_max = 8; + unsigned short currentUartSymbol = 0; + bool jitterCompensationNeeded = true; + + void updateSerialPtr(bool current_bit); + bool getNextUartBit() const; + void decodeNextUartBit(bool bitValue); + bool jitterCompensationProcedure(bool current_bit); + + bool m_hexDisplay = false; + bool escape_code_started = false; + + char convertedStream_string[SERIAL_BUFFER_LENGTH + 1]; + isoBufferBuffer m_serialBuffer; + + bool switched_on = false; +public: + +// QTimer m_updateTimer; // IMPORTANT: must be after m_serialBuffer. construction / destruction order matters + void UartDecode(); + int serialDistance() const; + + void wireDisconnected(int ch); + + void updateConsole(); + + void setSettings(UartSettings new_settings); + + char * getString(bool* parity_check); +private: + char decodeDatabit(int mode, short symbol) const; + char decodeBaudot(short symbol) const; + + std::mutex mutex; + + bool isParityCorrect(unsigned short bitField, bool bitValue) const; + UartParity parityOf(unsigned short bitField) const; + + bool parityCheckFailed = false; + + UartSettings m_settings; +}; + +#endif // UARTSTYLEDECODER_H diff --git a/Android_App/app/src/main/cpp/deps/librador/usbcallhandler.cpp b/Android_App/app/src/main/cpp/deps/librador/usbcallhandler.cpp new file mode 100644 index 000000000..e297e2775 --- /dev/null +++ b/Android_App/app/src/main/cpp/deps/librador/usbcallhandler.cpp @@ -0,0 +1,942 @@ +#include "usbcallhandler.h" +//#include + +#include +#include "logging_internal.h" +#include "uartstyledecoder.h" +#include +#include + +#ifdef PLATFORM_ANDROID +#include +#include +#include "SDL_android.h" +#endif + +void LIBUSB_CALL isoCallback(struct libusb_transfer * transfer){ + //Thread mutex?? + //printf("Copy the data...\n"); + usbCallHandler *usb_driver = (usbCallHandler *)transfer->user_data; + if(transfer->status==LIBUSB_TRANSFER_COMPLETED) + { + usb_driver->buffer_read_write_mutex.lock(); + for(int i=0;inum_iso_packets;i++){ + unsigned char *packetPointer = libusb_get_iso_packet_buffer_simple(transfer, i); + //TODO: a switch statement here to handle all the modes. + switch(usb_driver->deviceMode){ + case 0: + usb_driver->internal_o1_buffer_375_CHA->addVector((char*) packetPointer, 375); + break; + case 1: + usb_driver->internal_o1_buffer_375_CHA->addVector((char*) packetPointer, 375); + usb_driver->internal_o1_buffer_375_CHB->addVector((unsigned char*) &packetPointer[375], 375); + break; + case 2: + usb_driver->internal_o1_buffer_375_CHA->addVector((char*) packetPointer, 375); + usb_driver->internal_o1_buffer_375_CHB->addVector((char*) &packetPointer[375], 375); + break; + case 3: + usb_driver->internal_o1_buffer_375_CHA->addVector((unsigned char*) packetPointer, 375); + break; + case 4: + usb_driver->internal_o1_buffer_375_CHA->addVector((unsigned char*) packetPointer, 375); + usb_driver->internal_o1_buffer_375_CHB->addVector((unsigned char*) &packetPointer[375], 375); + break; + case 6: + usb_driver->internal_o1_buffer_750->addVector((char*) packetPointer, 750); + break; + case 7: + usb_driver->internal_o1_buffer_375_CHA->addVector((short*) packetPointer, 375); + break; + } + } + usb_driver->buffer_read_write_mutex.unlock(); + } + + //printf("Re-arm the endpoint...\n"); + if(!usb_driver->is_iso_thread_shutdown_requested()){ + int error = libusb_submit_transfer(transfer); + if(error){ + LIBRADOR_LOG(LOG_WARNING, "Error re-arming the endpoint!\n"); + usb_driver->begin_iso_thread_shutdown(); + usb_driver->decrement_remaining_transfers(); + LIBRADOR_LOG(LOG_WARNING, "Transfer not being rearmed! %d armed transfers remaining\n", usb_driver->iso_thread_shutdown_remaining_transfers); + } + } else { + usb_driver->decrement_remaining_transfers(); + LIBRADOR_LOG(LOG_WARNING, "Transfer not being rearmed! %d armed transfers remaining\n", usb_driver->iso_thread_shutdown_remaining_transfers); + } + return; +} + +int usbCallHandler::begin_iso_thread_shutdown(){ + iso_thread_shutdown_mutex.lock(); + iso_thread_shutdown_requested = true; + iso_thread_shutdown_mutex.unlock(); + return 0; +} + +bool usbCallHandler::is_iso_thread_shutdown_requested(){ + bool tempReturn; + iso_thread_shutdown_mutex.lock(); + tempReturn = iso_thread_shutdown_requested; + iso_thread_shutdown_mutex.unlock(); + return tempReturn; +} + +int usbCallHandler::decrement_remaining_transfers(){ + iso_thread_shutdown_mutex.lock(); + iso_thread_shutdown_remaining_transfers--; + iso_thread_shutdown_mutex.unlock(); + return 0; +} + +bool usbCallHandler::safe_to_exit_thread(){ + bool tempReturn; + iso_thread_shutdown_mutex.lock(); + tempReturn = (iso_thread_shutdown_remaining_transfers == 0); + iso_thread_shutdown_mutex.unlock(); + return tempReturn; +} + + +// it makes sense to call this iso_polling_function because we only use libusb's asynchronous API for isochronous transfers +void usbCallHandler::iso_polling_function(libusb_context *ctx){ + LIBRADOR_LOG(LOG_DEBUG, "iso_polling_function thread spawned\n"); + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0;//ISO_PACKETS_PER_CTX*4000; + while(!safe_to_exit_thread()){ + //printf("iso_polling_function begin loop\n"); + if(libusb_event_handling_ok(ctx)){ + libusb_handle_events_timeout(ctx, &tv); + } + } + get_set_iso_thread_active_mutex.lock(); + iso_thread_active = false; + get_set_iso_thread_active_mutex.unlock(); + LIBRADOR_LOG(LOG_DEBUG, "iso_polling_function thread finished\n"); +} + +usbCallHandler::usbCallHandler(unsigned short VID_in, unsigned short PID_in) +{ + VID = VID_in; + PID = PID_in; + + for(int k=0; kjoin(); + LIBRADOR_LOG(LOG_DEBUG, "USB polling thread stopped.\n"); + delete iso_polling_thread; + + for (int i=0; iGetObjectClass(MainActivityObject)); + jfieldID bootloader_mode_allowedID = env->GetFieldID(MainActivity, "bootloader_mode_allowed", "Z"); + env->SetBooleanField(MainActivityObject, bootloader_mode_allowedID, allowed); + LIBRADOR_LOG(LOG_DEBUG, "bootloader_mode_allowed set"); +} + +// call sequence: usbcallhandler.cpp(requestFirmwareFlash) -> MainActivity.java -> librador -> here, all just to get a dialog window before reset_device (below) triggers a usb permission request +void usbCallHandler::initiateFirmwareFlash() +{ + set_bootloader_mode_allowed(true); + reset_device(true); // will trigger respondToStartupOrUsbStateChange with the device in bootloader mode. + connected = false; + libusb_release_interface(handle, 0); + libusb_close(handle); + libusb_exit(ctx); + ctx = nullptr; + handle= nullptr; +} +#endif + +//Initialise libusb +int usbCallHandler::init_libusb(){ + if(ctx){ + LIBRADOR_LOG(LOG_DEBUG, "There is already a libusb context!\n"); + return 1; + } else LIBRADOR_LOG(LOG_DEBUG, "libusb context is null\n"); + struct libusb_init_option libusb_options[2] = { + {.option = LIBUSB_OPTION_NO_DEVICE_DISCOVERY}, + {.option = LIBUSB_OPTION_LOG_LEVEL, .value = {.ival = 3}} + }; + int error = libusb_init_context(&ctx, libusb_options, 2); + if(error){ + LIBRADOR_LOG(LOG_WARNING, "libusb_init FAILED\n"); + return -1; + } else { + LIBRADOR_LOG(LOG_DEBUG, "Libusb context initialised\n"); + return 0; + } +} + +int usbCallHandler::setup_usb_control(int file_descriptor){ + LIBRADOR_LOG(LOG_DEBUG, "usbCallHandler::setup_usb_control()\n"); + //Get a handle on the Labrador device + libusb_wrap_sys_device(ctx, file_descriptor, &handle); + + if(!handle){ + LIBRADOR_LOG(LOG_DEBUG, "DEVICE NOT FOUND\n"); + return -2; + } + LIBRADOR_LOG(LOG_DEBUG, "Device found!!\n"); + + if(libusb_kernel_driver_active(handle, 0)) { + LIBRADOR_LOG(LOG_DEBUG, "KERNEL DRIVER ACTIVE"); + } else { + LIBRADOR_LOG(LOG_DEBUG, "KERNEL DRIVER INACTIVE"); + } + if(libusb_kernel_driver_active(handle, 0)){ + libusb_detach_kernel_driver(handle, 0); + } + + //Claim the interface + int error = libusb_claim_interface(handle, 0); + if(error){ + LIBRADOR_LOG(LOG_WARNING, "libusb_claim_interface FAILED\n"); + libusb_close(handle); + handle = nullptr; + return -3; + } else LIBRADOR_LOG(LOG_DEBUG, "Interface claimed!\n"); + return 0; +} + +// should only be called if iso_polling_thread is not active. This means either the thread has never been set up OR it was previously set up but has exited iso_polling_function. +int usbCallHandler::setup_usb_iso(){ + LIBRADOR_LOG(LOG_DEBUG, "usbCallHandler::setup_usb_iso()\n"); + if(iso_polling_thread) { + LIBRADOR_LOG(LOG_DEBUG, "iso polling thead already exists"); + return -1; + } else { + LIBRADOR_LOG(LOG_DEBUG, "creating iso polling thread"); + + alloc_iso_transfers(); + + int error = submit_iso_transfers(); + if(error) { + return error; + LIBRADOR_LOG(LOG_WARNING, "setup_usb_iso failed\n"); + } + iso_polling_thread = new std::thread(&usbCallHandler::iso_polling_function, this, ctx); + iso_thread_active = true; + } + return 0; +} + +void usbCallHandler::alloc_iso_transfers(){ + for(int n=0;n 1 ms (amount of data per packet) * NUM_PACKETS_PER_CTX * NUM_FUTURE_CTX + libusb_set_iso_packet_lengths(isoCtx[k][n], ISO_PACKET_SIZE); + } + } +} + +int usbCallHandler::submit_iso_transfers(){ + for(int n=0;ntrfcntH0 << 8) + udsPtr->trfcntL0; + uint16_t trfcnt1 = (udsPtr->trfcntH1 << 8) + udsPtr->trfcntL1; + uint16_t medianTrfcnt = (udsPtr->medianTrfcntH << 8) + udsPtr->medianTrfcntL; + uint16_t outOfRange = (udsPtr->outOfRangeH << 8) + udsPtr->outOfRangeL; + uint16_t counter = (udsPtr->counterH << 8) + udsPtr->counterL; + uint16_t dma_ch0_cnt = (udsPtr->dma_ch0_cntH << 8) + udsPtr->dma_ch0_cntL; + uint16_t dma_ch1_cnt = (udsPtr->dma_ch1_cntH << 8) + udsPtr->dma_ch1_cntL; + + LIBRADOR_LOG(LOG_DEBUG, "%s", udsPtr->header); + LIBRADOR_LOG(LOG_DEBUG, "trfcnt0 = %d\n", trfcnt0); + LIBRADOR_LOG(LOG_DEBUG, "trfcnt1 = %d\n", trfcnt1); + LIBRADOR_LOG(LOG_DEBUG, "medianTrfcnt = %d\n", medianTrfcnt); + LIBRADOR_LOG(LOG_DEBUG, "outOfRange = %d\n", outOfRange); + LIBRADOR_LOG(LOG_DEBUG, "counter = %d\n", counter); + LIBRADOR_LOG(LOG_DEBUG, "calValNeg = %d\n", udsPtr->calValNeg); + LIBRADOR_LOG(LOG_DEBUG, "calValPos = %d\n", udsPtr->calValPos); + LIBRADOR_LOG(LOG_DEBUG, "CALA = %d\n", udsPtr->CALA); + LIBRADOR_LOG(LOG_DEBUG, "CALB = %d\n", udsPtr->CALB); + LIBRADOR_LOG(LOG_DEBUG, "dma_ch0_cnt = %d\n", dma_ch0_cnt); + LIBRADOR_LOG(LOG_DEBUG, "dma_ch1_cnt = %d\n", dma_ch1_cnt); + + return 0; +} + +std::vector* usbCallHandler::getMany_double(int channel, int numToGet, double interval_samples, int delay_sample, int filter_mode){ +std::vector* temp_to_return = nullptr; + +buffer_read_write_mutex.lock(); + int delay_including_trigger; + bool single_shot_reached = false; + int trigger_delay = 0; + switch(deviceMode){ + case 0: + if(channel == 1) { + delay_including_trigger = internal_o1_buffer_375_CHA->getDelayIncludingFromTrigger(delay_sample, round(interval_samples * numToGet)); + temp_to_return = internal_o1_buffer_375_CHA->getMany_double(numToGet, interval_samples, delay_including_trigger, filter_mode, current_scope_gain, false); + } + break; + case 1: + if(channel == 1) { + delay_including_trigger = internal_o1_buffer_375_CHA->getDelayIncludingFromTrigger(delay_sample, round(interval_samples * numToGet), &single_shot_reached, &trigger_delay); + internal_o1_buffer_375_CHB->setPaused(single_shot_reached,-trigger_delay, true); // dont multiply trigger_delay by 8 b/c each sample of the buffer is 8 subsamples + temp_to_return = internal_o1_buffer_375_CHA->getMany_double(numToGet, interval_samples, delay_including_trigger, filter_mode, current_scope_gain, false); + } + break; + case 2: + if(internal_o1_buffer_375_CHA->isTriggeringEnabled() && ((channel==1) || ((channel == 2) && !internal_o1_buffer_375_CHB->getPaused()))) { + delay_including_trigger = internal_o1_buffer_375_CHA->getDelayIncludingFromTrigger(delay_sample, round(interval_samples * numToGet), &single_shot_reached, &trigger_delay); + internal_o1_buffer_375_CHB->setPaused(single_shot_reached,-trigger_delay,true); // only relevant when single-shot triggering + } else if (internal_o1_buffer_375_CHB->isTriggeringEnabled() && ((channel==2) || ((channel == 1) && !internal_o1_buffer_375_CHA->getPaused()))) { + delay_including_trigger = internal_o1_buffer_375_CHB->getDelayIncludingFromTrigger(delay_sample, round(interval_samples * numToGet), &single_shot_reached, &trigger_delay); + internal_o1_buffer_375_CHA->setPaused(single_shot_reached,-trigger_delay,true);// only relevant when single-shot triggering + } else { + delay_including_trigger = delay_sample; + } + if(channel == 1) temp_to_return = internal_o1_buffer_375_CHA->getMany_double(numToGet, interval_samples, delay_including_trigger, filter_mode, current_scope_gain, false); + else if (channel == 2) temp_to_return = internal_o1_buffer_375_CHB->getMany_double(numToGet, interval_samples, delay_including_trigger, filter_mode, current_scope_gain, false); + break; + case 6: + if(channel == 1){ + delay_including_trigger = internal_o1_buffer_750->getDelayIncludingFromTrigger(delay_sample, round(interval_samples * numToGet)); + temp_to_return = internal_o1_buffer_750->getMany_double(numToGet, interval_samples, delay_including_trigger, filter_mode, current_scope_gain, false); + } + break; + case 7: + if(channel == 1) { + delay_including_trigger = internal_o1_buffer_375_CHA->getDelayIncludingFromTrigger(delay_sample, round(interval_samples * numToGet)); + temp_to_return = internal_o1_buffer_375_CHA->getMany_double(numToGet, interval_samples, delay_including_trigger, filter_mode, current_scope_gain, true); + } + break; + } + buffer_read_write_mutex.unlock(); + return temp_to_return; +} + +std::vector * usbCallHandler::getMany_singleBit(int channel, int numToGet, double interval_subsamples, int delay_subsamples){ + std::vector* temp_to_return = nullptr; + + buffer_read_write_mutex.lock(); + switch(deviceMode){ + case 1: + if(channel == 2) + { + int delay_including_trigger; + if(internal_o1_buffer_375_CHA->isTriggeringEnabled()) { + bool single_shot_reached = false; + int trigger_delay = 0; + delay_including_trigger = internal_o1_buffer_375_CHA->getDelayIncludingFromTrigger(static_cast(round(delay_subsamples/8.)), round(interval_subsamples/8. * numToGet), &single_shot_reached, &trigger_delay) * 8; + internal_o1_buffer_375_CHB->setPaused(single_shot_reached,-trigger_delay, true); // dont multiply trigger_delay by 8 b/c each sample of the buffer is 8 subsamples + } else { + delay_including_trigger = delay_subsamples; + } + + temp_to_return = internal_o1_buffer_375_CHB->getMany_singleBit(numToGet, interval_subsamples, delay_including_trigger); + internal_o1_buffer_375_CHB->UartDecode(); + } + break; + case 3: + if(channel == 1) + { + temp_to_return = internal_o1_buffer_375_CHA->getMany_singleBit(numToGet, interval_subsamples, delay_subsamples); + internal_o1_buffer_375_CHA->UartDecode(); + } + break; + case 4: + if(channel == 1){ + temp_to_return = internal_o1_buffer_375_CHA->getMany_singleBit(numToGet, interval_subsamples, delay_subsamples); + internal_o1_buffer_375_CHA->UartDecode(); + } + else if (channel == 2){ + temp_to_return = internal_o1_buffer_375_CHB->getMany_singleBit(numToGet, interval_subsamples, delay_subsamples); + internal_o1_buffer_375_CHB->UartDecode(); + } + try + { + m_i2c_decoder->run(); + } + catch(...) + { + LIBRADOR_LOG(LOG_DEBUG, "Resetting I2C"); + m_i2c_decoder->reset(); + } + break; + } + buffer_read_write_mutex.unlock(); + return temp_to_return; +} + +char * usbCallHandler::getUart_String(int channel, bool* parity_check) +{ + char * temp_to_return = nullptr; + if(channel == 1) + temp_to_return = internal_o1_buffer_375_CHA->getUart_String(parity_check); + else if (channel == 2) + temp_to_return = internal_o1_buffer_375_CHB->getUart_String(parity_check); + return temp_to_return; +} + +std::vector *usbCallHandler::getMany_sincelast(int channel, int feasible_window_begin, int feasible_window_end, int interval_samples, int filter_mode){ + std::vector* temp_to_return = nullptr; + buffer_read_write_mutex.lock(); + switch(deviceMode){ + case 0: + if(channel == 1) temp_to_return = internal_o1_buffer_375_CHA->getSinceLast(feasible_window_begin, feasible_window_end, interval_samples, filter_mode, current_scope_gain, false); + break; + case 1: + if(channel == 1) temp_to_return = internal_o1_buffer_375_CHA->getSinceLast(feasible_window_begin, feasible_window_end, interval_samples, filter_mode, current_scope_gain, false); + break; + case 2: + if(channel == 1) temp_to_return = internal_o1_buffer_375_CHA->getSinceLast(feasible_window_begin, feasible_window_end, interval_samples, filter_mode, current_scope_gain, false); + else if (channel == 2) temp_to_return = internal_o1_buffer_375_CHB->getSinceLast(feasible_window_begin, feasible_window_end, interval_samples, filter_mode, current_scope_gain, false); + break; + case 6: + if(channel == 1) temp_to_return = internal_o1_buffer_750->getSinceLast(feasible_window_begin, feasible_window_end, interval_samples, filter_mode, current_scope_gain, false); + break; + case 7: + if(channel == 1) temp_to_return = internal_o1_buffer_375_CHA->getSinceLast(feasible_window_begin, feasible_window_end, interval_samples, filter_mode, current_scope_gain, false); + break; + } + buffer_read_write_mutex.unlock(); + return temp_to_return; + +} + +int usbCallHandler::send_device_reset(){ + libusb_reset_device(handle); + return 0; +} + + +int usbCallHandler::set_device_mode(int mode){ + if((mode < 0) || (mode > MAX_SUPPORTED_DEVICE_MODE)){ + return -1; + } + deviceMode = mode; + send_control_transfer_with_error_checks(0x40, 0xa5, (mode == 5 ? 0 : mode), gainMask, 0, nullptr); + + send_function_gen_settings(1); + send_function_gen_settings(2); + + internal_o1_buffer_375_CHA->reset(false); + internal_o1_buffer_375_CHA->resetTrigger(current_scope_gain, deviceMode==7); + internal_o1_buffer_375_CHB->reset(false); + internal_o1_buffer_375_CHB->resetTrigger(current_scope_gain, deviceMode==7); + internal_o1_buffer_750->reset(false); + internal_o1_buffer_750->resetTrigger(current_scope_gain, deviceMode==7); + + return 0; +} + +int usbCallHandler::set_gain(double newGain){ + //See XMEGA_AU Manual, page 359. ADC.CTRL.GAIN. + if(newGain==0.5) gainMask = 0x07; + else if (newGain == 1) gainMask = 0x00; + else if (newGain == 2) gainMask = 0x01; + else if (newGain == 4) gainMask = 0x02; + else if (newGain == 8) gainMask = 0x03; + else if (newGain == 16) gainMask = 0x04; + else if (newGain == 32) gainMask = 0x05; + else if (newGain == 64) gainMask = 0x06; + else{ + LIBRADOR_LOG(LOG_ERROR, "Invalid gain value. Valid values are 0.1, 1, 2, 4, 8, 16, 32, 64\n"); + return -1; + } + + gainMask = gainMask << 2; + gainMask |= (gainMask << 8); + send_control_transfer_with_error_checks(0x40, 0xa5, deviceMode, gainMask, 0, nullptr); + current_scope_gain = newGain; + return 0; +} + +int usbCallHandler::update_function_gen_settings(int channel, unsigned char *sampleBuffer, int numSamples, double usecs_between_samples, double amplitude_v, double offset_v){ + + + //Parse the channel + fGenSettings *fGenSelected; + if(channel == 1){ + fGenSelected = &functionGen_CH1; + } else if (channel == 2){ + fGenSelected = &functionGen_CH2; + } else { + return -1; //Invalid channel + } + + //Update number of samples. + fGenSelected->numSamples = numSamples; + + //Does the output amplifier need to be enabled? + if ((amplitude_v+offset_v) > FGEN_LIMIT){ + amplitude_v = amplitude_v / 3; + offset_v = offset_v / 3; + if(channel == 1){ + fGenTriple |= 0b00000001; + } else { + fGenTriple |= 0b00000010; + } + } + else { + if(channel == 1){ + fGenTriple &= 0b11111110; + } else { + fGenTriple &= 0b11111101; + } + } + + //Fiddle with the waveform to deal with the fact that the Xmega has a minimum DAC output value. + double amplitude_sample = (amplitude_v * 255) / FGEN_LIMIT; + double offset_sample = (offset_v * 255) / FGEN_LIMIT; + if (offset_sample < FGEN_SAMPLE_MIN){ //If the offset is in the range where the XMEGA output cannot physically drive the signal that low... + if (amplitude_sample > FGEN_SAMPLE_MIN){ //And the amplitude of the signal can go above this minimum range + amplitude_sample -= FGEN_SAMPLE_MIN; //Then reduce the amplitude and add a small offset + } + else { + amplitude_sample = 0; + } + offset_sample = FGEN_SAMPLE_MIN; + } + + //Apply amplitude and offset scaling to all samples. + double tempDouble; + for (int i=0;isamples[i] = (uint8_t) tempDouble; + } + + //Calculate timer values + int validClockDivs[7] = {1, 2, 4, 8, 64, 256, 1024}; + + int clkSetting; + for(clkSetting = 0; clkSetting<7; clkSetting++){ + if ( ((XMEGA_MAIN_FREQ * usecs_between_samples)/(1000000 * validClockDivs[clkSetting])) < 65535) + break; + } + fGenSelected->timerPeriod = (usecs_between_samples * XMEGA_MAIN_FREQ) / (1000000 * validClockDivs[clkSetting]) - 0.5; + fGenSelected->clockDividerSetting = clkSetting + 1; + + return 0; +} + +int usbCallHandler::send_function_gen_settings(int channel){ + if(channel == 1){ + if(functionGen_CH1.numSamples == 0){ + return -1; //Channel not initialised + } + send_control_transfer_with_error_checks(0x40, 0xa2, functionGen_CH1.timerPeriod, functionGen_CH1.clockDividerSetting, functionGen_CH1.numSamples, functionGen_CH1.samples); + } else if (channel == 2){ + if(functionGen_CH2.numSamples == 0){ + return -1; //Channel not initialised + } + send_control_transfer_with_error_checks(0x40, 0xa1, functionGen_CH2.timerPeriod, functionGen_CH2.clockDividerSetting, functionGen_CH2.numSamples, functionGen_CH2.samples); + } else { + return -2; //Invalid channel + } + send_control_transfer_with_error_checks(0x40, 0xa4, fGenTriple, 0, 0, nullptr); + return 0; +} + +int usbCallHandler::set_psu_voltage(double voltage){ + double vinp = voltage/11; + double vinn = 0; + + uint8_t dutyPsu = (uint8_t) ((vinp - vinn)/vref_psu * gain_psu * PSU_ADC_TOP); + + + if ((dutyPsu>106) || (dutyPsu<21)){ + return -1; //Out of range + } + send_control_transfer_with_error_checks(0x40, 0xa3, dutyPsu, 0, 0, nullptr); + return 0; +} + +int usbCallHandler::set_digital_state(uint8_t digState){ + send_control_transfer_with_error_checks(0x40, 0xa6, digState, 0, 0, nullptr); + return 0; +} + +int usbCallHandler::reset_device(bool goToBootloader){ + LIBRADOR_LOG(LOG_DEBUG, "resetting device: unimportant error LIBUSB_ERROR_NO_DEVICE for send_control_transfer expected"); // only if goToBootloader? + send_control_transfer_with_error_checks(0x40, 0xa7, (goToBootloader ? 1 : 0), 0, 0, nullptr); + return 0; +} + +uint16_t usbCallHandler::get_firmware_version(){ + send_control_transfer_with_error_checks(0xc0, 0xa8, 0, 0, 2, nullptr); + return *((uint16_t *) inBuffer); +} + +uint8_t usbCallHandler::get_firmware_variant(){ + send_control_transfer_with_error_checks(0xc0, 0xa9, 0, 0, 1, nullptr); + return *((uint8_t *) inBuffer); +} + +double usbCallHandler::get_samples_per_second(){ + switch(deviceMode){ + case 0: + return (double)(375000.0); + case 1: + return (double)(375000.0); + case 2: + return (double)(375000.0); + case 3: + return (double)(375000.0); + case 4: + return (double)(375000.0); + case 6: + return (double)(750000.0); + case 7: + return (double)(375000.0); + default: + return 0; + } +} + +int usbCallHandler::set_synchronous_pause_state(bool newState){ + if(newState && !synchronous_pause_state){ + buffer_read_write_mutex.lock(); + synchronous_pause_state = true; + return 0; + } + + if(!newState && synchronous_pause_state){ + buffer_read_write_mutex.unlock(); + return 0; + } + + //Otherwise you don't want to do anything. You should never set the state twice. + return 1; +} + +int usbCallHandler::setPaused(int channel, bool is_paused) +{ + if(channel==1){ + if(deviceMode==6) + return internal_o1_buffer_750->setPaused(is_paused); + else + return internal_o1_buffer_375_CHA->setPaused(is_paused); + } else if (channel == 2){ + return internal_o1_buffer_375_CHB->setPaused(is_paused); + } + return 1; +} + +bool usbCallHandler::getPaused(int channel) +{ + if(channel==1){ + if(deviceMode==6) + return internal_o1_buffer_750->getPaused(); + else + return internal_o1_buffer_375_CHA->getPaused(); + } else if (channel == 2){ + return internal_o1_buffer_375_CHB->getPaused(); + } + return 1; +} + +void usbCallHandler::setTriggerSettings(int channel, o1buffer::trigger_settings new_trigger_settings) +{ + setSettingsForChannel(channel, new_trigger_settings, internal_o1_buffer_375_CHA, internal_o1_buffer_375_CHB, internal_o1_buffer_750, &o1buffer::setTriggerSettings); +} + +void usbCallHandler::setVirtualTransformSettings(int channel, o1buffer::virtual_transform_settings new_virtual_transform_settings) +{ + setSettingsForChannel(channel, new_virtual_transform_settings, internal_o1_buffer_375_CHA, internal_o1_buffer_375_CHB, internal_o1_buffer_750, &o1buffer::setVirtualTransformSettings); +} + +void usbCallHandler::setUartDecodeSettings(int channel, UartSettings new_settings) +{ + if(channel==1) + internal_o1_buffer_375_CHA->setUartDecodeSettings(new_settings); + else if (channel==2) + internal_o1_buffer_375_CHB->setUartDecodeSettings(new_settings); +} + char *usbCallHandler::getI2c_String() +{ + return m_i2c_decoder->getString(); +} + +void usbCallHandler::setI2cIsDecoding(bool new_decode_on){ + m_i2c_decoder->setIsDecoding(new_decode_on); +} + +bool usbCallHandler::isoThreadIsActive(){ + bool tempReturn; + get_set_iso_thread_active_mutex.lock(); + tempReturn = iso_thread_active; + get_set_iso_thread_active_mutex.unlock(); + return tempReturn; +} + +#ifdef PLATFORM_ANDROID +int usbCallHandler::flashFirmware(int file_descriptor){ + + libusb_device *device_ptr; + + init_libusb(); + int error = setup_usb_control(file_descriptor); + + JNIEnv *env = (JNIEnv *) SDL_GetAndroidJNIEnv(); + jobject MainActivityObject = (jobject) SDL_GetAndroidActivity(); + jclass MainActivity(env->GetObjectClass(MainActivityObject)); + + jfieldID asset_manager_id = env->GetFieldID(MainActivity, "mgr", "Landroid/content/res/AssetManager;"); + jobject mgr_java = (jobject)env->GetObjectField(MainActivityObject, asset_manager_id); + const char* external_filepath = SDL_GetAndroidExternalStoragePath(); + AAssetManager * mgr = AAssetManager_fromJava(env, mgr_java); + + const char* firmware_filename = "labrafirm_0007_02.hex"; + char apk_firmware_filepath[64]; + strcpy(apk_firmware_filepath, "firmware/"); + strcat(apk_firmware_filepath, firmware_filename); + + char firmware_copy_filepath[256]; + strcpy(firmware_copy_filepath, external_filepath); + strcat(firmware_copy_filepath, "/"); + strcat(firmware_copy_filepath, firmware_filename); + LIBRADOR_LOG(LOG_DEBUG, "firmware copy path: %s", firmware_copy_filepath); + + AAsset* asset = AAssetManager_open(mgr, apk_firmware_filepath, AASSET_MODE_STREAMING); + char buf[2048]; + int nb_read = 0; + FILE* out = fopen(firmware_copy_filepath, "w+"); + while ((nb_read = AAsset_read(asset, buf, 2048)) > 0) + fwrite(buf, nb_read, 1, out); + fclose(out); + AAsset_close(asset); + + LIBRADOR_LOG(LOG_DEBUG, "FLASHING %s", firmware_copy_filepath); + + //Set up interface to dfuprog + int exit_code; + char command[256]; + + //Run stage 1 + LIBRADOR_LOG(LOG_DEBUG, "\n\nFlashing Firmware, stage 1.\n\n"); + snprintf(command, sizeof command, "dfu-programmer atxmega32a4u erase --force --debug 300"); + exit_code = dfuprog_virtual_cmd(command, device_ptr, handle, ctx, 0); + if (exit_code) { + LIBRADOR_LOG(LOG_WARNING, "ERROR ERASING FIRMWARE."); + } else { + LIBRADOR_LOG(LOG_DEBUG, "ERASED FIRMWARE."); + } + ctx = nullptr; + handle= nullptr; + + init_libusb(); + error = setup_usb_control(file_descriptor); + + //Run stage 2 + LIBRADOR_LOG(LOG_DEBUG, "\n\nFlashing Firmware, stage 2.\n\n"); + snprintf(command, sizeof command, "dfu-programmer atxmega32a4u flash %s --debug 300", firmware_copy_filepath); + exit_code = dfuprog_virtual_cmd(command, device_ptr, handle, ctx, 0); + if (exit_code) { + LIBRADOR_LOG(LOG_WARNING, "\n\n\nERROR WRITING NEW FIRMWARE TO DEVICE.\n\n\n"); + //return exit_code+200; + } + ctx = nullptr; + handle= nullptr; + + init_libusb(); + error = setup_usb_control(file_descriptor); + + //Run stage 3 + LIBRADOR_LOG(LOG_DEBUG, "\n\nFlashing Firmware, stage 3.\n\n"); + dfu_launch(); + starting_after_flash = true; + return 0; +} +#endif + +void usbCallHandler::dfu_launch() { + LIBRADOR_LOG(LOG_DEBUG, "\n\n\nDFU LAUNCH.\n\n\n"); + int exit_code; + char command[256]; + libusb_device *device_ptr; + snprintf(command, sizeof command, "dfu-programmer atxmega32a4u launch"); + exit_code = dfuprog_virtual_cmd(command, device_ptr, handle, ctx, 0); + if (exit_code) { + LIBRADOR_LOG(LOG_WARNING, "\n\n\n DFU LAUNCH ERROR\n\n\n"); + //return exit_code+300; + } + ctx = nullptr; + handle= nullptr; +} + +#ifdef PLATFORM_ANDROID +void usbCallHandler::respondToStartupOrUsbStateChange(bool is_plugged_in, int file_descriptor, bool bootloader_mode){ + if(is_plugged_in) { + if(bootloader_mode) { + LIBRADOR_LOG(LOG_DEBUG, "found in bootloader mode"); + if(starting_after_flash) { + LIBRADOR_LOG(LOG_DEBUG, "startup after flash"); + starting_after_flash = false; + init_libusb(); + int error = setup_usb_control(file_descriptor); + set_bootloader_mode_allowed(false); + dfu_launch();// launch twice to clear eeprom flag after flashing + + JNIEnv *env = (JNIEnv *) SDL_GetAndroidJNIEnv(); + jobject MainActivityObject = (jobject) SDL_GetAndroidActivity(); + jclass MainActivity(env->GetObjectClass(MainActivityObject)); + jmethodID confirmFirmwareFlashID = env->GetMethodID(MainActivity, "confirmFirmwareFlash", "()V"); + env->CallVoidMethod(MainActivityObject,confirmFirmwareFlashID); + } else { + int flashRet = flashFirmware(file_descriptor); + LIBRADOR_LOG(LOG_DEBUG, "flashRet: %d", flashRet); + } + } else { + if(connected) { + return; + } + init_libusb(); + int control_setup_success = setup_usb_control(file_descriptor); + if(control_setup_success==0) { + connected = true; + uint16_t firmver = get_firmware_version(); + LIBRADOR_LOG(LOG_DEBUG, "BOARD IS RUNNING FIRMWARE VERSION 0x%04hx", firmver); + LIBRADOR_LOG(LOG_DEBUG, "EXPECTING FIRMWARE VERSION 0x%04hx", EXPECTED_FIRMWARE_VERSION); + + uint8_t variant = get_firmware_variant(); + LIBRADOR_LOG(LOG_DEBUG, "FIRMWARE VARIANT = 0x%02hx", variant); + LIBRADOR_LOG(LOG_DEBUG, "EXPECTED VARIANT = 0x%02hx", DEFINED_EXPECTED_VARIANT); + + if((firmver != EXPECTED_FIRMWARE_VERSION) || (variant != DEFINED_EXPECTED_VARIANT)){ + LIBRADOR_LOG(LOG_DEBUG, "Unexpected Firmware!!"); + + JNIEnv *env = (JNIEnv *) SDL_GetAndroidJNIEnv(); + jobject MainActivityObject = (jobject) SDL_GetAndroidActivity(); + jclass MainActivity(env->GetObjectClass(MainActivityObject)); + jmethodID requestFirmwareFlashID = env->GetMethodID(MainActivity, "requestFirmwareFlash", "()V"); + env->CallVoidMethod(MainActivityObject,requestFirmwareFlashID); + + return; + } else { + int iso_setup_success = setup_usb_iso(); + return; + } + } else { + connected = false; + if(handle){ + libusb_release_interface(handle, 0); + libusb_close(handle); + handle = nullptr; + } + return; + } + } + } else { + connected = false; + if(iso_polling_thread) { + begin_iso_thread_shutdown(); + iso_polling_thread->join(); + delete iso_polling_thread; + +// prepare for possible replug + iso_thread_shutdown_requested = false; + iso_polling_thread = nullptr; + iso_thread_shutdown_remaining_transfers = NUM_FUTURE_CTX; + for (int i=0; i +#include +#include + +#ifdef PLATFORM_ANDROID +#include +#endif + +#define NUM_ISO_ENDPOINTS (1) +#define NUM_FUTURE_CTX (4) +#define ISO_PACKET_SIZE (750) +#define ISO_PACKETS_PER_CTX (33) +#define MAX_SUPPORTED_DEVICE_MODE (7) +#define FGEN_LIMIT (3.2) +#define FGEN_MAX_SAMPLES (512) +#define FGEN_SAMPLE_MIN (5.0) +#define XMEGA_MAIN_FREQ (48000000) +#define PSU_ADC_TOP (128) + +#define EXPECTED_FIRMWARE_VERSION 0x0007 +#define DEFINED_EXPECTED_VARIANT 2 + +//EVERYTHING MUST BE SENT ONE BYTE AT A TIME, HIGH AND LOW BYTES SEPARATE, IN ORDER TO AVOID ISSUES WITH ENDIANNESS. +typedef struct uds{ + volatile char header[9]; + volatile uint8_t trfcntL0; + volatile uint8_t trfcntH0; + volatile uint8_t trfcntL1; + volatile uint8_t trfcntH1; + volatile uint8_t medianTrfcntL; + volatile uint8_t medianTrfcntH; + volatile uint8_t calValNeg; + volatile uint8_t calValPos; + volatile uint8_t CALA; + volatile uint8_t CALB; + volatile uint8_t outOfRangeL; + volatile uint8_t outOfRangeH; + volatile uint8_t counterL; + volatile uint8_t counterH; + volatile uint8_t dma_ch0_cntL; + volatile uint8_t dma_ch0_cntH; + volatile uint8_t dma_ch1_cntL; + volatile uint8_t dma_ch1_cntH; + +} unified_debug; + +typedef struct fGenSettings{ + uint8_t samples[FGEN_MAX_SAMPLES]; + int numSamples = 0; + uint16_t timerPeriod = 0; + uint8_t clockDividerSetting = 0; +} fGenSettings; + +#define send_control_transfer_with_error_checks(A, B, C, D, E, F) \ + int temp_control_transfer_error_value = send_control_transfer(A,B,C,D,E,F); \ + if(temp_control_transfer_error_value < 0){ \ + return temp_control_transfer_error_value - 1000; \ + } + +class usbCallHandler +{ +public: + usbCallHandler(unsigned short VID_in, unsigned short PID_in); + ~usbCallHandler(); + int setup_usb_control(int file_descriptor); + int setup_usb_iso(); + void alloc_iso_transfers(); + int submit_iso_transfers(); + void delete_iso_thread(); + int send_control_transfer(uint8_t RequestType, uint8_t Request, uint16_t Value, uint16_t Index, uint16_t Length, unsigned char *LDATA); + int avrDebug(void); + int send_device_reset(); + double get_samples_per_second(); + std::vector *getMany_double(int channel, int numToGet, double interval_samples, int delay_sample, int filter_mode); + std::vector * getMany_singleBit(int channel, int numToGet, double interval_subsamples, int delay_subsamples); + std::vector *getMany_sincelast(int channel, int feasible_window_begin, int feasible_window_end, int interval_samples, int filter_mode); + bool connected = false; + //Control Commands + int set_device_mode(int mode); + int set_gain(double newGain); + int update_function_gen_settings(int channel, unsigned char* sampleBuffer, int numSamples, double usecs_between_samples, double amplitude_v, double offset_v); + int send_function_gen_settings(int channel); + int set_psu_voltage(double voltage); + int set_digital_state(uint8_t digState); + int reset_device(bool goToBootloader); + uint16_t get_firmware_version(); + uint8_t get_firmware_variant(); + int set_synchronous_pause_state(bool newState); + int setPaused(int channel, bool is_paused); + bool getPaused(int channel); + void setTriggerSettings(int channel, o1buffer::trigger_settings new_trigger_settings); + void setVirtualTransformSettings(int channel, o1buffer::virtual_transform_settings new_virtual_transform_settings); + template + void setSettingsForChannel(int ch, T channel_scope_settings, o1buffer* ch1_375, o1buffer* ch2_375, o1buffer* ch1_750, bool (o1buffer::*setSettings)(T)); + void setUartDecodeSettings(int channel, UartSettings new_settings); + char * getUart_String(int channel, bool* parity_check); + char * getI2c_String(); + void setI2cIsDecoding(bool new_decode_on); + bool isoThreadIsActive(); + int init_libusb(); + void respondToStartupOrUsbStateChange(bool is_plugged_in, int file_descriptor, bool bootloader_mode); + void set_bootloader_mode_allowed(bool allowed); + void initiateFirmwareFlash(); +private: + + unsigned short VID, PID; + libusb_context *ctx = nullptr; + libusb_device_handle *handle = nullptr; + unsigned char inBuffer[256]; + + //USBIso Vars + unsigned char pipeID[NUM_ISO_ENDPOINTS]; + libusb_transfer *isoCtx[NUM_ISO_ENDPOINTS][NUM_FUTURE_CTX]; + unsigned char dataBuffer[NUM_ISO_ENDPOINTS][NUM_FUTURE_CTX][ISO_PACKET_SIZE*ISO_PACKETS_PER_CTX]; + std::thread *iso_polling_thread = nullptr; + //Control Vars + uint8_t fGenTriple = 0; + fGenSettings functionGen_CH1; + fGenSettings functionGen_CH2; + double gain_psu = 1; + double vref_psu = 1.65; + uint16_t gainMask = 0x0000; + double current_scope_gain = 1; + bool synchronous_pause_state = false; + i2c::i2cDecoder* m_i2c_decoder; + + bool starting_after_flash = false; + + int flashFirmware(int file_descriptor); + void closeDevice_cpp(); + int findDevice_cpp(); + void dfu_launch(); + + friend void isoCallback(struct libusb_transfer * transfer); + int deviceMode = 0; + + o1buffer *internal_o1_buffer_375_CHA; + o1buffer *internal_o1_buffer_375_CHB; + o1buffer *internal_o1_buffer_750; + + int begin_iso_thread_shutdown(); + bool is_iso_thread_shutdown_requested(); + int decrement_remaining_transfers(); + void iso_polling_function(libusb_context *ctx); + bool safe_to_exit_thread(); + + + bool iso_thread_shutdown_requested = false; + int iso_thread_shutdown_remaining_transfers = NUM_FUTURE_CTX; + bool iso_thread_active = false; + + std::mutex iso_thread_shutdown_mutex; + std::mutex buffer_read_write_mutex; + std::mutex get_set_iso_thread_active_mutex; +}; + +template +void usbCallHandler::setSettingsForChannel(int ch, T channel_scope_settings, o1buffer* ch1_375, o1buffer* ch2_375, o1buffer* ch1_750, bool (o1buffer::*setSettings)(T)) +{ + bool trigger_reset_needed; + if(ch==1) { + trigger_reset_needed = (ch1_375->*setSettings)(channel_scope_settings); + if(trigger_reset_needed) + ch1_375->resetTrigger(current_scope_gain, deviceMode==7); + trigger_reset_needed = (ch1_750->*setSettings)(channel_scope_settings); + if(trigger_reset_needed) + ch1_750->resetTrigger(current_scope_gain, deviceMode==7); + } else if (ch==2) { + trigger_reset_needed = (ch2_375->*setSettings)(channel_scope_settings); + if(trigger_reset_needed) + ch2_375->resetTrigger(current_scope_gain, deviceMode==7); + } +} +#endif // USBCALLHANDLER_H diff --git a/Android_App/app/src/main/cpp/inputs_ui.cpp b/Android_App/app/src/main/cpp/inputs_ui.cpp new file mode 100644 index 000000000..d64e38236 --- /dev/null +++ b/Android_App/app/src/main/cpp/inputs_ui.cpp @@ -0,0 +1,220 @@ +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" +#include "librador.h" +#include "custom_imgui.h" +#include +#include "imgui_internal.h" +#include "inputs_ui.h" + +ImVec2 center_text(float col_width, float text_width, ImGuiStyle& style) { + return ImGui::GetCursorScreenPos() + ImVec2((col_width - text_width)/2. - style.CellPadding.x, 0.0); // for centered text +} + +ImVec2 center_checkbox_delta(float full_col_width, ImGuiStyle& style) { + return ImVec2((full_col_width - CHECKBOX_SIZE)/2. - style.CellPadding.x, 0.0); // for centered checkbox +} + +void draw_rules(ImVec2 p0, double row_height, double header_row_height, double col_width) +{ + ImGuiStyle& style = ImGui::GetStyle(); + ImVec2 row1_col1_pos = p0 + ImVec2(col_width, header_row_height); + ImVec2 row1_col2_pos = row1_col1_pos + ImVec2(col_width,0); + ImVec2 row3_col2_pos = row1_col1_pos + ImVec2(col_width,2*row_height); + ImVec2 row4_col1_pos = row1_col1_pos + ImVec2(0.,3*row_height); + ImVec2 row4_col2_pos = row1_col1_pos + ImVec2(col_width,3*row_height); + ImVec2 row5_col2_pos = row1_col1_pos + ImVec2(col_width,4*row_height); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddLine(row1_col1_pos, row4_col1_pos, IM_COL32(120, 120, 160, 255)); + draw_list->AddLine(row1_col2_pos, row3_col2_pos, IM_COL32(120, 120, 160, 255)); + draw_list->AddLine(row4_col2_pos, row5_col2_pos, IM_COL32(120, 120, 160, 255)); +} + +void inputsUI::draw(float width_pixels, inputsUI* inputs_ui) +{ + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::BeginGroup(); + standard_header(width_pixels); + if(!is_expanded) + { + ImGui::EndGroup(); + return; + } + ImVec2 start_pos = ImGui::GetCursorScreenPos(); + + bool mode_update = false; + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(style.CellPadding.x, style.CellPadding.y * 2));// if this line is active, make sure that the line that resets CellPadding at the end of this function is active as well + float header_row_height = ImGui::GetFontSize() + style.CellPadding.y*2; + float row_height = (ImGui::GetFontSize() + (style.FramePadding.y + style.CellPadding.y)*2); + float col_width; + if (ImGui::BeginTable("scope_mode", 3, ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_RowBg , ImVec2(width_pixels,0.f))) + { + ImGui::TableNextRow(); + int i = 0; + for(const char * ch_header : {"1","2","CH"}) + { + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(center_text(ImGui::GetColumnWidth() + 2*style.CellPadding.x, ImGui::CalcTextSize(ch_header).x, style)); + ImGui::Text("%s", ch_header); + col_width = ImGui::GetColumnWidth() + 2 * style.CellPadding.x; + i+=1; + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGui::GetColorU32(ImGuiCol_TableHeaderBg) ); + } + + bool scope_checkbox_enable[2] = { + !logic_enable[1] && !mm, + scope_enable[0] && !(logic_enable[0]) && !scope750 + }; + bool logic_checkbox_enable[2] = { + !scope_enable[1] && !scope750 && !mm, + logic_enable[0] && !scope_enable[0] + }; + + bool checkbox_enable[2][2] = { + {scope_checkbox_enable[0], scope_checkbox_enable[1]}, + {logic_checkbox_enable[0], logic_checkbox_enable[1]} + }; + + bool* checkbox_bool[2][2] = { + {&scope_enable[0], &scope_enable[1]}, + {&logic_enable[0], &logic_enable[1]} + }; + const char * checkbox_label_base[2] = {"##enable_scope", "##enable_logic"}; + const char * glyphs[2] = {"\xee\xa4\x81","\xee\xa4\x80"};// includes custom glyphs defined in /font/waveform-glyphs3.ttf + const char * checkbox_label_suffix[2] = {"_ch1","_ch2"}; + char full_checkbox_label[32]{}; + for(int j=0; j<2; j++) + { + ImGui::TableNextRow(); + for(int ch : {1,2} ) + { + ImGui::TableNextColumn(); + strcpy(full_checkbox_label, checkbox_label_base[j]); + strcat(full_checkbox_label, checkbox_label_suffix[ch-1]); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + center_checkbox_delta(ImGui::GetColumnWidth() + 2*style.CellPadding.x, style)); + ImGui::BeginDisabled(!(checkbox_enable[j][ch-1])); + if((ImGui::custom_Checkbox(full_checkbox_label, checkbox_bool[j][ch-1]))||(*checkbox_bool[j][ch-1] && !checkbox_enable[j][ch-1])) { + mode_update = true; + changed = true; + } + *(checkbox_bool[j][ch-1]) &= checkbox_enable[j][ch-1]; + + ImGui::EndDisabled(); + } + + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(center_text(ImGui::GetColumnWidth() + 2*style.CellPadding.x, ImGui::CalcTextSize(glyphs[j]).x, style)); + ImGui::Text("%s",glyphs[j]); + } + ImGui::TableNextRow(0, row_height); + ImGui::TableNextRow(0, row_height); + ImGui::EndTable(); + } + ImVec2 saved_pos = ImGui::GetCursorScreenPos(); + ImVec2 p0 = ImGui::GetItemRectMin(); + ImVec2 end_pos = ImGui::GetItemRectMax(); + int real_height = (end_pos - start_pos).y; + draw_rules(p0, row_height, header_row_height, col_width); + ImVec2 row3_pos = p0 + ImVec2(style.CellPadding.x,header_row_height + 2*row_height + style.CellPadding.y);; + ImVec2 row4_pos = row3_pos + ImVec2(0.f,row_height); + + bool* checkbox_bool[2] = {&scope750, &mm}; + bool checkbox_enable[2] = {scope_enable[0] && !(scope_enable[1] || logic_enable[0]), (!scope_enable[0] && !scope_enable[1] && !logic_enable[0] && !logic_enable[1])}; + ImVec2 positions[2] = + { + row3_pos + center_checkbox_delta(col_width, style), + row4_pos + ImVec2(2 * col_width ,0.f) + center_checkbox_delta(col_width, style) + }; + const char* internal_labels[2] = {"##750 kHz","##multimeter Mode"}; + + for(int i = 0; i < 2; i++) + { + ImGui::SetCursorScreenPos(positions[i]); + ImGui::BeginDisabled(!checkbox_enable[i]); + if((ImGui::custom_Checkbox(internal_labels[i], checkbox_bool[i])) || (*checkbox_bool[i] && !checkbox_enable[i])) { + changed = true; + mode_update = true; + } + *checkbox_bool[i] &= checkbox_enable[i]; + ImGui::EndDisabled(); + } + ImGui::SetCursorScreenPos(row3_pos + ImVec2(col_width - style.CellPadding.x + (2*col_width - ImGui::CalcTextSize("750 kHz").x)/2.,2 * style.FramePadding.y - style.ItemSpacing.y)); + ImGui::Text("750 kHz"); + ImGui::PushFont(NULL, style.FontSizeBase * 1.3); + ImGui::SetCursorScreenPos(row4_pos + ImVec2(col_width - style.CellPadding.x - ImGui::CalcTextSize("\xee\xa4\x82").x/2.,0.f)); + ImGui::Text("\xee\xa4\x82"); + ImGui::PopFont(); + ImGui::PopStyleVar(); +// ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,{0.f,0.f}); +// ImGui::Dummy({0.f, saved_pos.y - ImGui::GetCursorScreenPos().y}); +// ImGui::PopStyleVar(); + ImGui::EndGroup(); + + if (mode_update) + update_device_mode(); +} + +bool inputsUI::ch_enabled(int ch) +{ + // for argument 'ch': 1==ChA ; 2==ChB, where ChA/B refer to plotted lines. Except in ScopeLogic and Multimeter modes, ChA is scope or logic Ch1 and ChB is scope or logic Ch2. + if(mode == Mode::ScopeLogic) + return true; + else if (mode == Mode::Multimeter) + return ch==1; + else + return scope_enable[ch-1]||logic_enable[ch-1]; + return false; +} + +bool inputsUI::changed_since_last() +{ + bool changed_temp = changed; + changed = false; + return changed_temp; +} + +bool inputsUI::logic_on() +{ + return (logic_enable[0] || logic_enable[1]); +} + +bool inputsUI::scopelogic_mode() +{ + return mode == Mode::ScopeLogic; +} + +int inputsUI::get_height() +{ + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(style.CellPadding.x, style.CellPadding.y * 2));// if this line is active, make sure that the line that resets CellPadding at the end of this function is active as well + style = ImGui::GetStyle(); + int height = 2 * style.ItemSpacing.y + ImGui::GetFontSize() + \ + ImGui::GetFontSize() + style.CellPadding.y*2 + \ + 4 * (ImGui::GetFontSize() + (style.CellPadding.y + style.FramePadding.y)*2); + + ImGui::PopStyleVar(); + return height; +} + +void inputsUI::update_device_mode() +{ + if(scope_enable[0] && !scope_enable[1] && !logic_enable[0] && !scope750) + mode = Mode::Ch1Scope; + else if (scope_enable[0] && logic_enable[0]) + mode = Mode::ScopeLogic; + else if (scope_enable[0] && scope_enable[1]) + mode = Mode::ScopeScope; + else if (logic_enable[0] && !logic_enable[1] && !scope_enable[0]) + mode = Mode::Ch1Logic; + else if (logic_enable[0] && logic_enable[1]) + mode = Mode::LogicLogic; + else if (!(scope_enable[0] || scope_enable[1] || logic_enable[0] || logic_enable[1] || mm)) + mode = Mode::None; + else if (scope750) + mode = Mode::Scope750; + else if (mm) + mode = Mode::Multimeter; + + librador_set_device_mode((int) mode); +} + diff --git a/Android_App/app/src/main/cpp/inputs_ui.h b/Android_App/app/src/main/cpp/inputs_ui.h new file mode 100644 index 000000000..4ed51e9fa --- /dev/null +++ b/Android_App/app/src/main/cpp/inputs_ui.h @@ -0,0 +1,25 @@ +#ifndef INPUTSUI_H +#define INPUTSUI_H + +#include "ui_tile.h" +class inputsUI : public UI_tile +{ + bool scope750 = false; + bool changed = false; +public: + inputsUI() : UI_tile("Inputs","Inputs",UI_tile::Width::singlet, 6) {}; + void update_device_mode(); + bool logic_enable[2] = {0,0}; + bool scope_enable[2] = {true,false}; + void draw(float width, inputsUI* inputs_ui = nullptr) override; + bool changed_since_last(); + bool ch_enabled(int ch); + enum Mode {Ch1Scope,ScopeLogic,ScopeScope,Ch1Logic,LogicLogic,None,Scope750,Multimeter}; + Mode mode = Mode::Ch1Scope; + bool mm = false; //multimeter + bool logic_on(); + bool scopelogic_mode(); + int get_height() override; +}; + +#endif // INPUTSUI_H diff --git a/Android_App/app/src/main/cpp/logic_decode_ui.cpp b/Android_App/app/src/main/cpp/logic_decode_ui.cpp new file mode 100644 index 000000000..71c264ec7 --- /dev/null +++ b/Android_App/app/src/main/cpp/logic_decode_ui.cpp @@ -0,0 +1,327 @@ +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" +#include "librador.h" +#include "custom_imgui.h" +#include "imgui_internal.h" +#include "logic_decode_ui.h" +#include "inputs_ui.h" + +float logicDecodeUI::draw_grabber(float grabber_height, const char * label, float* backlog, int ch, bool parity_check) +{ + ImGui::PushID(ch); + char chAB[2] = {'A', 'B'}; + ImGuiStyle& style = ImGui::GetStyle(); + ImVec2 init_pos = ImGui::GetCursorScreenPos(); + ImVec2 end_pos = init_pos + ImVec2(ImGui::GetContentRegionAvail().x,0.f); + ImGui::SetNextItemAllowOverlap(); + ImGui::InvisibleButton(label, ImVec2(-1, grabber_height - 2 * style.ItemSpacing.y)); + if (ImGui::IsItemActivated()) { + *backlog = 0.f; + } + ImVec2 p0 = ImGui::GetItemRectMin(); + ImVec2 p1 = ImGui::GetItemRectMax(); + float hcenter = (p0.x + p1.x)/2.; + float ycenter = (p0.y + p1.y)/2.; + float yspan = (p1.y - p0.y)/2.; + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddLine(ImVec2(hcenter - ImGui::GetFontSize(), ycenter - yspan/2),ImVec2(hcenter + ImGui::GetFontSize(), ycenter - yspan/2), IM_COL32(120, 120, 160, 255)); + draw_list->AddLine(ImVec2(hcenter - ImGui::GetFontSize(), ycenter),ImVec2(hcenter + ImGui::GetFontSize(), ycenter ), IM_COL32(120, 120, 160, 255)); + draw_list->AddLine(ImVec2(hcenter - ImGui::GetFontSize(), ycenter + yspan/2),ImVec2(hcenter + ImGui::GetFontSize(), ycenter + yspan/2), IM_COL32(120, 120, 160, 255)); + float return_val = 0.f; + if (ImGui::IsItemActive()) { + float mouse_delta = ImGui::GetIO().MouseDelta.y; + if( (*backlog==0) || ((mouse_delta > 0) == (*backlog > 0)) ) { + return_val = mouse_delta; + } else { + *backlog += mouse_delta; + } + } + + // uart settings + uart_settings* curr_ch_uart_settings = &both_ch_uart_settings[ch-1]; + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetColorU32(ImGuiCol_ChildBg,0)); + bool uart_changed = false; + ImGui::SetCursorScreenPos(init_pos); + ImVec2 positions[2] = {init_pos, end_pos + ImVec2(-ImGui::CalcTextSize("Even").x - 2 * style.FramePadding.x, 0.f)}; + const char ** uart_options_sublabels[2] = {baud_rate_labels, parity_labels}; + int sublabels_counts[2] = {IM_COUNTOF(baud_rate_labels), IM_COUNTOF(parity_labels)}; + int * curr_options_sel[2] = {&curr_ch_uart_settings->baud_idx_sel, &curr_ch_uart_settings->parity_idx_sel}; + ImGui::PushStyleVar(ImGuiStyleVar_DisabledAlpha,1.0); + for(int k: {0,1}) + { + ImGui::PushID(k); + ImGui::SetCursorScreenPos(positions[k]); + if(k==0) { + ImGui::PushItemWidth(ImGui::CalcTextSize(" A ").x + 2*style.FramePadding.x); + ImGui::LabelText("##console_ch_label"," %c ",chAB[ch-1]); + p0 = ImGui::GetItemRectMin() + style.FramePadding; + p1 = ImGui::GetItemRectMax() - style.FramePadding; + draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRect(p0, p1, IM_COL32(255, 255, 255, 255)); + ImGui::SameLine(); + } + + ImGui::PushItemWidth(ImGui::CalcTextSize(uart_options_sublabels[k][*curr_options_sel[k] + 1]).x + 2 * style.FramePadding.x); +#define POP_COLOR if(need_pop) {ImGui::PopStyleColor(); need_pop = false;} + + ImU32 label_col = IM_COL32(255,255,255,255); + + bool need_pop = false; + if(k==1 && !parity_check) { + label_col = IM_COL32(255,0,0,255); + ImGui::PushStyleColor(ImGuiCol_Text, label_col); + need_pop = true; + } + if(ImGui::BeginCombo("##uart_option_combo", uart_options_sublabels[k][*curr_options_sel[k] + 1], ImGuiComboFlags_NoArrowButton)) { + POP_COLOR + for(int n=0; n < sublabels_counts[k]; n++) { + if(ImGui::Selectable(uart_options_sublabels[k][n], *curr_options_sel[k]==(n-1), (n==0 ? ImGuiSelectableFlags_Disabled : ImGuiSelectableFlags_None))) { + uart_changed = true; + *curr_options_sel[k]=n-1; // n-1 because n=0 is the header + } + } + ImGui::EndCombo(); + } + POP_COLOR + p0 = ImGui::GetItemRectMin() + style.FramePadding; + p1 = ImGui::GetItemRectMax() - style.FramePadding; + draw_list = ImGui::GetWindowDrawList(); + draw_list->AddLine(ImVec2(p0.x,p1.y), p1, label_col); + ImGui::PopID(); + ImGui::SameLine(); + } + ImGui::PopStyleVar(); + ImGui::NewLine(); + ImGui::PopStyleColor(); + + if(uart_changed) + librador_set_uart_decode_settings(ch, + (UartSettings) + {.decode_on=curr_ch_uart_settings->decode_on, .baudRate=static_cast(baud_rates[curr_ch_uart_settings->baud_idx_sel]), .parity=parities[curr_ch_uart_settings->parity_idx_sel]}); + + ImGui::PopID(); //ch + return return_val; +} + +void logicDecodeUI::print_stream(int id, const char * text, bool *at_bottom, float window_content_width, float ch_console_height) +{ + ImGui::PushID(id); + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); + if (ImGui::BeginChild("console",ImVec2(window_content_width, ch_console_height ), ImGuiChildFlags_AlwaysUseWindowPadding)) { + ImGui::TextWrapped("%s", text); + ImGuiContext& g = *ImGui::GetCurrentContext(); + ImGuiWindow* window = g.CurrentWindow; + + ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; + bool scrolling = ImGui::ScrollWhenDraggingAnywhere(ImVec2(0.0f, -mouse_delta.y), ImGuiMouseButton_Left); + if(!scrolling && *at_bottom){ + ImGui::SetScrollY(window, ImGui::GetScrollMaxY()); + } + if (ImGui::GetScrollMaxY() == window->Scroll.y) + *at_bottom=true; + else + *at_bottom=false; + + } + ImGui::EndChild(); + ImGui::PopStyleVar(); + ImGui::PopID(); +} + +float logicDecodeUI::get_grabber_height() +{ + ImGuiStyle& style = ImGui::GetStyle(); + return 2 * style.ItemSpacing.y + 2 * style.FramePadding.y + ImGui::GetFontSize(); +} + +void logicDecodeUI::draw_console(float window_content_width) +{ + ImGuiStyle& style = ImGui::GetStyle(); + bool parity_check; + float grabber_height = get_grabber_height(); + float y_avail = ImGui::GetContentRegionAvail().y; + if(protocol_sel == Protocol::UART) { + for(int i: {0,1}) { + ch_console_height[i] *= both_ch_uart_settings[i].decode_on; + } + for(int i:{1,0}) { + if(both_ch_uart_settings[i].decode_on) { + float clamped_console_height = fmin(ch_console_height[i], y_avail - ch_console_height[(i+1)%2] - grabber_height * (1 + both_ch_uart_settings[(i+1)%2].decode_on) - style.ItemSpacing.y); + clamped_console_height = fmax(clamped_console_height, 2 * grabber_height); + grabber2_backlog += ch_console_height[i] - clamped_console_height; // note: the grabber is only ever changing one of the console heights, so grabber2_backlog will only ever be incremented for one of the consoles + ch_console_height[i] = clamped_console_height; + } + } + if(both_ch_uart_settings[0].decode_on) + { + print_stream(1,librador_get_uart_string(1, &parity_check), &uart_ch_console_at_bottom[0], window_content_width, ch_console_height[0]); + } + float next_ch1_height = ch_console_height[1]; + if(both_ch_uart_settings[0].decode_on && both_ch_uart_settings[1].decode_on) + { + float console_sep_delta = draw_grabber(grabber_height, "chA_chB_splitter", &grabber1_backlog, 1, parity_check); + float clamped_console_sep_delta = fmin(console_sep_delta, (ch_console_height[1] - 2 * grabber_height)); + clamped_console_sep_delta = fmax(clamped_console_sep_delta, -(ch_console_height[0] - 2 * grabber_height)); + next_ch1_height -= clamped_console_sep_delta; + ch_console_height[0] += clamped_console_sep_delta; + grabber1_backlog += console_sep_delta - clamped_console_sep_delta; + } + if(both_ch_uart_settings[1].decode_on) + { + print_stream(2, librador_get_uart_string(2, &parity_check), &uart_ch_console_at_bottom[1], window_content_width, ch_console_height[1]); + ch_console_height[1] = next_ch1_height; + } + } else if(protocol_sel == Protocol::I2C) { + ch_console_height[1] = 0.f; + float clamped_console_height = fmin(ch_console_height[0], y_avail - grabber_height - style.ItemSpacing.y); + clamped_console_height = fmax(clamped_console_height, 2 * grabber_height); + grabber2_backlog += ch_console_height[0] - clamped_console_height; + ch_console_height[0] = clamped_console_height; + print_stream(3, librador_get_i2c_string(), &i2c_console_at_bottom, window_content_width, ch_console_height[0]); + } + float console_height_delta = draw_grabber(grabber_height, "plot_console_splitter", &grabber2_backlog, both_ch_uart_settings[1].decode_on ? 2 : 1, parity_check); + if(both_ch_uart_settings[1].decode_on) { + ch_console_height[1] += console_height_delta; + } else if (both_ch_uart_settings[0].decode_on || (protocol_sel == Protocol::I2C)) { + ch_console_height[0] += console_height_delta; + } +} + +bool logicDecodeUI::decoding_on() +{ + return both_ch_uart_settings[0].decode_on || both_ch_uart_settings[1].decode_on || protocol_sel == Protocol::I2C; +} + +void logicDecodeUI::draw(float width_pixels, inputsUI* inputs_ui) +{ + ImGuiStyle& style = ImGui::GetStyle(); + float grabber_height = get_grabber_height(); + ImGui::BeginGroup(); + standard_header(width_pixels); + if(!is_expanded) + { + ImGui::EndGroup(); + return; + } + + bool logic_enable[2]; + if(inputs_ui->scopelogic_mode()) { + logic_enable[0] = false; + logic_enable[1] = true; + } else { + memcpy(logic_enable, inputs_ui->logic_enable, 2 * sizeof(bool)); + } + bool i2c_changed = false; + bool uart_allowed = logic_enable[0] || logic_enable[1]; + bool i2c_allowed = logic_enable[0] && logic_enable[1] && !both_ch_uart_settings[0].decode_on && !both_ch_uart_settings[1].decode_on; + + + ImGui::BeginDisabled(!(logic_enable[0] || logic_enable[1])); //covers nearly entire fn. + + Protocol prots[2] = {Protocol::UART, Protocol::I2C}; + const char * labels[2] = {"UART", "I2C"}; + + bool open_ch_serial_settings = false; + char chAB[2] = {'A', 'B'}; + + ImGui::BeginGroup(); // for bounding rect + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,{0.f,0.f}); + ImGui::Dummy(ImVec2(width_pixels,0.f)); + ImGui::PopStyleVar(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2((width_pixels - ImGui::CalcTextSize("UART").x)/2.,style.FramePadding.y)); + ImGui::Text("UART"); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2( (width_pixels - ImGui::CalcTextSize("CH ACH B").x - style.ItemSpacing.x - 4 * style.FramePadding.x)/2., 0.f )); + for (int ch: {1,2}) + { + ImGui::BeginDisabled(!logic_enable[ch-1] || !(protocol_sel==Protocol::UART)); + char buf[20]; + sprintf(buf,"CH %c##serial_decode",chAB[ch-1]); + bool need_pop = false; + if(both_ch_uart_settings[ch-1].decode_on) { + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); + need_pop = true; + } + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_Button)); + if(ImGui::Button(buf)) { + both_ch_uart_settings[ch-1].decode_on = !both_ch_uart_settings[ch-1].decode_on; + if (both_ch_uart_settings[ch-1].decode_on) { + ch_console_height[ch-1] = init_console_height_per_ch - grabber_height; + } else { + ch_console_height[ch-1] = 0.f; + } + librador_set_uart_decode_settings(ch, + (UartSettings) + {.decode_on=both_ch_uart_settings[ch-1].decode_on, .baudRate=static_cast(baud_rates[both_ch_uart_settings[ch-1].baud_idx_sel]), .parity=parities[both_ch_uart_settings[ch-1].parity_idx_sel]}); + } + ImGui::PopStyleColor(); + if(need_pop) { + ImGui::PopStyleColor(); + } + ImGui::EndDisabled(); + ImGui::SameLine(); + } +// draw_list->AddLine(ImGui::GetCursorScreenPos(), ImGui::GetCursorScreenPos() + ImVec2(width_pixels,0.f), IM_COL32(90, 90, 120, 255)); +// +// ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2( (width_pixels - ImGui::CalcTextSize("I2C").x - style.ItemInnerSpacing.x - CHECKBOX_SIZE)/2., style.FramePadding.y )); +// ImGui::BeginDisabled(!i2c_allowed); +// if(ImGui::Checkbox("I2C", (bool *) &protocol_sel)) { +// i2c_changed = true; +// } +// ImGui::EndDisabled(); + + ImGui::EndGroup(); + ImVec2 p0 = ImGui::GetItemRectMin(); + ImVec2 p1 = ImGui::GetItemRectMax() + ImVec2(0.f,style.FramePadding.y); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRect(p0, p1, IM_COL32(90, 90, 120, 255)); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2(0.f,style.FramePadding.y - style.ItemSpacing.y)); + ImGui::Dummy({0.f,0.f}); // prevents issue with this draw() command affecting the vertical alignment of whatever ui element comes after it + ImGui::EndGroup(); + + ImGui::EndDisabled(); //!logic_enable[0] && !logic_enable[1]); +// if(i2c_changed) +// { +// librador_set_i2c_is_decoding(protocol_sel == Protocol::I2C); +// if(protocol_sel == Protocol::I2C) +// ch_console_height[0] = init_console_height_per_ch - grabber_height; +// } +} + +void logicDecodeUI::update(inputsUI* inputs) +{ + bool logic_enable[2]; + if(inputs->scopelogic_mode()) { + logic_enable[0] = false; + logic_enable[1] = true; + } else { + memcpy(logic_enable, inputs->logic_enable, 2 * sizeof(bool)); + } + for (int ch : {1,2}) + { + uart_settings* curr_ch_uart_settings = &both_ch_uart_settings[ch-1]; + if((!logic_enable[ch-1] || !(protocol_sel==Protocol::UART)) && curr_ch_uart_settings->decode_on) + { + curr_ch_uart_settings->decode_on = false; + librador_set_uart_decode_settings(ch, + (UartSettings) + {.decode_on=curr_ch_uart_settings->decode_on, .baudRate=static_cast(baud_rates[curr_ch_uart_settings->baud_idx_sel]), .parity=parities[curr_ch_uart_settings->parity_idx_sel]}); + } + } + if((!logic_enable[0] || !logic_enable[1]) && (protocol_sel==Protocol::I2C)) + { + protocol_sel=Protocol::UART; //with ch_uart_settings->decode_on = false for both channels, so effectively disabling decoding; + librador_set_i2c_is_decoding(false); + } + +} + +int logicDecodeUI::get_height() +{ + ImGuiStyle& style = ImGui::GetStyle(); + int calc_height = 2 * style.ItemSpacing.y + ImGui::GetFontSize() + \ + style.FramePadding.y + ImGui::GetFontSize() + style.ItemSpacing.y + \ + 2 * style.FramePadding.y + ImGui::GetFontSize() + 2 * style.ItemSpacing.y; + return calc_height; +// 3 * style.FramePadding.y + ImGui::GetFontSize() + // for i2c checkbox +} diff --git a/Android_App/app/src/main/cpp/logic_decode_ui.h b/Android_App/app/src/main/cpp/logic_decode_ui.h new file mode 100644 index 000000000..662f9b379 --- /dev/null +++ b/Android_App/app/src/main/cpp/logic_decode_ui.h @@ -0,0 +1,73 @@ +#ifndef LOGICDECODEUI_H +#define LOGICDECODEUI_H + +#include "ui_tile.h" +#include "uartstyledecoder.h" + +class logicDecodeUI : public UI_tile +{ + enum class Protocol {UART, I2C}; + Protocol protocol_sel = Protocol::UART; + + static const int num_baud_options = 12; + const int baud_rates[num_baud_options] = { + 300, + 600, + 1200, + 2400, + 4800, + 9600, + 14400, + 19200, + 28800, + 38400, + 57600, + 115200 + }; + const char* baud_rate_labels[num_baud_options+1] = { + "Baud:", + "300", + "600", + "1200", + "2400", + "4800", + "9600", + "14400", + "19200", + "28800", + "38400", + "57600", + "115200"}; + + static const int num_parity_options = 3; + const UartParity parities[num_parity_options] = {UartParity::None, UartParity::Even, UartParity::Odd}; + const char* parity_labels[num_parity_options+1] = {"Parity:", "None", "Even", "Odd"}; + + struct uart_settings { + bool decode_on = false; + int baud_idx_sel = 0; + int parity_idx_sel = 0; + }; + + uart_settings both_ch_uart_settings[2]; + float ch_console_height[2] = {0.f, 0.f}; + bool draw_uart_settings(float grabber_height, float width_pixels); + float init_console_height_per_ch = 300.f; + float grabber_height; + float grabber1_backlog = 0.f; + float grabber2_backlog = 0.f; + float grabber_delta_tracker2 = 0.f; + float draw_grabber(float grabber_height, const char * label, float* backlog, int ch_idx, bool parity_check); + void print_stream(int id, const char * text, bool *at_bottom, float window_content_width, float ch_console_height); + bool uart_ch_console_at_bottom[2] = {true, true}; + bool i2c_console_at_bottom = true; + float get_grabber_height(); +public: + logicDecodeUI() : UI_tile("Logic Decoding", "Logic Dec.", UI_tile::Width::singlet, 4) {}; + bool decoding_on(); + void draw(float width, inputsUI* inputs_ui = nullptr) override; + void update(inputsUI* inputs_ui); + void draw_console(float window_content_width);//const char * from_librador_1, const char * from_librador_2 = nullptr); + int get_height() override; +}; +#endif // LOGICDECODEUI_H diff --git a/Android_App/app/src/main/cpp/main.cpp b/Android_App/app/src/main/cpp/main.cpp new file mode 100644 index 000000000..c9211eebf --- /dev/null +++ b/Android_App/app/src/main/cpp/main.cpp @@ -0,0 +1,341 @@ +// Dear ImGui: standalone example application for SDL3 + OpenGL +// (SDL is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan/Metal graphics context creation, etc.) + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#include "librador.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "implot.h" +#include "imgui.h" +#include "settings_panel.h" +#include "ui_tile.h" +#include "sig_gen_ui.h" +#include "inputs_ui.h" +#include "trigger_ui.h" +#include "virtual_transform_ui.h" +#include "psu_ui.h" +#include "logic_decode_ui.h" +#include "plot_ui.h" +#include "custom_imgui.h" +#include "imgui_impl_sdl3.h" +#include "imgui_impl_opengl3.h" +#include +#include +#include +// #include "SDL_android.h" +#include +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include +#else +#include +#endif +#include "imgui_internal.h" +#include +#include +#include + + +// Main code +int main(int, char**) +{ + // Setup SDL + // [If using SDL_MAIN_USE_CALLBACKS: all code below until the main loop starts would likely be your SDL_AppInit() function] + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) + { + printf("Error: SDL_Init(): %s\n", SDL_GetError()); + return 1; + } + + // Decide GL+GLSL versions +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 (WebGL 1.0) + const char* glsl_version = "#version 100"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#elif defined(IMGUI_IMPL_OPENGL_ES3) + // GL ES 3.0 + GLSL 300 es (WebGL 2.0) + const char* glsl_version = "#version 300 es"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#elif defined(__APPLE__) + // GL 3.2 Core + GLSL 150 + const char* glsl_version = "#version 150"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); +#else + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#endif + + // Create window with graphics context + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); + SDL_Rect bounds; + SDL_zero(bounds); + SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds); + + float pixel_6a_main_scale = 2.625; + float pixel_6a_dpi = 428.6; + + JNIEnv *env = (JNIEnv *) SDL_GetAndroidJNIEnv(); + jobject MainActivityObject = (jobject) SDL_GetAndroidActivity(); + jclass MainActivity(env->GetObjectClass(MainActivityObject)); + jmethodID getDpiID = env->GetMethodID(MainActivity, "getDpi", "()F"); + float dpi = (float) env->CallFloatMethod(MainActivityObject,getDpiID); + LIBRADOR_LOG(LOG_DEBUG, "dpi: %.2f", dpi); + SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; + SDL_Window* window = SDL_CreateWindow("main window", (int)bounds.w, (int)bounds.h, window_flags); + if (window == nullptr) + { + printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); + return 1; + } + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + if (gl_context == nullptr) + { + printf("Error: SDL_GL_CreateContext(): %s\n", SDL_GetError()); + return 1; + } + + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // Enable vsync + SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + SDL_ShowWindow(window); + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImPlot::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + SDL_PropertiesID propsIme = SDL_CreateProperties(); // for allowing specification of keyboard type (numeric, alpha, ...) + SDL_SetNumberProperty(propsIme, SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER, 2); + io.UserData = &propsIme; + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); +// style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again). +// style.FontScaleDpi = main_scale; // Set initial font scale. (in docking branch: using io.ConfigDpiScaleFonts=true automatically overrides this for every window depending on the current monitor) + + style.ScaleAllSizes( (pixel_6a_main_scale / pixel_6a_dpi) * dpi); // brentfpage : not scaling by a given device's main_scale because doing so doesn't actually bring about consistent sizing across devices. + style.FontScaleDpi = (pixel_6a_main_scale / pixel_6a_dpi) * dpi; // brentfpage : same for the font sizes + + style.FontSizeBase = 19.f; + style.WindowPadding = ImVec2(style.WindowPadding.x/2,style.WindowPadding.y/2); + + // Setup Platform/Renderer backends + ImGui_ImplSDL3_InitForOpenGL(window, gl_context); + ImGui_ImplOpenGL3_Init(glsl_version); + + // Load Fonts + // - If fonts are not explicitly loaded, Dear ImGui will call AddFontDefault() to select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). + // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. + // - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit). + // - Read 'docs/FONTS.md' for more instructions and details. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefaultVector(); + //io.Fonts->AddFontDefaultBitmap(); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); + //IM_ASSERT(font != nullptr); + + ImFont* defaultFont = io.Fonts->AddFontDefault(); + + // for accessing android app resources + jfieldID asset_manager_id = env->GetFieldID(MainActivity, "mgr", "Landroid/content/res/AssetManager;"); + jobject mgr_java = (jobject)env->GetObjectField(MainActivityObject, asset_manager_id); + AAssetManager * mgr = AAssetManager_fromJava(env, mgr_java); + + ImFontConfig config; + config.MergeMode = true; + config.FontDataOwnedByAtlas = false; // prevents imperceptible crash when the app is closed +// https://stackoverflow.com/a/13317651/3474552 + + float glyph_y_offsets[2] = {3.f, 4.5f}; + float fontsizes[2] = {13.f, 12.f}; + char buf[2][2048]; + int fi = 0; + for (const char* filename: {"font/waveform-glyphs3.ttf","font/greek_delta.ttf"}) { + config.GlyphOffset = { 0.f, glyph_y_offsets[fi] }; + AAsset* asset = AAssetManager_open(mgr, filename, AASSET_MODE_STREAMING); + int nb_read = 0; + nb_read = AAsset_read(asset, buf[fi], 2048); + ImFont* new_font = io.Fonts->AddFontFromMemoryTTF(buf[fi], nb_read, fontsizes[fi], &config); + AAsset_close(asset); + fi++; + } + + jmethodID getStatusBarHeightID = env->GetMethodID(MainActivity, "getStatusBarHeight", "()I"); + jmethodID getNavigationBarHeightID = env->GetMethodID(MainActivity, "getNavigationBarHeight", "()I"); + jmethodID getScreenWidth = env->GetMethodID(MainActivity, "getScreenWidth", "()I"); + jmethodID getScreenHeight = env->GetMethodID(MainActivity, "getScreenHeight", "()I"); + + int sw = (int) env->CallIntMethod(MainActivityObject,getScreenWidth); + int sh = (int) env->CallIntMethod(MainActivityObject,getScreenHeight); + int sbh = (int) env->CallIntMethod(MainActivityObject,getStatusBarHeightID); + int nbh = (int) env->CallIntMethod(MainActivityObject,getNavigationBarHeightID); + LIBRADOR_LOG(LOG_DEBUG, "screen width: %d", sw); + LIBRADOR_LOG(LOG_DEBUG, "screen height: %d", sh); + LIBRADOR_LOG(LOG_DEBUG, "status bar beight: %d", sbh); + LIBRADOR_LOG(LOG_DEBUG, "navigation bar beight: %d", nbh); + + // Our state + bool show_mainwindow = true; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + virtual_transform_ui.is_visible = true; + virtual_transform_ui.is_expanded = false; + virtual_transform_ui.next_is_expanded = false; + psu_ui.is_expanded = false; + psu_ui.is_visible = false; + plotUI plot_ui = plotUI(); + + // Main loop + bool done = false; + bool iso_thread_active = false; + bool need_board_init = true; + while (!done) + { + + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + // [If using SDL_MAIN_USE_CALLBACKS: call ImGui_ImplSDL3_ProcessEvent() from your SDL_AppEvent() function] + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL3_ProcessEvent(&event); + if (event.type == SDL_EVENT_QUIT) + done = true; + if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window)) + done = true; + } + + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppIterate() function] + if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) + { + SDL_Delay(10); + continue; + } + + // will intentionally be zero sometimes; see MainActivity + int statusBarHeight = (int) env->CallIntMethod(MainActivityObject,getStatusBarHeightID); + int navigationBarHeight = (int) env->CallIntMethod(MainActivityObject,getNavigationBarHeightID); + + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + +// important to have this iso_thread_active check after the new frame starts. Otherwise, (board connected -> user puts phone to sleep -> user unplugs board -> user wakes phone) leads to a crash. The crash arises from the librador_get_(analog/digital)_data block below thinking iso_thread_active=true when it's not. + iso_thread_active = librador_iso_thread_is_active(); + if(!iso_thread_active) { + need_board_init = true; + } + if(need_board_init && iso_thread_active) { + inputs_ui.update_device_mode(); + sig_gen_ui.usb_send_data(1); + sig_gen_ui.usb_send_data(2); + psu_ui.usb_send_data(); + librador_set_oscilloscope_gain(4.); + need_board_init = false; + } + + ImGuiIO& io = ImGui::GetIO(); + + static bool landscape = io.DisplaySize.y < io.DisplaySize.x; + bool new_landscape = io.DisplaySize.y < io.DisplaySize.x; + bool orientation_changed = (landscape != new_landscape); + landscape = new_landscape; + + plot_ui.recompute_x_bounds(inputs_ui.changed_since_last(), inputs_ui.mode); + logic_decode_ui.update(&inputs_ui); + + ImGui::SetNextWindowPos(ImVec2(0.f,statusBarHeight)); + ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x,io.DisplaySize.y - statusBarHeight - navigationBarHeight)); + + ImGuiStyle& style = ImGui::GetStyle(); + + bool screen_keyboard_shown = SDL_ScreenKeyboardShown(window); + ImGui::Begin("MainWindow", + &show_mainwindow, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | (screen_keyboard_shown ? ImGuiWindowFlags_NoMouseInputs : 0)); + float data_width; + float data_height; + do_settings_panel_layout(&data_width, &data_height, landscape, io.DisplaySize.y - statusBarHeight - navigationBarHeight - 2 * style.WindowPadding.y, dpi, pixel_6a_dpi); + + ImGui::BeginChild("data",ImVec2(data_width, data_height), 0, (screen_keyboard_shown ? ImGuiWindowFlags_NoMouseInputs : 0)); + { + if(logic_decode_ui.decoding_on()) { + logic_decode_ui.draw_console(data_width); + } + + plot_ui.draw(iso_thread_active, inputs_ui.mode, inputs_ui.ch_enabled(1), inputs_ui.ch_enabled(2), data_width, 0.); + + } + ImVec2 dataWindowBottomLeft = ImGui::GetWindowPos() + ImVec2(0.f,ImGui::GetWindowSize().y); + ImVec2 dataWindowBottomRight = ImGui::GetWindowPos() + ImGui::GetWindowSize(); + ImGui::EndChild(); + if(landscape) { + ImGui::SameLine(); + } + + draw_settings_panel(landscape, screen_keyboard_shown); + draw_collapse_button(landscape, dataWindowBottomLeft, dataWindowBottomRight); + draw_selector_popup(landscape, orientation_changed); + + ImGui::End(); + + // Rendering + ImGui::Render(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(window); + } + + // Cleanup + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppQuit() function] + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImPlot::DestroyContext(); + ImGui::DestroyContext(); + + SDL_GL_DestroyContext(gl_context); + SDL_DestroyProperties(propsIme); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} diff --git a/Android_App/app/src/main/cpp/plot_ui.cpp b/Android_App/app/src/main/cpp/plot_ui.cpp new file mode 100644 index 000000000..2884481cf --- /dev/null +++ b/Android_App/app/src/main/cpp/plot_ui.cpp @@ -0,0 +1,324 @@ +#define IMGUI_DEFINE_MATH_OPERATORS +#include "implot.h" +#include "imgui_internal.h" +#include "implot_internal.h" +#include "plot_ui.h" +#include "librador.h" + +void plotUI::recompute_x_bounds(bool mode_changed, inputsUI::Mode mode) +{ + if(mode_changed) + { + if(mode==inputsUI::Mode::Scope750) { + time_window = std::min(5., time_window); + delay = std::min(5. - time_window, delay); + ImPlot::SetNextAxisLimits(ImAxis_X1, -(delay+time_window), -delay, ImPlotCond_Always); + x_constraint_min = -5.; + x_constraint_max = 0.; +// } else if (xy) { +// xmin = ymin; +// xmax = ymax; +// ImPlot::SetNextAxisLimits(ImAxis_X1, xmin, xmax, ImPlotCond_Always); +// x_constraint_min = -20.; +// x_constraint_max = 20.; + } else { + time_window = std::min(max_time_window_375khz, time_window); + delay = std::min(max_time_window_375khz - time_window, delay); + ImPlot::SetNextAxisLimits(ImAxis_X1, -(delay+time_window), -delay, ImPlotCond_Always); + x_constraint_min = -max_time_window_375khz; + x_constraint_max = 0.; + } + } else { +// if(!xy) { + delay = -xmax; + time_window = (xmax - xmin); +// } + } +} + +void get_ref_line_label(char * label, int size, char X_or_Y, ImPlotAxis ax, double ref_a, double ref_b) { + + double ref_1 = ImMin(ref_a, ref_b); + double ref_2 = ImMax(ref_a, ref_b); + double difference = ref_2 - ref_1; + int max_prec = 3 - floor(ImLog10(ImMin(ax.Range.Max - ax.Range.Min, ImAbs(ref_2 - ref_1)))); + + int n_sig_figs_needed_1 = max_prec + floor(ImLog10(ImAbs(ref_1))); + int n_sig_figs_needed_2 = max_prec + floor(ImLog10(ImAbs(ref_2))); + int n_sig_figs_needed_diff = max_prec + floor(ImLog10(difference)); + + n_sig_figs_needed_1 = ImMax(ImMin(n_sig_figs_needed_1, 8),0); + n_sig_figs_needed_2 = ImMax(ImMin(n_sig_figs_needed_2, 8),0); + n_sig_figs_needed_diff = ImMax(ImMin(n_sig_figs_needed_diff, 8),0); + + int buf_size = 64; + char label_template[buf_size]; + ImFormatString(label_template, buf_size, "%%c1: %%.%dg\n%%c2: %%.%dg\n\xee\xa4\x84%%c: %%.%dg", n_sig_figs_needed_1, n_sig_figs_needed_2, n_sig_figs_needed_diff); + + ImFormatString(label, size, label_template, X_or_Y, ref_1, X_or_Y, ref_2, X_or_Y, difference); +} + +void plotUI::draw(bool iso_thread_active, inputsUI::Mode mode, bool chA_enabled, bool chB_enabled, double data_width, double plot_height) +{ + std::vector *from_librador_chA; + std::vector *from_librador_chB; + std::vector blank_data{}; + std::vector time_array; + + if(iso_thread_active){ + time_array = librador_get_time_array(delay, time_window, GRAPH_SAMPLES); + switch(mode) { + case inputsUI::Mode::Ch1Scope: + from_librador_chA = librador_get_analog_data(1,time_window,GRAPH_SAMPLES,delay, 0); + break; + case inputsUI::Mode::ScopeLogic: + from_librador_chA = librador_get_analog_data(1,time_window,GRAPH_SAMPLES,delay, 0); + from_librador_chB = librador_get_digital_data(2,time_window,GRAPH_SAMPLES,delay); + break; + case inputsUI::Mode::ScopeScope: + from_librador_chA = librador_get_analog_data(1,time_window,GRAPH_SAMPLES,delay, 0); + from_librador_chB = librador_get_analog_data(2,time_window,GRAPH_SAMPLES,delay, 0); + break; + case inputsUI::Mode::Ch1Logic: + from_librador_chA = librador_get_digital_data(1,time_window,GRAPH_SAMPLES,delay); + break; + case inputsUI::Mode::LogicLogic: + from_librador_chA = librador_get_digital_data(1,time_window,GRAPH_SAMPLES,delay); + from_librador_chB = librador_get_digital_data(2,time_window,GRAPH_SAMPLES,delay); + break; + case inputsUI::Mode::None: + break; + case inputsUI::Mode::Scope750: + from_librador_chA = librador_get_analog_data(1,time_window,GRAPH_SAMPLES,delay, 0); + break; + case inputsUI::Mode::Multimeter: + from_librador_chA = librador_get_analog_data(1,time_window,GRAPH_SAMPLES,delay, 0); + break; + } + } else { + from_librador_chA = &blank_data; + from_librador_chB = &blank_data; + time_array = blank_data; + } + + ImGui::BeginChild("plot",ImVec2(data_width, plot_height)); + { + if (ImPlot::BeginPlot("##scope traces", ImGui::GetContentRegionAvail(),ImPlotFlags_NoMouseText)) { + ImPlot::SetupAxes("time (s)","volts"); + + ImPlot::SetupAxisFormat(ImAxis_X1, ImPlot::Formatter_Offset_Plus_Delta, (void*) "%g"); + ImPlot::SetupAxisFormat(ImAxis_Y1, ImPlot::Formatter_Offset_Plus_Delta, (void*) "%g"); + ImPlot::SetupAxisLimitsConstraints(ImAxis_X1, x_constraint_min, x_constraint_max); + ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1, -max_voltage, max_voltage); + ImPlot::SetupAxesLimits(xmin, xmax, ymin, ymax, ImPlotCond_Once); + + ImPlotSpec spec = ImPlotSpec(); + spec.LineWeight = 2; + if(chA_enabled) + ImPlot::PlotLine("CH A", time_array.data(), from_librador_chA->data(), from_librador_chA->size(), spec); + if(chB_enabled) + ImPlot::PlotLine("CH B", time_array.data(), from_librador_chB->data(), from_librador_chB->size(), spec); + + ImGuiContext& g = *GImGui; + + if(ImPlot::IsAxisActivated(ImAxis_X1)) { + double mouse_down_clicked_val = ImPlot::getMouseDownClickedVal(ImAxis_X1); + cursor_drag_tool_toggle = ImAbs(x_ref_1 - mouse_down_clicked_val) > ImAbs(x_ref_2 - mouse_down_clicked_val); + } + + ImPlotDragToolFlags x1_drag_tool_flags = cursor_drag_tool_toggle ? ImPlotDragToolFlags_NoAxisInputs : 0; + ImPlotDragToolFlags x2_drag_tool_flags = cursor_drag_tool_toggle ? 0 : ImPlotDragToolFlags_NoAxisInputs; + + if(ImPlot::IsAxisClicked(ImAxis_X1)) { + if(!enable_x_ref_lines) { + x_ref_1 = ImPlot::getMouseDownClickedVal(ImAxis_X1); + x_ref_2 = ImPlot::getMouseDownClickedVal(ImAxis_X1); + } + enable_x_ref_lines = true; + if(g.IO.MouseClickedLastCount[0]==2) { + enable_x_ref_lines = false; + } + } + + if(enable_x_ref_lines) { + ImPlot::DragLineX(0,&x_ref_1,ImVec4(1,1,1,1), 1.f, x1_drag_tool_flags); + ImPlot::DragLineX(1,&x_ref_2,ImVec4(1,1,1,1), 1.f, x2_drag_tool_flags); + } + + if(ImPlot::IsAxisActivated(ImAxis_Y1)) { + double mouse_down_clicked_val = ImPlot::getMouseDownClickedVal(ImAxis_Y1); + cursor_drag_tool_toggle = ImAbs(y_ref_1 - mouse_down_clicked_val) > ImAbs(y_ref_2 - mouse_down_clicked_val); + } + + ImPlotDragToolFlags y1_drag_tool_flags = cursor_drag_tool_toggle ? ImPlotDragToolFlags_NoAxisInputs : 0; + ImPlotDragToolFlags y2_drag_tool_flags = cursor_drag_tool_toggle ? 0 : ImPlotDragToolFlags_NoAxisInputs; + + if(ImPlot::IsAxisClicked(ImAxis_Y1)) { + if(!enable_y_ref_lines) { + y_ref_1 = ImPlot::getMouseDownClickedVal(ImAxis_Y1); + y_ref_2 = ImPlot::getMouseDownClickedVal(ImAxis_Y1); + } + enable_y_ref_lines = true; + if(g.IO.MouseClickedLastCount[0]==2) { + enable_y_ref_lines = false; + } + } + + if(enable_y_ref_lines) { + ImPlot::DragLineY(0,&y_ref_1,ImVec4(1,1,1,1), 1.f, y1_drag_tool_flags); + ImPlot::DragLineY(1,&y_ref_2,ImVec4(1,1,1,1), 1.f, y2_drag_tool_flags); + } + + ImPlotRect axes_limits = ImPlot::GetPlotLimits(); + if(ImPlot::IsAxisLongPressed(ImAxis_X1)) { + ImGui::SetNextWindowPos(ImGui::GetWindowPos() + ImGui::GetWindowSize()/2.,0,{0.5,0.5}); + ImGui::OpenPopup("select x lims"); + } + if(ImGui::BeginPopup("select x lims")) { + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::Text("X-axis limits:"); + ImGui::PushItemWidth(ImGui::CalcTextSize("-000000000. s").x + 2 * style.FramePadding.x); + double window = xmax - xmin; + double delay = ImAbs(-xmax); // fabs to prevent signed 0 + int n_prec = 3; + int max_prec = n_prec - floor(ImLog10(ImAbs(window))); // + + char min_label_template[32]; + int n_sig_figs_needed_min = max_prec + floor(ImLog10(ImAbs(xmin))); + n_sig_figs_needed_min = ImMax(ImMin(n_sig_figs_needed_min, 8),0); + ImFormatString(min_label_template, 32, "%%.%dg s", n_sig_figs_needed_min); + double xmin_saved = xmin; + if(ImGui::InputDouble("Min", &xmin, 0.f, 0.f, min_label_template)) { + if(xmin < 0) { + xmin = ImMax(x_constraint_min, xmin); + xmin = ImMin(x_constraint_max, xmin); + } else { + xmin = xmin_saved; + } + } + + bool modified = false; + char window_label_template[32]; + ImFormatString(window_label_template, 32, "%%.%dg s", n_prec); + if(ImGui::InputDouble("Window", &window, 0.f, 0.f, window_label_template)) { + if(window <= 0) { + window = xmax - xmin; + } else { + window = ImMax((double) window, min_window_size); + modified = true; + } + } + + char delay_label_template[32]; + int n_sig_figs_needed_delay = max_prec + (delay != 0 ? floor(ImLog10(ImAbs(delay))) : 0); + n_sig_figs_needed_delay = ImMax(ImMin(n_sig_figs_needed_delay, 8),0); + ImFormatString(delay_label_template, 32, "%%.%dg s", n_sig_figs_needed_delay); + if(ImGui::InputDouble("Delay", &delay, 0.f, 0.f, delay_label_template)) { + if(delay < 0) { + delay = fabs(-xmax); + } else { + modified = true; + } + } + if(modified) { + double new_xmin = -delay - window; + double new_xmax = -delay; + new_xmin = ImMax(x_constraint_min, new_xmin); + new_xmin = ImMin(x_constraint_max - min_window_size, new_xmin); + new_xmax = ImMax(new_xmin + min_window_size, new_xmax); + new_xmax = ImMin(x_constraint_max, new_xmax); + if(new_xmin!=new_xmax) { + xmin = new_xmin; + xmax = new_xmax; + } + } + ImGui::EndPopup(); + } else { + xmin = axes_limits.X.Min; + xmax = axes_limits.X.Max; + } + + if(ImPlot::IsAxisLongPressed(ImAxis_Y1)) { + ImGui::SetNextWindowPos(ImGui::GetWindowPos() + ImGui::GetWindowSize()/2.,0,{0.5,0.5}); + ImGui::OpenPopup("select y lims"); + } + if(ImGui::BeginPopup("select y lims")) { + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::Text("Y-axis limits:"); + ImGui::PushItemWidth(ImGui::CalcTextSize("-000000000. V").x + 2 * style.FramePadding.x); + int n_sig_figs_needed = ymin != ymax ? ImMax(ImLog10(ImMax(ImAbs(ymin),ImAbs(ymax)) / ImAbs(ymin-ymax)),0.) + 3 : 0; + n_sig_figs_needed = ImMin(n_sig_figs_needed, 8); + char label_template[32]; + ImFormatString(label_template, 32, "%%.%dg V", n_sig_figs_needed); + double ymax_saved = ymax; + if(ImGui::InputDouble("Max", &ymax, 0.f, 0.f, label_template)) { + ymax = ImMin(max_voltage, ymax); + if(ymax <= ymin) { + ymax = ymax_saved; + } + } + double ymin_saved = ymin; + if(ImGui::InputDouble("Min", &ymin, 0.f, 0.f, label_template)) { + ymin = ImMax(-max_voltage, ymin); + if(ymin >= ymax) { + ymin = ymin_saved; + } + } + ImGui::EndPopup(); + } else { + ymin = axes_limits.Y.Min; + ymax = axes_limits.Y.Max; + } + + ImPlotPlot* mainplot = ImPlot::GetCurrentPlot(); + ImPlot::EndPlot(); + + ImPlot::SetNextAxisLimits(ImAxis_X1, xmin, xmax, ImPlotCond_Always); + ImPlot::SetNextAxisLimits(ImAxis_Y1, ymin, ymax, ImPlotCond_Always); + + int buffer_size = 64; + char x_ref_line_label[buffer_size]; + char y_ref_line_label[buffer_size]; + if(enable_x_ref_lines) { + get_ref_line_label(x_ref_line_label, buffer_size, 'X', mainplot->XAxis(0), x_ref_1, x_ref_2); + } else { + x_ref_line_label[0] = '\0'; + } + if(enable_y_ref_lines) { + get_ref_line_label(y_ref_line_label, buffer_size, 'Y', mainplot->YAxis(0), y_ref_1, y_ref_2); + } else { + y_ref_line_label[0] = '\0'; + } + + + if(enable_x_ref_lines || enable_y_ref_lines) { + ImGuiStyle& style = ImGui::GetStyle(); + + ImPlotContext& gp = *GImPlot; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {2 * style.ItemSpacing.x, style.ItemSpacing.y}); + float ref_legend_width = ImGui::CalcTextSize(x_ref_line_label).x + ImGui::CalcTextSize(y_ref_line_label).x + (enable_x_ref_lines && enable_y_ref_lines) * style.ItemSpacing.x + 2 * style.FramePadding.x; + float ref_legend_height = ImMax(ImGui::CalcTextSize(x_ref_line_label).y, ImGui::CalcTextSize(y_ref_line_label).y) + 2 * style.FramePadding.y; + + ImGui::SetCursorScreenPos(mainplot->PlotRect.Max - ImVec2(ref_legend_width, ref_legend_height) - gp.Style.LegendPadding ); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(ImGui::GetCursorScreenPos(), ImGui::GetCursorScreenPos() + ImVec2(ref_legend_width, ref_legend_height), ImGui::GetColorU32(ImGuiCol_WindowBg,.75)); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + style.FramePadding); + + ImGui::BeginGroup(); + if(enable_x_ref_lines) { + ImGui::Text("%s", x_ref_line_label); + } + if(enable_x_ref_lines && enable_y_ref_lines) { + ImGui::SameLine(); + } + if(enable_y_ref_lines) { + ImGui::Text("%s", y_ref_line_label); + } + ImGui::EndGroup(); + ImGui::PopStyleVar(); + } + + } + } + ImGui::EndChild(); +} diff --git a/Android_App/app/src/main/cpp/plot_ui.h b/Android_App/app/src/main/cpp/plot_ui.h new file mode 100644 index 000000000..00a663ecc --- /dev/null +++ b/Android_App/app/src/main/cpp/plot_ui.h @@ -0,0 +1,36 @@ +#ifndef PLOTUI_H +#define PLOTUI_H + +#include "inputs_ui.h" +#include + +#define GRAPH_SAMPLES 512 +class plotUI +{ + double xmin = -.5; + double xmax = 0.; + double ymin = -2.; + double ymax = 2.; + const double max_time_window_375khz = 10; + const double max_voltage = 20; + double x_constraint_min = -max_time_window_375khz; + double x_constraint_max = 0.; + const double min_window_size = 1.e-6; + const double min_voltage_diff = 1.e-3; + + double x_ref_1; + double x_ref_2; + double y_ref_1; + double y_ref_2; + + bool cursor_drag_tool_toggle; // determines which of the two reference lines for a given axis is begin dragged + bool enable_x_ref_lines = false; + bool enable_y_ref_lines = false; + +public: + void recompute_x_bounds(bool mode_changed, inputsUI::Mode mode); + void draw(bool iso_thread_active, inputsUI::Mode mode, bool chA_enabled, bool chB_enabled, double data_width, double plot_height); + double delay; + double time_window; +}; +#endif // PLOTUI_H diff --git a/Android_App/app/src/main/cpp/psu_ui.cpp b/Android_App/app/src/main/cpp/psu_ui.cpp new file mode 100644 index 000000000..7f7dea5ca --- /dev/null +++ b/Android_App/app/src/main/cpp/psu_ui.cpp @@ -0,0 +1,69 @@ +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" +#include "librador.h" +#include "custom_imgui.h" +#include +#include "imgui_internal.h" +#include "psu_ui.h" + +void psuUI::draw(float width_pixels, inputsUI* inputs_ui) +{ + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::BeginGroup(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2(0.f,style.ItemSpacing.y)); // combined with lines in main.cpp, effectively folds itemspacing.y into the group covering this ui_tile. + ImVec2 baseline_loc = ImGui::GetCursorScreenPos(); + + const float psu_button_width = style.FramePadding.x*2 + ImGui::CalcTextSize(" PSU ").x; + ImVec2 slider_loc = baseline_loc + ImVec2(psu_button_width + style.ItemInnerSpacing.x, 0.f); + float close_button_width = ImGui::GetFontSize() + style.FramePadding.x; + ImVec2 close_button_loc = baseline_loc + ImVec2(width_pixels - close_button_width, style.FramePadding.y ); + + ImGui::BeginGroup(); // for bounding rect + ButtonForSlider(" PSU ", "##psu_slider", ImVec2(psu_button_width,0.f)); + ImGui::SameLine(); + ImGui::SetCursorScreenPos(slider_loc); + ImGui::PushItemWidth(width_pixels - psu_button_width - 2 * style.ItemInnerSpacing.x - 1 - close_button_width); // -1 to give space for bounding rect + if(ImGui::custom_SliderFloat("##psu_slider", "V", &psu, 4.5f, 12.0f, "%.1f V", ImGuiSliderFlags_ClampOnInput) || ImGui::IsItemDeactivatedAfterEdit()) { + need_usb_send = true; + } + ImGui::SameLine(); + ImGui::EndGroup(); + + ImVec2 p0 = ImGui::GetItemRectMin(); + ImVec2 p1 = ImGui::GetItemRectMax(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRect(p0, p1, IM_COL32(90, 90, 120, 255),0,0,2); + + if(ImGui::CloseButton(ImGui::GetID("psu_close"), close_button_loc)) { + is_expanded = false; + is_visible = false; + } + ImGui::SameLine(); + // mimic the spacing/cursor advancement that a standard Button(...) would generate + ImGui::SetCursorScreenPos(close_button_loc); + ImGui::Dummy({close_button_width,0.f}); + + ImGui::EndGroup(); + + if(need_usb_send) { + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + if(std::chrono::duration_cast(now - last_usb_send) > between_usb_sends_min) { + usb_send_data(); + last_usb_send = now; + need_usb_send = false; + } + } +} + +void psuUI::usb_send_data() +{ + librador_set_power_supply_voltage(psu); +} + +int psuUI::get_height() +{ + ImGuiStyle& style = ImGui::GetStyle(); + return 2 * style.CellPadding.y + 2 * style.FramePadding.y + style.ItemSpacing.y + ImGui::GetFontSize(); +} + + diff --git a/Android_App/app/src/main/cpp/psu_ui.h b/Android_App/app/src/main/cpp/psu_ui.h new file mode 100644 index 000000000..1ec0c9243 --- /dev/null +++ b/Android_App/app/src/main/cpp/psu_ui.h @@ -0,0 +1,17 @@ +#ifndef PSUUI_H +#define PSUUI_H + +#include "ui_tile.h" +class psuUI : public UI_tile +{ + const std::chrono::milliseconds between_usb_sends_min{100}; + float psu = 4.5f; + std::chrono::steady_clock::time_point last_usb_send; + bool need_usb_send = false; +public: + psuUI() : UI_tile("PSU", "PSU", UI_tile::Width::duplex, 1) {}; + void usb_send_data(); + void draw(float width, inputsUI* inputs_ui = nullptr) override; + int get_height() override; +}; +#endif // PSUUI_H diff --git a/Android_App/app/src/main/cpp/settings_panel.cpp b/Android_App/app/src/main/cpp/settings_panel.cpp new file mode 100644 index 000000000..46b76a304 --- /dev/null +++ b/Android_App/app/src/main/cpp/settings_panel.cpp @@ -0,0 +1,265 @@ +#include "settings_panel.h" + +inputsUI inputs_ui = inputsUI(); +triggerUI trigger_ui = triggerUI(); +virtualTransformUI virtual_transform_ui = virtualTransformUI(); +sigGenUI sig_gen_ui = sigGenUI(); +psuUI psu_ui = psuUI(); +logicDecodeUI logic_decode_ui = logicDecodeUI(); + +const int n_tiles = 6; +UI_tile* tiles[n_tiles] = {&inputs_ui, &trigger_ui, &virtual_transform_ui, &sig_gen_ui, &psu_ui, &logic_decode_ui}; + +float pixel_6a_screen_width = 1080.f; +float pixel_6a_setting_panel_aspect = 1.13; // width to height +float settings_height_max; +float adjustment; +float tile_singlet_width_pixels; +float settings_width; +bool row_col_tiling; +float col1_width, col2_width; +float tile_col_heights[2]; +int n_singlet_tiles_visible; +float singlet_tile_height_when_row_col_tiling; +float settings_height; + +bool maybe_clicked_background = false; +bool collapse_settings = false; + +ImVec2 settings_window_center; + +// singlet-width tiles are (tile_singlet_width_pixels + adjustment) wide +// duplex-width tiles are (2 * tile_singlet_width_pixels - adjustment) wide +// two-col tiling: two columns of tiles side-by-side, the one on the left containing singlet-width tiles and the one on the right containing duplex-width tiles +// row-col tiling: draw singlet-width tiles in the top row; below this row, draw duplex-width tiles in a column +void do_settings_panel_layout(float* data_width, float* data_height, bool landscape, int y_size, float dpi, float pixel_6a_dpi) { + ImGuiIO& io = ImGui::GetIO(); + ImGuiStyle& style = ImGui::GetStyle(); + + adjustment = 0.f; + // should compute these values only once, but there's no way to get the navigation/status bar heights in landscape mode when in portrait mode or vice-versa; it's necessary to wait until the device actually enters a given orientation to access the heights + // in landscape mode, allow scrolling the settings panel in the y direction, so there's no need to account for different y sizes across devices + if(landscape) { + settings_height_max = y_size; + *data_height = y_size; + tile_singlet_width_pixels = settings_height_max * pixel_6a_setting_panel_aspect / 3.f; + } else { + // if the current device's screen is smaller width-wise than the pixel 6a's screen, make sure the singlet-width tiles remain the same width in inches as on the pixel 6a. do this by transfering space from the duplex-width tiles (which aren't as space-constrained) + adjustment = ((pixel_6a_screen_width * dpi / pixel_6a_dpi) - static_cast(io.DisplaySize.x))/3.; + adjustment = adjustment < 0 ? 0 : adjustment; + tile_singlet_width_pixels = (io.DisplaySize.x - style.ItemSpacing.x - 2 * style.WindowPadding.x)/3.; + } + // col1 and grp1 contain singlet-width tiles, col2 and grp2 have duplex-width tiles + tile_col_heights[0] = 0.f; + tile_col_heights[1] = 0.f; + + n_singlet_tiles_visible = 0; + singlet_tile_height_when_row_col_tiling = 0.f; + for(int i=0; i < n_tiles; i++) { + if(tiles[i]->is_visible) { + float height = tiles[i]->next_is_expanded ? tiles[i]->get_height() : tiles[i]->get_collapsed_height(); + tile_col_heights[static_cast(tiles[i]->width)] += height; + if(tiles[i]->width == UI_tile::Width::singlet) { + singlet_tile_height_when_row_col_tiling = fmax(singlet_tile_height_when_row_col_tiling, height); + n_singlet_tiles_visible++; + } + } + } + + // these widths only relevent to two-col tiling + col1_width = (n_singlet_tiles_visible > 0) ? tile_singlet_width_pixels : 0; + col2_width = (tile_col_heights[1] > 0) ? 2 * tile_singlet_width_pixels : 0; + + row_col_tiling = (!landscape && (n_singlet_tiles_visible == 2) && ((tile_col_heights[1] + singlet_tile_height_when_row_col_tiling) < fmax(tile_col_heights[0], tile_col_heights[1]))) || \ + (landscape && (n_singlet_tiles_visible > 0) && ((tile_col_heights[1] + singlet_tile_height_when_row_col_tiling) < settings_height_max)); + + if(landscape) { + if(collapse_settings) { + *data_width = ImGui::GetContentRegionAvail().x; + } else { + if(row_col_tiling) { + if (tile_col_heights[1] > 0.f) { + settings_width = 2 * tile_singlet_width_pixels + style.ItemSpacing.x; + } else if (n_singlet_tiles_visible > 0) { + settings_width = tile_singlet_width_pixels + (n_singlet_tiles_visible == 2) * (tile_singlet_width_pixels + style.ItemSpacing.x); + } else { + settings_width = 0.f; + } + } else { + settings_width = col1_width + col2_width + ((col1_width>0)&&(col2_width>0)) * style.ItemSpacing.x; + float max_col_height = fmax(tile_col_heights[0], tile_col_heights[1]); + if(max_col_height > settings_height_max) { + settings_width += style.ScrollbarSize; + } + } + settings_width = fmax(settings_width, ImGui::GetFontSize() + 2 * style.FramePadding.x); + *data_width = ImGui::GetContentRegionAvail().x - style.ItemSpacing.x - settings_width; + } + } else { + settings_width = io.DisplaySize.x - 2 * style.WindowPadding.x; + *data_width = settings_width; + if(collapse_settings) { + *data_height = ImGui::GetContentRegionAvail().y; + } else { + if(row_col_tiling) { + settings_height = singlet_tile_height_when_row_col_tiling + tile_col_heights[1]; + } else { + settings_height = fmax(tile_col_heights[0], tile_col_heights[1]); + } + settings_height = fmax(settings_height, ImGui::GetFontSize() + 2 * style.ItemSpacing.y); + *data_height = ImGui::GetContentRegionAvail().y - settings_height; + } + } +} + +#define INDENTUP ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() - ImVec2(0.f,style.ItemSpacing.y)); // remove gaps between ui_tile groups in order to avoid unwanted presses on the background that open the ui_tile selector popup. this ItemSpacing is added back in by the tiles within their BeginGroup()/EndGroup() wrappings. ImGuiContext.DebugShowGroupRects is very handy for debugging the groups + +void draw_settings_panel(bool landscape, bool screen_keyboard_shown) { + ImGuiIO& io = ImGui::GetIO(); + ImGuiStyle& style = ImGui::GetStyle(); + if(!landscape) { + INDENTUP + } + if(!collapse_settings) { + ImGui::BeginChild("settings",ImVec2(0.f, 0.f), 0, (screen_keyboard_shown ? ImGuiWindowFlags_NoMouseInputs : 0)); + settings_window_center = ImGui::GetWindowPos() + ImGui::GetWindowSize()/2.; + ImGuiContext& g = *GImGui; + ImVec2 settings_start_pos = ImGui::GetCursorScreenPos(); + ImGui::SetNextItemAllowOverlap(); + if(ImGui::InvisibleButton("open ui_tile selector", {0.f, 0.f})) { + maybe_clicked_background = true; + } + ImGui::SetCursorScreenPos(settings_start_pos); + + if(row_col_tiling) { + for(int grp : {0,1}) { + if(grp==1) + INDENTUP + ImGui::BeginGroup(); + bool first = true; + for(int i=0; iis_visible && (static_cast(tiles[i]->width) == grp)) { + if((grp==1)&&(!first)) { + INDENTUP + } + first = false; + tiles[i]->draw(tile_singlet_width_pixels + grp * (tile_singlet_width_pixels + style.ItemSpacing.x), &inputs_ui); + maybe_clicked_background &= !ImGui::IsItemHovered(); + // items in group 0 are stacked side-by-side; those in group 1 are stacked vertically + if(grp==0) { + ImGui::SameLine(); // seems to be invalidated after endgroup, which is convenient here + } + } + } + ImGui::EndGroup(); + } + } else { + for(int col : {0,1}) { + if(tile_col_heights[col] > 0) + { + ImGui::BeginGroup(); + bool first = true; + for(int i=0; iis_visible && (static_cast(tiles[i]->width) == col)) { + if(!first) + INDENTUP + first=false; + if(landscape) { + tiles[i]->draw((static_cast(tiles[i]->width) + 1) * tile_singlet_width_pixels, &inputs_ui); + } else { + tiles[i]->draw((static_cast(tiles[i]->width) + 1) * tile_singlet_width_pixels + (-2*static_cast(tiles[i]->width) + 1) * adjustment, &inputs_ui); + } + maybe_clicked_background &= !ImGui::IsItemHovered(); + } + } + ImGui::EndGroup(); + ImGui::SameLine(); + } + } + ImGui::NewLine(); + } + ImGui::EndChild(); + + } +} + +int get_selector_popup_height() +{ + ImGuiStyle& style = ImGui::GetStyle(); + return style.WindowPadding.y + style.WindowBorderSize + \ + style.ItemSpacing.y + ImGui::GetFontSize() + \ + style.ItemSpacing.y + style.SeparatorSize + \ + CHECKBOX_SIZE * n_tiles + style.ItemSpacing.y * (n_tiles - 1) + \ + style.WindowPadding.y + style.WindowBorderSize; +} + +int get_selector_popup_width() +{ + ImGuiStyle& style = ImGui::GetStyle(); + float text_width = 0; + for(int i = 0; i < n_tiles; i++) { + text_width = fmax(text_width, ImGui::CalcTextSize(tiles[i]->name).x); + } + return CHECKBOX_SIZE + style.ItemInnerSpacing.x + text_width + 2 * (style.WindowBorderSize + style.WindowPadding.x); +} + +void draw_selector_popup(bool landscape, bool orientation_changed) { + if(maybe_clicked_background || (orientation_changed && ImGui::IsPopupOpen("config_settings"))) { + ImVec2 main_window_bottom_right = ImGui::GetWindowPos() + ImGui::GetWindowSize(); + ImVec2 centered_selector_window_bottom_right = settings_window_center + ImVec2(get_selector_popup_width(), get_selector_popup_height())/2.; + if(\ + (landscape && (centered_selector_window_bottom_right.x > main_window_bottom_right.x)) || + (!landscape && (centered_selector_window_bottom_right.y > main_window_bottom_right.y))) { + ImVec2 edge_selector_window_pos = ImGui::GetWindowPos() + \ + ImVec2((ImGui::GetWindowSize().x - get_selector_popup_width()) * (landscape ? 1. : 0.5), (ImGui::GetWindowSize().y - get_selector_popup_height()) * (landscape ? 0.5 : 1)); + ImGui::SetNextWindowPos(edge_selector_window_pos,0,ImVec2(0.f,0.f)); + } else { + ImGui::SetNextWindowPos(settings_window_center,0,ImVec2(0.5f,0.5f)); + } + ImGui::OpenPopup("config_settings"); + maybe_clicked_background = false; + } + if(ImGui::BeginPopup("config_settings")) { + ImGui::Text("Select tiles"); + ImGui::Separator(); + for (int i=0; i< n_tiles; i++) { + if(ImGui::Checkbox(tiles[i]->name, &tiles[i]->is_visible)) { + if(tiles[i]->is_visible) + { + tiles[i]->next_is_expanded = true; + } + } + } + ImGui::EndPopup(); + } +} + +void draw_collapse_button(bool landscape, ImVec2 dataWindowBottomLeft, ImVec2 dataWindowBottomRight) { + ImGuiStyle& style = ImGui::GetStyle(); + ImGuiID collapse_id = ImGui::GetID("collapse"); + ImVec2 collapse_button_pos; + char label[36]; + if(landscape) { + if(collapse_settings) { + strcpy(label, " < "); + } else { + strcpy(label, " > "); + } + collapse_button_pos = dataWindowBottomRight - ImGui::CalcTextSize(" < ") - style.FramePadding * 2; + } else { + if(collapse_settings) { + strcpy(label, " ^ "); + } else { + strcpy(label, " v "); + } + collapse_button_pos = dataWindowBottomLeft - ImVec2(0.f,ImGui::CalcTextSize(" ^ ").y + style.FramePadding.y * 2); + } + ImGui::BeginChild("data"); // to append to the 'plot' child, need to re-enter into each of its parents + ImGui::BeginChild("plot"); + ImGui::SetCursorScreenPos(collapse_button_pos); + if(ImGui::custom_ButtonEx(label)) { + collapse_settings = !collapse_settings; + } + ImGui::EndChild(); + ImGui::EndChild(); +} diff --git a/Android_App/app/src/main/cpp/settings_panel.h b/Android_App/app/src/main/cpp/settings_panel.h new file mode 100644 index 000000000..d1bf6ec9a --- /dev/null +++ b/Android_App/app/src/main/cpp/settings_panel.h @@ -0,0 +1,27 @@ +#ifndef SETTINGSPANEL_H +#define SETTINGSPANEL_H + +#include "ui_tile.h" +#include "sig_gen_ui.h" +#include "inputs_ui.h" +#include "trigger_ui.h" +#include "virtual_transform_ui.h" +#include "psu_ui.h" +#include "logic_decode_ui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "custom_imgui.h" +#include "imgui.h" +#include "imgui_internal.h" + +extern inputsUI inputs_ui; +extern triggerUI trigger_ui; +extern virtualTransformUI virtual_transform_ui; +extern sigGenUI sig_gen_ui; +extern psuUI psu_ui; +extern logicDecodeUI logic_decode_ui; + +void do_settings_panel_layout(float* data_width, float* data_height, bool landscape, int y_size, float dpi, float pixel_6a_dpi); +void draw_settings_panel(bool landscape, bool screen_keyboard_shown); +void draw_selector_popup(bool landscape, bool orientation_changed); +void draw_collapse_button(bool landscape, ImVec2 dataWindowBottomLeft, ImVec2 dataWindowBottomRight); +#endif // SETTINGSPANEL_H diff --git a/Android_App/app/src/main/cpp/sig_gen_ui.cpp b/Android_App/app/src/main/cpp/sig_gen_ui.cpp new file mode 100644 index 000000000..d91f18e31 --- /dev/null +++ b/Android_App/app/src/main/cpp/sig_gen_ui.cpp @@ -0,0 +1,134 @@ +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" +#include "imgui_internal.h" +#include "librador.h" +#include "custom_imgui.h" +#include +#include "imgui_internal.h" +#include "sig_gen_ui.h" +#include "inputs_ui.h" + + +void sigGenUI::amp_or_min_slider_and_button(const char* slider_label, const char* button_label, float *amp_or_min, float *amp_or_min_delayed, float *min_or_amp, float *min_or_amp_delayed) { + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(-1); + if(ImGui::custom_SliderFloat(slider_label, "V", amp_or_min, 0.f, 9.6 - *min_or_amp_delayed, "%.2f V") || ImGui::IsItemDeactivatedAfterEdit()) { + need_usb_send = true; + if(ImGui::IsItemDeactivatedAfterEdit()) { + *amp_or_min = std::min(*amp_or_min, 9.6f); + *amp_or_min = std::max(*amp_or_min, 0.f); + *min_or_amp = std::min(*min_or_amp, 9.6f - *amp_or_min);//only can have an effect on manual input + *min_or_amp_delayed = *min_or_amp; + *amp_or_min_delayed = *amp_or_min; + } + } + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() - ImVec2(style.CellPadding.x,0.f)); + ButtonForSlider(button_label, slider_label, ImVec2(ImGui::GetContentRegionAvail().x + style.CellPadding.x,0.f)); +} + +void sigGenUI::draw(float width_pixels, inputsUI* inputs_ui) +{ + bool ch2_disabled = inputs_ui->logic_on(); + ImGuiStyle& style = ImGui::GetStyle(); + if(ch2_disabled) { + both_ch_data[1] = ch_data(); + } + + ImGui::BeginGroup(); + standard_header(width_pixels); + if(!is_expanded) + { + ImGui::EndGroup(); + return; + } + + ImDrawList* draw_list; + ImVec2 p0; + ImVec2 p1; + + if (ImGui::BeginTable("sg_table", 2, ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_RowBg , ImVec2(width_pixels,0.f))) + { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.75f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.25f); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::RadioButton("CH1", &ch_sel, 1); ImGui::SameLine(); + ImGui::RadioButton("CH2", &ch_sel, 2); + ImGui::BeginDisabled(ch2_disabled && ch_sel == 2); + curr_ch_data = &both_ch_data[ch_sel-1]; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(-1); + if(ImGui::Combo("##combo3", &curr_ch_data->wf, wf_names, IM_COUNTOF(wf_names))) + need_usb_send = true; + + ImGui::TableNextColumn(); + ImGui::Text("Type"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(-1); + float freq_conv = curr_ch_data->freq / freq_slider_bases[curr_ch_data->slider_base]; + if(ImGui::custom_SliderFloat("##sg_freq_slider", freq_slider_suffixs[curr_ch_data->slider_base], &freq_conv, 0.0f, freq_slider_maxima[curr_ch_data->slider_base] / freq_slider_bases[curr_ch_data->slider_base], freq_slider_formats[curr_ch_data->slider_base]) + || ImGui::IsItemDeactivatedAfterEdit()) + { + need_usb_send = true; + curr_ch_data->freq = freq_conv * freq_slider_bases[curr_ch_data->slider_base]; + if(ImGui::IsItemDeactivatedAfterEdit()) { + curr_ch_data->freq = std::min(curr_ch_data->freq, freq_slider_maxima[n_bases-1]); + curr_ch_data->freq = std::max(curr_ch_data->freq, 0.f); + if(curr_ch_data->freq <= freq_slider_maxima[0]) { + curr_ch_data->slider_base = 0; + } else { + int i; + for(i=1;i= curr_ch_data->freq) && (freq_slider_maxima[i-1] < curr_ch_data->freq)) // find optimal freq scale to use for the slider given the value of the manually input freq. + break; + } + curr_ch_data->slider_base=i; + } + } + } + + ImGui::TableNextColumn(); + + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() - ImVec2(style.CellPadding.x,0.f)); + if(ButtonForSlider("Freq.", "##sg_freq_slider", ImVec2(ImGui::GetContentRegionAvail().x + style.CellPadding.x,0.f))) { // short press on button + curr_ch_data->slider_base = (curr_ch_data->slider_base + 1) % n_bases; // toggle frequency base + curr_ch_data->freq = std::min(curr_ch_data->freq, freq_slider_maxima[curr_ch_data->slider_base]); + } + + amp_or_min_slider_and_button("##sg_amp_slider", "Amp.", &curr_ch_data->amp, &curr_ch_data->amp_delayed, &curr_ch_data->min_val, &curr_ch_data->min_val_delayed); + amp_or_min_slider_and_button("##sg_min_slider", "Min.", &curr_ch_data->min_val, &curr_ch_data->min_val_delayed, &curr_ch_data->amp, &curr_ch_data->amp_delayed); + + ImGui::EndDisabled(); // ch_sel==2 && ch2_disabled + + ImGui::EndTable(); + } + ImGui::EndGroup(); + if(need_usb_send) { + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + if(std::chrono::duration_cast(now - last_usb_send) > between_usb_sends_min) { + usb_send_data(ch_sel); + last_usb_send = now; + need_usb_send = false; + } + } +} + +void sigGenUI::usb_send_data(int ch) +{ + librador_send_wave(both_ch_data[ch-1].wf, ch, both_ch_data[ch-1].freq, both_ch_data[ch-1].amp, both_ch_data[ch-1].min_val); +} + +int sigGenUI::get_height() +{ + ImGuiStyle& style = ImGui::GetStyle(); + int calc_height = 2 * style.ItemSpacing.y + ImGui::GetFontSize() + \ + CHECKBOX_SIZE + 2 * style.CellPadding.y + \ + 4 * (ImGui::GetFontSize() + 2 * style.FramePadding.y + 2 * style.CellPadding.y); + return calc_height; +} diff --git a/Android_App/app/src/main/cpp/sig_gen_ui.h b/Android_App/app/src/main/cpp/sig_gen_ui.h new file mode 100644 index 000000000..8700b2762 --- /dev/null +++ b/Android_App/app/src/main/cpp/sig_gen_ui.h @@ -0,0 +1,39 @@ +#ifndef SIGGENUI_H +#define SIGGENUI_H + +#include +#include "ui_tile.h" + +class sigGenUI : public UI_tile +{ + const static int n_bases = 5; + const int freq_slider_bases[n_bases] = {1,1,1,1000,1000}; + const float freq_slider_maxima[n_bases] = {10.f,100.f,1000.f,10000.f,64000.f}; + const char* freq_slider_formats[n_bases] = {"%.1f Hz", "%.0f Hz", "%.0f Hz", "%.1f kHz", "%.1f kHz"}; + const char* freq_slider_suffixs[n_bases] = {"Hz", "Hz", "Hz", "kHz", "kHz"}; + const char* wf_names[4] = { "Sin", "Square", "Triangle", "Sawtooth" }; + const std::chrono::milliseconds between_usb_sends_min{100}; + + void amp_or_min_slider_and_button(const char* slider_label, const char* button_label, float *amp_or_min, float *amp_or_min_delayed, float *min_or_amp, float *min_or_amp_delayed); + std::chrono::steady_clock::time_point last_usb_send; + struct ch_data { + int wf = 0; + int slider_base = 2; + float freq = 500.f; + float amp = 0.f; + float min_val = 0.f; + float amp_delayed = 0.f; + float min_val_delayed = 0.f; + }; + ch_data both_ch_data[2]; + ch_data * curr_ch_data = both_ch_data; // helper + bool need_usb_send; + int ch_sel = 1; +public: + sigGenUI() : UI_tile("Signal Generator", "Signal Generator", UI_tile::Width::duplex, 6) {}; + void draw(float width, inputsUI* inputs_ui = nullptr) override; + void usb_send_data(int ch); + int get_height() override; +}; + +#endif // SIGGENUI_H diff --git a/Android_App/app/src/main/cpp/trigger_ui.cpp b/Android_App/app/src/main/cpp/trigger_ui.cpp new file mode 100644 index 000000000..47e06bfd1 --- /dev/null +++ b/Android_App/app/src/main/cpp/trigger_ui.cpp @@ -0,0 +1,218 @@ +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" +#include "librador.h" +#include "custom_imgui.h" +#include +#include "imgui_internal.h" +#include "trigger_ui.h" + + +void triggerUI::draw(float width_pixels, inputsUI* inputs_ui) +{ + bool enable_helper[2] = {inputs_ui->scope_enable[0] || inputs_ui->mm, inputs_ui->scope_enable[1]}; + for (int ch:{1,2}) + { + if(!enable_helper[ch-1]) { + both_ch_trigger_settings[ch-1] = o1buffer::trigger_settings(); + } + } + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::BeginGroup(); + standard_header(width_pixels); + if(!is_expanded) + { + ImGui::EndGroup(); + return; + } + ImGui::BeginGroup(); // for bounding rect + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f)); + if(ImGui::BeginTable("trigger_helper1",1, ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_NoHostExtendX, ImVec2(width_pixels, 0.))) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2(0.f, style.FramePadding.y - style.ItemSpacing.y)); +#define ALIGN_Y ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() - ImVec2(0.f, style.FramePadding.y - style.ItemSpacing.y)); + ImGui::Text("CH: "); + ImGui::SameLine(); + ALIGN_Y + ImGui::RadioButton("A ", &ch_sel, 1); + ImGui::SameLine(); + ALIGN_Y + ImGui::RadioButton("B", &ch_sel, 2); + ImGui::EndTable(); + } + curr_ch_trigger_settings = &both_ch_trigger_settings[ch_sel - 1]; + ImGui::BeginDisabled(!enable_helper[ch_sel-1]); + if(ImGui::BeginTable("trigger_helper2",2, ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_NoHostExtendX, ImVec2(width_pixels, 0.))) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.5f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.5f); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2((ImGui::GetContentRegionAvail().x - CHECKBOX_SIZE - style.ItemInnerSpacing.x - ImGui::CalcTextSize("Rising").x)/2.,0.f)); + ImGui::custom_RadioButton("Rising", (int *) &curr_ch_trigger_settings->trigger_type, 1); + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2((ImGui::GetContentRegionAvail().x - CHECKBOX_SIZE - style.ItemInnerSpacing.x - ImGui::CalcTextSize("Falling").x)/2.,0.f)); + ImGui::custom_RadioButton("Falling", (int *) &curr_ch_trigger_settings->trigger_type, 2); + ImGui::EndTable(); + } + if(ImGui::BeginTable("trigger_helper3",2, ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_NoHostExtendX, ImVec2(width_pixels, 0.))) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.75f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.25f); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(-1); + ImGui::custom_SliderFloat("##trigger level", "V", &curr_ch_trigger_settings->trigger_level, -20.f, 20.f, "%.1f V", ImGuiSliderFlags_ClampOnInput); + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() - ImVec2(style.CellPadding.x,0.f)); + ButtonForSlider("Level", "##trigger level", ImVec2(ImGui::GetContentRegionAvail().x + style.CellPadding.x,0.f)); + ImGui::EndTable(); + } + if(ImGui::BeginTable("trigger_helper4",1, ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_NoHostExtendX, ImVec2(width_pixels, 0.))) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2((ImGui::GetContentRegionAvail().x - CHECKBOX_SIZE - style.ItemInnerSpacing.x - ImGui::CalcTextSize("Single shot").x)/2.,0.f)); + ImGui::Checkbox("Single shot", &curr_ch_trigger_settings->is_single_shot); + ImGui::EndTable(); + } + ImGui::EndDisabled(); + + ImGui::PopStyleVar(); //itemspacing + ImGui::EndGroup(); + ImVec2 p0 = ImGui::GetItemRectMin(); + ImVec2 p1 = ImGui::GetItemRectMax(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRect(p0, p1, IM_COL32(90, 90, 120, 255)); + ImGui::EndGroup(); + + if(curr_ch_trigger_settings->trigger_type != o1buffer::TriggerType::Disabled) + { + both_ch_trigger_settings[ch_sel%2].trigger_type = o1buffer::TriggerType::Disabled; + librador_set_trigger_settings(ch_sel%2+1, both_ch_trigger_settings[ch_sel%2]); + } + + librador_set_trigger_settings(ch_sel, both_ch_trigger_settings[ch_sel-1]); + +} + +int triggerUI::get_height() +{ + ImGuiStyle& style = ImGui::GetStyle(); + int calc_height = 2 * style.ItemSpacing.y + ImGui::GetFontSize() + \ + 3 * (CHECKBOX_SIZE + 2 * style.CellPadding.y) + \ + (ImGui::GetFontSize() + 2 * style.FramePadding.y + 2 * style.CellPadding.y); + return calc_height; +} + +// singlet-width version that could become useful in the future +// void triggerUI::draw(float width_pixels, inputsUI* inputs_ui) +// { +// bool enable_helper[2] = {inputs_ui->scope_enable[0] || inputs_ui->mm, inputs_ui->scope_enable[1]}; +// for (int ch:{1,2}) +// { +// if(!enable_helper[ch-1]) { +// both_ch_trigger_settings[ch-1] = o1buffer::trigger_settings(); +// } +// } +// ImGuiStyle& style = ImGui::GetStyle(); +// ImGui::BeginGroup(); +// standard_header(width_pixels); +// if(!is_expanded) +// { +// ImGui::EndGroup(); +// return; +// } +// ImGui::BeginGroup(); // for bounding rect +// // get_height() line 1 end +// +// ImGui::BeginGroup(); // for bounding rect +// int free_space = width_pixels - ImGui::CalcTextSize("ChAChB").x - 2 * CHECKBOX_SIZE - 2 * style.ItemInnerSpacing.x - style.FramePadding.x * 2; +// ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + style.FramePadding); +// ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(free_space,0.f)); +// ImGui::RadioButton("ChA", &ch_sel, 1); ImGui::SameLine(); +// ImGui::RadioButton("ChB", &ch_sel, 2); +// ImGui::PopStyleVar(); +// ImGui::EndGroup(); +// curr_ch_trigger_settings = &both_ch_trigger_settings[ch_sel - 1]; +// ImVec2 p0 = ImGui::GetItemRectMin(); +// ImVec2 p1 = ImGui::GetItemRectMax() + style.FramePadding; +// ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, p1.y)); // replacing ItemSpacing with FramePadding +// ImVec2 slider_top_right = p1; +// ImDrawList* draw_list = ImGui::GetWindowDrawList(); +// draw_list->AddRect(p0, p1, IM_COL32(90, 90, 120, 255)); +// // get_height() line 2 end +// +// float slider_left1 = (p0 + (p1-p0) * .75).x; +// ImGui::BeginDisabled(!enable_helper[ch_sel-1]); +// ImGui::BeginGroup(); +// ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + style.FramePadding); +// ImGui::custom_RadioButton("Rising", (int *) &curr_ch_trigger_settings->trigger_type, 1); +// ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2(style.FramePadding.x,0.f)); +// ImGui::custom_RadioButton("Falling", (int *) &curr_ch_trigger_settings->trigger_type, 2); +// p1 = ImGui::GetItemRectMax() + style.FramePadding; +// float slider_left2 = p1.x; +// float slider_left = fmax(slider_left1, slider_left2); +// p0 = ImVec2(ImGui::GetItemRectMin().x - style.FramePadding.x, p1.y); +// ImVec2 saved_pos = p0; +// // get_height() line 3 end +// draw_list = ImGui::GetWindowDrawList(); +// draw_list->AddLine(p0, p1, IM_COL32(90, 90, 120, 255)); +// float ss_text_height = ImGui::CalcTextSize("Single\n shot").y + 2 * style.ItemSpacing.y + 2; // +2 is a fudge factor that makes this correct +// ImGui::SetCursorScreenPos(ImVec2(ImGui::GetItemRectMin().x, p0.y + (ss_text_height - CHECKBOX_SIZE)/2.)); +// ImGui::Checkbox("##ss", &curr_ch_trigger_settings->is_single_shot); +// ImGui::SameLine(); +// ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, saved_pos.y)); +// ImGui::Text("Single\n shot"); +// ImGui::EndGroup(); +// // height relative to init_pos; +// p0 = ImGui::GetItemRectMin(); +// p1 = ImGui::GetItemRectMax() + style.FramePadding; +// draw_list = ImGui::GetWindowDrawList(); +// draw_list->AddRect(p0, p1, IM_COL32(90, 90, 120, 255)); +// +// ImVec2 value_text_size = ImGui::CalcTextSize("-20.0 V", NULL); +// ImVec2 value_text_button_size = value_text_size + style.FramePadding + style.FramePadding; +// ImVec2 value_text_button_pos = p1 + ImVec2(-value_text_button_size.x, 0.f); +// ImGui::SetCursorScreenPos(value_text_button_pos); +// // get_height() line 4 end +// ButtonForSlider("##trigger_button", "##trigger_slider", value_text_button_size); +// ImVec2 saved_pos2 = ImGui::GetCursorScreenPos(); +// float slider_bottom = saved_pos2.y - style.ItemSpacing.y; +// +// // get_height() line 5 end +// +// ImGui::SetCursorScreenPos(ImVec2(slider_left, slider_top_right.y)); +// +// ImGui::custom_VSliderFloat("##trigger_slider", "V", +// ImVec2(slider_top_right.x - slider_left , slider_bottom - slider_top_right.y), +// &curr_ch_trigger_settings->trigger_level, -20.f, 20.f, "%.1f V", ImGuiSliderFlags_ClampOnInput, value_text_button_pos + style.FramePadding, value_text_size); +// ImGui::EndDisabled(); +// ImGui::SetCursorScreenPos({ImGui::GetCursorScreenPos().x, slider_bottom}); +// ImGui::Dummy({0.f,0.f}); // prevents issue with this draw() command affecting the vertical alignment of whatever ui element comes after it +// ImGui::EndGroup(); +// p0 = ImGui::GetItemRectMin(); +// p1 = ImGui::GetItemRectMax(); +// draw_list = ImGui::GetWindowDrawList(); +// draw_list->AddRect(p0, p1, IM_COL32(90, 90, 120, 255)); +// +// if(curr_ch_trigger_settings->trigger_type != o1buffer::TriggerType::Disabled) +// { +// both_ch_trigger_settings[ch_sel%2].trigger_type = o1buffer::TriggerType::Disabled; +// librador_set_trigger_settings(ch_sel%2+1, both_ch_trigger_settings[ch_sel%2]); +// } +// +// librador_set_trigger_settings(ch_sel, both_ch_trigger_settings[ch_sel-1]); +// ImGui::EndGroup(); +// } + +// for the singlet-width version of this widget +// int triggerUI::get_height() +// { +// ImGuiStyle& style = ImGui::GetStyle(); +// int calc_height = 2 * style.ItemSpacing.y + ImGui::GetFontSize() + \ +// CHECKBOX_SIZE + 2 * style.FramePadding.y + \ +// 2 * CHECKBOX_SIZE + 2 * style.FramePadding.y + style.ItemSpacing.y + \ +// ImGui::CalcTextSize("Single\n shot").y + style.FramePadding.y + style.ItemSpacing.y + 2 + \ +// ImGui::GetFontSize() + 2 * style.FramePadding.y; +// return calc_height; +// } + diff --git a/Android_App/app/src/main/cpp/trigger_ui.h b/Android_App/app/src/main/cpp/trigger_ui.h new file mode 100644 index 000000000..25d360a10 --- /dev/null +++ b/Android_App/app/src/main/cpp/trigger_ui.h @@ -0,0 +1,17 @@ +#ifndef TRIGGERUI_H +#define TRIGGERUI_H + +#include "o1buffer.h" +#include "ui_tile.h" +#include "inputs_ui.h" +class triggerUI : public UI_tile +{ + int ch_sel = 1; + o1buffer::trigger_settings both_ch_trigger_settings[2]; + o1buffer::trigger_settings* curr_ch_trigger_settings = &both_ch_trigger_settings[ch_sel-1]; +public: + triggerUI() : UI_tile("Trigger","Trigger",UI_tile::Width::duplex, 5) {}; + void draw(float width, inputsUI* inputs_ui = nullptr) override; + int get_height() override; +}; +#endif diff --git a/Android_App/app/src/main/cpp/ui_tile.cpp b/Android_App/app/src/main/cpp/ui_tile.cpp new file mode 100644 index 000000000..cdec80f2d --- /dev/null +++ b/Android_App/app/src/main/cpp/ui_tile.cpp @@ -0,0 +1,52 @@ +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" +#include "imgui_internal.h" +#include "ui_tile.h" +void UI_tile::standard_header(float width_pixels) +{ + is_expanded = next_is_expanded; + ImGuiStyle& style = ImGui::GetStyle(); + + float close_button_width = ImGui::GetFontSize() + style.FramePadding.x; + ImVec2 start_pos = ImGui::GetCursorScreenPos(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2(0.f,style.ItemSpacing.y)); // combined with lines in settings_panel.cpp, effectively folds ItemSpacing.y into the individual ui_tile groups + ImGui::Text("%s",short_name); + ImGui::SetCursorScreenPos(start_pos); + ImGui::PushID(short_name); +// TODO : make button larger, remove Dummy at the end + float invisible_button_width; + ImVec2 close_button_pos = start_pos + ImVec2(width_pixels - close_button_width,style.ItemSpacing.y); + if(is_expanded) { + invisible_button_width = width_pixels; + } else { + invisible_button_width = width_pixels - close_button_width; + } + float itemspacingy = style.ItemSpacing.y; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,{style.ItemSpacing.x,0.f}); // fold ItemSpacing.y into the invisible button below + if(ImGui::InvisibleButton("toggle is_expanded", {invisible_button_width, ImGui::GetFontSize() + 2 * itemspacingy})) + { + next_is_expanded = !next_is_expanded; + } + ImGui::PopID(); + + if(!is_expanded) + { + char buf[64]; + sprintf(buf, "%s_close", short_name); + if(ImGui::CloseButton(ImGui::GetID(buf), close_button_pos)) + { + is_visible = false; + } + ImGui::SameLine(); + // mimic the spacing/cursor advancement that a standard Button(...) would generate + ImGui::SetCursorScreenPos(close_button_pos); + ImGui::Dummy({close_button_width,0.f}); + } + ImGui::PopStyleVar(); +} + +int UI_tile::get_collapsed_height() +{ + ImGuiStyle& style = ImGui::GetStyle(); + return ImGui::GetFontSize() + 2 * style.ItemSpacing.y; +} diff --git a/Android_App/app/src/main/cpp/ui_tile.h b/Android_App/app/src/main/cpp/ui_tile.h new file mode 100644 index 000000000..c23c3eda3 --- /dev/null +++ b/Android_App/app/src/main/cpp/ui_tile.h @@ -0,0 +1,23 @@ +#ifndef UITILE_H +#define UITILE_H +class inputsUI; + +class UI_tile +{ + public: + virtual ~UI_tile() {}; + enum Width {singlet, duplex}; + UI_tile(const char* name, const char* short_name, Width width, int n_lines) : name(name), short_name(short_name), width(width), n_lines(n_lines) {}; + virtual int get_height() = 0; + int get_collapsed_height(); + virtual void draw(float width_pixels, inputsUI* inputs_ui = nullptr) = 0; + const Width width; + int n_lines; + const char* name; + const char* short_name; + void standard_header(float width_pixels); + bool is_expanded = true; + bool next_is_expanded = true; // to prevent expansion/contraction of tiles mid-frame + bool is_visible = true; +}; +#endif diff --git a/Android_App/app/src/main/cpp/virtual_transform_ui.cpp b/Android_App/app/src/main/cpp/virtual_transform_ui.cpp new file mode 100644 index 000000000..6e96f9aa0 --- /dev/null +++ b/Android_App/app/src/main/cpp/virtual_transform_ui.cpp @@ -0,0 +1,96 @@ +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" +#include "librador.h" +#include "custom_imgui.h" +#include +#include "imgui_internal.h" +#include "virtual_transform_ui.h" + + +void virtualTransformUI::draw(float width_pixels, inputsUI* inputs_ui) +{ + ImGuiStyle& style = ImGui::GetStyle(); + + for (int ch : {1,2}) { + both_ch_settings[ch-1].is_paused = librador_get_paused(ch); // could have been set to true by a singleshot trigger + } +// if(xy && j==2) // sync ch1 and ch2 pause states in xy mode +// *(checkbox_bool[j] + (i+1)%2) = *(checkbox_bool[j] + i); + + ImGui::BeginGroup(); + standard_header(width_pixels); + if(!is_expanded) { + ImGui::EndGroup(); + return; + } + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f)); + ImGui::BeginGroup(); // for bounding rect + if(ImGui::BeginTable("helper1",2, ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_NoHostExtendX, ImVec2(width_pixels, 0.))) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.4f); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2(0.f, style.FramePadding.y - style.ItemSpacing.y)); +#define ALIGN_Y ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() - ImVec2(0.f, style.FramePadding.y - style.ItemSpacing.y)); + ImGui::Text("CH: "); + ImGui::SameLine(); + ALIGN_Y + ImGui::RadioButton("A ", &ch_sel, 1); + ImGui::SameLine(); + ALIGN_Y + ImGui::RadioButton("B", &ch_sel, 2); + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2((ImGui::GetContentRegionAvail().x - CHECKBOX_SIZE - ImGui::CalcTextSize("||").x - style.ItemInnerSpacing.x)/2.,0.f)); + ImGui::Checkbox("||", &curr_ch_settings->is_paused); + ImGui::EndTable(); + } + + const float offset_button_width = style.FramePadding.x*2 + ImGui::CalcTextSize("Offset").x; + if(ImGui::BeginTable("helper2",2, ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_RowBg , ImVec2(width_pixels, 0.))) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.7f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.3f); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(-1); + ImGui::custom_SliderFloat("##offset", "V", &curr_ch_settings->offset, -20.f, 20.f, "%.1f V", ImGuiSliderFlags_ClampOnInput); + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() - ImVec2(style.CellPadding.x,0.f)); + ButtonForSlider("Offset", "##offset", ImVec2(ImGui::GetContentRegionAvail().x + style.CellPadding.x,0.f)); + ImGui::EndTable(); + } + if(ImGui::BeginTable("helper3", 2, ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH, ImVec2(width_pixels, 0.f))) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.4f); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); +// ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {style.FramePadding.x/4.f,style.FramePadding.y}); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - style.FramePadding.x - ImGui::CalcTextSize("Gain").x); + ImGui::Combo("Gain", &curr_ch_settings->gain_sel, gain_labels, IM_COUNTOF(gain_labels)); +// ImGui::PopStyleVar(); + ImGui::TableNextColumn(); + ImGui::SetCursorScreenPos(ImGui::GetCursorScreenPos() + ImVec2((ImGui::GetContentRegionAvail().x - CHECKBOX_SIZE - ImGui::CalcTextSize("AC").x - style.ItemInnerSpacing.x)/2.,0.f)); + ImGui::Checkbox("AC", &curr_ch_settings->is_ac); + ImGui::EndTable(); + } + ImGui::PopStyleVar(); //iteminnerspacing + ImGui::EndGroup(); + + curr_ch_settings = &both_ch_settings[ch_sel-1]; + ImVec2 p0 = ImGui::GetItemRectMin(); + ImVec2 p1 = ImGui::GetItemRectMax(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRect(p0, p1, IM_COL32(90, 90, 120, 255),0,0,2); + ImGui::EndGroup(); + librador_set_virtual_transform_settings(ch_sel, + (o1buffer::virtual_transform_settings) + {.offset=curr_ch_settings->offset, .gain=gains[curr_ch_settings->gain_sel], .is_ac=curr_ch_settings->is_ac, .is_paused=curr_ch_settings->is_paused}); +} + +int virtualTransformUI::get_height() +{ + ImGuiStyle& style = ImGui::GetStyle(); + int calc_height = 2 * style.ItemSpacing.y + ImGui::GetFontSize() + \ + CHECKBOX_SIZE + 2 * style.CellPadding.y + \ + 2 * (ImGui::GetFontSize() + 2 * style.FramePadding.y + 2 * style.CellPadding.y); + return calc_height; +} diff --git a/Android_App/app/src/main/cpp/virtual_transform_ui.h b/Android_App/app/src/main/cpp/virtual_transform_ui.h new file mode 100644 index 000000000..33decc06c --- /dev/null +++ b/Android_App/app/src/main/cpp/virtual_transform_ui.h @@ -0,0 +1,25 @@ +#ifndef VIRTUALTRANSFORMUI_H +#define VIRTUALTRANSFORMUI_H + +#include "ui_tile.h" +class virtualTransformUI : public UI_tile +{ + static const int num_gain_options = 3; + const int gains[num_gain_options] = {1, 5, 10}; + const char* gain_labels[num_gain_options] = {"1x", "5x", "10x"}; + struct ch_settings { + float offset = 0.f; + int gain_sel = 0; + bool is_ac = false; + bool is_paused = false; + }; + + int ch_sel = 1; + ch_settings both_ch_settings[2]; + ch_settings* curr_ch_settings = both_ch_settings; +public: + virtualTransformUI() : UI_tile("Virtual Transforms", "Virtual Transforms", UI_tile::Width::duplex, 4) {}; + void draw(float width, inputsUI* inputs_ui = nullptr) override; + int get_height() override; +}; +#endif // VIRTUALTRANSFORMUI_H diff --git a/Android_App/app/src/main/java/com/EspoTek/Labrador/MainActivity.java b/Android_App/app/src/main/java/com/EspoTek/Labrador/MainActivity.java new file mode 100644 index 000000000..0bff0e553 --- /dev/null +++ b/Android_App/app/src/main/java/com/EspoTek/Labrador/MainActivity.java @@ -0,0 +1,309 @@ +package com.EspoTek.Labrador; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbDeviceConnection; +import android.content.Context; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.BroadcastReceiver; + +import android.content.res.AssetManager; +import android.os.Bundle; +import android.util.Log; + +import android.os.Build; +import android.content.res.Configuration; + +import org.libsdl.app.SDLActivity; + +import android.content.DialogInterface; +import android.app.AlertDialog; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import java.util.HashMap; +import java.util.Iterator; +import java.lang.String; + +import androidx.core.view.WindowInsetsCompat; +import androidx.core.view.ViewCompat; +import android.content.pm.ActivityInfo; +// import android.graphics.Insets; + +// TODO: handle edge cases related to user unplugging board in non-bootloader state, replugging it back in in bootloader state +public class MainActivity extends SDLActivity { + private static final String ACTION_USB_PERMISSION = "com.EspoTek.Labrador.USB_PERMISSION"; + private static final String usbStateChangeAction = "android.hardware.usb.action.USB_STATE"; + private static final String TAG = "com.EspoTek.Labrador"; + public AssetManager mgr; + private boolean usb_permission_request_allowed = true; + + private UsbDeviceConnection connection = null; + public boolean bootloader_mode_allowed = false; // modified by usbcallhandler + private native void nativeRespondToStartupOrUsbStateChange(boolean is_plugged_in, int file_descriptor, boolean bootloader_mode); // fn in librador.cpp. **this fn initiates librador** + private native void nativeInitiateFirmwareFlash(); + + @Override + protected String[] getLibraries() { + return new String[]{"SDL3", "labrador-imgui-android"}; + } + + @Override + protected String getMainFunction() { + return "main"; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +// https://stackoverflow.com/questions/10941802/cant-access-aassetmanager-in-native-code-passed-from-java-in-wallpaperservice/11617834#11617834 + mgr = getResources().getAssets(); + } + +// https://stackoverflow.com/questions/6981736/android-3-1-usb-host-broadcastreceiver-does-not-receive-usb-device-attached/9814826#9814826 +// https://stackoverflow.com/questions/8619883/onnewintent-lifecycle-and-registered-listeners +// called when the Labrador board is plugged in or restarted when the app is already running + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + Log.d(TAG, "on new intent"); + setIntent(intent); // set intent for subsequent call to onResume() + } + +// called on startup and on usb stage changes + @Override + protected void onResume() { + super.onResume(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + // don't need to add the ACTION_USB_DEVICE_ATTACHED intent because it is already declared in the manifest and accordingly gets forwarded to this app's onNewIntent() by android software. + registerReceiver(myUsbDetachBroadcastReceiver, filter); + + Intent intent = getIntent(); + Log.d(TAG, "new intent: " + intent.getAction()); + UsbDevice device; + HashMap device_info = new HashMap(); + // look for the device + UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); //Handle to system USB service? + HashMap deviceList = manager.getDeviceList(); + Iterator deviceIterator = deviceList.values().iterator(); + if(!deviceIterator.hasNext()){ + Log.d(TAG, "no device found"); + } + while(device_info.isEmpty() && deviceIterator.hasNext()) { + Log.d(TAG, "device list has device"); + device = deviceIterator.next(); + device_info = processUsbDevice(device); + } + if(device_info.isEmpty()) { + nativeRespondToStartupOrUsbStateChange(false, -1, false); + } else { + int file_descriptor = device_info.get("file_descriptor"); + boolean bootloader_mode = device_info.get("bootloader_mode") == 1 ? true : false; + if(!bootloader_mode_allowed && bootloader_mode) { + AlertDialog alert = new AlertDialog.Builder(MainActivity.this) + .setMessage("Board found in bootloader mode, which is intended for firmware updates. Please unplug the board, disconnect Digital Out 1 from GND, plug the board back in, and then unplug and replug the board a second time. If a firmware update is needed, it will be performed automatically.") + .setPositiveButton("OK", new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int id) + { + } + } + ) + .setCancelable(false) + .create(); + alert.show(); + nativeRespondToStartupOrUsbStateChange(false, file_descriptor, bootloader_mode); + return; + } + nativeRespondToStartupOrUsbStateChange(true, file_descriptor, bootloader_mode); + } + } + + @Override + protected void onPause() { + super.onPause(); + unregisterReceiver(myUsbDetachBroadcastReceiver); + } + + @Override + protected void onStop() { + super.onStop(); + disconnectIfConnected(); + } + + private HashMap processUsbDevice(UsbDevice device) { + HashMap device_info = new HashMap(); + PendingIntent mPermissionIntent; + Intent intent = new Intent(MainActivity.ACTION_USB_PERMISSION); + intent.setPackage(MainActivity.getContext().getPackageName()); + + mPermissionIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); + + UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); //Handle to system USB service? + + // handle edge case: Labrador board is connected -> user puts phone to sleep -> user unplugs Labrador board -> user wakes phone back up (at which point onResume() gets called with intent= the most recent intent received by the app, i.e., the original intent signaling that the board was connected) + HashMap dl = manager.getDeviceList(); + if (!manager.getDeviceList().containsValue(device)) { + return device_info; + } + + int DeviceID = device.getDeviceId(); + int VID = device.getVendorId(); + int PID = device.getProductId(); + + // Block below: for the rare case that the app finds the board and android software hasn't handled the usb permissions, so they need to be handled by the app. E.g., if the user connects the board for the first time when their phone is asleep + if(!manager.hasPermission(device)) { + // block below: open a dialog window that prompts the user for usb connection permission. meanwhile, this function returns an empty device_info, which gets handled cleanly. After the user responds to the dialog window, onResume() gets called, which leads back to this fn. In the case of rejection by the user, the usb_permission_request_allowed boolean prevents the request from being repeated. This bool gets reset to true if the user disconnects the board. + if(usb_permission_request_allowed) { + manager.requestPermission(device, mPermissionIntent); + usb_permission_request_allowed = false; + } + return device_info; + } + + connection = manager.openDevice(device); + int file_descriptor; + if(connection==null) { + Log.d(TAG, "device connection fail"); + return device_info; + } else { + file_descriptor = connection.getFileDescriptor(); + } + Log.d(TAG, "fd = " + file_descriptor); + int bootloader_mode; + if((VID==0x03eb) && (PID==0x2fe4)) { + bootloader_mode = 1; + } else { + bootloader_mode = 0; + } + device_info.put("file_descriptor", file_descriptor); + device_info.put("bootloader_mode", bootloader_mode); + + Log.d(TAG, "Returning..."); + return device_info; + } + +// doesn't work ideally at the moment because iso_polling_thread in +// usbcallhandler.cpp encounters LIBUSB_ERROR_NO_DEVICE before the broadcast +// receiver below shuts down the thread. Then, usbcallhandler.cpp flushes out +// all remaining iso transfers that it had previously submitted. After that +// finishes, this broadcast receiver fires and does some more cleanup of the +// iso thread and also deletes the libusb handle on the Labrador board. + BroadcastReceiver myUsbDetachBroadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + Log.d(TAG,"detach detector reached"); + String action = intent.getAction(); + if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { + UsbDevice device; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice.class); + } else { + device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + } + int VID = device.getVendorId(); + int PID = device.getProductId(); + if((VID==0x03eb) && (PID==0xba94)){ + disconnectIfConnected(); + } + if(connection != null) { + connection.close(); + } + } + } + }; + + // for APIs 35 and higher, the status bar is included in the main display, so it's necessary to account for it and avoid it. Otherwise, return 0; + public int getStatusBarHeight() + { + int statusBarHeight; + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(getWindow().getDecorView()); + statusBarHeight = insets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.systemBars()).top; + } else { + statusBarHeight = 0; + } + return statusBarHeight; + } + // for APIs 35 and higher, the navigation bar is included in the main display, so it's necessary to account for it and avoid it. Otherwise, return 0; + public int getNavigationBarHeight() + { + int navigationBarHeight; + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(getWindow().getDecorView()); + navigationBarHeight = insets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.systemBars()).bottom; + } else { + navigationBarHeight = 0; + } + return navigationBarHeight; + } + + public int getScreenWidth() + { + return getResources().getSystem().getDisplayMetrics().widthPixels; + } + + public int getScreenHeight() + { + return getResources().getSystem().getDisplayMetrics().heightPixels; + } + + public float getDpi() + { + return getResources().getSystem().getDisplayMetrics().xdpi; + } + + + // called by usbcallhandler.cpp + public void requestFirmwareFlash() + { + AlertDialog alert = new AlertDialog.Builder(MainActivity.this) + .setMessage("A firmware update is required. This process will produce several USB permission requests. Please accept them all.") + .setPositiveButton("OK", new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int id) + { + nativeInitiateFirmwareFlash(); + } + } + ) + .setCancelable(false) + .create(); + alert.show(); + } + + // called by usbcallhandler.cpp + public void confirmFirmwareFlash() + { + AlertDialog alert = new AlertDialog.Builder(MainActivity.this) + .setMessage("New firmware has been successfully uploaded to the Labrador board.") + .setPositiveButton("OK", new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int id) + { + } + } + ) + .setCancelable(false) + .create(); + alert.show(); + } + + public void disconnectIfConnected() + { + nativeRespondToStartupOrUsbStateChange(false, -1, false); + if(connection != null) { + connection.close(); + } + usb_permission_request_allowed = true; + } +} diff --git a/Android_App/app/src/main/res/layout/activity_main.xml b/Android_App/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..0fdf98529 --- /dev/null +++ b/Android_App/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/Android_App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/Android_App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..4ae7d1237 --- /dev/null +++ b/Android_App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Android_App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/Android_App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..4ae7d1237 --- /dev/null +++ b/Android_App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 000000000..517455146 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp b/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp new file mode 100644 index 000000000..f66bbd7d6 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp differ diff --git a/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 000000000..52a3881bd Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 000000000..5e70bfe3a Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 000000000..57a54099a Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp b/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp new file mode 100644 index 000000000..e25f556b1 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp differ diff --git a/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 000000000..7c4bb5d56 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 000000000..bf0c62d8a Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 000000000..ea2f74954 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp b/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp new file mode 100644 index 000000000..c2d0ca136 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 000000000..2da75fb9b Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..1951e1b37 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 000000000..16d2cb34b Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp b/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp new file mode 100644 index 000000000..9b3322b3e Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 000000000..53086e438 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..4584cc101 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 000000000..16aeb91fd Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp b/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp new file mode 100644 index 000000000..38f5f8c38 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 000000000..50af534bc Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..e82031206 Binary files /dev/null and b/Android_App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/Android_App/app/src/main/res/values-night/themes.xml b/Android_App/app/src/main/res/values-night/themes.xml new file mode 100644 index 000000000..54202f550 --- /dev/null +++ b/Android_App/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/Android_App/app/src/main/res/values/colors.xml b/Android_App/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..f8c6127d3 --- /dev/null +++ b/Android_App/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/Android_App/app/src/main/res/values/strings.xml b/Android_App/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..70f8fa474 --- /dev/null +++ b/Android_App/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Labrador + diff --git a/Android_App/app/src/main/res/values/themes.xml b/Android_App/app/src/main/res/values/themes.xml new file mode 100644 index 000000000..503477bfe --- /dev/null +++ b/Android_App/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/Android_App/app/src/main/res/xml/backup_rules.xml b/Android_App/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 000000000..4df925582 --- /dev/null +++ b/Android_App/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/Android_App/app/src/main/res/xml/data_extraction_rules.xml b/Android_App/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 000000000..9ee9997b0 --- /dev/null +++ b/Android_App/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/Android_App/app/src/main/res/xml/device_filter.xml b/Android_App/app/src/main/res/xml/device_filter.xml new file mode 100644 index 000000000..85724f81f --- /dev/null +++ b/Android_App/app/src/main/res/xml/device_filter.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/Android_App/build.gradle.kts b/Android_App/build.gradle.kts new file mode 100644 index 000000000..327342f6c --- /dev/null +++ b/Android_App/build.gradle.kts @@ -0,0 +1,4 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.android.application) apply false +} diff --git a/Android_App/gradle.properties b/Android_App/gradle.properties new file mode 100644 index 000000000..534063aa9 --- /dev/null +++ b/Android_App/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true diff --git a/Android_App/gradle/libs.versions.toml b/Android_App/gradle/libs.versions.toml new file mode 100644 index 000000000..485e997bc --- /dev/null +++ b/Android_App/gradle/libs.versions.toml @@ -0,0 +1,20 @@ +[versions] +agp = "9.0.0" +junit = "4.13.2" +junitVersion = "1.3.0" +espressoCore = "3.7.0" +appcompat = "1.7.1" +material = "1.13.0" +constraintlayout = "2.2.1" + +[libraries] +junit = { group = "junit", name = "junit", version.ref = "junit" } +ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } + diff --git a/Android_App/gradle/wrapper/gradle-wrapper.jar b/Android_App/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..8bdaf60c7 Binary files /dev/null and b/Android_App/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Android_App/gradle/wrapper/gradle-wrapper.properties b/Android_App/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..1e9b251d5 --- /dev/null +++ b/Android_App/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +#Tue Feb 03 14:11:51 PST 2026 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Android_App/gradlew b/Android_App/gradlew new file mode 100755 index 000000000..ef07e0162 --- /dev/null +++ b/Android_App/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Android_App/settings.gradle.kts b/Android_App/settings.gradle.kts new file mode 100644 index 000000000..0d0bb93aa --- /dev/null +++ b/Android_App/settings.gradle.kts @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "Labrador" +include(":app") diff --git a/libdfuprog/CMakeLists.txt b/libdfuprog/CMakeLists.txt new file mode 100644 index 000000000..920d970d8 --- /dev/null +++ b/libdfuprog/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.16) +project(libdfuprog VERSION 1.0 LANGUAGES C CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +add_library(libdfuprog STATIC + config.h + dfu-programmer/src/arguments.c dfu-programmer/src/arguments.h + dfu-programmer/src/atmel.c dfu-programmer/src/atmel.h + dfu-programmer/src/commands.c dfu-programmer/src/commands.h + dfu-programmer/src/dfu.c dfu-programmer/src/dfu.h + dfu-programmer/src/dfu-bool.h + dfu-programmer/src/dfu-device.h + dfu-programmer/src/intel_hex.c dfu-programmer/src/intel_hex.h + dfu-programmer/src/stm32.c dfu-programmer/src/stm32.h + dfu-programmer/src/util.c dfu-programmer/src/util.h + libdfuprog.c libdfuprog.h +) + +target_include_directories(libdfuprog PRIVATE + dfu-programmer/src + ../Android_App/app/src/main/cpp/deps/librador/librador_deps/libusb +) + +target_compile_definitions(libdfuprog PRIVATE + HAVE_CONFIG_H + PLATFORM_ANDROID +) + +install(TARGETS libdfuprog + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + FRAMEWORK DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +)