├── .clang-format ├── .github └── workflows │ ├── cd-release.yml │ ├── ci-build.yml │ └── rp2040-fw-release.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── bootloader_components ├── appfs └── main ├── components ├── driver_fsoverbus │ ├── CMakeLists.txt │ ├── Kconfig │ ├── README │ ├── appfsfunctions.c │ ├── backend.c │ ├── driver_fsoverbus.c │ ├── filefunctions.c │ ├── include │ │ ├── appfsfunctions.h │ │ ├── driver_fsoverbus.h │ │ ├── filefunctions.h │ │ ├── fsob_backend.h │ │ ├── functions.h │ │ ├── packetutils.h │ │ └── specialfunctions.h │ ├── packetutils.c │ ├── project_include.cmake │ ├── specialfunctions.c │ ├── uart_backend.c │ └── uartnaive_backend.c └── gui-toolkit │ ├── CMakeLists.txt │ ├── graphics_wrapper.c │ ├── gui_element_header.c │ ├── gui_input.c │ ├── include │ ├── graphics_wrapper.h │ ├── gui_element_header.h │ └── menu.h │ └── menu.c ├── generate_version_header.sh ├── main ├── CMakeLists.txt ├── adc_test.c ├── app_management.c ├── app_update.c ├── appfs_wrapper.c ├── audio.c ├── bootscreen.c ├── button_test.c ├── factory_test.c ├── file_browser.c ├── filesystems.c ├── fpga_download.c ├── fpga_test.c ├── fpga_util.c ├── http_download.c ├── include │ ├── adc_test.h │ ├── app_management.h │ ├── app_update.h │ ├── appfs_wrapper.h │ ├── audio.h │ ├── bootscreen.h │ ├── button_test.h │ ├── factory_test.h │ ├── file_browser.h │ ├── filesystems.h │ ├── fpga_download.h │ ├── fpga_test.h │ ├── fpga_util.h │ ├── http_download.h │ ├── metadata.h │ ├── msc.h │ ├── nametag.h │ ├── rp2040_updater.h │ ├── rtc_memory.h │ ├── sao_eeprom.h │ ├── settings.h │ ├── system_wrapper.h │ ├── terminal.h │ ├── test_common.h │ ├── webusb.h │ ├── wifi_cert.h │ ├── wifi_defaults.h │ ├── wifi_ota.h │ └── wifi_test.h ├── main.c ├── menus │ ├── dev.c │ ├── dev.h │ ├── hatchery.c │ ├── hatchery.h │ ├── ir.c │ ├── ir.h │ ├── launcher.c │ ├── launcher.h │ ├── sao.c │ ├── sao.h │ ├── settings.c │ ├── settings.h │ ├── start.c │ ├── start.h │ ├── wifi.c │ └── wifi.h ├── metadata.c ├── msc.c ├── nametag.c ├── rp2040_updater.c ├── rtc_memory.c ├── sao_eeprom.c ├── settings.c ├── system_wrapper.c ├── terminal.c ├── test_common.c ├── webusb.c ├── wifi_cert.c ├── wifi_defaults.c ├── wifi_ota.c └── wifi_test.c ├── partitions.csv ├── partitions.ods ├── resources ├── boot.snd ├── custom_ota_cert.pem ├── fpga_selftest.bin ├── icons │ ├── apps.png │ ├── bitstream.png │ ├── dev.png │ ├── hatchery.png │ ├── home.png │ ├── hourglass.png │ ├── python.png │ ├── sao.png │ ├── settings.png │ ├── tag.png │ └── update.png ├── isrgrootx1.pem ├── mch2022_logo.png └── rp2040_firmware.bin └── sdkconfig /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -1 5 | AlignAfterOpenBracket: Align 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveMacros: true 8 | AlignConsecutiveAssignments: true 9 | AlignConsecutiveBitFields: true 10 | AlignConsecutiveDeclarations: true 11 | AlignEscapedNewlines: Left 12 | AlignOperands: Align 13 | AlignTrailingComments: true 14 | AllowAllArgumentsOnNextLine: true 15 | AllowAllConstructorInitializersOnNextLine: true 16 | AllowAllParametersOfDeclarationOnNextLine: true 17 | AllowShortEnumsOnASingleLine: true 18 | AllowShortBlocksOnASingleLine: Never 19 | AllowShortCaseLabelsOnASingleLine: false 20 | AllowShortFunctionsOnASingleLine: All 21 | AllowShortLambdasOnASingleLine: All 22 | AllowShortIfStatementsOnASingleLine: WithoutElse 23 | AllowShortLoopsOnASingleLine: true 24 | AlwaysBreakAfterDefinitionReturnType: None 25 | AlwaysBreakAfterReturnType: None 26 | AlwaysBreakBeforeMultilineStrings: true 27 | AlwaysBreakTemplateDeclarations: Yes 28 | AttributeMacros: 29 | - __capability 30 | BinPackArguments: true 31 | BinPackParameters: true 32 | BraceWrapping: 33 | AfterCaseLabel: false 34 | AfterClass: false 35 | AfterControlStatement: Never 36 | AfterEnum: false 37 | AfterFunction: false 38 | AfterNamespace: false 39 | AfterObjCDeclaration: false 40 | AfterStruct: false 41 | AfterUnion: false 42 | AfterExternBlock: false 43 | BeforeCatch: false 44 | BeforeElse: false 45 | BeforeLambdaBody: false 46 | BeforeWhile: false 47 | IndentBraces: false 48 | SplitEmptyFunction: true 49 | SplitEmptyRecord: true 50 | SplitEmptyNamespace: true 51 | BreakBeforeBinaryOperators: None 52 | BreakBeforeConceptDeclarations: true 53 | BreakBeforeBraces: Attach 54 | BreakBeforeInheritanceComma: false 55 | BreakInheritanceList: BeforeColon 56 | BreakBeforeTernaryOperators: true 57 | BreakConstructorInitializersBeforeComma: false 58 | BreakConstructorInitializers: BeforeColon 59 | BreakAfterJavaFieldAnnotations: false 60 | BreakStringLiterals: true 61 | ColumnLimit: 160 62 | CommentPragmas: '^ IWYU pragma:' 63 | CompactNamespaces: false 64 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 65 | ConstructorInitializerIndentWidth: 4 66 | ContinuationIndentWidth: 4 67 | Cpp11BracedListStyle: true 68 | DeriveLineEnding: true 69 | DerivePointerAlignment: true 70 | DisableFormat: false 71 | EmptyLineAfterAccessModifier: Never 72 | EmptyLineBeforeAccessModifier: LogicalBlock 73 | ExperimentalAutoDetectBinPacking: false 74 | FixNamespaceComments: true 75 | ForEachMacros: 76 | - foreach 77 | - Q_FOREACH 78 | - BOOST_FOREACH 79 | IfMacros: 80 | - KJ_IF_MAYBE 81 | IncludeBlocks: Regroup 82 | IncludeCategories: 83 | - Regex: '^' 84 | Priority: 2 85 | SortPriority: 0 86 | CaseSensitive: false 87 | - Regex: '^<.*\.h>' 88 | Priority: 1 89 | SortPriority: 0 90 | CaseSensitive: false 91 | - Regex: '^<.*' 92 | Priority: 2 93 | SortPriority: 0 94 | CaseSensitive: false 95 | - Regex: '.*' 96 | Priority: 3 97 | SortPriority: 0 98 | CaseSensitive: false 99 | IncludeIsMainRegex: '([-_](test|unittest))?$' 100 | IncludeIsMainSourceRegex: '' 101 | IndentAccessModifiers: false 102 | IndentCaseLabels: true 103 | IndentCaseBlocks: true 104 | IndentGotoLabels: true 105 | IndentPPDirectives: None 106 | IndentExternBlock: AfterExternBlock 107 | IndentRequires: false 108 | IndentWidth: 4 109 | IndentWrappedFunctionNames: false 110 | InsertTrailingCommas: None 111 | JavaScriptQuotes: Leave 112 | JavaScriptWrapImports: true 113 | KeepEmptyLinesAtTheStartOfBlocks: false 114 | LambdaBodyIndentation: Signature 115 | MacroBlockBegin: '' 116 | MacroBlockEnd: '' 117 | MaxEmptyLinesToKeep: 1 118 | NamespaceIndentation: None 119 | ObjCBinPackProtocolList: Never 120 | ObjCBlockIndentWidth: 2 121 | ObjCBreakBeforeNestedBlockParam: true 122 | ObjCSpaceAfterProperty: false 123 | ObjCSpaceBeforeProtocolList: true 124 | PenaltyBreakAssignment: 2 125 | PenaltyBreakBeforeFirstCallParameter: 1 126 | PenaltyBreakComment: 300 127 | PenaltyBreakFirstLessLess: 120 128 | PenaltyBreakString: 1000 129 | PenaltyBreakTemplateDeclaration: 10 130 | PenaltyExcessCharacter: 1000000 131 | PenaltyReturnTypeOnItsOwnLine: 200 132 | PenaltyIndentedWhitespace: 0 133 | PointerAlignment: Left 134 | PPIndentWidth: -1 135 | RawStringFormats: 136 | - Language: Cpp 137 | Delimiters: 138 | - cc 139 | - CC 140 | - cpp 141 | - Cpp 142 | - CPP 143 | - 'c++' 144 | - 'C++' 145 | CanonicalDelimiter: '' 146 | BasedOnStyle: google 147 | - Language: TextProto 148 | Delimiters: 149 | - pb 150 | - PB 151 | - proto 152 | - PROTO 153 | EnclosingFunctions: 154 | - EqualsProto 155 | - EquivToProto 156 | - PARSE_PARTIAL_TEXT_PROTO 157 | - PARSE_TEST_PROTO 158 | - PARSE_TEXT_PROTO 159 | - ParseTextOrDie 160 | - ParseTextProtoOrDie 161 | - ParseTestProto 162 | - ParsePartialTestProto 163 | CanonicalDelimiter: pb 164 | BasedOnStyle: google 165 | ReferenceAlignment: Pointer 166 | ReflowComments: true 167 | ShortNamespaceLines: 1 168 | SortIncludes: CaseSensitive 169 | SortJavaStaticImport: Before 170 | SortUsingDeclarations: true 171 | SpaceAfterCStyleCast: true 172 | SpaceAfterLogicalNot: false 173 | SpaceAfterTemplateKeyword: true 174 | SpaceBeforeAssignmentOperators: true 175 | SpaceBeforeCaseColon: false 176 | SpaceBeforeCpp11BracedList: false 177 | SpaceBeforeCtorInitializerColon: true 178 | SpaceBeforeInheritanceColon: true 179 | SpaceBeforeParens: ControlStatements 180 | SpaceAroundPointerQualifiers: Default 181 | SpaceBeforeRangeBasedForLoopColon: true 182 | SpaceInEmptyBlock: false 183 | SpaceInEmptyParentheses: false 184 | SpacesBeforeTrailingComments: 2 185 | SpacesInAngles: Never 186 | SpacesInConditionalStatement: false 187 | SpacesInContainerLiterals: true 188 | SpacesInCStyleCastParentheses: false 189 | SpacesInLineCommentPrefix: 190 | Minimum: 1 191 | Maximum: -1 192 | SpacesInParentheses: false 193 | SpacesInSquareBrackets: false 194 | SpaceBeforeSquareBrackets: false 195 | BitFieldColonSpacing: Both 196 | Standard: Auto 197 | StatementAttributeLikeMacros: 198 | - Q_EMIT 199 | StatementMacros: 200 | - Q_UNUSED 201 | - QT_REQUIRE_VERSION 202 | TabWidth: 8 203 | UseCRLF: false 204 | UseTab: Never 205 | WhitespaceSensitiveMacros: 206 | - STRINGIZE 207 | - PP_STRINGIZE 208 | - BOOST_PP_STRINGIZE 209 | - NS_SWIFT_NAME 210 | - CF_SWIFT_NAME 211 | ... 212 | 213 | -------------------------------------------------------------------------------- /.github/workflows/cd-release.yml: -------------------------------------------------------------------------------- 1 | name: Release build 2 | run-name: > 3 | ${{ github.event.release.prerelease && 'Prerelease' || 'Release' }} build: 4 | ${{ github.event.release.name }}" 5 | 6 | on: 7 | release: 8 | types: [released, prereleased] 9 | 10 | env: 11 | RELEASE_TYPE: ${{ github.event.release.prerelease && 'Prerelease' || 'Release' }} 12 | RELEASE_CHANNEL: ${{ github.event.release.prerelease && 'dev' || 'release' }} 13 | 14 | jobs: 15 | build-release: 16 | name: Build & upload to release 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout repo + submodules 20 | uses: actions/checkout@v3 21 | with: 22 | submodules: recursive 23 | 24 | - name: Build with ESP-IDF 25 | uses: espressif/esp-idf-ci-action@v1 26 | with: 27 | esp_idf_version: v4.4.4 28 | target: esp32 29 | 30 | - name: Upload release assets 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | run: > 34 | gh release upload "${{ github.event.release.tag_name }}" 35 | build/MCH2022.bin 36 | build/MCH2022.elf 37 | 38 | - name: Dispatch OTA hook 39 | uses: peter-evans/repository-dispatch@v2 40 | with: 41 | token: ${{ secrets.OTA_PUSH_TOKEN }} 42 | repository: badgeteam/ota 43 | event-type: firmware-release 44 | client-payload: > 45 | { 46 | "device_id": "mch2022", 47 | "device_name": "MCH2022", 48 | "tag": "${{ github.event.release.tag_name }}", 49 | "channel": "${{ env.RELEASE_CHANNEL }}", 50 | "fw_main": "MCH2022.bin" 51 | } 52 | 53 | - name: Generate release build report 54 | if: success() || failure() 55 | env: 56 | repo: ${{ github.repository }} 57 | tag: ${{ github.event.release.tag_name }} 58 | compare_url_template: ${{ format('/{0}/compare/{{base}}...{{head}}', github.repository) }} 59 | run: | 60 | previous_tag=$(git tag --sort '-refname' | grep -A1 "$tag" | tail -1) 61 | tag_compare_url=$(sed "s!{base}!$previous_tag!; s!{head}!$tag!" <<< $compare_url_template) 62 | 63 | build_size_main=$(du build/MCH2022.bin | awk '{ print $1 }') 64 | 65 | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) 66 | 67 | cat << $EOF >> $GITHUB_STEP_SUMMARY 68 | # $RELEASE_TYPE build summary 🚀${{ github.event.release.prerelease && '🚧' || '' }}🔨 69 | 70 | **Release:** [${{ github.event.release.name }}](${{ github.event.release.html_url }}) 71 | 72 | **Source:** [${repo}@\`${tag}\`](/${repo}/tree/${tag}) 73 | 74 | ## Build details 75 | **Size of \`MCH2022.bin\`:** $build_size_main kB 76 | 77 | \`\`\`console 78 | \$ du -h build/*.bin build/*.elf build/*/*.bin build/*/*.elf 79 | $(du -h build/*.bin build/*.elf build/*/*.bin build/*/*.elf) 80 | \`\`\` 81 | 82 | ### Source 83 | **Diff with previous tag:** $tag_compare_url 84 | 85 | #### Submodules 86 | \`\`\` 87 | $( 88 | git submodule --quiet foreach ' 89 | branch=$(grep -C1 "$(git config --get remote.origin.url)" $toplevel/.gitmodules | grep "branch =" | rev | cut -d" " -f1 | rev) 90 | git fetch origin $branch --unshallow >&2 91 | commits_behind=$(git --no-pager log --oneline HEAD..origin/$branch) 92 | [ -n "$commits_behind" ] && echo "$name has new commits upstream:\n$commits_behind" >&2 93 | echo \ 94 | "$path\t" \ 95 | "$branch\t" \ 96 | "$(git rev-parse --short HEAD)\t" \ 97 | $(if [ -z "$commits_behind" ]; 98 | then echo "✅ up to date"; 99 | else echo "⚠️ $(echo "$commits_behind" | wc -l) commits behind origin/$branch"; 100 | fi) 101 | ' | column -t -s $'\t' 102 | ) 103 | \`\`\` 104 | 105 | $EOF 106 | -------------------------------------------------------------------------------- /.github/workflows/ci-build.yml: -------------------------------------------------------------------------------- 1 | name: Build with ESP-IDF 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '**.md' 9 | - '.clang-format' 10 | - '.gitignore' 11 | 12 | pull_request: 13 | branches: [master] 14 | paths-ignore: 15 | - '**.md' 16 | - '.clang-format' 17 | - '.gitignore' 18 | 19 | workflow_dispatch: 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout repo + submodules 26 | uses: actions/checkout@v3 27 | with: 28 | submodules: recursive 29 | 30 | - name: Build with ESP-IDF 31 | uses: espressif/esp-idf-ci-action@v1 32 | with: 33 | esp_idf_version: v4.4.4 34 | target: esp32 35 | 36 | - name: Upload build to artifact 37 | uses: actions/upload-artifact@v3 38 | with: 39 | name: build 40 | path: | 41 | build/MCH2022.bin 42 | build/MCH2022.elf 43 | build/bootloader/bootloader.bin 44 | build/bootloader/bootloader.elf 45 | 46 | - name: Generate build report 47 | env: 48 | base_branch: master 49 | current_ref: ${{ github.ref_name }} 50 | compare_url_template: ${{ format('/{0}/compare/{{base}}...{{head}}', github.repository) }} 51 | commit_hash: ${{ github.event.after }} 52 | head_compare_url: ${{ github.event.compare }} 53 | new_commits_json: ${{ toJSON(github.event.commits) }} 54 | run: | 55 | build_size_main=$(du build/MCH2022.bin | awk '{ print $1 }') 56 | ref_compare_url=$(sed "s/{base}/$base_branch/; s/{head}/$commit_hash/" <<< $compare_url_template) 57 | 58 | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) 59 | 60 | cat << $EOF >> $GITHUB_STEP_SUMMARY 61 | # Build summary 🔨${{ github.ref_name != 'master' && '🚧' || '' }} 62 | 63 | **Source:** ${{ github.ref_type }} \`$current_ref\` -> [${{ github.repository }}@\`${commit_hash:0:7}\`](${{ github.event.head_commit.url }}) 64 | 65 | **Size of \`MCH2022.bin\`:** $build_size_main kB 66 | 67 | \`\`\`console 68 | \$ du -h build/*.bin build/*/*.bin 69 | $(du -h build/*.bin build/*/*.bin) 70 | \`\`\` 71 | 72 | ## Build details 73 | **Build trigger:** ${{ github.event.forced && '☢️ forced' || '' }} ${{ github.event_name }} \`${{ github.event.ref }}\` 74 | 75 | ### Source 76 | **HEAD:** [${{ github.repository }}@\`${commit_hash:0:7}\`](${{ github.event.head_commit.url }}) on ${{ github.ref_type }} [$current_ref]($ref_compare_url) 77 | 78 | **Diff with previous HEAD:** $head_compare_url 79 | 80 | #### New commits 81 | $(jq -r 'map([ 82 | "**Commit [`\(.id[0:7])`](\(.url)) by \(if .author.username then "@"+.author.username else .author.name end):**", 83 | .message, 84 | (if .committer.name != .author.name then "\n> **Committer:** \(.committer.name) <\(.committer.email)>" else "" end), 85 | "**Timestamp:** \(.timestamp)" 86 | ] | map("> \(.)\n") | join("")) | join("\n")' <<< $new_commits_json) 87 | 88 | #### Submodules 89 | \`\`\` 90 | $( 91 | git submodule --quiet foreach ' 92 | branch=$(grep -C1 "$(git config --get remote.origin.url)" $toplevel/.gitmodules | grep "branch =" | rev | cut -d" " -f1 | rev) 93 | git fetch origin $branch --unshallow >&2 94 | commits_behind=$(git --no-pager log --oneline HEAD..origin/$branch) 95 | [ -n "$commits_behind" ] && echo "$name has new commits upstream:\n$commits_behind" >&2 96 | echo \ 97 | "$path\t" \ 98 | "$branch\t" \ 99 | "$(git rev-parse --short HEAD)\t" \ 100 | $(if [ -z "$commits_behind" ]; 101 | then echo "✅ up to date"; 102 | else echo "⚠️ $(echo "$commits_behind" | wc -l) commits behind origin/$branch"; 103 | fi) 104 | ' | column -t -s $'\t' 105 | ) 106 | \`\`\` 107 | 108 | $EOF 109 | continue-on-error: true 110 | -------------------------------------------------------------------------------- /.github/workflows/rp2040-fw-release.yml: -------------------------------------------------------------------------------- 1 | name: RP2040 firmware release hook 2 | run-name: RP2040 firmware release ${{ github.event.client_payload.fw_version }} 3 | 4 | on: 5 | repository_dispatch: 6 | types: [rp2040-firmware-released] 7 | 8 | jobs: 9 | pull-and-pr: 10 | name: Get new RP2040 firmware & create PR 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repo 14 | uses: actions/checkout@v3 15 | 16 | - name: Check payload 17 | id: payload 18 | run: | 19 | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) 20 | payload=$( 21 | cat << $EOF 22 | ${{ toJson(github.event.client_payload) }} 23 | $EOF 24 | ); 25 | 26 | valid_payload=$(jq -r 'select( 27 | (.tag | type == "string") and 28 | (.fw_main | type == "string") and 29 | (.fw_version | type == "string") 30 | )' <<< "$payload"); 31 | 32 | [ -n "$valid_payload" ] || exit 1 33 | 34 | # output checked payload fields 35 | echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT 36 | echo "fw_main=${{ github.event.client_payload.fw_main }}" >> $GITHUB_OUTPUT 37 | echo "fw_version=${{ github.event.client_payload.fw_version }}" >> $GITHUB_OUTPUT 38 | 39 | - name: Get release info 40 | id: release_info 41 | env: 42 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | run: | 44 | release_info=$( 45 | gh release \ 46 | --repo badgeteam/mch2022-firmware-rp2040 \ 47 | view "${{ steps.payload.outputs.tag }}" 48 | ); 49 | echo "Release info:"; 50 | echo "$release_info"; 51 | 52 | release_header=$(awk '/[a-z]+:/' <<< "$release_info") 53 | release_assets=$(grep "^asset:" <<< "$release_header" | cut -f 2-) 54 | release_description=$(awk '/--/,0' <<< "$release_info" | tail +2) 55 | 56 | # output info fields 57 | for label in 'title' 'tag' 'draft' 'prerelease' 'author' 'created' 'published' 'url'; do 58 | echo "$label=$(grep "^${label}:" <<< "$release_header" | cut -f 2-)" >> $GITHUB_OUTPUT; 59 | done 60 | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) 61 | echo "assets<<$EOF" >> $GITHUB_OUTPUT 62 | echo "$release_assets" >> $GITHUB_OUTPUT 63 | echo "$EOF" >> $GITHUB_OUTPUT 64 | echo "description<<$EOF" >> $GITHUB_OUTPUT 65 | echo "$release_description" >> $GITHUB_OUTPUT 66 | echo "$EOF" >> $GITHUB_OUTPUT 67 | 68 | - name: Integrate new RP2040 firmware 69 | env: 70 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | run: | 72 | gh release --repo badgeteam/mch2022-firmware-rp2040 \ 73 | download "${{ steps.payload.outputs.tag }}" \ 74 | -p ${{ steps.payload.outputs.fw_main }} \ 75 | -O resources/rp2040_firmware.bin --clobber 76 | 77 | version="${{ steps.payload.outputs.fw_version }}" 78 | perl -pi -e "s/(?<=#define RP2040_TARGET_FW)(\s+)0x[0-9a-fA-F]{1,2}$/\${1}${version}/" main/rp2040_updater.c 79 | 80 | - id: create-pr 81 | name: Create PR 82 | uses: peter-evans/create-pull-request@v5 83 | env: 84 | message: "RP2040 firmware release ${{ github.event.client_payload.fw_version }}" 85 | with: 86 | branch: ci/update-rp2040-firmware 87 | title: ${{ env.message }} 88 | commit-message: ${{ env.message }} 89 | assignees: ${{ steps.release_info.outputs.author }} 90 | body: | 91 | ## ${{ steps.release_info.outputs.title }} 92 | ${{ steps.release_info.outputs.url }} 93 | 94 | ${{ steps.release_info.outputs.description }} 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | main/build 3 | sdkconfig.old 4 | *.bak 5 | *.old 6 | .vscode 7 | tools/__pycache__ 8 | .cache 9 | esp-idf 10 | dependencies.lock -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "components/bus-i2c"] 2 | path = components/bus-i2c 3 | url = https://github.com/Nicolai-Electronics/esp32-component-bus-i2c.git 4 | branch = master 5 | [submodule "components/i2c-bno055"] 6 | path = components/i2c-bno055 7 | url = https://github.com/Nicolai-Electronics/esp32-component-i2c-bno055.git 8 | branch = master 9 | [submodule "components/spi-ili9341"] 10 | path = components/spi-ili9341 11 | url = https://github.com/Nicolai-Electronics/esp32-component-spi-ili9341.git 12 | branch = master 13 | [submodule "components/spi-ice40"] 14 | path = components/spi-ice40 15 | url = https://github.com/Nicolai-Electronics/esp32-component-spi-ice40.git 16 | branch = master 17 | [submodule "components/sdcard"] 18 | path = components/sdcard 19 | url = https://github.com/Nicolai-Electronics/esp32-component-sdcard.git 20 | branch = master 21 | [submodule "components/pax-graphics"] 22 | path = components/pax-graphics 23 | url = https://github.com/robotman2412/pax-graphics.git 24 | branch = main 25 | [submodule "components/mch2022-rp2040"] 26 | path = components/mch2022-rp2040 27 | url = https://github.com/badgeteam/esp32-component-mch2022-rp2040.git 28 | branch = master 29 | [submodule "components/esp32-component-appfs"] 30 | path = components/appfs 31 | url = https://github.com/badgeteam/esp32-component-appfs.git 32 | branch = master 33 | [submodule "components/ws2812"] 34 | path = components/ws2812 35 | url = https://github.com/badgeteam/esp32-component-ws2812.git 36 | branch = master 37 | [submodule "components/mch2022-efuse"] 38 | path = components/mch2022-efuse 39 | url = https://github.com/badgeteam/esp32-component-mch2022-efuse.git 40 | branch = master 41 | [submodule "components/mch2022-bsp"] 42 | path = components/mch2022-bsp 43 | url = https://github.com/badgeteam/esp32-component-mch2022-bsp.git 44 | branch = master 45 | [submodule "components/pax-codecs"] 46 | path = components/pax-codecs 47 | url = https://github.com/robotman2412/pax-codecs.git 48 | branch = main 49 | [submodule "main/pax-keyboard"] 50 | path = components/pax-keyboard 51 | url = https://github.com/robotman2412/pax-keyboard 52 | branch = main 53 | [submodule "components/eeprom"] 54 | path = components/eeprom 55 | url = https://github.com/Nicolai-Electronics/esp32-component-i2c-eeprom.git 56 | branch = main 57 | [submodule "components/i2c-bme680"] 58 | path = components/i2c-bme680 59 | url = https://github.com/badgeteam/esp32-component-bme680.git 60 | branch = main 61 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 3 | 4 | set(PROJECT_NAME "MCH2022") 5 | set(PROJECT_VER "2.0.9") 6 | 7 | project(${PROJECT_NAME}) 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Julian Scheffers 2 | Copyright 2022 Jeroen Domburg 3 | Copyright 2022 Renze Nicolai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PORT ?= /dev/ttyACM0 2 | BUILDDIR ?= build 3 | IDF_PATH ?= $(shell pwd)/esp-idf 4 | IDF_EXPORT_QUIET ?= 0 5 | SHELL := /usr/bin/env bash 6 | 7 | .PHONY: prepare clean build flash erase monitor menuconfig image qemu install size size-components size-files format 8 | 9 | all: build flash 10 | 11 | prepare: 12 | git submodule update --init --recursive 13 | rm -rf "$(IDF_PATH)" 14 | git clone --recursive --branch v4.4.4 https://github.com/espressif/esp-idf.git 15 | cd "$(IDF_PATH)"; bash install.sh 16 | 17 | clean: 18 | rm -rf "$(BUILDDIR)" 19 | 20 | fullclean: 21 | source "$(IDF_PATH)/export.sh" && idf.py fullclean 22 | 23 | build: 24 | source "$(IDF_PATH)/export.sh" && idf.py build 25 | 26 | flash: build 27 | source "$(IDF_PATH)/export.sh" && idf.py flash -p $(PORT) 28 | 29 | erase: 30 | source "$(IDF_PATH)/export.sh" && idf.py erase-flash -p $(PORT) 31 | 32 | monitor: 33 | source "$(IDF_PATH)/export.sh" && idf.py monitor -p $(PORT) 34 | 35 | menuconfig: 36 | source "$(IDF_PATH)/export.sh" && idf.py menuconfig 37 | 38 | image: 39 | cd "$(BUILDDIR)"; dd if=/dev/zero bs=1M count=16 of=flash.bin 40 | cd "$(BUILDDIR)"; dd if=bootloader/bootloader.bin bs=1 seek=4096 of=flash.bin conv=notrunc 41 | cd "$(BUILDDIR)"; dd if=partition_table/partition-table.bin bs=1 seek=36864 of=flash.bin conv=notrunc 42 | cd "$(BUILDDIR)"; dd if=main.bin bs=1 seek=65536 of=flash.bin conv=notrunc 43 | 44 | qemu: image 45 | cd "$(BUILDDIR)"; qemu-system-xtensa -nographic -machine esp32 -drive 'file=flash.bin,if=mtd,format=raw' 46 | 47 | install: flash 48 | 49 | size: 50 | source "$(IDF_PATH)/export.sh" && idf.py size 51 | 52 | size-components: 53 | source "$(IDF_PATH)/export.sh" && idf.py size-components 54 | 55 | size-files: 56 | source "$(IDF_PATH)/export.sh" && idf.py size-files 57 | 58 | format: 59 | find main/ -iname '*.h' -o -iname '*.c' -o -iname '*.cpp' | xargs clang-format -i 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCH2022 ESP32 firmware: Launcher 2 | 3 | This repository contains the ESP32 part of the firmware for the MCH2022 badge. This firmware allows for device testing, setup, OTA updates and of course launching apps. 4 | 5 | ## ESP-IDF and submodules 6 | 7 | This project uses the ESP-IDF SDK (v4.4.4). You can either install this SDK manually following the instructiosn on the Espressif website or you can automatically install the SDK by running `make prepare` in a git clone of this repository. 8 | 9 | Downloading this repository as a ZIP file on Github results in an incomplete archive missing all submodules. Be sure to clone using git! 10 | 11 | ## License 12 | 13 | The source code contained in this repository is licensed under terms of the MIT license, more information can be found in the LICENSE file. 14 | 15 | Some source code is licensed separately, please check the following table for details. 16 | 17 | | Location | Version | License | Author | 18 | |-----------------------------|-------------|-----------------------------------|-------------------------------------------------------------------------------------------------| 19 | | esp-idf | 4.4.4 | Apache License 2.0 | Espressif Systems (Shanghai) CO LTD | 20 | | components/appfs | | THE BEER-WARE LICENSE Revision 42 | Jeroen Domburg | 21 | | components/bus-i2c | | MIT | Nicolai Electronics | 22 | | components/i2c-bno055 | | MIT | Nicolai Electronics | 23 | | components/mch2022-rp2040 | | MIT | Renze Nicolai | 24 | | components/pax-graphics | | MIT | Julian Scheffers | 25 | | components/pax-keyboard | | MIT | Julian Scheffers | 26 | | components/sdcard | | MIT | Nicolai Electronics | 27 | | components/spi-ice40 | | MIT | Nicolai Electronics | 28 | | components/spi-ili9341 | | MIT | Nicolai Electronics | 29 | | components/ws2812 | | MIT | Unlicense / Public domain | 30 | | tools/[libusb-1.0.dll] | | GNU LGPL 2.1 | See the [AUTHORS](https://github.com/libusb/libusb/blob/master/AUTHORS) document of the project | 31 | 32 | [libusb-1.0.dll]: https://libusb.info 33 | 34 | Some of the icons in `resources/icons` are licensed under MIT license `Copyright (c) 2019-2021 The Bootstrap Authors`. The source files for these icons can be found at https://icons.getbootstrap.com/. 35 | 36 | The [BadgePython logo](resources/icons/python.png) may only be used as icon for the BadgePython project. 37 | 38 | ## How to build the firmware 39 | 40 | Downloading the source code, installing the SDK and building the firmware can be done using the following commands: 41 | 42 | ```sh 43 | git clone --recursive https://github.com/badgeteam/mch2022-firmware-esp32 44 | cd mch2022-firmware-esp32 45 | make prepare 46 | make build 47 | ``` 48 | 49 | ## How to flash and start the monitor 50 | 51 | Flashing the firmware to a device and starting the debug monitor can be done using the following commands: 52 | 53 | ```sh 54 | make flash 55 | make monitor 56 | ``` 57 | 58 | ## USB tools 59 | In [`mch2022-tools`](https://github.com/badgeteam/mch2022-tools) you will find command line tools to push files and apps to the badge etc., and a short manual on how to use them. 60 | 61 | ## Linux permissions 62 | Create `/etc/udev/rules.d/99-mch2022.rules` with the following contents: 63 | 64 | ``` 65 | SUBSYSTEM=="usb", ATTR{idVendor}=="16d0", ATTR{idProduct}=="0f9a", MODE="0666" 66 | ``` 67 | 68 | Then run the following commands to apply the new rule: 69 | 70 | ``` 71 | sudo udevadm control --reload-rules 72 | sudo udevadm trigger 73 | ``` 74 | 75 | ### Ubuntu snap 76 | While we have no idea why Canonical thinks breaking things by forcing Ubuntu users to use a bad software distribution system, we do have a solution! 77 | 78 | Install the Chromium snap package, then run the following command (in addition to adding the udev rule above). If you already have the badge connected turn it off and back on after running the command. 79 | 80 | ``` 81 | sudo snap connect chromium:raw-usb 82 | ``` 83 | -------------------------------------------------------------------------------- /bootloader_components/appfs: -------------------------------------------------------------------------------- 1 | ../components/appfs -------------------------------------------------------------------------------- /bootloader_components/main: -------------------------------------------------------------------------------- 1 | ../components/appfs/bootloader_main -------------------------------------------------------------------------------- /components/driver_fsoverbus/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(CONFIG_DRIVER_FSOVERBUS_ENABLE) 2 | set(srcs 3 | "backend.c" 4 | "driver_fsoverbus.c" 5 | "filefunctions.c" 6 | "packetutils.c" 7 | "specialfunctions.c" 8 | "uart_backend.c" 9 | "uartnaive_backend.c" 10 | ) 11 | else() 12 | set(srcs "") 13 | endif() 14 | 15 | if(CONFIG_DRIVER_FSOVERBUS_APPFS_SUPPORT) 16 | list(APPEND srcs "appfsfunctions.c") 17 | endif() 18 | 19 | idf_component_register(SRCS "${srcs}" 20 | INCLUDE_DIRS "include" 21 | REQUIRES spi_flash) -------------------------------------------------------------------------------- /components/driver_fsoverbus/Kconfig: -------------------------------------------------------------------------------- 1 | menu "Driver: FS over bus support" 2 | config DRIVER_FSOVERBUS_ENABLE 3 | bool "Enable the FS over bus driver" 4 | default n 5 | config DRIVER_FSOVERBUS_BACKEND 6 | int 7 | default 0 if FSOB_BACKEND_NONE 8 | default 1 if FSOB_BACKEND_UART 9 | default 2 if FSOB_BACKEND_NAIVE_UART 10 | choice 11 | prompt "Set internal backend system" 12 | default FSOB_BACKEND_UART 13 | help 14 | Select backend for fsob 15 | 16 | config FSOB_BACKEND_NONE 17 | bool "None" 18 | config FSOB_BACKEND_UART 19 | bool "Uart" 20 | config FSOB_BACKEND_NAIVE_UART 21 | bool "Uart_naive" 22 | endchoice 23 | config DRIVER_FSOVERBUS_NOBACKEND_HELPER 24 | bool "Enable fsob_receive_bytes" 25 | default n 26 | depends on DRIVER_FSOVERBUS_BACKEND = 0 27 | config DRIVER_FSOVERBUS_NOBACKEND_HELPER_Size 28 | int "FSOB helper buffer size" 29 | default 1024 30 | depends on DRIVER_FSOVERBUS_NOBACKEND_HELPER = y 31 | config DRIVER_FSOVERBUS_APPFS_SUPPORT 32 | bool "Enable appfs support" 33 | default n 34 | depends on DRIVER_FSOVERBUS_ENABLE 35 | config DRIVER_FSOVERBUS_RTCMEM_SUPPORT 36 | bool "Enable rtcmem support" 37 | default n 38 | depends on DRIVER_FSOVERBUS_ENABLE 39 | config DRIVER_FSOVERBUS_UART_NUM 40 | int "Uart hardware number" 41 | default 2 42 | depends on DRIVER_FSOVERBUS_BACKEND = 1 || DRIVER_FSOVERBUS_BACKEND = 2 43 | config DRIVER_FSOVERBUS_UART_TX 44 | int "TX pin" 45 | default 32 46 | depends on DRIVER_FSOVERBUS_BACKEND = 1 || DRIVER_FSOVERBUS_BACKEND = 2 47 | config DRIVER_FSOVERBUS_UART_RX 48 | int "RX pin" 49 | default 35 50 | depends on DRIVER_FSOVERBUS_BACKEND = 1 || DRIVER_FSOVERBUS_BACKEND = 2 51 | config DRIVER_FSOVERBUS_UART_CTS 52 | int "CTS pin" 53 | default 2 54 | depends on DRIVER_FSOVERBUS_BACKEND = 1 55 | config DRIVER_FSOVERBUS_UART_BAUD 56 | int "Baud rate" 57 | default 256000 58 | depends on DRIVER_FSOVERBUS_BACKEND = 1 || DRIVER_FSOVERBUS_BACKEND = 2 59 | config DRIVER_FSOVERBUS_UART_BUFFER_SIZE 60 | int "UART fifo size" 61 | default 2048 62 | depends on DRIVER_FSOVERBUS_BACKEND = 1 63 | endmenu 64 | -------------------------------------------------------------------------------- /components/driver_fsoverbus/README: -------------------------------------------------------------------------------- 1 | The FS over uart module provides a way to access the underlying FS of the badge. 2 | 3 | 4 | The uart needs to be connected to an external device which would provide the interfacing. 5 | In the Campzone2020 badge this is done by a stm32 which translates the uart to a webusb site. 6 | The uart CTS might be necessary for stable operation. Due to the slow write speed of the esp32 spi flash there is a high chance the uart buffer will overflow without CTS. 7 | 8 | 9 | The driver itself uses packet based format. The packet header consists of 12 bytes. 10 | The first 2 bytes is to indicate command id. The next 4 bytes provide the length of the data field. 11 | The next 2 bytes are always 0xDEAD. After the packet header the data field is provided. 12 | The last 4 bytes is the message id send. The master can generate any message id. The esp32 will respond with the same message id. 13 | 14 | 15 | The command id can be grouped in 4 different categories: 16 | 1. Special function (0-4095) : these ids are designated for starting apps/restarting the esp/etc. These are technically not FS functions but are quite convenient 17 | 2. File functions (4096-8191) : normal fs operations. del/save/list files 18 | 3. Badge specific functions (8192-12287) : functions used for a specific badge. 19 | 4. Intermediate specific functions (12288-16383) : handled by the intermediate layer. For example sending commands to stm32 in badge campzone2020. These command should never be send towards the esp32. 20 | 21 | 22 | Note all filename access used in the file functions are absolute paths. The system is designed to be stateless so no chdir is provided. 23 | All functions return OK or ER in the datafield except for functions that expect a response. 24 | 25 | 26 | File functions overview: 27 | getdir (4096): reads the content of the directory, datafield consists of the directory to read. rootdir is "/". Respone is newline seperated list of files/directories. The first entry will be the requested directory contents. Where the first character indicates if it is a directory (d) or a file (f). 28 | readfile (4097): reads the content of the file. Datafield specifies the filename. 29 | writefile (4098): write contents to disk. Datafield first specifies the filename which is null terminated to indicate EOF. Afterwhich the data that needs to written follows. 30 | delfile (4099): delete file. Datafield specifies the filename 31 | duplfile (4100): duplicate file. Datafield specifies first the filename to copy and null terminated to indicate end of file. Afterwhich the targer directory ended with a "/" or a filename is directory. 32 | mvfile (4101): move file. Similar as duplicate but the source file is deleted 33 | makedir (4102): make dir. Datafield specifies which directory to create. 34 | 35 | 36 | -------------------------------------------------------------------------------- /components/driver_fsoverbus/appfsfunctions.c: -------------------------------------------------------------------------------- 1 | #include "packetutils.h" 2 | #include 3 | #include 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/task.h" 6 | #include "soc/rtc.h" 7 | #include "soc/rtc_cntl_reg.h" 8 | #include "esp_sleep.h" 9 | #include "fsob_backend.h" 10 | #include "esp_spi_flash.h" 11 | #include "driver_fsoverbus.h" 12 | 13 | #define TAG "fsob_appfs" 14 | 15 | /** 16 | * @brief Redefine the appfs functions used. This allows to compile the component when appfs support is disabled. 17 | * 18 | */ 19 | #define APPFS_INVALID_FD (-1) 20 | typedef int appfs_handle_t; 21 | void appfsEntryInfo(appfs_handle_t fd, const char **name, int *size); 22 | appfs_handle_t appfsNextEntry(appfs_handle_t fd); 23 | esp_err_t appfsDeleteFile(const char *filename); 24 | esp_err_t appfsCreateFile(const char *filename, size_t size, appfs_handle_t *handle); 25 | esp_err_t appfsErase(appfs_handle_t fd, size_t start, size_t len); 26 | esp_err_t appfsWrite(appfs_handle_t fd, size_t start, uint8_t *buf, size_t len); 27 | appfs_handle_t appfsOpen(const char *filename); 28 | 29 | int appfslist(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length) { 30 | if(received != size) return 0; 31 | 32 | uint32_t amount_of_files = 0; 33 | uint32_t buffer_size = 0; 34 | appfs_handle_t appfs_fd = APPFS_INVALID_FD; 35 | while (1) { 36 | appfs_fd = appfsNextEntry(appfs_fd); 37 | if (appfs_fd == APPFS_INVALID_FD) break; 38 | const char* name; 39 | int app_size; 40 | appfsEntryInfo(appfs_fd, &name, &app_size); 41 | amount_of_files++; 42 | buffer_size += strlen(name); 43 | } 44 | int payloadlength = 4 + buffer_size + amount_of_files*(4+4); //amount of files + all string length + for every entry app size + app name length 45 | 46 | uint8_t header[12]; 47 | createMessageHeader(header, command, payloadlength, message_id); 48 | fsob_write_bytes((const char*) header, 12); 49 | fsob_write_bytes((char *) &amount_of_files, 4); 50 | 51 | appfs_fd = APPFS_INVALID_FD; 52 | while (1) { 53 | appfs_fd = appfsNextEntry(appfs_fd); 54 | if (appfs_fd == APPFS_INVALID_FD) break; 55 | const char* name; 56 | int app_size; 57 | appfsEntryInfo(appfs_fd, &name, &app_size); 58 | fsob_write_bytes((char *) &app_size, 4); 59 | uint32_t name_length = strlen(name); 60 | fsob_write_bytes((char *) &name_length, 4); 61 | fsob_write_bytes(name, name_length); 62 | } 63 | return 1; 64 | } 65 | 66 | int appfsdel(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length) { 67 | if(received != size) return 0; 68 | esp_err_t res = appfsDeleteFile((char *) data); 69 | if (res == ESP_OK) { 70 | sendok(command, message_id); 71 | } else { 72 | sender(command, message_id); 73 | } 74 | return 1; 75 | } 76 | 77 | int appfswrite(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length) { 78 | static appfs_handle_t handle = APPFS_INVALID_FD; 79 | static bool failed_open = false; 80 | static int app_size = 0; 81 | static int written = 0; 82 | 83 | if(received == length) { //Opening new file, cleaning up statics just in case 84 | failed_open = false; 85 | handle = APPFS_INVALID_FD; 86 | app_size = 0; 87 | written = 0; 88 | } 89 | 90 | if(handle == APPFS_INVALID_FD && failed_open == false) { 91 | for(int i = 0; i < received; i++) { 92 | if(data[i] == 0) { 93 | app_size = size-i; 94 | esp_err_t res = appfsCreateFile((char *) data, app_size, &handle); 95 | if (res != ESP_OK) { 96 | failed_open = true; 97 | return 1; 98 | } 99 | 100 | int roundedSize=(app_size+(SPI_FLASH_MMU_PAGE_SIZE-1))&(~(SPI_FLASH_MMU_PAGE_SIZE-1)); 101 | fsob_log("Erasing flash"); 102 | res = appfsErase(handle, 0, roundedSize); 103 | 104 | if(length > i) { 105 | appfsWrite(handle, 0, &data[i+1], length-i); 106 | written = length - i; 107 | } 108 | 109 | if(received == size) { //Creating an empty file or short. Close the file and send reply 110 | failed_open = 0; 111 | if(handle != APPFS_INVALID_FD) { 112 | sendok(command, message_id); 113 | handle = APPFS_INVALID_FD; 114 | } else { 115 | sender(command, message_id); 116 | } 117 | } 118 | return 1; 119 | } 120 | } 121 | } else if(handle != APPFS_INVALID_FD && failed_open == false) { 122 | appfsWrite(handle, written, data, length); 123 | written += length; 124 | } 125 | 126 | if(received == size) { //Creating an empty file or short. Close the file and send reply 127 | failed_open = 0; 128 | if(handle != APPFS_INVALID_FD) { 129 | sendok(command, message_id); 130 | handle = APPFS_INVALID_FD; 131 | } else { 132 | sender(command, message_id); 133 | } 134 | } 135 | return 0; 136 | } 137 | 138 | int appfsboot(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length) { 139 | if(received != size) return 0; 140 | appfs_handle_t fd = appfsOpen((char *) data); 141 | if (fd == APPFS_INVALID_FD) { 142 | sender(command, message_id); 143 | return 1; 144 | } 145 | sendok(command, message_id); 146 | vTaskDelay(100 / portTICK_PERIOD_MS); 147 | if (fd<0 || fd>255) { 148 | REG_WRITE(RTC_CNTL_STORE0_REG, 0); 149 | } else { 150 | REG_WRITE(RTC_CNTL_STORE0_REG, 0xA5000000|fd); 151 | } 152 | 153 | esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); 154 | esp_sleep_enable_timer_wakeup(10); 155 | esp_deep_sleep_start(); 156 | return 1; 157 | } 158 | -------------------------------------------------------------------------------- /components/driver_fsoverbus/backend.c: -------------------------------------------------------------------------------- 1 | #include "include/fsob_backend.h" 2 | #include "include/driver_fsoverbus.h" 3 | #include 4 | #include 5 | 6 | #include "freertos/FreeRTOS.h" 7 | #include "freertos/task.h" 8 | #include "freertos/semphr.h" 9 | #include "freertos/queue.h" 10 | #include "freertos/ringbuf.h" 11 | 12 | #define TAG "FSoverBus" 13 | 14 | #define min(a,b) (((a) < (b)) ? (a) : (b)) 15 | 16 | /* 17 | * Write src array with length size to used bus. 18 | * Implement this function in custom backend when used. Block function call if not possible to write data until possible. 19 | */ 20 | __attribute__((weak)) void fsob_write_bytes(const char *src, size_t size) { 21 | abort(); 22 | } 23 | 24 | #if CONFIG_DRIVER_FSOVERBUS_NOBACKEND_HELPER 25 | //Create ring buffer 26 | RingbufHandle_t buf_handle; 27 | static TaskHandle_t fsob_task_handle = NULL; 28 | 29 | int receiving = 0; 30 | uint32_t message_id = 0; 31 | 32 | void clearBuffer() { 33 | RingbufHandle_t buf_handle_old = buf_handle; 34 | buf_handle = xRingbufferCreate(CONFIG_DRIVER_FSOVERBUS_NOBACKEND_HELPER_Size, RINGBUF_TYPE_BYTEBUF); 35 | vRingbufferDelete(buf_handle_old); 36 | } 37 | 38 | 39 | void fsob_task(void *pvParameters) { 40 | uint16_t command = 0; //Message command id 41 | uint32_t size = 0; //Total message size 42 | uint32_t recv = 0; //Total bytes received so far 43 | uint16_t verif = 0; //Verif field 44 | uint32_t continue_reading; 45 | for( ;; ) { 46 | ulTaskNotifyTake(pdTRUE, portMAX_DELAY ); 47 | continue_reading = 1; 48 | while(continue_reading) { 49 | size_t freebuf = xRingbufferGetCurFreeSize(buf_handle); 50 | if(!receiving) { 51 | if((CONFIG_DRIVER_FSOVERBUS_NOBACKEND_HELPER_Size-freebuf) >= PACKET_HEADER_SIZE) { 52 | fsob_stop_timeout(); 53 | size_t fetched, fetched_split; 54 | uint8_t header_full[PACKET_HEADER_SIZE]; 55 | 56 | //Some extra code is necessary incase of wrap around. This part just checks if the fetch is continues. if not fetch another time 57 | 58 | uint8_t *header = (uint8_t *) xRingbufferReceiveUpTo(buf_handle, &fetched, 10, PACKET_HEADER_SIZE); 59 | if(header == NULL) { 60 | return; //This shouldn't happen because we checked if there is data in the buffer 61 | } 62 | memcpy(header_full, header, fetched); 63 | vRingbufferReturnItem(buf_handle, header); 64 | if(fetched != PACKET_HEADER_SIZE) { 65 | header = (uint8_t *) xRingbufferReceiveUpTo(buf_handle, &fetched_split, 10, PACKET_HEADER_SIZE-fetched); 66 | if(header == NULL) { 67 | return; //This shouldn't happen because we checked if there is data in the buffer 68 | } 69 | memcpy(&header_full[fetched], header, PACKET_HEADER_SIZE-fetched); 70 | vRingbufferReturnItem(buf_handle, header); 71 | } 72 | 73 | //Check the payload header 74 | command = *((uint16_t *) &header_full[0]); 75 | size = *((uint32_t *) &header_full[2]); 76 | verif = *((uint16_t *) &header_full[6]); 77 | message_id = *((uint32_t *) &header_full[8]); 78 | fsob_log("new packet: %d %d %d %d", command, size, verif, message_id); 79 | if(verif == 0xADDE) { 80 | receiving = 1; 81 | fsob_start_timeout(); 82 | recv = 0; 83 | continue_reading = !(xRingbufferGetCurFreeSize(buf_handle) == CONFIG_DRIVER_FSOVERBUS_NOBACKEND_HELPER_Size); 84 | } else { 85 | receiving = 0; 86 | fsob_log("Packet header not correct."); 87 | clearBuffer(); 88 | //Received wrong command, flushing uart queue 89 | } 90 | 91 | } else { 92 | fsob_start_timeout(); 93 | continue_reading = 0; 94 | } 95 | } else { 96 | fsob_stop_timeout(); //Stop timeout time since we have received some data 97 | size_t data_sz; 98 | size_t max_read = min(RD_BUF_SIZE, size-recv); 99 | uint8_t *data = (uint8_t *) xRingbufferReceiveUpTo(buf_handle, &data_sz, 0, max_read); 100 | if(data != NULL) { 101 | recv += data_sz; 102 | ESP_LOGD(TAG, "len: %d, recv: %d, size: %d", size, recv, data_sz); 103 | handleFSCommand(data, command, message_id, size, recv, data_sz); 104 | vRingbufferReturnItem(buf_handle, data); 105 | if(recv == size) { 106 | receiving = 0; 107 | ESP_LOGD(TAG, "Packet receive complete"); 108 | } else { 109 | fsob_start_timeout(); //Re enable the timeout timer since the message is still not fully received 110 | } 111 | } 112 | continue_reading = !(xRingbufferGetCurFreeSize(buf_handle) == CONFIG_DRIVER_FSOVERBUS_NOBACKEND_HELPER_Size); 113 | } 114 | } 115 | } 116 | } 117 | 118 | void fsob_init() { 119 | buf_handle = xRingbufferCreate(CONFIG_DRIVER_FSOVERBUS_NOBACKEND_HELPER_Size, RINGBUF_TYPE_BYTEBUF); 120 | if (buf_handle == NULL) { 121 | ESP_LOGE(TAG, "Failed to create ring buffer\n"); 122 | } 123 | xTaskCreatePinnedToCore(fsob_task, "fsoverbus_helper", 16000, NULL, 100, &fsob_task_handle, 0); 124 | } 125 | 126 | void fsob_reset() { 127 | receiving = 0; 128 | ESP_LOGD(TAG, "Wiping buffer..."); 129 | clearBuffer(); 130 | } 131 | 132 | void fsob_receive_bytes(uint8_t *data, size_t len) { 133 | while(xRingbufferSend(buf_handle, data, len, pdMS_TO_TICKS(1000)) == pdFALSE) { 134 | vTaskDelay(1); 135 | xTaskNotifyGive(fsob_task_handle); 136 | } 137 | xTaskNotifyGive(fsob_task_handle); //Notify the fsoverbus worker thread there is data to be processed 138 | } 139 | #else 140 | 141 | __attribute__((weak)) void fsob_init() { 142 | 143 | } 144 | 145 | __attribute__((weak)) void fsob_reset() { 146 | 147 | } 148 | 149 | void fsob_receive_bytes(uint8_t *data, size_t len) { 150 | abort(); 151 | } 152 | #endif 153 | -------------------------------------------------------------------------------- /components/driver_fsoverbus/driver_fsoverbus.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "freertos/FreeRTOS.h" 16 | #include "freertos/task.h" 17 | #include "freertos/semphr.h" 18 | #include "freertos/queue.h" 19 | #include "freertos/timers.h" 20 | 21 | #include "include/driver_fsoverbus.h" 22 | #include "include/filefunctions.h" 23 | #include "include/packetutils.h" 24 | #include "include/specialfunctions.h" 25 | #include "include/fsob_backend.h" 26 | #include "include/appfsfunctions.h" 27 | #include "include/functions.h" 28 | 29 | #define TAG "fsob" 30 | #define min(a,b) (((a) < (b)) ? (a) : (b)) 31 | #define CACHE_SIZE (2048) 32 | 33 | TimerHandle_t timeout; 34 | 35 | uint8_t command_in[CACHE_SIZE]; 36 | void fsob_timeout_function( TimerHandle_t xTimer ); 37 | 38 | 39 | //Function lookup tables 40 | 41 | int (*specialfunction[SPECIALFUNCTIONSLEN])(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 42 | int (*filefunction[FILEFUNCTIONSLEN])(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 43 | fsob_log_fn_t log_fn = NULL; 44 | 45 | void handleFSCommand(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length) { 46 | static uint32_t write_pos; 47 | if(received == length) { //First data of the packet 48 | write_pos = 0; 49 | } 50 | uint8_t *buffer = command_in; 51 | 52 | if(length > CACHE_SIZE){ //Incoming buffer exceeds local cache, directly use buffer instead of copying 53 | buffer = data; 54 | } else if(length > 0) { 55 | memcpy(&command_in[write_pos], data, length); 56 | write_pos += length; 57 | } 58 | 59 | int return_val = 0; 60 | if(command < FILEFUNCTIONSBASE) { 61 | if(command < SPECIALFUNCTIONSLEN) { 62 | return_val = specialfunction[command](buffer, command, message_id, size, received, length); 63 | } 64 | } else if(command < BADGEFUNCTIONSBASE) { 65 | if((command-FILEFUNCTIONSBASE) < FILEFUNCTIONSLEN) { 66 | return_val = filefunction[command-FILEFUNCTIONSBASE](buffer, command, message_id, size, received, length); 67 | } 68 | } 69 | if(return_val) { //Function has indicated that next payload should write at start of buffer. 70 | write_pos = 0; 71 | } 72 | } 73 | 74 | void fsob_timeout_function( TimerHandle_t xTimer ) { 75 | fsob_log("Saw no message for 1s assuming task crashed. Resetting..."); 76 | fsob_reset(); 77 | } 78 | 79 | void fsob_stop_timeout() { 80 | xTimerStop(timeout, 1); 81 | } 82 | 83 | void fsob_start_timeout() { 84 | xTimerStart(timeout, 1); 85 | } 86 | 87 | void fsob_log(char* fmt, ...) { 88 | char* buffer = malloc(256); 89 | if (buffer == NULL) return; 90 | buffer[255] = '\0'; 91 | va_list va; 92 | va_start(va, fmt); 93 | vsnprintf(buffer, 255, fmt, va); 94 | va_end (va); 95 | if (log_fn != NULL) { 96 | log_fn(buffer); 97 | } else { 98 | ESP_LOGI(TAG, "%s", buffer); 99 | free(buffer); 100 | } 101 | } 102 | 103 | esp_err_t driver_fsoverbus_init(fsob_log_fn_t a_log_fn) { 104 | log_fn = a_log_fn; 105 | specialfunction[EXECFILE] = execfile; 106 | specialfunction[HEARTBEAT] = heartbeat; 107 | specialfunction[PYTHONSTDIN] = pythonstdin; 108 | 109 | filefunction[GETDIR] = getdir; 110 | filefunction[READFILE] = readfile; 111 | filefunction[WRITEFILE] = writefile; 112 | filefunction[DELFILE] = delfile; 113 | filefunction[DUPLFILE] = duplfile; 114 | filefunction[MVFILE] = mvfile; 115 | filefunction[MAKEDIR] = makedir; 116 | 117 | #if CONFIG_DRIVER_FSOVERBUS_APPFS_SUPPORT 118 | specialfunction[APPFSBOOT] = appfsboot; 119 | filefunction[APPFSDIR] = appfslist; 120 | filefunction[APPFSDEL] = appfsdel; 121 | filefunction[APPFSWRITE] = appfswrite; 122 | #else 123 | specialfunction[APPFSBOOT] = notsupported; 124 | filefunction[APPFSDIR] = notsupported; 125 | filefunction[APPFSDEL] = notsupported; 126 | filefunction[APPFSWRITE] = notsupported; 127 | #endif 128 | 129 | fsob_init(); 130 | 131 | fsob_log("fs over bus registered."); 132 | 133 | timeout = xTimerCreate("FSoverBUS_timeout", 100, false, 0, fsob_timeout_function); 134 | return ESP_OK; 135 | } 136 | -------------------------------------------------------------------------------- /components/driver_fsoverbus/include/appfsfunctions.h: -------------------------------------------------------------------------------- 1 | #ifndef __APPFSFUNCTIONS_H__ 2 | #define __APPFSFUNCTIONS_H__ 3 | 4 | int appfslist(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 5 | int appfsdel(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 6 | int appfswrite(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 7 | int appfsboot(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 8 | 9 | #endif -------------------------------------------------------------------------------- /components/driver_fsoverbus/include/driver_fsoverbus.h: -------------------------------------------------------------------------------- 1 | #ifndef DRIVER_FSOVERUART_H 2 | #define DRIVER_FSOVERUART_H 3 | 4 | #include 5 | #include 6 | 7 | typedef void (*fsob_log_fn_t)(char*); 8 | 9 | esp_err_t driver_fsoverbus_init(fsob_log_fn_t log_fn); 10 | 11 | void fsob_log(char* fmt, ...); 12 | void handleFSCommand(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 13 | void fsob_start_timeout(); 14 | void fsob_stop_timeout(); 15 | void fsob_receive_bytes(uint8_t *data, size_t len); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /components/driver_fsoverbus/include/filefunctions.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_FUNCTIONS_H 2 | #define FILE_FUNCTIONS_H 3 | 4 | #include 5 | #include 6 | 7 | int getdir(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 8 | int readfile(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 9 | int writefile(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 10 | int delfile(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 11 | int duplfile(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 12 | int mvfile(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 13 | int makedir(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /components/driver_fsoverbus/include/fsob_backend.h: -------------------------------------------------------------------------------- 1 | #ifndef FSOB_BACKEND_H 2 | #define FSOB_BACKEND_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "filefunctions.h" 11 | #include "specialfunctions.h" 12 | #include "packetutils.h" 13 | 14 | 15 | void fsob_init(); 16 | 17 | void fsob_write_bytes(const char *src, size_t size); 18 | 19 | void fsob_reset(); 20 | 21 | #endif -------------------------------------------------------------------------------- /components/driver_fsoverbus/include/functions.h: -------------------------------------------------------------------------------- 1 | #ifndef __FSOB_FUNCTIONS_H__ 2 | #define __FSOB_FUNCTIONS_H__ 3 | 4 | #define SPECIALFUNCTIONSBASE (0) 5 | #define FILEFUNCTIONSBASE (4096) 6 | #define BADGEFUNCTIONSBASE (8192) 7 | 8 | enum SPECIALFUNCTIONS { 9 | EXECFILE = 0, 10 | HEARTBEAT, 11 | PYTHONSTDIN, 12 | APPFSBOOT, 13 | SPECIALFUNCTIONSLEN 14 | }; 15 | 16 | enum FILEFUNCTIONS { 17 | GETDIR=0, 18 | READFILE, 19 | WRITEFILE, 20 | DELFILE, 21 | DUPLFILE, 22 | MVFILE, 23 | MAKEDIR, 24 | APPFSDIR, 25 | APPFSDEL, 26 | APPFSWRITE, 27 | FILEFUNCTIONSLEN 28 | }; 29 | 30 | enum BADGEFUNCTIONS { 31 | BADGEFUNCTIONSLEN = 0 32 | }; 33 | 34 | #endif -------------------------------------------------------------------------------- /components/driver_fsoverbus/include/packetutils.h: -------------------------------------------------------------------------------- 1 | #ifndef PACKETUTILS_H 2 | #define PACKETUTILS_H 3 | 4 | #include 5 | 6 | #define RD_BUF_SIZE 512 7 | 8 | #define PACKET_HEADER_SIZE 12 9 | 10 | void createMessageHeader(uint8_t *header, uint16_t command, uint32_t size, uint32_t message_id); 11 | void sendok(uint16_t command, uint32_t message_id); 12 | void sender(uint16_t command, uint32_t message_id); 13 | void sendte(uint16_t command, uint32_t message_id); 14 | void sendto(uint16_t command, uint32_t message_id); 15 | void sendns(uint16_t command, uint32_t message_id); 16 | void buildfile(char *source, char *target); 17 | 18 | #endif -------------------------------------------------------------------------------- /components/driver_fsoverbus/include/specialfunctions.h: -------------------------------------------------------------------------------- 1 | #ifndef SPECIALFUNCTIONS_H 2 | #define SPECIALFUNCTIONS_H 3 | 4 | #include 5 | #include 6 | 7 | int execfile(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 8 | int heartbeat(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 9 | int pythonstdin(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 10 | int notsupported(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length); 11 | #endif 12 | 13 | 14 | -------------------------------------------------------------------------------- /components/driver_fsoverbus/packetutils.c: -------------------------------------------------------------------------------- 1 | #include "include/packetutils.h" 2 | #include "include/fsob_backend.h" 3 | #include 4 | #include 5 | 6 | #define TAG "fsoveruart_pu" 7 | 8 | void createMessageHeader(uint8_t *header, uint16_t command, uint32_t size, uint32_t messageid) { 9 | uint16_t *com = (uint16_t *) header; 10 | *com = command; 11 | uint32_t *siz = (uint32_t *) &header[2]; 12 | *siz = size; 13 | header[6] = 0xDE; 14 | header[7] = 0xAD; 15 | uint32_t *id = (uint32_t *) &header[8]; 16 | *id = messageid; 17 | } 18 | 19 | //Error executing function 20 | void sender(uint16_t command, uint32_t message_id) { 21 | uint8_t header[PACKET_HEADER_SIZE+3]; 22 | createMessageHeader(header, command, 3, message_id); 23 | strcpy((char *) &header[PACKET_HEADER_SIZE], "er"); 24 | fsob_write_bytes((const char*) header, 15); 25 | } 26 | 27 | //Okay 28 | void sendok(uint16_t command, uint32_t message_id) { 29 | uint8_t header[PACKET_HEADER_SIZE+3]; 30 | createMessageHeader(header, command, 3, message_id); 31 | strcpy((char *) &header[PACKET_HEADER_SIZE], "ok"); 32 | fsob_write_bytes((const char*) header, 15); 33 | } 34 | 35 | //Transmission error 36 | void sendte(uint16_t command, uint32_t message_id) { 37 | uint8_t header[PACKET_HEADER_SIZE+3]; 38 | createMessageHeader(header, command, 3, message_id); 39 | strcpy((char *) &header[PACKET_HEADER_SIZE], "te"); 40 | fsob_write_bytes((const char*) header, 15); 41 | } 42 | 43 | //Timeout error 44 | void sendto(uint16_t command, uint32_t message_id) { 45 | uint8_t header[PACKET_HEADER_SIZE+3]; 46 | createMessageHeader(header, command, 3, message_id); 47 | strcpy((char *) &header[PACKET_HEADER_SIZE], "to"); 48 | fsob_write_bytes((const char*) header, 15); 49 | } 50 | 51 | //Not supported error 52 | void sendns(uint16_t command, uint32_t message_id) { 53 | uint8_t header[PACKET_HEADER_SIZE+3]; 54 | createMessageHeader(header, command, 3, message_id); 55 | strcpy((char *) &header[PACKET_HEADER_SIZE], "ns"); 56 | fsob_write_bytes((const char*) header, 15); 57 | } 58 | 59 | void buildfile(char *source, char *target) { 60 | if(strncmp(source, "/flash", 6) == 0) { 61 | strcpy(target, "/internal"); 62 | strcat(target, &source[6]); 63 | } else if(strncmp(source, "/sdcard", 7) == 0) { 64 | strcpy(target, "/sd"); 65 | strcat(target, &source[7]); 66 | } 67 | } -------------------------------------------------------------------------------- /components/driver_fsoverbus/project_include.cmake: -------------------------------------------------------------------------------- 1 | #Define the name of your module here 2 | set(mod_name "fsoverbus") 3 | set(mod_register "fsoverbus") 4 | 5 | if(CONFIG_DRIVER_FSOVERBUS_ENABLE) 6 | message(STATUS "fsoverbus enabled") 7 | set(EXTMODS_INIT "${EXTMODS_INIT}" "\"${mod_name}\"@\"${mod_register}\"^" CACHE INTERNAL "") 8 | else() 9 | message(STATUS "fsoverbus disabled") 10 | endif() -------------------------------------------------------------------------------- /components/driver_fsoverbus/specialfunctions.c: -------------------------------------------------------------------------------- 1 | #include "include/packetutils.h" 2 | #include "include/specialfunctions.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define TAG "fsoveruart_sf" 9 | 10 | #if CONFIG_DRIVER_FSOVERBUS_RTCMEM_SUPPORT 11 | //This function is provided by the rtcmem driver. 12 | esp_err_t driver_rtcmem_string_write(const char* str); 13 | 14 | int execfile(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length) { 15 | if(received != size) return 0; 16 | 17 | ESP_LOGI(TAG, "Starting: %s", data); 18 | sendok(command, message_id); 19 | driver_rtcmem_string_write((char*) data); 20 | esp_deep_sleep(1000000); 21 | return 1; 22 | } 23 | #else 24 | int execfile(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length) { 25 | if(received != size) return 0; 26 | 27 | sendns(command, message_id); 28 | return 1; 29 | } 30 | #endif 31 | 32 | int heartbeat(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length) { 33 | if(received != size) return 0; 34 | sendok(command, message_id); 35 | return 1; 36 | } 37 | 38 | //Old function used in CZ20, currently unsupported. Function still exists to prevent reuse of the command. 39 | int pythonstdin(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length) { 40 | sendok(command, message_id); 41 | return 1; 42 | } 43 | 44 | int notsupported(uint8_t *data, uint16_t command, uint32_t message_id, uint32_t size, uint32_t received, uint32_t length) { 45 | if(received != size) return 1; 46 | sendns(command, message_id); 47 | return 1; 48 | } -------------------------------------------------------------------------------- /components/driver_fsoverbus/uart_backend.c: -------------------------------------------------------------------------------- 1 | #include "include/fsob_backend.h" 2 | #include "include/driver_fsoverbus.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* 10 | * This is the uart backend implementation used in CZ20-badge 11 | */ 12 | #if (CONFIG_DRIVER_FSOVERBUS_BACKEND == 1) 13 | 14 | #define TAG "FSOVERBUS_UART" 15 | 16 | #define UART_EMPTY_THRESH_DEFAULT (10) 17 | #define UART_FULL_THRESH_DEFAULT (120) 18 | #define UART_TOUT_THRESH_DEFAULT (10) 19 | #define UART_CLKDIV_FRAG_BIT_WIDTH (3) 20 | #define UART_TOUT_REF_FACTOR_DEFAULT (UART_CLK_FREQ/(REF_CLK_FREQ< CONFIG_DRIVER_FSOVERBUS_UART_BUFFER_SIZE/4) { 49 | gpio_pad_select_gpio(CONFIG_DRIVER_FSOVERBUS_UART_CTS); 50 | gpio_set_direction(CONFIG_DRIVER_FSOVERBUS_UART_CTS, GPIO_MODE_OUTPUT); 51 | gpio_set_level(CONFIG_DRIVER_FSOVERBUS_UART_CTS, 1); 52 | } else { 53 | uart_set_pin(CONFIG_DRIVER_FSOVERBUS_UART_NUM, CONFIG_DRIVER_FSOVERBUS_UART_TX, CONFIG_DRIVER_FSOVERBUS_UART_RX, CONFIG_DRIVER_FSOVERBUS_UART_CTS, -1); //Change pins 54 | } 55 | } 56 | 57 | void fsoveruartTask(void *pvParameter) { 58 | uart_event_t event; 59 | uint8_t* dtmp = (uint8_t*) malloc(RD_BUF_SIZE); 60 | uint16_t command = 0; 61 | uint32_t size = 0; 62 | uint32_t recv = 0; 63 | uint16_t verif = 0; 64 | 65 | 66 | 67 | for(;;) { 68 | //Waiting for UART event. 69 | if(xQueueReceive(uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) { 70 | bzero(dtmp, RD_BUF_SIZE); 71 | fixcts(false); 72 | uint32_t bytesread = 0; 73 | uint32_t bytestoread; 74 | switch(event.type) { 75 | //Event of UART receving data 76 | /*We'd better handler data event fast, there would be much more data events than 77 | other types of events. If we take too much time on data event, the queue might 78 | be full.*/ 79 | case UART_DATA: 80 | //ESP_LOGI(TAG, "siz: %d", event.size); 81 | while(bytesread < event.size) { 82 | if(!receiving) { 83 | if((event.size-bytesread) < PACKET_HEADER_SIZE) break; //Break while loop if non complete header is inside 84 | uart_read_bytes(CONFIG_DRIVER_FSOVERBUS_UART_NUM, dtmp, PACKET_HEADER_SIZE, portMAX_DELAY); 85 | bytesread += PACKET_HEADER_SIZE; 86 | command = *((uint16_t *) &dtmp[0]); 87 | size = *((uint32_t *) &dtmp[2]); 88 | verif = *((uint16_t *) &dtmp[6]); 89 | message_id = *((uint32_t *) &dtmp[8]); 90 | ESP_LOGI(TAG, "new packet: %d %d %d %d %d", command, size, verif, event.size-PACKET_HEADER_SIZE, message_id); 91 | if(verif == 0xADDE) { 92 | receiving = 1; 93 | recv = 0; 94 | } else { 95 | receiving = 0; 96 | uart_flush_input(CONFIG_DRIVER_FSOVERBUS_UART_NUM); 97 | xQueueReset(uart_queue); 98 | //Received wrong command, flushing uart queue 99 | } 100 | } else { 101 | fsob_stop_timeout(); 102 | //ESP_LOGI(TAG, "%d %d %d", (event.size-bytesread), (size-recv), RD_BUF_SIZE); 103 | bytestoread = min(min((event.size-bytesread), (size-recv)), RD_BUF_SIZE); 104 | //ESP_LOGI(TAG, "Max read: %d", bytestoread); 105 | bytestoread = uart_read_bytes(CONFIG_DRIVER_FSOVERBUS_UART_NUM, dtmp, bytestoread, portMAX_DELAY); 106 | recv = recv + bytestoread; 107 | bytesread += bytestoread; 108 | ESP_LOGI(TAG, "processing packet: %d %d %d %d %d", command, size, recv, verif, bytestoread); 109 | fixcts(true); 110 | handleFSCommand(dtmp, command, message_id, size, recv, bytestoread); 111 | fixcts(false); 112 | if(recv == size) { 113 | receiving = 0; 114 | } 115 | } 116 | } 117 | break; 118 | //Event of HW FIFO overflow detected 119 | case UART_FIFO_OVF: 120 | ESP_LOGW(TAG, "hw fifo overflow"); 121 | // If fifo overflow happened, you should consider adding flow control for your application. 122 | // The ISR has already reset the rx FIFO, 123 | // As an example, we directly flush the rx buffer here in order to read more data. 124 | uart_flush_input(CONFIG_DRIVER_FSOVERBUS_UART_NUM); 125 | xQueueReset(uart_queue); 126 | break; 127 | //Event of UART ring buffer full 128 | case UART_BUFFER_FULL: 129 | ESP_LOGW(TAG, "ring buffer full"); 130 | // If buffer full happened, you should consider encreasing your buffer size 131 | // As an example, we directly flush the rx buffer here in order to read more data. 132 | uart_flush_input(CONFIG_DRIVER_FSOVERBUS_UART_NUM); 133 | xQueueReset(uart_queue); 134 | break; 135 | //Event of UART RX break detected 136 | case UART_BREAK: 137 | ESP_LOGI(TAG, "uart rx break"); 138 | break; 139 | //Event of UART parity check error 140 | case UART_PARITY_ERR: 141 | ESP_LOGI(TAG, "uart parity error"); 142 | break; 143 | //Event of UART frame error 144 | case UART_FRAME_ERR: 145 | ESP_LOGI(TAG, "uart frame error"); 146 | break; 147 | //UART_PATTERN_DET 148 | case UART_PATTERN_DET: 149 | 150 | break; 151 | //Others 152 | default: 153 | ESP_LOGI(TAG, "uart event type: %d", event.type); 154 | break; 155 | } 156 | if(receiving) { 157 | fsob_start_timeout(); 158 | } else { 159 | fsob_stop_timeout(); 160 | } 161 | fixcts(false); 162 | } 163 | } 164 | free(dtmp); 165 | dtmp = NULL; 166 | vTaskDelete(NULL); 167 | } 168 | 169 | 170 | void fsob_init() { 171 | uart_param_config(CONFIG_DRIVER_FSOVERBUS_UART_NUM, &uart_config); //Configure the uart hardware 172 | uart_set_pin(CONFIG_DRIVER_FSOVERBUS_UART_NUM, CONFIG_DRIVER_FSOVERBUS_UART_TX, CONFIG_DRIVER_FSOVERBUS_UART_RX, CONFIG_DRIVER_FSOVERBUS_UART_CTS, -1); //Change pins 173 | uart_driver_install(CONFIG_DRIVER_FSOVERBUS_UART_NUM, CONFIG_DRIVER_FSOVERBUS_UART_BUFFER_SIZE, CONFIG_DRIVER_FSOVERBUS_UART_BUFFER_SIZE, 40, &uart_queue, 0); //Install driver 174 | 175 | uart_intr_config_t uart_intr = { 176 | .intr_enable_mask = UART_RXFIFO_FULL_INT_ENA_M 177 | | UART_RXFIFO_TOUT_INT_ENA_M 178 | | UART_FRM_ERR_INT_ENA_M 179 | | UART_RXFIFO_OVF_INT_ENA_M 180 | | UART_BRK_DET_INT_ENA_M 181 | | UART_PARITY_ERR_INT_ENA_M, 182 | .rxfifo_full_thresh = 64, 183 | .rx_timeout_thresh = UART_TOUT_THRESH_DEFAULT, 184 | .txfifo_empty_intr_thresh = UART_EMPTY_THRESH_DEFAULT 185 | }; 186 | uart_intr_config(CONFIG_DRIVER_FSOVERBUS_UART_NUM, &uart_intr); 187 | xTaskCreatePinnedToCore(fsoveruartTask, "fsoverbus_uart", 16000, NULL, 100, NULL, 0); 188 | 189 | } 190 | 191 | void fsob_reset() { 192 | receiving = 0; 193 | sendto(1, message_id); 194 | } 195 | 196 | void fsob_write_bytes(const char *src, size_t size) { 197 | uart_write_bytes(CONFIG_DRIVER_FSOVERBUS_UART_NUM, src, size); 198 | } 199 | 200 | #endif -------------------------------------------------------------------------------- /components/driver_fsoverbus/uartnaive_backend.c: -------------------------------------------------------------------------------- 1 | #include "include/fsob_backend.h" 2 | #include "include/driver_fsoverbus.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define TAG "fsob_nuart" 9 | 10 | #if (CONFIG_DRIVER_FSOVERBUS_BACKEND == 2) 11 | 12 | bool fsob_uart_sync(uint32_t* size, uint16_t* command, uint32_t* message_id) { 13 | uint16_t verif = 0; //Verif field 14 | uint8_t rx_buffer[12]; 15 | int read = uart_read_bytes(CONFIG_DRIVER_FSOVERBUS_UART_NUM, rx_buffer, sizeof(rx_buffer), pdMS_TO_TICKS(1000)); 16 | if (read != sizeof(rx_buffer)) return false; 17 | verif = *((uint16_t *) &rx_buffer[6]); 18 | if (verif != 0xADDE) return false; 19 | *command = *((uint16_t *) &rx_buffer[0]); 20 | *size = *((uint32_t *) &rx_buffer[2]); 21 | *message_id = *((uint32_t *) &rx_buffer[8]); 22 | return true; 23 | } 24 | 25 | /*typedef struct { 26 | uint16_t command; 27 | uint32_t size; 28 | uint16_t magic; 29 | uint32_t message_id; 30 | } fsob_uart_sync_data_t; 31 | 32 | bool fsob_uart_sync(uint32_t* size, uint16_t* command, uint32_t* message_id) { 33 | fsob_uart_sync_data_t packet; 34 | 35 | int read = uart_read_bytes(CONFIG_DRIVER_FSOVERBUS_UART_NUM, (uint8_t*) &packet, sizeof(packet), pdMS_TO_TICKS(1000)); 36 | if (read != sizeof(packet)) { 37 | return false; 38 | } 39 | 40 | if (packet.magic != 0xADDE) { 41 | return false; 42 | } 43 | 44 | if (command != NULL) { 45 | *command = packet.command; 46 | } 47 | 48 | if (size != NULL) { 49 | *size = packet.size; 50 | } 51 | 52 | if (message_id != NULL) { 53 | *message_id = packet.message_id; 54 | } 55 | 56 | return true; 57 | }*/ 58 | 59 | void fsob_task(void *pvParameter) { 60 | uint32_t size, message_id; 61 | uint16_t command; 62 | 63 | while (true) { 64 | // 1) Wait for webusb header 65 | fsob_log("Waiting for sync..."); 66 | while (!fsob_uart_sync(&size, &command, &message_id)) { 67 | vTaskDelay(10); 68 | } 69 | 70 | fsob_log("Sync received!"); 71 | 72 | // 2) Allocate RAM for the data to be received if there is a payload 73 | uint8_t* buffer = NULL; 74 | if (size > 0) { 75 | buffer = malloc(size); 76 | if (buffer == NULL) { 77 | fsob_log("Failed to allocate buffer"); 78 | continue; 79 | } 80 | 81 | // 3) Receive data into the buffer 82 | int read = uart_read_bytes(CONFIG_DRIVER_FSOVERBUS_UART_NUM, buffer, size, pdMS_TO_TICKS(50)); 83 | if (read != size) { 84 | free(buffer); 85 | fsob_log("Failed to read all data"); 86 | continue; 87 | } 88 | } 89 | 90 | fsob_log("Handle command!"); 91 | handleFSCommand(buffer, command, message_id, size, size, size); 92 | if(buffer != NULL) { 93 | free(buffer); 94 | } 95 | } 96 | } 97 | 98 | void fsob_init() { 99 | ESP_ERROR_CHECK(uart_driver_install(CONFIG_DRIVER_FSOVERBUS_UART_NUM, 16*1024, 0, 0, NULL, 0)); 100 | uart_config_t uart_config = { 101 | .baud_rate = CONFIG_DRIVER_FSOVERBUS_UART_BAUD, 102 | .data_bits = UART_DATA_8_BITS, 103 | .parity = UART_PARITY_DISABLE, 104 | .stop_bits = UART_STOP_BITS_1, 105 | .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, 106 | .source_clk = UART_SCLK_APB, 107 | }; 108 | ESP_ERROR_CHECK(uart_param_config(CONFIG_DRIVER_FSOVERBUS_UART_NUM, &uart_config)); 109 | 110 | xTaskCreatePinnedToCore(fsob_task, "fsoverbus_uart", 16000, NULL, 100, NULL, 0); 111 | } 112 | 113 | void fsob_reset() { 114 | 115 | } 116 | 117 | void fsob_write_bytes(const char *src, size_t size) { 118 | uart_write_bytes(CONFIG_DRIVER_FSOVERBUS_UART_NUM, src, size); 119 | } 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /components/gui-toolkit/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "menu.c" 3 | "gui_element_header.c" 4 | "graphics_wrapper.c" 5 | "menu.c" 6 | INCLUDE_DIRS "." "include" 7 | REQUIRES 8 | "pax-graphics" 9 | "pax-codecs" 10 | "pax-keyboard" 11 | "spi-ili9341" 12 | "mch2022-rp2040" 13 | "mch2022-bsp" 14 | ) 15 | -------------------------------------------------------------------------------- /components/gui-toolkit/graphics_wrapper.c: -------------------------------------------------------------------------------- 1 | #include "graphics_wrapper.h" 2 | 3 | #include 4 | 5 | #include "hardware.h" 6 | #include "pax_keyboard.h" 7 | #include "rp2040.h" 8 | 9 | void render_outline(float position_x, float position_y, float width, float height, pax_col_t border_color, pax_col_t background_color) { 10 | pax_buf_t* pax_buffer = get_pax_buffer(); 11 | pax_simple_rect(pax_buffer, background_color, position_x, position_y, width, height); 12 | pax_outline_rect(pax_buffer, border_color, position_x, position_y, width, height); 13 | } 14 | 15 | void render_message(char* message) { 16 | pax_buf_t* pax_buffer = get_pax_buffer(); 17 | const pax_font_t* font = pax_font_saira_regular; 18 | pax_vec1_t size = pax_text_size(font, 18, message); 19 | float margin = 4; 20 | float width = size.x + (margin * 2); 21 | float posX = (pax_buffer->width - width) / 2; 22 | float height = size.y + (margin * 2); 23 | float posY = (pax_buffer->height - height) / 2; 24 | pax_col_t fgColor = 0xFFfa448c; 25 | pax_col_t bgColor = 0xFFFFFFFF; 26 | pax_simple_rect(pax_buffer, bgColor, posX, posY, width, height); 27 | pax_outline_rect(pax_buffer, fgColor, posX, posY, width, height); 28 | pax_clip(pax_buffer, posX + 1, posY + 1, width - 2, height - 2); 29 | pax_center_text(pax_buffer, fgColor, font, 18, pax_buffer->width / 2, ((pax_buffer->height - height) / 2) + margin, message); 30 | pax_noclip(pax_buffer); 31 | } 32 | 33 | bool keyboard(xQueueHandle buttonQueue, float aPosX, float aPosY, float aWidth, float aHeight, const char* aTitle, 34 | const char* aHint, char* aOutput, size_t aOutputSize) { 35 | pax_buf_t* pax_buffer = get_pax_buffer(); 36 | const pax_font_t* font = pax_font_saira_regular; 37 | bool accepted = false; 38 | pkb_ctx_t kb_ctx; 39 | pkb_init(pax_buffer, &kb_ctx, 1024); 40 | pkb_set_content(&kb_ctx, aOutput); 41 | kb_ctx.kb_font = font; 42 | kb_ctx.text_font = font; 43 | 44 | pax_col_t bgColor = 0xFFFFFFFF; 45 | pax_col_t shadowColor = 0xFFC0C3C8; 46 | pax_col_t borderColor = 0xFF0000AA; 47 | pax_col_t titleBgColor = 0xFF080764; 48 | pax_col_t titleColor = 0xFFFFFFFF; 49 | pax_col_t selColor = 0xff007fff; 50 | 51 | kb_ctx.text_col = borderColor; 52 | kb_ctx.sel_text_col = bgColor; 53 | kb_ctx.sel_col = selColor; 54 | kb_ctx.bg_col = bgColor; 55 | 56 | kb_ctx.kb_font_size = 18; 57 | 58 | float titleHeight = 20; 59 | float hintHeight = 14; 60 | 61 | pax_noclip(pax_buffer); 62 | pax_simple_rect(pax_buffer, shadowColor, aPosX + 5, aPosY + 5, aWidth, aHeight); 63 | pax_simple_rect(pax_buffer, bgColor, aPosX, aPosY, aWidth, aHeight); 64 | pax_outline_rect(pax_buffer, borderColor, aPosX, aPosY, aWidth, aHeight); 65 | pax_simple_rect(pax_buffer, titleBgColor, aPosX, aPosY, aWidth, titleHeight); 66 | pax_simple_line(pax_buffer, titleColor, aPosX + 1, aPosY + titleHeight, aPosX + aWidth - 2, aPosY + titleHeight - 1); 67 | pax_clip(pax_buffer, aPosX + 1, aPosY + 1, aWidth - 2, titleHeight - 2); 68 | pax_draw_text(pax_buffer, titleColor, font, titleHeight - 2, aPosX + 1, aPosY + 1, aTitle); 69 | pax_clip(pax_buffer, aPosX + 1, aPosY + aHeight - hintHeight, aWidth - 2, hintHeight); 70 | pax_draw_text(pax_buffer, borderColor, font, hintHeight - 2, aPosX + 1, aPosY + aHeight - hintHeight, aHint); 71 | pax_noclip(pax_buffer); 72 | 73 | kb_ctx.x = aPosX + 1; 74 | kb_ctx.y = aPosY + titleHeight + 1; 75 | kb_ctx.width = aWidth - 2; 76 | kb_ctx.height = aHeight - 3 - titleHeight - hintHeight; 77 | 78 | bool running = true; 79 | while (running) { 80 | rp2040_input_message_t buttonMessage = {0}; 81 | if (xQueueReceive(buttonQueue, &buttonMessage, 16 / portTICK_PERIOD_MS) == pdTRUE) { 82 | uint8_t pin = buttonMessage.input; 83 | bool value = buttonMessage.state; 84 | switch (pin) { 85 | case RP2040_INPUT_JOYSTICK_DOWN: 86 | if (value) { 87 | pkb_press(&kb_ctx, PKB_DOWN); 88 | } else { 89 | pkb_release(&kb_ctx, PKB_DOWN); 90 | } 91 | break; 92 | case RP2040_INPUT_JOYSTICK_UP: 93 | if (value) { 94 | pkb_press(&kb_ctx, PKB_UP); 95 | } else { 96 | pkb_release(&kb_ctx, PKB_UP); 97 | } 98 | break; 99 | case RP2040_INPUT_JOYSTICK_LEFT: 100 | if (value) { 101 | pkb_press(&kb_ctx, PKB_LEFT); 102 | } else { 103 | pkb_release(&kb_ctx, PKB_LEFT); 104 | } 105 | break; 106 | case RP2040_INPUT_JOYSTICK_RIGHT: 107 | if (value) { 108 | pkb_press(&kb_ctx, PKB_RIGHT); 109 | } else { 110 | pkb_release(&kb_ctx, PKB_RIGHT); 111 | } 112 | break; 113 | case RP2040_INPUT_JOYSTICK_PRESS: 114 | if (value) { 115 | pkb_press(&kb_ctx, PKB_SHIFT); 116 | } else { 117 | pkb_release(&kb_ctx, PKB_SHIFT); 118 | } 119 | break; 120 | case RP2040_INPUT_BUTTON_ACCEPT: 121 | if (value) { 122 | pkb_press(&kb_ctx, PKB_CHARSELECT); 123 | } else { 124 | pkb_release(&kb_ctx, PKB_CHARSELECT); 125 | } 126 | break; 127 | case RP2040_INPUT_BUTTON_BACK: 128 | if (value) { 129 | pkb_press(&kb_ctx, PKB_DELETE_BEFORE); 130 | } else { 131 | pkb_release(&kb_ctx, PKB_DELETE_BEFORE); 132 | } 133 | break; 134 | case RP2040_INPUT_BUTTON_SELECT: 135 | if (value) { 136 | pkb_press(&kb_ctx, PKB_MODESELECT); 137 | } else { 138 | pkb_release(&kb_ctx, PKB_MODESELECT); 139 | } 140 | break; 141 | case RP2040_INPUT_BUTTON_HOME: 142 | if (value) { 143 | running = false; 144 | } 145 | break; 146 | default: 147 | break; 148 | } 149 | } 150 | pkb_loop(&kb_ctx); 151 | if (kb_ctx.dirty) { 152 | pkb_redraw(pax_buffer, &kb_ctx); 153 | display_flush(); 154 | } 155 | if (kb_ctx.input_accepted) { 156 | memset(aOutput, 0, aOutputSize); 157 | strncpy(aOutput, kb_ctx.content, aOutputSize - 1); 158 | running = false; 159 | accepted = true; 160 | } 161 | } 162 | pkb_destroy(&kb_ctx); 163 | return accepted; 164 | } 165 | -------------------------------------------------------------------------------- /components/gui-toolkit/gui_element_header.c: -------------------------------------------------------------------------------- 1 | #include "gui_element_header.h" 2 | 3 | void render_header(pax_buf_t* pax_buffer, float position_x, float position_y, float width, float height, float text_height, pax_col_t text_color, 4 | pax_col_t bg_color, pax_buf_t* icon, const char* label) { 5 | const pax_font_t* font = pax_font_saira_regular; 6 | pax_simple_rect(pax_buffer, bg_color, position_x, position_y, width, height); 7 | pax_clip(pax_buffer, position_x + 1, position_y + ((height - text_height) / 2) + 1, width - 2, text_height); 8 | pax_draw_text(pax_buffer, text_color, font, text_height, position_x + ((icon != NULL) ? 32 : 0) + 1, position_y + ((height - text_height) / 2) + 1, label); 9 | if (icon != NULL) { 10 | pax_clip(pax_buffer, position_x, position_y, 32, 32); 11 | pax_draw_image(pax_buffer, icon, position_x, position_y); 12 | } 13 | pax_noclip(pax_buffer); 14 | } 15 | -------------------------------------------------------------------------------- /components/gui-toolkit/gui_input.c: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/gui-toolkit/include/graphics_wrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "pax_gfx.h" 11 | 12 | void render_outline(float position_x, float position_y, float width, float height, pax_col_t border_color, pax_col_t background_color); 13 | void render_message(char* message); 14 | bool keyboard(xQueueHandle button_queue, float aPosX, float aPosY, float aWidth, float aHeight, const char* aTitle, const char* aHint, char* aOutput, size_t aOutputSize); 15 | -------------------------------------------------------------------------------- /components/gui-toolkit/include/gui_element_header.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pax_gfx.h" 4 | 5 | void render_header(pax_buf_t* pax_buffer, float position_x, float position_y, float width, float height, float text_height, pax_col_t text_color, pax_col_t bg_color, pax_buf_t* icon, const char* label); 6 | -------------------------------------------------------------------------------- /components/gui-toolkit/include/menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif //__cplusplus 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "pax_gfx.h" 12 | 13 | typedef bool (*menu_callback_t)(); 14 | 15 | typedef struct _menu_item { 16 | char* label; 17 | menu_callback_t callback; 18 | void* callback_arguments; 19 | 20 | pax_buf_t* icon; 21 | 22 | // Linked list 23 | struct _menu_item* previousItem; 24 | struct _menu_item* nextItem; 25 | } menu_item_t; 26 | 27 | typedef struct menu { 28 | char* title; 29 | menu_item_t* firstItem; 30 | size_t length; 31 | size_t position; 32 | size_t prev_pos; 33 | float entry_height; 34 | float text_height; 35 | pax_buf_t* icon; 36 | 37 | pax_col_t fgColor; 38 | pax_col_t bgColor; 39 | pax_col_t selectedItemColor; 40 | pax_col_t bgTextColor; 41 | pax_col_t borderColor; 42 | pax_col_t titleColor; 43 | pax_col_t titleBgColor; 44 | pax_col_t scrollbarBgColor; 45 | pax_col_t scrollbarFgColor; 46 | 47 | float grid_margin_x; 48 | float grid_margin_y; 49 | float grid_entry_count_x; 50 | float grid_entry_count_y; 51 | } menu_t; 52 | 53 | menu_t* menu_alloc(const char* title, float entry_height, float text_height); 54 | void menu_free(menu_t* menu); 55 | void menu_set_icon(menu_t* menu, pax_buf_t* icon); 56 | bool menu_insert_item(menu_t* menu, const char* label, menu_callback_t callback, void* callback_arguments, size_t position); 57 | bool menu_insert_item_icon(menu_t* menu, const char* label, menu_callback_t callback, void* callback_arguments, size_t position, pax_buf_t* icon); 58 | bool menu_remove_item(menu_t* menu, size_t position); 59 | bool menu_navigate_to(menu_t* menu, size_t position); 60 | void menu_navigate_previous(menu_t* menu); 61 | void menu_navigate_next(menu_t* menu); 62 | void menu_navigate_previous_row(menu_t* menu); 63 | void menu_navigate_next_row(menu_t* menu); 64 | size_t menu_get_position(menu_t* menu); 65 | void menu_set_position(menu_t* menu, size_t position); 66 | size_t menu_get_length(menu_t* menu); 67 | void* menu_get_callback_args(menu_t* menu, size_t position); 68 | pax_buf_t* menu_get_icon(menu_t* menu, size_t position); 69 | void menu_debug(menu_t* menu); 70 | void menu_render(pax_buf_t* pax_buffer, menu_t* menu, float position_x, float position_y, float width, float height); 71 | void menu_render_grid(pax_buf_t* buffer, menu_t* menu, float position_x, float position_y, float width, float height); 72 | void menu_render_grid_changes(pax_buf_t* buffer, menu_t* menu, float position_x, float position_y, float width, float height); 73 | 74 | #ifdef __cplusplus 75 | } 76 | #endif //__cplusplus 77 | -------------------------------------------------------------------------------- /generate_version_header.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | GIT_DESCRIPTION="$(git describe --dirty --always --tags 2> /dev/null)" 4 | GIT_TAG="$(git describe --exact-match --tags 2> /dev/null)" 5 | GIT_SHA="$(git rev-parse HEAD 2> /dev/null)" 6 | GIT_BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)" 7 | 8 | if [[ -z "$GIT_TAG" ]]; then 9 | GIT_TAG="N/A" 10 | fi 11 | if [[ -z "$GIT_DESCRIPTION" ]]; then 12 | GIT_DESCRIPTION="N/A" 13 | fi 14 | if [[ -z "$GIT_SHA" ]]; then 15 | GIT_SHA="N/A" 16 | fi 17 | if [[ -z "$GIT_BRANCH" ]]; then 18 | GIT_BRANCH="N/A" 19 | fi 20 | 21 | 22 | echo "#pragma once" 23 | echo "# Note: this is an automatically generated file, do not edit" 24 | echo "#define GIT_TAG \"$GIT_TAG\"" 25 | echo "#define GIT_DESCRIPTION \"$GIT_DESCRIPTION\"" 26 | echo "#define GIT_SHA \"$GIT_SHA\"" 27 | echo "#define GIT_BRANCH \"$GIT_BRANCH\"" 28 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.c" 3 | "appfs_wrapper.c" 4 | "fpga_test.c" 5 | "rp2040_updater.c" 6 | "settings.c" 7 | "system_wrapper.c" 8 | "wifi_ota.c" 9 | "fpga_download.c" 10 | "fpga_util.c" 11 | "audio.c" 12 | "bootscreen.c" 13 | "menus/hatchery.c" 14 | "menus/settings.c" 15 | "menus/start.c" 16 | "menus/dev.c" 17 | "menus/wifi.c" 18 | "menus/sao.c" 19 | "menus/ir.c" 20 | "menus/launcher.c" 21 | "nametag.c" 22 | "file_browser.c" 23 | "test_common.c" 24 | "factory_test.c" 25 | "button_test.c" 26 | "adc_test.c" 27 | "webusb.c" 28 | "wifi_test.c" 29 | "sao_eeprom.c" 30 | "rtc_memory.c" 31 | "metadata.c" 32 | "wifi_defaults.c" 33 | "wifi_cert.c" 34 | "http_download.c" 35 | "filesystems.c" 36 | "app_management.c" 37 | "app_update.c" 38 | "msc.c" 39 | "terminal.c" 40 | INCLUDE_DIRS "." 41 | "include" 42 | "menus" 43 | EMBED_TXTFILES ${project_dir}/resources/isrgrootx1.pem 44 | ${project_dir}/resources/custom_ota_cert.pem 45 | EMBED_FILES ${project_dir}/resources/fpga_selftest.bin 46 | ${project_dir}/resources/rp2040_firmware.bin 47 | ${project_dir}/resources/boot.snd 48 | ${project_dir}/resources/mch2022_logo.png 49 | ${project_dir}/resources/icons/dev.png 50 | ${project_dir}/resources/icons/home.png 51 | ${project_dir}/resources/icons/settings.png 52 | ${project_dir}/resources/icons/apps.png 53 | ${project_dir}/resources/icons/hatchery.png 54 | ${project_dir}/resources/icons/tag.png 55 | ${project_dir}/resources/icons/bitstream.png 56 | ${project_dir}/resources/icons/python.png 57 | ${project_dir}/resources/icons/hourglass.png 58 | ${project_dir}/resources/icons/update.png 59 | ${project_dir}/resources/icons/sao.png 60 | ) 61 | -------------------------------------------------------------------------------- /main/adc_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "gui_element_header.h" 8 | #include "hardware.h" 9 | #include "pax_gfx.h" 10 | #include "rp2040.h" 11 | 12 | void test_adc(xQueueHandle button_queue) { 13 | pax_buf_t* pax_buffer = get_pax_buffer(); 14 | bool quit = false; 15 | 16 | RP2040* rp2040 = get_rp2040(); 17 | const pax_font_t* font = pax_font_sky_mono; 18 | 19 | while (!quit) { 20 | bool error = false; 21 | 22 | float vbat = 0; 23 | if (rp2040_read_vbat(rp2040, &vbat) != ESP_OK) { 24 | error = true; 25 | } 26 | 27 | float vusb = 0; 28 | if (rp2040_read_vusb(rp2040, &vusb) != ESP_OK) { 29 | error = true; 30 | } 31 | 32 | uint16_t raw_temperature = 0; 33 | if (rp2040_read_temp(rp2040, &raw_temperature) != ESP_OK) { 34 | error = true; 35 | } 36 | 37 | uint8_t charging = 0; 38 | if (rp2040_get_charging(rp2040, &charging) != ESP_OK) { 39 | error = true; 40 | } 41 | 42 | const float conversion_factor = 3.3f / (1 << 12); // 12-bit ADC with 3.3v vref 43 | float vtemperature = raw_temperature * conversion_factor; // Inside of RP2040 chip 44 | float temperature = 27 - (vtemperature - 0.706) / 0.001721; // From https://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__adc.html 45 | 46 | pax_noclip(pax_buffer); 47 | pax_background(pax_buffer, 0x325aa8); 48 | render_header(pax_buffer, 0, 0, pax_buffer->width, 34, 18, 0xFF000000, 0xFFFFFFFF, NULL, "Analog inputs"); 49 | char buffer[64]; 50 | if (error) { 51 | pax_draw_text(pax_buffer, 0xFFFFFFFF, font, 18, 0, 20 * 2, "Error, failed to read!"); 52 | } else { 53 | snprintf(buffer, sizeof(buffer), "Batt. voltage %0.2f v", vbat); 54 | pax_draw_text(pax_buffer, 0xFFFFFFFF, font, 18, 0, 20 * 2, buffer); 55 | snprintf(buffer, sizeof(buffer), "USB voltage %0.2f v", vusb); 56 | pax_draw_text(pax_buffer, 0xFFFFFFFF, font, 18, 0, 20 * 3, buffer); 57 | snprintf(buffer, sizeof(buffer), "Temperature %0.2f *c", temperature); 58 | pax_draw_text(pax_buffer, 0xFFFFFFFF, font, 18, 0, 20 * 4, buffer); 59 | snprintf(buffer, sizeof(buffer), "Charging %s", charging ? "Yes" : "No"); 60 | pax_draw_text(pax_buffer, 0xFFFFFFFF, font, 18, 0, 20 * 5, buffer); 61 | } 62 | display_flush(); 63 | 64 | rp2040_input_message_t buttonMessage = {0}; 65 | if (xQueueReceive(button_queue, &buttonMessage, 250 / portTICK_PERIOD_MS) == pdTRUE) { 66 | uint8_t pin = buttonMessage.input; 67 | bool value = buttonMessage.state; 68 | if (value) { 69 | switch (pin) { 70 | case RP2040_INPUT_BUTTON_HOME: 71 | case RP2040_INPUT_BUTTON_BACK: 72 | quit = true; 73 | default: 74 | break; 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /main/app_management.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "appfs_wrapper.h" 13 | #include "bootscreen.h" 14 | #include "cJSON.h" 15 | #include "filesystems.h" 16 | #include "graphics_wrapper.h" 17 | #include "hardware.h" 18 | #include "http_download.h" 19 | #include "menu.h" 20 | #include "metadata.h" 21 | #include "pax_codecs.h" 22 | #include "pax_gfx.h" 23 | #include "rp2040.h" 24 | #include "system_wrapper.h" 25 | #include "wifi_connect.h" 26 | 27 | static const char* TAG = "App management"; 28 | 29 | static const char* sdcard_path = "/sd"; 30 | static const char* internal_path = "/internal"; 31 | static const char* esp32_type = "esp32"; 32 | static const char* esp32_bin_fn = "main.bin"; 33 | static const char* metadata_json_fn = "metadata.json"; 34 | 35 | bool create_dir(const char* path) { 36 | struct stat st = {0}; 37 | if (stat(path, &st) == 0) { 38 | return (st.st_mode & S_IFDIR) != 0; 39 | } 40 | return mkdir(path, 0777) == 0; 41 | } 42 | 43 | bool install_app(xQueueHandle button_queue, const char* type_slug, bool to_sd_card, char* data_app_info, size_t size_app_info, cJSON* json_app_info) { 44 | cJSON* slug_obj = cJSON_GetObjectItem(json_app_info, "slug"); 45 | cJSON* name_obj = cJSON_GetObjectItem(json_app_info, "name"); 46 | // cJSON* author_obj = cJSON_GetObjectItem(json_app_info, "author"); 47 | // cJSON* license_obj = cJSON_GetObjectItem(json_app_info, "license"); 48 | // cJSON* description_obj = cJSON_GetObjectItem(json_app_info, "description"); 49 | cJSON* version_obj = cJSON_GetObjectItem(json_app_info, "version"); 50 | cJSON* files_obj = cJSON_GetObjectItem(json_app_info, "files"); 51 | 52 | char buffer[257]; 53 | buffer[sizeof(buffer) - 1] = '\0'; 54 | 55 | // Create folders 56 | snprintf(buffer, sizeof(buffer) - 1, "Installing %s:\nCreating folders...", name_obj->valuestring); 57 | render_message(buffer); 58 | display_flush(); 59 | 60 | snprintf(buffer, sizeof(buffer) - 1, "%s/apps", to_sd_card ? sdcard_path : internal_path); 61 | printf("Creating dir: %s\r\n", buffer); 62 | if (!create_dir(buffer)) { 63 | // Failed to create app directory 64 | ESP_LOGI(TAG, "Failed to create %s", buffer); 65 | render_message("Failed create folder"); 66 | display_flush(); 67 | if (button_queue != NULL) wait_for_button(); 68 | return false; 69 | } 70 | 71 | snprintf(buffer, sizeof(buffer) - 1, "%s/apps/%s", to_sd_card ? sdcard_path : internal_path, type_slug); 72 | printf("Creating dir: %s\r\n", buffer); 73 | if (!create_dir(buffer)) { 74 | // failed to create app type directory 75 | ESP_LOGI(TAG, "Failed to create %s", buffer); 76 | render_message("Failed create folder"); 77 | display_flush(); 78 | if (button_queue != NULL) wait_for_button(); 79 | return false; 80 | } 81 | 82 | snprintf(buffer, sizeof(buffer) - 1, "%s/apps/%s/%s", to_sd_card ? sdcard_path : internal_path, type_slug, slug_obj->valuestring); 83 | printf("Creating dir: %s\r\n", buffer); 84 | if (!create_dir(buffer)) { 85 | // failed to create app directory 86 | ESP_LOGI(TAG, "Failed to create %s", buffer); 87 | render_message("Failed create folder"); 88 | display_flush(); 89 | if (button_queue != NULL) wait_for_button(); 90 | return false; 91 | } 92 | 93 | // Download files 94 | cJSON* file_obj; 95 | cJSON_ArrayForEach(file_obj, files_obj) { 96 | cJSON* name_obj = cJSON_GetObjectItem(file_obj, "name"); 97 | cJSON* url_obj = cJSON_GetObjectItem(file_obj, "url"); 98 | // cJSON* size_obj = cJSON_GetObjectItem(file_obj, "size"); 99 | if ((strcmp(type_slug, esp32_type) == 0) && (strcmp(name_obj->valuestring, esp32_bin_fn) == 0)) { 100 | snprintf(buffer, sizeof(buffer) - 1, "Installing %s:\nDownloading '%s' to AppFS", name_obj->valuestring, name_obj->valuestring); 101 | render_message(buffer); 102 | display_flush(); 103 | snprintf(buffer, sizeof(buffer) - 1, "%s/apps/%s/%s/%s", to_sd_card ? sdcard_path : internal_path, type_slug, slug_obj->valuestring, 104 | name_obj->valuestring); 105 | uint8_t* esp32_binary_data; 106 | size_t esp32_binary_size; 107 | bool success = download_ram(url_obj->valuestring, (uint8_t**) &esp32_binary_data, &esp32_binary_size); 108 | if (!success) { 109 | ESP_LOGI(TAG, "Failed to download %s to RAM", url_obj->valuestring); 110 | render_message("Failed to download file"); 111 | display_flush(); 112 | if (button_queue != NULL) wait_for_button(); 113 | return false; 114 | } 115 | if (esp32_binary_data != NULL) { // Ignore 0 bytes files 116 | esp_err_t res = appfs_store_in_memory_app(button_queue, slug_obj->valuestring, name_obj->valuestring, version_obj->valueint, esp32_binary_size, 117 | esp32_binary_data); 118 | if (res != ESP_OK) { 119 | free(esp32_binary_data); 120 | ESP_LOGI(TAG, "Failed to store ESP32 binary"); 121 | render_message("Failed to install app to AppFS"); 122 | display_flush(); 123 | if (button_queue != NULL) wait_for_button(); 124 | return false; 125 | } 126 | if (to_sd_card) { 127 | render_message("Storing a copy of the ESP32\nbinary to the SD card..."); 128 | display_flush(); 129 | printf("Creating file: %s\r\n", buffer); 130 | FILE* binary_fd = fopen(buffer, "w"); 131 | if (binary_fd == NULL) { 132 | free(esp32_binary_data); 133 | ESP_LOGI(TAG, "Failed to install ESP32 binary to %s", buffer); 134 | render_message("Failed to install app to SD card"); 135 | display_flush(); 136 | if (button_queue != NULL) wait_for_button(); 137 | return false; 138 | } 139 | fwrite(esp32_binary_data, 1, esp32_binary_size, binary_fd); 140 | fclose(binary_fd); 141 | } 142 | free(esp32_binary_data); 143 | } 144 | } else { 145 | snprintf(buffer, sizeof(buffer) - 1, "Installing %s:\nDownloading '%s'...", name_obj->valuestring, name_obj->valuestring); 146 | render_message(buffer); 147 | display_flush(); 148 | snprintf(buffer, sizeof(buffer) - 1, "%s/apps/%s/%s/%s", to_sd_card ? sdcard_path : internal_path, type_slug, slug_obj->valuestring, 149 | name_obj->valuestring); 150 | printf("Downloading file: %s\r\n", buffer); 151 | if (!download_file(url_obj->valuestring, buffer)) { 152 | ESP_LOGI(TAG, "Failed to download %s to %s", url_obj->valuestring, buffer); 153 | render_message("Failed to download file"); 154 | display_flush(); 155 | if (button_queue != NULL) wait_for_button(); 156 | return false; 157 | } 158 | } 159 | } 160 | 161 | // Install metadata.json 162 | snprintf(buffer, sizeof(buffer) - 1, "%s/apps/%s/%s/%s", to_sd_card ? sdcard_path : internal_path, type_slug, slug_obj->valuestring, metadata_json_fn); 163 | FILE* metadata_fd = fopen(buffer, "w"); 164 | if (metadata_fd == NULL) { 165 | ESP_LOGI(TAG, "Failed to install metadata to %s", buffer); 166 | render_message("Failed to install metadata"); 167 | display_flush(); 168 | if (button_queue != NULL) wait_for_button(); 169 | return false; 170 | } 171 | fwrite(data_app_info, 1, size_app_info, metadata_fd); 172 | fclose(metadata_fd); 173 | 174 | ESP_LOGI(TAG, "App installed!"); 175 | render_message("App has been installed!"); 176 | display_flush(); 177 | if (button_queue != NULL) wait_for_button(); 178 | return true; 179 | } 180 | -------------------------------------------------------------------------------- /main/app_update.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "app_management.h" 15 | #include "appfs.h" 16 | #include "appfs_wrapper.h" 17 | #include "bootscreen.h" 18 | #include "fpga_download.h" 19 | #include "fpga_util.h" 20 | #include "graphics_wrapper.h" 21 | #include "gui_element_header.h" 22 | #include "hardware.h" 23 | #include "http_download.h" 24 | #include "launcher.h" 25 | #include "menu.h" 26 | #include "metadata.h" 27 | #include "pax_codecs.h" 28 | #include "pax_gfx.h" 29 | #include "rp2040.h" 30 | #include "rtc_memory.h" 31 | #include "system_wrapper.h" 32 | #include "wifi_connect.h" 33 | 34 | static const char* TAG = "Updater"; 35 | 36 | typedef struct _update_apps_callback_args { 37 | xQueueHandle button_queue; 38 | bool sdcard; 39 | } update_apps_callback_args_t; 40 | 41 | static bool connect_to_wifi(xQueueHandle button_queue) { 42 | if (!wifi_connect_to_stored()) { 43 | wifi_disconnect_and_disable(); 44 | render_message("Unable to connect to\nthe WiFi network"); 45 | display_flush(); 46 | wait_for_button(); 47 | return false; 48 | } 49 | return true; 50 | } 51 | 52 | static char* data_app_info = NULL; 53 | static size_t size_app_info = 0; 54 | static cJSON* json_app_info = NULL; 55 | 56 | static bool load_app_info(const char* type_slug, const char* category_slug, const char* app_slug) { 57 | char url[128]; 58 | snprintf(url, sizeof(url) - 1, "https://mch2022.badge.team/v2/mch2022/%s/%s/%s", type_slug, category_slug, app_slug); 59 | bool success = download_ram(url, (uint8_t**) &data_app_info, &size_app_info); 60 | if (!success) return false; 61 | if (data_app_info == NULL) return false; 62 | json_app_info = cJSON_ParseWithLength(data_app_info, size_app_info); 63 | if (json_app_info == NULL) return false; 64 | return true; 65 | } 66 | 67 | static void free_app_info() { 68 | if (json_app_info != NULL) { 69 | cJSON_Delete(json_app_info); 70 | json_app_info = NULL; 71 | } 72 | 73 | if (data_app_info != NULL) { 74 | free(data_app_info); 75 | data_app_info = NULL; 76 | size_app_info = 0; 77 | } 78 | } 79 | 80 | #define AMOUNT_OF_LINES 8 81 | char* terminal_lines[AMOUNT_OF_LINES] = {NULL}; 82 | 83 | static void terminal_render() { 84 | pax_buf_t* pax_buffer = get_pax_buffer(); 85 | pax_background(pax_buffer, 0xFFFFFF); 86 | render_header(pax_buffer, 0, 0, pax_buffer->width, 34, 18, 0xFFfa448c, 0xFF491d88, NULL, "Updating apps..."); 87 | uint8_t printed_lines = 0; 88 | for (uint8_t line = 0; line < AMOUNT_OF_LINES; line++) { 89 | if (terminal_lines[line] != NULL) { 90 | pax_draw_text(pax_buffer, 0xFF491d88, pax_font_saira_regular, 18, 2, 48 + 20 * printed_lines, terminal_lines[line]); 91 | printed_lines++; 92 | } 93 | } 94 | display_flush(); 95 | } 96 | 97 | static void terminal_add(char* buffer) { 98 | if (terminal_lines[0] != NULL) { 99 | free(terminal_lines[0]); 100 | terminal_lines[0] = NULL; 101 | } 102 | for (uint8_t i = 0; i < AMOUNT_OF_LINES - 1; i++) { 103 | terminal_lines[i] = terminal_lines[i + 1]; 104 | } 105 | terminal_lines[AMOUNT_OF_LINES - 1] = buffer; 106 | } 107 | 108 | static void terminal_free() { 109 | for (uint8_t i = 0; i < AMOUNT_OF_LINES; i++) { 110 | if (terminal_lines[i] != NULL) { 111 | free(terminal_lines[i]); 112 | terminal_lines[i] = NULL; 113 | } 114 | } 115 | } 116 | 117 | static void callback(const char* path, const char* entity, void* user) { 118 | update_apps_callback_args_t* args = (update_apps_callback_args_t*) user; 119 | char string_buffer[128]; 120 | string_buffer[sizeof(string_buffer) - 1] = '\0'; 121 | char metadata_path[128]; 122 | metadata_path[sizeof(metadata_path) - 1] = '\0'; 123 | snprintf(metadata_path, sizeof(metadata_path) - 1, "%s/%s/metadata.json", path, entity); 124 | 125 | char* device = NULL; 126 | char* type = NULL; 127 | char* category = NULL; 128 | char* slug = NULL; 129 | char* name = NULL; 130 | int installed_version = 0; 131 | parse_metadata(metadata_path, &device, &type, &category, &slug, &name, NULL, NULL, &installed_version, NULL); 132 | 133 | if ((slug == NULL) || (strcmp(slug, entity) != 0)) { 134 | snprintf(string_buffer, sizeof(string_buffer) - 1, "%s/%s: no metadata", path, entity); 135 | ESP_LOGW(TAG, "%s", string_buffer); 136 | terminal_add(strdup(string_buffer)); 137 | terminal_render(); 138 | goto end; 139 | } 140 | 141 | if ((type == NULL) || (category == NULL)) { 142 | snprintf(string_buffer, sizeof(string_buffer) - 1, "%s: incomplete metadata", slug); 143 | ESP_LOGW(TAG, "%s", string_buffer); 144 | terminal_add(strdup(string_buffer)); 145 | terminal_render(); 146 | goto end; 147 | } 148 | 149 | if (!load_app_info(type, category, slug)) { 150 | snprintf(string_buffer, sizeof(string_buffer) - 1, "%s: fetching metadata failed", slug); 151 | ESP_LOGW(TAG, "%s", string_buffer); 152 | terminal_add(strdup(string_buffer)); 153 | terminal_render(); 154 | goto end; 155 | } 156 | 157 | cJSON* version_obj = cJSON_GetObjectItem(json_app_info, "version"); 158 | 159 | if (!version_obj) { 160 | snprintf(string_buffer, sizeof(string_buffer) - 1, "%s: hatchery has no version", slug); 161 | ESP_LOGW(TAG, "%s", string_buffer); 162 | terminal_add(strdup(string_buffer)); 163 | terminal_render(); 164 | goto end; 165 | } 166 | 167 | if (installed_version >= version_obj->valueint) { 168 | snprintf(string_buffer, sizeof(string_buffer) - 1, "%s: up to date", slug); 169 | ESP_LOGW(TAG, "%s", string_buffer); 170 | terminal_add(strdup(string_buffer)); 171 | terminal_render(); 172 | goto end; 173 | } 174 | 175 | snprintf(string_buffer, sizeof(string_buffer) - 1, "%s: r%d to r%d", slug, installed_version, version_obj->valueint); 176 | ESP_LOGI(TAG, "%s", string_buffer); 177 | terminal_add(strdup(string_buffer)); 178 | terminal_render(); 179 | 180 | if (install_app(NULL, type, args->sdcard, data_app_info, size_app_info, json_app_info)) { 181 | snprintf(string_buffer, sizeof(string_buffer) - 1, "%s: installed r%d", slug, version_obj->valueint); 182 | ESP_LOGI(TAG, "%s", string_buffer); 183 | } else { 184 | snprintf(string_buffer, sizeof(string_buffer) - 1, "%s: failed to install", slug); 185 | ESP_LOGE(TAG, "%s", string_buffer); 186 | } 187 | 188 | terminal_add(strdup(string_buffer)); 189 | terminal_render(); 190 | 191 | end: 192 | free_app_info(); 193 | if (device != NULL) free(device); 194 | if (type != NULL) free(type); 195 | if (slug != NULL) free(slug); 196 | if (category != NULL) free(category); 197 | if (name != NULL) free(name); 198 | } 199 | 200 | void update_apps(xQueueHandle button_queue) { 201 | size_t ram_before = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); 202 | terminal_add(strdup("Connecting to WiFi...")); 203 | terminal_render(); 204 | 205 | if (!connect_to_wifi(button_queue)) { 206 | terminal_add(strdup("Failed to connect to WiFi")); 207 | terminal_render(); 208 | terminal_free(); 209 | return; 210 | } 211 | 212 | terminal_add(strdup("Connected to WiFi")); 213 | terminal_render(); 214 | 215 | update_apps_callback_args_t args; 216 | args.button_queue = button_queue; 217 | args.sdcard = false; 218 | 219 | for_entity_in_path("/internal/apps/esp32", true, &callback, &args); 220 | for_entity_in_path("/internal/apps/python", true, &callback, &args); 221 | for_entity_in_path("/internal/apps/ice40", true, &callback, &args); 222 | args.sdcard = true; 223 | for_entity_in_path("/sd/apps/esp32", true, &callback, &args); 224 | for_entity_in_path("/sd/apps/python", true, &callback, &args); 225 | for_entity_in_path("/sd/apps/ice40", true, &callback, &args); 226 | 227 | terminal_free(); 228 | wifi_disconnect_and_disable(); 229 | size_t ram_after = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); 230 | printf("Leak: %d (%u to %u)\r\n", ram_before - ram_after, ram_before, ram_after); 231 | } 232 | -------------------------------------------------------------------------------- /main/appfs_wrapper.c: -------------------------------------------------------------------------------- 1 | #include "appfs_wrapper.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "appfs.h" 14 | #include "bootscreen.h" 15 | #include "esp_sleep.h" 16 | #include "graphics_wrapper.h" 17 | #include "hardware.h" 18 | #include "menu.h" 19 | #include "pax_gfx.h" 20 | #include "rp2040.h" 21 | #include "soc/rtc.h" 22 | #include "soc/rtc_cntl_reg.h" 23 | #include "system_wrapper.h" 24 | 25 | static const char* TAG = "appfs wrapper"; 26 | 27 | esp_err_t appfs_init(void) { return appfsInit(APPFS_PART_TYPE, APPFS_PART_SUBTYPE); } 28 | 29 | appfs_handle_t appfs_detect_crash() { 30 | uint32_t r = REG_READ(RTC_CNTL_STORE0_REG); 31 | ESP_LOGI(TAG, "RTC store0 reg: %x", r); 32 | if ((r & 0xFF000000) != 0xA6000000) return APPFS_INVALID_FD; 33 | return r & 0xff; 34 | } 35 | 36 | void appfs_boot_app(int fd) { 37 | if (fd < 0 || fd > 255) { 38 | REG_WRITE(RTC_CNTL_STORE0_REG, 0); 39 | } else { 40 | REG_WRITE(RTC_CNTL_STORE0_REG, 0xA5000000 | fd); 41 | } 42 | 43 | esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); 44 | esp_sleep_enable_timer_wakeup(10); 45 | esp_deep_sleep_start(); 46 | } 47 | 48 | void appfs_store_app(xQueueHandle button_queue, const char* path, const char* name, const char* title, uint16_t version) { 49 | display_boot_screen("Installing app..."); 50 | esp_err_t res; 51 | FILE* app_fd = fopen(path, "rb"); 52 | if (app_fd == NULL) { 53 | render_message("Failed to open file"); 54 | display_flush(); 55 | ESP_LOGE(TAG, "Failed to open file"); 56 | if (button_queue != NULL) wait_for_button(); 57 | return; 58 | } 59 | size_t app_size = get_file_size(app_fd); 60 | uint8_t* app = load_file_to_ram(app_fd); 61 | fclose(app_fd); 62 | if (app == NULL) { 63 | render_message("Failed to load app to RAM"); 64 | display_flush(); 65 | ESP_LOGE(TAG, "Failed to load application into RAM"); 66 | if (button_queue != NULL) wait_for_button(); 67 | return; 68 | } 69 | 70 | ESP_LOGI(TAG, "Application size %d", app_size); 71 | 72 | res = appfs_store_in_memory_app(button_queue, name, title, version, app_size, app); 73 | if (res == ESP_OK) { 74 | render_message("App installed!"); 75 | display_flush(); 76 | if (button_queue != NULL) wait_for_button(); 77 | } 78 | 79 | free(app); 80 | } 81 | 82 | esp_err_t appfs_store_in_memory_app(xQueueHandle button_queue, const char* name, const char* title, uint16_t version, size_t app_size, uint8_t* app) { 83 | appfs_handle_t handle; 84 | esp_err_t res = appfsCreateFileExt(name, title, version, app_size, &handle); 85 | if (res != ESP_OK) { 86 | render_message("Failed to create file"); 87 | display_flush(); 88 | ESP_LOGE(TAG, "Failed to create file on AppFS (%d)", res); 89 | if (button_queue != NULL) wait_for_button(); 90 | return res; 91 | } 92 | int roundedSize = (app_size + (SPI_FLASH_MMU_PAGE_SIZE - 1)) & (~(SPI_FLASH_MMU_PAGE_SIZE - 1)); 93 | res = appfsErase(handle, 0, roundedSize); 94 | if (res != ESP_OK) { 95 | render_message("Failed to erase file"); 96 | display_flush(); 97 | ESP_LOGE(TAG, "Failed to erase file on AppFS (%d)", res); 98 | if (button_queue != NULL) wait_for_button(); 99 | return res; 100 | } 101 | res = appfsWrite(handle, 0, app, app_size); 102 | if (res != ESP_OK) { 103 | render_message("Failed to write file"); 104 | display_flush(); 105 | ESP_LOGE(TAG, "Failed to write to file on AppFS (%d)", res); 106 | if (button_queue != NULL) wait_for_button(); 107 | return res; 108 | } 109 | ESP_LOGI(TAG, "Application is now stored in AppFS"); 110 | return res; 111 | } 112 | -------------------------------------------------------------------------------- /main/audio.c: -------------------------------------------------------------------------------- 1 | #include "audio.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "driver/i2s.h" 9 | #include "driver/rtc_io.h" 10 | #include "esp_system.h" 11 | 12 | void _audio_init(int i2s_num) { 13 | i2s_config_t i2s_config = {.mode = I2S_MODE_MASTER | I2S_MODE_TX, 14 | .sample_rate = 8000, 15 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 16 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 17 | .communication_format = I2S_COMM_FORMAT_STAND_I2S, 18 | .dma_buf_count = 8, 19 | .dma_buf_len = 256, 20 | .intr_alloc_flags = 0, 21 | .use_apll = false, 22 | .bits_per_chan = I2S_BITS_PER_SAMPLE_16BIT}; 23 | 24 | i2s_driver_install(i2s_num, &i2s_config, 0, NULL); 25 | 26 | i2s_pin_config_t pin_config = {.mck_io_num = 0, .bck_io_num = 4, .ws_io_num = 12, .data_out_num = 13, .data_in_num = I2S_PIN_NO_CHANGE}; 27 | 28 | i2s_set_pin(i2s_num, &pin_config); 29 | } 30 | 31 | typedef struct _audio_player_cfg { 32 | uint8_t* buffer; 33 | size_t size; 34 | bool free_buffer; 35 | } audio_player_cfg_t; 36 | 37 | void audio_player_task(void* arg) { 38 | audio_player_cfg_t* config = (audio_player_cfg_t*) arg; 39 | size_t sample_length = config->size; 40 | uint8_t* sample_buffer = config->buffer; 41 | 42 | size_t count; 43 | size_t position = 0; 44 | 45 | while (position < sample_length) { 46 | size_t length = sample_length - position; 47 | if (length > 256) length = 256; 48 | uint8_t buffer[256]; 49 | memcpy(buffer, &sample_buffer[position], length); 50 | for (size_t l = 0; l < length; l += 2) { 51 | int16_t* sample = (int16_t*) &buffer[l]; 52 | *sample *= 0.55; 53 | } 54 | i2s_write(0, buffer, length, &count, portMAX_DELAY); 55 | if (count != length) { 56 | printf("i2s_write_bytes: count (%d) != length (%d)\n", count, length); 57 | abort(); 58 | } 59 | position += length; 60 | } 61 | 62 | i2s_zero_dma_buffer(0); // Fill buffer with silence 63 | if (config->free_buffer) free(sample_buffer); 64 | vTaskDelete(NULL); // Tell FreeRTOS that the task is done 65 | } 66 | 67 | void audio_init() { _audio_init(0); } 68 | 69 | extern const uint8_t boot_snd_start[] asm("_binary_boot_snd_start"); 70 | extern const uint8_t boot_snd_end[] asm("_binary_boot_snd_end"); 71 | 72 | audio_player_cfg_t bootsound; 73 | 74 | void play_bootsound() { 75 | TaskHandle_t handle; 76 | 77 | bootsound.buffer = (uint8_t*) (boot_snd_start); 78 | bootsound.size = boot_snd_end - boot_snd_start; 79 | bootsound.free_buffer = false; 80 | 81 | xTaskCreate(&audio_player_task, "Audio player", 4096, (void*) &bootsound, 10, &handle); 82 | } 83 | -------------------------------------------------------------------------------- /main/bootscreen.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "hardware.h" 6 | #include "pax_codecs.h" 7 | #include "pax_gfx.h" 8 | 9 | extern const uint8_t mch2022_logo_png_start[] asm("_binary_mch2022_logo_png_start"); 10 | extern const uint8_t mch2022_logo_png_end[] asm("_binary_mch2022_logo_png_end"); 11 | extern const uint8_t hourglass_png_start[] asm("_binary_hourglass_png_start"); 12 | extern const uint8_t hourglass_png_end[] asm("_binary_hourglass_png_end"); 13 | 14 | void display_boot_screen(const char* text) { 15 | pax_buf_t* pax_buffer = get_pax_buffer(); 16 | const pax_font_t* font = pax_font_saira_regular; 17 | pax_noclip(pax_buffer); 18 | pax_background(pax_buffer, 0xFFFFFF); 19 | float x = (320 / 2) - (212 / 2); 20 | float y = ((240 - 32 - 10) / 2) - (160 / 2); 21 | pax_insert_png_buf(pax_buffer, mch2022_logo_png_start, mch2022_logo_png_end - mch2022_logo_png_start, x, y, 0); 22 | pax_vec1_t size = pax_text_size(font, 18, text); 23 | pax_draw_text(pax_buffer, 0xFF000000, font, 18, (320 / 2) - (size.x / 2), 240 - 32, text); 24 | display_flush(); 25 | } 26 | 27 | void display_busy() { 28 | pax_buf_t* pax_buffer = get_pax_buffer(); 29 | pax_noclip(pax_buffer); 30 | pax_buf_t icon; 31 | pax_decode_png_buf(&icon, (void*) hourglass_png_start, hourglass_png_end - hourglass_png_start, PAX_BUF_32_8888ARGB, 0); 32 | float x = (pax_buffer->width - icon.width) / 2; 33 | float y = (pax_buffer->height - icon.height) / 2; 34 | pax_simple_rect(pax_buffer, 0xFFFFFFFF, x - 1, y - 1, icon.width + 2, icon.height + 2); 35 | pax_outline_rect(pax_buffer, 0xff491d88, x - 1, y - 1, icon.width + 2, icon.height + 2); 36 | pax_draw_image(pax_buffer, &icon, x, y); 37 | pax_buf_destroy(&icon); 38 | display_flush(); 39 | } 40 | -------------------------------------------------------------------------------- /main/button_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "hardware.h" 8 | #include "pax_gfx.h" 9 | #include "rp2040.h" 10 | 11 | void test_buttons(xQueueHandle button_queue) { 12 | pax_buf_t* pax_buffer = get_pax_buffer(); 13 | const pax_font_t* font = pax_font_saira_regular; 14 | 15 | bool render = true; 16 | bool quit = false; 17 | 18 | bool btn_joy_down = false; 19 | bool btn_joy_up = false; 20 | bool btn_joy_left = false; 21 | bool btn_joy_right = false; 22 | bool btn_joy_press = false; 23 | bool btn_home = false; 24 | bool btn_menu = false; 25 | bool btn_select = false; 26 | bool btn_start = false; 27 | bool btn_accept = false; 28 | bool btn_back = false; 29 | 30 | bool btn_joy_down_green = false; 31 | bool btn_joy_up_green = false; 32 | bool btn_joy_left_green = false; 33 | bool btn_joy_right_green = false; 34 | bool btn_joy_press_green = false; 35 | bool btn_home_green = false; 36 | bool btn_menu_green = false; 37 | bool btn_select_green = false; 38 | bool btn_start_green = false; 39 | bool btn_accept_green = false; 40 | bool btn_back_green = false; 41 | 42 | while (!quit) { 43 | rp2040_input_message_t buttonMessage = {0}; 44 | if (xQueueReceive(button_queue, &buttonMessage, 16 / portTICK_PERIOD_MS) == pdTRUE) { 45 | uint8_t pin = buttonMessage.input; 46 | bool value = buttonMessage.state; 47 | render = true; 48 | switch (pin) { 49 | case RP2040_INPUT_JOYSTICK_DOWN: 50 | btn_joy_down = value; 51 | if (value) btn_joy_down_green = true; 52 | break; 53 | case RP2040_INPUT_JOYSTICK_UP: 54 | btn_joy_up = value; 55 | if (value) btn_joy_up_green = true; 56 | break; 57 | case RP2040_INPUT_JOYSTICK_LEFT: 58 | btn_joy_left = value; 59 | if (value) btn_joy_left_green = true; 60 | break; 61 | case RP2040_INPUT_JOYSTICK_RIGHT: 62 | btn_joy_right = value; 63 | if (value) btn_joy_right_green = true; 64 | break; 65 | case RP2040_INPUT_JOYSTICK_PRESS: 66 | btn_joy_press = value; 67 | if (value) btn_joy_press_green = true; 68 | break; 69 | case RP2040_INPUT_BUTTON_HOME: 70 | btn_home = value; 71 | if (value) btn_home_green = true; 72 | break; 73 | case RP2040_INPUT_BUTTON_MENU: 74 | btn_menu = value; 75 | if (value) btn_menu_green = true; 76 | break; 77 | case RP2040_INPUT_BUTTON_SELECT: 78 | btn_select = value; 79 | if (value) btn_select_green = true; 80 | break; 81 | case RP2040_INPUT_BUTTON_START: 82 | btn_start = value; 83 | if (value) btn_start_green = true; 84 | break; 85 | case RP2040_INPUT_BUTTON_ACCEPT: 86 | btn_accept = value; 87 | if (value) btn_accept_green = true; 88 | break; 89 | case RP2040_INPUT_BUTTON_BACK: 90 | btn_back = value; 91 | if (value) btn_back_green = true; 92 | default: 93 | break; 94 | } 95 | } 96 | 97 | if (render) { 98 | pax_noclip(pax_buffer); 99 | pax_background(pax_buffer, 0x325aa8); 100 | pax_draw_text(pax_buffer, 0xFFFFFFFF, font, 18, 0, 20 * 0, "Press HOME + START to exit"); 101 | char buffer[64]; 102 | snprintf(buffer, sizeof(buffer), "JOY DOWN %s", btn_joy_down ? "PRESSED" : "released"); 103 | pax_draw_text(pax_buffer, btn_joy_down_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 1, buffer); 104 | snprintf(buffer, sizeof(buffer), "JOY UP %s", btn_joy_up ? "PRESSED" : "released"); 105 | pax_draw_text(pax_buffer, btn_joy_up_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 2, buffer); 106 | snprintf(buffer, sizeof(buffer), "JOY LEFT %s", btn_joy_left ? "PRESSED" : "released"); 107 | pax_draw_text(pax_buffer, btn_joy_left_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 3, buffer); 108 | snprintf(buffer, sizeof(buffer), "JOY RIGHT %s", btn_joy_right ? "PRESSED" : "released"); 109 | pax_draw_text(pax_buffer, btn_joy_right_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 4, buffer); 110 | snprintf(buffer, sizeof(buffer), "JOY PRESS %s", btn_joy_press ? "PRESSED" : "released"); 111 | pax_draw_text(pax_buffer, btn_joy_press_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 5, buffer); 112 | snprintf(buffer, sizeof(buffer), "BTN HOME %s", btn_home ? "PRESSED" : "released"); 113 | pax_draw_text(pax_buffer, btn_home_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 6, buffer); 114 | snprintf(buffer, sizeof(buffer), "BTN MENU %s", btn_menu ? "PRESSED" : "released"); 115 | pax_draw_text(pax_buffer, btn_menu_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 7, buffer); 116 | snprintf(buffer, sizeof(buffer), "BTN SELECT %s", btn_select ? "PRESSED" : "released"); 117 | pax_draw_text(pax_buffer, btn_select_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 8, buffer); 118 | snprintf(buffer, sizeof(buffer), "BTN START %s", btn_start ? "PRESSED" : "released"); 119 | pax_draw_text(pax_buffer, btn_start_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 9, buffer); 120 | snprintf(buffer, sizeof(buffer), "BTN A %s", btn_accept ? "PRESSED" : "released"); 121 | pax_draw_text(pax_buffer, btn_accept_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 10, buffer); 122 | snprintf(buffer, sizeof(buffer), "BTN B %s", btn_back ? "PRESSED" : "released"); 123 | pax_draw_text(pax_buffer, btn_back_green ? 0xFF00FF00 : 0xFFFFFFFF, font, 18, 0, 20 * 11, buffer); 124 | display_flush(); 125 | render = false; 126 | } 127 | 128 | if (btn_home && btn_start) { 129 | quit = true; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /main/factory_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "audio.h" 10 | #include "fpga_test.h" 11 | #include "hardware.h" 12 | #include "ice40.h" 13 | #include "pax_gfx.h" 14 | #include "rp2040.h" 15 | #include "settings.h" 16 | #include "test_common.h" 17 | #include "wifi_defaults.h" 18 | #include "ws2812.h" 19 | 20 | static const char* TAG = "factory"; 21 | 22 | /* Test routines */ 23 | 24 | bool test_rp2040_init(uint32_t* rc) { 25 | esp_err_t res = bsp_rp2040_init(); 26 | *rc = (uint32_t) res; 27 | return (res == ESP_OK); 28 | } 29 | 30 | bool test_ice40_init(uint32_t* rc) { 31 | esp_err_t res = bsp_ice40_init(); 32 | *rc = (uint32_t) res; 33 | return (res == ESP_OK); 34 | } 35 | 36 | bool test_bno055_init(uint32_t* rc) { 37 | esp_err_t res = bsp_bno055_init(); 38 | *rc = (uint32_t) res; 39 | return (res == ESP_OK); 40 | } 41 | 42 | bool test_bme680_init(uint32_t* rc) { 43 | esp_err_t res = bsp_bme680_init(); 44 | *rc = (uint32_t) res; 45 | return (res == ESP_OK); 46 | } 47 | 48 | bool test_stuck_buttons(uint32_t* rc) { 49 | RP2040* rp2040 = get_rp2040(); 50 | uint16_t state; 51 | esp_err_t res = rp2040_read_buttons(rp2040, &state); 52 | if (res != ESP_OK) { 53 | *rc = 0xFFFFFFFF; 54 | return false; 55 | } 56 | 57 | state &= ~(1 << RP2040_INPUT_FPGA_CDONE); // Ignore FPGA CDONE 58 | 59 | *rc = state; 60 | 61 | return (state == 0x0000); 62 | } 63 | 64 | bool test_adc_vbat(uint32_t* rc) { 65 | float value = 0; 66 | esp_err_t res = rp2040_read_vbat(get_rp2040(), &value); 67 | *rc = value * 100; 68 | return ((res == ESP_OK) && (value < 4.3) && (value > 3.9)); 69 | } 70 | 71 | bool test_adc_vusb(uint32_t* rc) { 72 | float value = 0; 73 | esp_err_t res = rp2040_read_vusb(get_rp2040(), &value); 74 | *rc = value * 100; 75 | return ((res == ESP_OK) && (value > 4.5)); 76 | } 77 | 78 | bool test_sd_power(uint32_t* rc) { 79 | *rc = 0x00000000; 80 | // Init all GPIO pins for SD card and LED 81 | if (gpio_reset_pin(GPIO_SD_PWR) != ESP_OK) return false; 82 | if (gpio_set_direction(GPIO_SD_PWR, GPIO_MODE_INPUT) != ESP_OK) return false; 83 | if (gpio_reset_pin(GPIO_SD_CMD) != ESP_OK) return false; 84 | if (gpio_set_direction(GPIO_SD_CMD, GPIO_MODE_INPUT) != ESP_OK) return false; 85 | if (gpio_reset_pin(GPIO_SD_CLK) != ESP_OK) return false; 86 | if (gpio_set_direction(GPIO_SD_CLK, GPIO_MODE_INPUT) != ESP_OK) return false; 87 | if (gpio_reset_pin(GPIO_SD_D0) != ESP_OK) return false; 88 | if (gpio_set_direction(GPIO_SD_D0, GPIO_MODE_INPUT) != ESP_OK) return false; 89 | if (gpio_reset_pin(GPIO_LED_DATA) != ESP_OK) return false; 90 | if (gpio_set_direction(GPIO_LED_DATA, GPIO_MODE_INPUT) != ESP_OK) return false; 91 | 92 | if (gpio_get_level(GPIO_SD_PWR)) { 93 | *rc = 0x01; 94 | return false; 95 | } // Check that power enable is pulled low 96 | 97 | if (gpio_set_direction(GPIO_SD_PWR, GPIO_MODE_OUTPUT) != ESP_OK) return false; 98 | if (gpio_set_level(GPIO_SD_PWR, 1) != ESP_OK) return false; 99 | 100 | vTaskDelay(10 / portTICK_PERIOD_MS); 101 | 102 | // SD pins should be pulled high 103 | if (!gpio_get_level(GPIO_SD_CMD)) { 104 | *rc = 0x02; 105 | return false; 106 | } 107 | if (!gpio_get_level(GPIO_SD_CLK)) { 108 | *rc = 0x04; 109 | return false; 110 | } 111 | if (!gpio_get_level(GPIO_SD_D0)) { 112 | *rc = 0x08; 113 | return false; 114 | } 115 | 116 | return true; 117 | } 118 | 119 | bool run_basic_tests() { 120 | pax_buf_t* pax_buffer = get_pax_buffer(); 121 | const pax_font_t* font; 122 | int line = 0; 123 | bool ok = true; 124 | 125 | /* Screen init */ 126 | font = pax_font_sky_mono; 127 | 128 | pax_noclip(pax_buffer); 129 | pax_background(pax_buffer, 0x8060f0); 130 | display_flush(); 131 | 132 | /* Run mandatory tests */ 133 | RUN_TEST_MANDATORY("RP2040", test_rp2040_init); 134 | RUN_TEST_MANDATORY("ICE40", test_ice40_init); 135 | RUN_TEST_MANDATORY("BNO055", test_bno055_init); 136 | RUN_TEST_MANDATORY("BME680", test_bme680_init); 137 | 138 | /* Run tests */ 139 | RUN_TEST("STUCK BUTTONS", test_stuck_buttons); 140 | RUN_TEST("SD/LED POWER", test_sd_power); 141 | RUN_TEST("Battery voltage", test_adc_vbat); 142 | RUN_TEST("USB voltage", test_adc_vusb); 143 | 144 | error: 145 | /* Fail result on screen */ 146 | if (!ok) pax_draw_text(pax_buffer, 0xffff0000, font, 36, 0, 20 * line, "FAIL"); 147 | display_flush(); 148 | return ok; 149 | } 150 | 151 | const uint8_t led_green[15] = {50, 0, 0, 50, 0, 0, 50, 0, 0, 50, 0, 0, 50, 0, 0}; 152 | const uint8_t led_red[15] = {0, 50, 0, 0, 50, 0, 0, 50, 0, 0, 50, 0, 0, 50, 0}; 153 | const uint8_t led_blue[15] = {0, 0, 50, 0, 0, 50, 0, 0, 50, 0, 0, 50, 0, 0, 50}; 154 | 155 | void factory_test() { 156 | pax_buf_t* pax_buffer = get_pax_buffer(); 157 | uint8_t factory_test_done = nvs_get_u8_default("system", "factory_test", 0); 158 | if (!factory_test_done) { 159 | bool result; 160 | 161 | ESP_LOGI(TAG, "Factory test start"); 162 | 163 | result = run_basic_tests(); 164 | 165 | gpio_set_direction(GPIO_SD_PWR, GPIO_MODE_OUTPUT); 166 | gpio_set_level(GPIO_SD_PWR, 1); 167 | if (result) { 168 | ws2812_send_data(led_blue, sizeof(led_blue)); 169 | } else { 170 | ws2812_send_data(led_red, sizeof(led_red)); 171 | } 172 | 173 | if (!result) goto test_end; 174 | 175 | RP2040* rp2040 = get_rp2040(); 176 | 177 | result = run_fpga_tests(rp2040->queue); 178 | if (!result) { 179 | ws2812_send_data(led_red, sizeof(led_red)); 180 | goto test_end; 181 | } 182 | 183 | // Wait for the operator to unplug the badge 184 | test_end: 185 | 186 | if (result) { 187 | esp_err_t res = nvs_set_u8_fixed("system", "factory_test", 0x01); 188 | if (res != ESP_OK) { 189 | ESP_LOGE(TAG, "Failed to store test result %d\n", res); 190 | result = false; 191 | ws2812_send_data(led_red, sizeof(led_red)); 192 | pax_noclip(pax_buffer); 193 | pax_background(pax_buffer, 0xa85a32); 194 | display_flush(); 195 | } 196 | nvs_set_u8_fixed("system", "force_sponsors", 0x01); // Force showing sponsors on first boot 197 | wifi_set_defaults(); 198 | pax_noclip(pax_buffer); 199 | pax_background(pax_buffer, 0x00FF00); 200 | display_flush(); 201 | ws2812_send_data(led_green, sizeof(led_green)); 202 | } 203 | 204 | while (true) { 205 | if (result) play_bootsound(); 206 | vTaskDelay(3000 / portTICK_PERIOD_MS); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /main/filesystems.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "esp_vfs.h" 13 | #include "esp_vfs_fat.h" 14 | #include "hardware.h" 15 | #include "sdcard.h" 16 | 17 | static const char* TAG = "fs"; 18 | 19 | static bool locfd_mounted = false; 20 | static bool sdcard_mounted = false; 21 | static wl_handle_t s_wl_handle = WL_INVALID_HANDLE; 22 | 23 | esp_err_t mount_internal_filesystem() { 24 | const esp_partition_t* fs_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "locfd"); 25 | if (fs_partition == NULL) { 26 | ESP_LOGE(TAG, "failed to mount locfd: partition not found"); 27 | return ESP_FAIL; 28 | } 29 | 30 | const esp_vfs_fat_mount_config_t mount_config = { 31 | .format_if_mount_failed = true, 32 | .max_files = 5, 33 | .allocation_unit_size = CONFIG_WL_SECTOR_SIZE, 34 | }; 35 | 36 | esp_err_t res = esp_vfs_fat_spiflash_mount("/internal", "locfd", &mount_config, &s_wl_handle); 37 | if (res != ESP_OK) { 38 | ESP_LOGE(TAG, "failed to mount locfd (%d)", res); 39 | return res; 40 | } 41 | 42 | locfd_mounted = true; 43 | 44 | return ESP_OK; 45 | } 46 | 47 | esp_err_t unmount_internal_filesystem() { 48 | esp_err_t res = esp_vfs_fat_spiflash_unmount("/internal", s_wl_handle); 49 | if (res != ESP_OK) { 50 | ESP_LOGE(TAG, "Failed to unmount locfd (%d)", res); 51 | } else { 52 | locfd_mounted = false; 53 | } 54 | return res; 55 | } 56 | 57 | bool get_internal_mounted() { return locfd_mounted; } 58 | 59 | esp_err_t format_internal_filesystem() { 60 | esp_err_t res = unmount_internal_filesystem(); 61 | const esp_partition_t* fs_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "locfd"); 62 | if (fs_partition == NULL) { 63 | ESP_LOGE(TAG, "failed to mount locfd: partition not found"); 64 | return ESP_FAIL; 65 | } 66 | uint32_t first_sector = fs_partition->address / SPI_FLASH_SEC_SIZE; 67 | uint32_t amount_of_sectors = fs_partition->size / SPI_FLASH_SEC_SIZE; 68 | for (uint32_t i = 0; i < amount_of_sectors; i++) { 69 | ESP_LOGI(TAG, "Erasing FAT filesystem sector %u of %u...", i, amount_of_sectors); 70 | res = spi_flash_erase_sector(first_sector + i); 71 | if (res != ESP_OK) { 72 | ESP_LOGE(TAG, "Failed to erase sector %u of the locfd partition (%d)\n", i, res); 73 | } 74 | taskYIELD(); 75 | } 76 | return mount_internal_filesystem(); 77 | } 78 | 79 | esp_err_t mount_sdcard_filesystem() { 80 | esp_err_t res = mount_sd(GPIO_SD_CMD, GPIO_SD_CLK, GPIO_SD_D0, GPIO_SD_PWR, "/sd", false, 5); 81 | sdcard_mounted = (res == ESP_OK); 82 | return res; 83 | } 84 | 85 | esp_err_t unmount_sdcard_filesystem() { 86 | esp_err_t res = esp_vfs_fat_sdmmc_unmount(); 87 | if (res != ESP_OK) { 88 | ESP_LOGE(TAG, "Failed to unmount sdcard (%d)", res); 89 | } else { 90 | sdcard_mounted = false; 91 | } 92 | return res; 93 | } 94 | 95 | bool get_sdcard_mounted() { return sdcard_mounted; } 96 | 97 | void get_internal_filesystem_size_and_available(uint64_t* fs_size, uint64_t* fs_free) { 98 | FATFS* fs; 99 | DWORD fre_clust, fre_sect, tot_sect; 100 | 101 | /* Get volume information and free clusters of drive 0 */ 102 | FRESULT res = f_getfree("0:", &fre_clust, &fs); 103 | /* Get total sectors and free sectors */ 104 | if (res == FR_OK) { 105 | tot_sect = (fs->n_fatent - 2) * fs->csize; 106 | fre_sect = fre_clust * fs->csize; 107 | } else { 108 | tot_sect = 0; 109 | fre_sect = 0; 110 | } 111 | 112 | if (fs_size != NULL) *fs_size = tot_sect * CONFIG_WL_SECTOR_SIZE; 113 | if (fs_free != NULL) *fs_free = fre_sect * CONFIG_WL_SECTOR_SIZE; 114 | } 115 | 116 | void get_sdcard_filesystem_size_and_available(uint64_t* fs_size, uint64_t* fs_free) { 117 | if (!sdcard_mounted) { 118 | if (fs_size != NULL) *fs_size = 0; 119 | if (fs_free != NULL) *fs_free = 0; 120 | return; 121 | } 122 | FATFS* fs; 123 | DWORD fre_clust, fre_sect, tot_sect; 124 | 125 | /* Get volume information and free clusters of drive 1 */ 126 | FRESULT res = f_getfree("1:", &fre_clust, &fs); 127 | /* Get total sectors and free sectors */ 128 | if (res == FR_OK) { 129 | tot_sect = (fs->n_fatent - 2) * fs->csize; 130 | fre_sect = fre_clust * fs->csize; 131 | } else { 132 | tot_sect = 0; 133 | fre_sect = 0; 134 | } 135 | 136 | if (fs_size != NULL) *fs_size = tot_sect * 512; 137 | if (fs_free != NULL) *fs_free = fre_sect * 512; 138 | } 139 | -------------------------------------------------------------------------------- /main/http_download.c: -------------------------------------------------------------------------------- 1 | #include "esp_event.h" 2 | #include "esp_http_client.h" 3 | #include "esp_system.h" 4 | #include "esp_vfs.h" 5 | #include "esp_vfs_fat.h" 6 | #include "freertos/FreeRTOS.h" 7 | #include "freertos/queue.h" 8 | #include "freertos/task.h" 9 | #include "hardware.h" 10 | #include "nvs.h" 11 | #include "nvs_flash.h" 12 | #include "pax_codecs.h" 13 | #include "pax_gfx.h" 14 | #include "soc/rtc.h" 15 | #include "soc/rtc_cntl_reg.h" 16 | #include "wifi_connect.h" 17 | #include "wifi_connection.h" 18 | 19 | static const char* TAG = "HTTP download"; 20 | 21 | typedef struct { 22 | FILE* fd; // For downloading directly to file on filesystem 23 | uint8_t** buffer; // Dynamically allocated buffer for downloading to RAM (malloced in event handler, used if fd is not set) 24 | size_t size; // File size as indicated by content-length header (set in event handler) 25 | size_t received; // Amount of data received (set in event handler) 26 | bool error; // Indication that an error event happened (set in event handler) 27 | bool connected; // Indication that the HTTP client has connected to the server (set in event handler) 28 | bool finished; // Indication that the operation has completed (set in event handler) 29 | bool disconnected; // Indication that the HTTP client has disconnected from the server (set in event handler) 30 | bool out_of_memory; // Indication that malloc failed 31 | bool out_of_allocated; // Indication that the server sent more data than indicated with the content-length header 32 | } http_download_info_t; 33 | 34 | static esp_err_t _event_handler(esp_http_client_event_t* evt) { 35 | http_download_info_t* info = (http_download_info_t*) evt->user_data; 36 | switch (evt->event_id) { 37 | case HTTP_EVENT_ERROR: 38 | info->error = true; 39 | break; 40 | case HTTP_EVENT_ON_CONNECTED: 41 | info->connected = true; 42 | break; 43 | case HTTP_EVENT_HEADERS_SENT: 44 | break; 45 | case HTTP_EVENT_ON_HEADER: 46 | { 47 | const char content_length_key[] = "Content-Length"; 48 | if ((strlen(evt->header_key) == strlen(content_length_key)) && 49 | (strncasecmp(content_length_key, evt->header_key, strlen(content_length_key)) == 0)) { 50 | // Header value is content length 51 | info->size = atoi(evt->header_value); 52 | printf("SIZE KNOWN: %u bytes\r\n", info->size); 53 | if ((info->size > 0) && (info->buffer != NULL)) { // Buffer poiner is set, buffer pointer points to NULL 54 | *info->buffer = malloc(info->size); 55 | printf("BUFFER MALLOC'ED (%u bytes)\r\n", info->size); 56 | if (*info->buffer == NULL) { 57 | info->out_of_memory = true; 58 | return ESP_ERR_NO_MEM; 59 | } 60 | } 61 | } else { 62 | // printf("HTTP_EVENT_ON_HEADER, key=%s, value=%s\r\n", evt->header_key, evt->header_value); 63 | } 64 | break; 65 | } 66 | case HTTP_EVENT_ON_DATA: 67 | if (info->fd != NULL) { // Write directly to file on filesystem 68 | printf("Writing to FILE @ %p (%u bytes): %u of %u bytes.\r\n", info->fd, evt->data_len, info->received + evt->data_len, info->size); 69 | fwrite(evt->data, 1, evt->data_len, info->fd); 70 | } else if (info->buffer != NULL && *info->buffer != NULL) { 71 | if (info->received + evt->data_len <= info->size) { 72 | uint8_t* dest = &((*info->buffer)[info->received]); 73 | printf("Writing to RAM @ %p (%u bytes): %u of %u bytes.\r\n", dest, evt->data_len, info->received + evt->data_len, info->size); 74 | memcpy(dest, evt->data, evt->data_len); 75 | } else { 76 | printf("Downloaded too much? %u with %u in content-length header\r\n", info->received + evt->data_len, info->size); 77 | info->out_of_allocated = true; 78 | return ESP_ERR_NO_MEM; 79 | } 80 | } else { 81 | return ESP_FAIL; 82 | } 83 | info->received += evt->data_len; 84 | break; 85 | case HTTP_EVENT_ON_FINISH: 86 | info->finished = true; 87 | break; 88 | case HTTP_EVENT_DISCONNECTED: 89 | info->disconnected = true; 90 | break; 91 | } 92 | return ESP_OK; 93 | } 94 | 95 | static bool download_success(esp_err_t err, http_download_info_t* info) { 96 | return (err == ESP_OK) && (!(info->error || info->out_of_allocated || info->out_of_memory)) && info->finished && (info->received == info->size); 97 | } 98 | 99 | static bool _download_file(const char* url, const char* path) { 100 | FILE* fd = fopen(path, "w"); 101 | if (fd == NULL) { 102 | ESP_LOGE(TAG, "Failed to open file"); 103 | return false; 104 | } 105 | 106 | http_download_info_t info = {0}; 107 | info.fd = fd; 108 | 109 | esp_http_client_config_t config = { 110 | .url = url, .use_global_ca_store = true, .keep_alive_enable = true, .timeout_ms = 10000, .user_data = (void*) &info, .event_handler = _event_handler}; 111 | esp_http_client_handle_t client = esp_http_client_init(&config); 112 | esp_err_t err = esp_http_client_perform(client); 113 | fclose(fd); 114 | esp_http_client_cleanup(client); 115 | return download_success(err, &info); 116 | } 117 | 118 | bool download_file(const char* url, const char* path) { 119 | int retry = 3; 120 | while (retry--) { 121 | if (_download_file(url, path)) return true; 122 | printf("DL waiting to retry ..."); 123 | vTaskDelay(pdMS_TO_TICKS(5000)); 124 | } 125 | return false; 126 | } 127 | 128 | static bool _download_ram(const char* url, uint8_t** ptr, size_t* size) { 129 | http_download_info_t info = {0}; 130 | info.buffer = ptr; 131 | esp_http_client_config_t config = { 132 | .url = url, .use_global_ca_store = true, .keep_alive_enable = true, .timeout_ms = 10000, .user_data = (void*) &info, .event_handler = _event_handler}; 133 | esp_http_client_handle_t client = esp_http_client_init(&config); 134 | esp_err_t err = esp_http_client_perform(client); 135 | bool success = download_success(err, &info); 136 | if (success && (size != NULL)) *size = info.size; 137 | printf("Buffer: %p -> %p\r\n", ptr, *ptr); 138 | esp_http_client_cleanup(client); 139 | return success; 140 | } 141 | 142 | bool download_ram(const char* url, uint8_t** ptr, size_t* size) { 143 | int retry = 3; 144 | while (retry--) { 145 | if (_download_ram(url, ptr, size)) return true; 146 | printf("DL waiting to retry ..."); 147 | vTaskDelay(pdMS_TO_TICKS(5000)); 148 | } 149 | return false; 150 | } 151 | -------------------------------------------------------------------------------- /main/include/adc_test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void test_adc(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/include/app_management.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | bool create_dir(const char* path); 9 | bool install_app(xQueueHandle button_queue, const char* type_slug, bool to_sd_card, char* data_app_info, size_t size_app_info, cJSON* json_app_info); 10 | -------------------------------------------------------------------------------- /main/include/app_update.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void update_apps(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/include/appfs_wrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "appfs.h" 8 | 9 | esp_err_t appfs_init(void); 10 | appfs_handle_t appfs_detect_crash(); 11 | void appfs_boot_app(int fd); 12 | void appfs_store_app(xQueueHandle button_queue, const char* path, const char* name, const char* title, uint16_t version); 13 | esp_err_t appfs_store_in_memory_app(xQueueHandle button_queue, const char* name, const char* title, uint16_t version, size_t app_size, uint8_t* app); 14 | -------------------------------------------------------------------------------- /main/include/audio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void audio_init(); 4 | void play_bootsound(); 5 | -------------------------------------------------------------------------------- /main/include/bootscreen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void display_boot_screen(); 4 | void display_busy(); 5 | -------------------------------------------------------------------------------- /main/include/button_test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void test_buttons(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/include/factory_test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void factory_test(); 4 | -------------------------------------------------------------------------------- /main/include/file_browser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void list_files_in_folder(const char* path); 7 | void file_browser(xQueueHandle button_queue, const char* initial_path); 8 | -------------------------------------------------------------------------------- /main/include/filesystems.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "esp_err.h" 6 | 7 | esp_err_t mount_internal_filesystem(); 8 | esp_err_t unmount_internal_filesystem(); 9 | bool get_internal_mounted(); 10 | esp_err_t format_internal_filesystem(); 11 | esp_err_t mount_sdcard_filesystem(); 12 | esp_err_t unmount_sdcard_filesystem(); 13 | bool get_sdcard_mounted(); 14 | void get_internal_filesystem_size_and_available(uint64_t* fs_size, uint64_t* fs_free); 15 | void get_sdcard_filesystem_size_and_available(uint64_t* fs_size, uint64_t* fs_free); 16 | -------------------------------------------------------------------------------- /main/include/fpga_download.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "ice40.h" 6 | 7 | void fpga_download(xQueueHandle button_queue, ICE40* ice40); 8 | bool fpga_host(xQueueHandle button_queue, ICE40* ice40, bool enable_uart, const char* prefix); 9 | -------------------------------------------------------------------------------- /main/include/fpga_test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void fpga_test(xQueueHandle button_queue); 7 | bool run_fpga_tests(xQueueHandle button_queue); 8 | -------------------------------------------------------------------------------- /main/include/fpga_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * fpga_util.h 3 | * 4 | * Copyright (C) 2022 Sylvain Munaut 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "ice40.h" 16 | 17 | /* SPI protocol ---------------------------------------------------------- */ 18 | 19 | /* Commands */ 20 | #define SPI_CMD_NOP1 0x00 21 | #define SPI_CMD_WISHBONE 0xf0 22 | #define SPI_CMD_LOOPBACK 0xf1 23 | #define SPI_CMD_LCD_PASSTHROUGH 0xf2 24 | #define SPI_CMD_BUTTON_REPORT 0xf4 25 | #define SPI_CMD_FREAD_GET 0xf8 26 | #define SPI_CMD_FREAD_PUT 0xf9 27 | #define SPI_CMD_IRQ_ACK 0xfd 28 | #define SPI_CMD_RESP_ACK 0xfe 29 | #define SPI_CMD_NOP2 0xff 30 | 31 | /* Request bits */ 32 | #define SPI_REQ_FREAD (1 << 0) 33 | 34 | /* FPGA IRQ --------------------------------------------------------------- */ 35 | 36 | esp_err_t fpga_irq_setup(ICE40 *ice40); 37 | void fpga_irq_cleanup(ICE40 *ice40); 38 | bool fpga_irq_wait(TickType_t wait); 39 | 40 | /* Wishbone bridge -------------------------------------------------------- */ 41 | 42 | struct fpga_wb_cmdbuf; 43 | 44 | struct fpga_wb_cmdbuf *fpga_wb_alloc(int n); 45 | void fpga_wb_free(struct fpga_wb_cmdbuf *cb); 46 | 47 | bool fpga_wb_queue_write(struct fpga_wb_cmdbuf *cb, int dev, uint32_t addr, uint32_t val); 48 | bool fpga_wb_queue_read(struct fpga_wb_cmdbuf *cb, int dev, uint32_t addr, uint32_t *val); 49 | bool fpga_wb_queue_write_burst(struct fpga_wb_cmdbuf *cb, int dev, uint32_t addr, const uint32_t *val, int n, bool inc); 50 | bool fpga_wb_queue_read_burst(struct fpga_wb_cmdbuf *cb, int dev, uint32_t addr, uint32_t *val, int n, bool inc); 51 | 52 | bool fpga_wb_exec(struct fpga_wb_cmdbuf *cb, ICE40 *ice40); 53 | 54 | /* Button reports --------------------------------------------------------- */ 55 | 56 | void fpga_btn_reset(void); 57 | bool fpga_btn_forward_events(ICE40 *ice40, xQueueHandle buttonQueue, esp_err_t *err); 58 | 59 | /* Request processing ----------------------------------------------------- */ 60 | 61 | void fpga_req_setup(void); 62 | void fpga_req_cleanup(void); 63 | int fpga_req_add_file_alias(uint32_t fid, const char *path); 64 | int fpga_req_add_file_data(uint32_t fid, void *data, size_t len); 65 | void fpga_req_del_file(uint32_t fid); 66 | 67 | bool fpga_req_process(const char *prefix, ICE40 *ice40, TickType_t wait, esp_err_t *err); 68 | -------------------------------------------------------------------------------- /main/include/http_download.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | bool download_file(const char* url, const char* path); 7 | bool download_ram(const char* url, uint8_t** ptr, size_t* size); 8 | -------------------------------------------------------------------------------- /main/include/metadata.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "appfs.h" 9 | #include "menu.h" 10 | 11 | typedef struct { 12 | char* path; 13 | char* type; 14 | char* slug; 15 | char* title; 16 | char* description; 17 | char* category; 18 | char* author; 19 | char* license; 20 | int version; 21 | pax_buf_t* icon; 22 | appfs_handle_t appfs_fd; 23 | } launcher_app_t; 24 | 25 | typedef void (*path_callback_t)(const char*, const char*, void*); 26 | 27 | void free_launcher_app(launcher_app_t* app); 28 | 29 | void parse_metadata(const char* path, char** device, char** type, char** category, char** slug, char** name, char** description, char** author, int* version, 30 | char** license); 31 | 32 | void populate_menu_entry_from_path(menu_t* menu, const char* path, const char* arg_type, const char* arg_name, void* default_icon_data, 33 | size_t default_icon_size); 34 | 35 | bool populate_menu_from_path(menu_t* menu, const char* path, const char* arg_type, void* default_icon_data, size_t default_icon_size); 36 | 37 | bool for_entity_in_path(const char* path, bool directories, path_callback_t callback, void* user); 38 | -------------------------------------------------------------------------------- /main/include/msc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void msc_main(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/include/nametag.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void edit_nickname(xQueueHandle button_queue); 7 | void show_nametag(xQueueHandle button_queue); 8 | -------------------------------------------------------------------------------- /main/include/rp2040_updater.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "rp2040.h" 7 | 8 | void rp2040_update_start(RP2040* rp2040); 9 | void rp2040_updater(RP2040* rp2040); 10 | -------------------------------------------------------------------------------- /main/include/rtc_memory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | esp_err_t rtc_memory_int_write(int pos, int val); 6 | esp_err_t rtc_memory_int_read(int pos, int* val); 7 | esp_err_t rtc_memory_string_write(const char* str); 8 | esp_err_t rtc_memory_string_read(const char** str); 9 | esp_err_t rtc_memory_clear(); 10 | -------------------------------------------------------------------------------- /main/include/sao_eeprom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef enum _sao_type { SAO_NONE, SAO_UNFORMATTED, SAO_BINARY, SAO_JSON } sao_type_t; 7 | 8 | #define SAO_MAX_FIELD_LENGTH 256 9 | #define SAO_MAX_NUM_DRIVERS 8 10 | 11 | typedef struct _SAO_DRIVER { 12 | char name[SAO_MAX_FIELD_LENGTH + 1]; 13 | uint8_t data[SAO_MAX_FIELD_LENGTH]; 14 | uint8_t data_length; 15 | } sao_driver_t; 16 | 17 | typedef struct _SAO { 18 | uint8_t type; // sao_type_t; 19 | char name[SAO_MAX_FIELD_LENGTH + 1]; 20 | uint8_t amount_of_drivers; 21 | sao_driver_t drivers[SAO_MAX_NUM_DRIVERS]; 22 | } SAO; 23 | 24 | typedef struct __attribute__((__packed__)) _sao_binary_header { 25 | uint8_t magic[4]; 26 | uint8_t name_length; 27 | uint8_t driver_name_length; 28 | uint8_t driver_data_length; 29 | uint8_t number_of_extra_drivers; 30 | } sao_binary_header_t; 31 | 32 | typedef struct __attribute__((__packed__)) _sao_binary_extra_driver { 33 | uint8_t driver_name_length; 34 | uint8_t driver_data_length; 35 | } sao_binary_extra_driver_t; 36 | 37 | // Storage driver 38 | // Note: this driver can also be used as driver for SAOs with basic LED and button IO 39 | 40 | #define SAO_DRIVER_STORAGE_NAME "storage" 41 | 42 | typedef struct __attribute__((__packed__)) _sao_driver_storage_data { 43 | uint8_t flags; // Reserved, set to 0 44 | uint8_t address; // I2C address of the data EEPROM (0x50 when using main EEPROM, usually 0x51 when using a separate data EEPROM) 45 | uint8_t size_exp; // For example 15 for 32 kbit 46 | uint8_t page_size_exp; // For example 6 for 64 bytes 47 | uint8_t data_offset; // In pages, needed to skip header 48 | uint8_t reserved; // Reserved, set to 0 49 | } sao_driver_storage_data_t; 50 | 51 | // Basic IO driver 52 | 53 | #define SAO_DRIVER_BASIC_IO_NAME "basic_io" 54 | 55 | typedef struct __attribute__((__packed__)) _sao_driver_basic_io_data { 56 | uint8_t io1_function; 57 | uint8_t io2_function; 58 | uint8_t reserved; // Reserved, set to 0 59 | } sao_driver_basic_io_data_t; 60 | 61 | #define SAO_DRIVER_BASIC_IO_FUNC_NONE 0 62 | #define SAO_DRIVER_BASIC_IO_FUNC_LED 1 63 | #define SAO_DRIVER_BASIC_IO_FUNC_BUTTON 2 64 | #define SAO_DRIVER_BASIC_IO_FUNC_LED_RED 3 65 | #define SAO_DRIVER_BASIC_IO_FUNC_LED_GREEN 4 66 | #define SAO_DRIVER_BASIC_IO_FUNC_LED_BLUE 5 67 | #define SAO_DRIVER_BASIC_IO_FUNC_LED_YELLOW 6 68 | #define SAO_DRIVER_BASIC_IO_FUNC_LED_AMBER 7 69 | #define SAO_DRIVER_BASIC_IO_FUNC_LED_WHITE 8 70 | 71 | // Neopixel driver 72 | 73 | #define SAO_DRIVER_NEOPIXEL_NAME "neopixel" 74 | 75 | enum SAO_DRIVER_NEOPIXEL_COLOR_ORDER { 76 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_RGB = 0, 77 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_RBG = 1, 78 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_GRB = 2, 79 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_GBR = 3, 80 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_BRG = 4, 81 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_BGR = 5, 82 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_WRGB = 6, 83 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_WRBG = 7, 84 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_WGRB = 8, 85 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_WGBR = 9, 86 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_WBRG = 10, 87 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_WBGR = 11, 88 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_RWGB = 12, 89 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_RWBG = 13, 90 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_RGWB = 14, 91 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_RGBW = 15, 92 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_RBWG = 16, 93 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_RBGW = 17, 94 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_GWRB = 18, 95 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_GWBR = 19, 96 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_GRWB = 20, 97 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_GRBW = 21, 98 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_GBWR = 22, 99 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_GBRW = 23, 100 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_BWRG = 24, 101 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_BWGR = 25, 102 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_BRWG = 26, 103 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_BRGW = 27, 104 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_BGWR = 28, 105 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_BGRW = 29, 106 | SAO_DRIVER_NEOPIXEL_COLOR_ORDER_MAX 107 | }; 108 | 109 | typedef struct __attribute__((__packed__)) _sao_driver_neopixel_data { 110 | uint16_t length; // Length in LEDs 111 | uint8_t color_order; // One of the values defined in the color order enum 112 | uint8_t reserved; // Reserved, set to 0 113 | } sao_driver_neopixel_data_t; 114 | 115 | // SSD1306 driver 116 | 117 | #define SAO_DRIVER_SSD1306_NAME "ssd1306" 118 | 119 | typedef struct __attribute__((__packed__)) _sao_driver_ssd1306_data { 120 | uint8_t address; // I2C address of the SSD1306 OLED (usually 0x3C) 121 | uint8_t height; // 32 or 64, in pixels 122 | uint8_t reserved; // Reserved, set to 0 123 | } sao_driver_ssd1306_data_t; 124 | 125 | // NTAG NFC driver 126 | 127 | #define SAO_DRIVER_NTAG_NAME "ntag" 128 | 129 | typedef struct __attribute__((__packed__)) _sao_driver_ntag_data { 130 | uint8_t address; // I2C address of the NTAG IC (usually 0x55) 131 | uint8_t size_exp; // 10 (1k) for NT3H2111 or 11 (2k) for NT3H2211 132 | uint8_t interrupt_pin; // 0 for not connected, 1 for IO1 and 2 for IO2 133 | uint8_t reserved; // Reserved, set to 0 134 | } sao_driver_ntag_data_t; 135 | 136 | // App link driver 137 | 138 | #define SAO_DRIVER_APP_NAME "app" 139 | // data is a string containing the slug name of the app, null terminated 140 | 141 | void dump_eeprom_contents(); 142 | 143 | esp_err_t sao_identify(SAO* sao); 144 | esp_err_t sao_write_raw(size_t offset, uint8_t* buffer, size_t buffer_length); 145 | esp_err_t sao_format(const char* name, const char* driver, const uint8_t* driver_data, uint8_t driver_data_length, const char* driver2, 146 | const uint8_t* driver2_data, uint8_t driver2_data_length, const char* driver3, const uint8_t* driver3_data, uint8_t driver3_data_length, 147 | bool small); 148 | -------------------------------------------------------------------------------- /main/include/settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | esp_err_t nvs_init(); 7 | esp_err_t nvs_get_str_fixed(const char* nvs_namespace, const char* key, char* target, size_t target_size, size_t* size); 8 | uint8_t nvs_get_u8_default(const char* nvs_namespace, const char* key, uint8_t default_value); 9 | esp_err_t nvs_set_u8_fixed(const char* nvs_namespace, const char* key, uint8_t value); 10 | -------------------------------------------------------------------------------- /main/include/system_wrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esp_vfs.h" 4 | 5 | void restart(); 6 | bool wait_for_button(); 7 | uint8_t* load_file_to_ram(FILE* fd); 8 | size_t get_file_size(FILE* fd); 9 | bool remove_recursive(const char* path); 10 | -------------------------------------------------------------------------------- /main/include/terminal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void terminal_printf(char* fmt, ...); 7 | void terminal_log(char* buffer); 8 | void terminal_log_wrapped(char* buffer); 9 | void terminal_start(); 10 | -------------------------------------------------------------------------------- /main/include/test_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef bool (*test_fn)(uint32_t *rc); 7 | 8 | bool test_wait_for_response(uint32_t *rc); 9 | bool run_test(const pax_font_t *font, int line, const char *test_name, test_fn fn); 10 | 11 | #define RUN_TEST(name, fn) \ 12 | do { \ 13 | ok &= run_test(font, line++, name, fn); \ 14 | } while (0) 15 | 16 | #define RUN_TEST_MANDATORY(name, fn) \ 17 | do { \ 18 | if (!run_test(font, line++, name, fn)) { \ 19 | pax_draw_text(pax_buffer, 0xffff0000, font, 18, 0, 20 * line, "Aborted"); \ 20 | display_flush(); \ 21 | ok = false; \ 22 | goto error; \ 23 | } \ 24 | } while (0) 25 | 26 | #define RUN_TEST_BLIND(name, fn) \ 27 | do { \ 28 | ok &= run_test(font, line++, name, fn); \ 29 | } while (0) 30 | -------------------------------------------------------------------------------- /main/include/webusb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void webusb_main(xQueueHandle button_queue); 7 | void webusb_new_main(xQueueHandle button_queue); 8 | -------------------------------------------------------------------------------- /main/include/wifi_cert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esp_err.h" 4 | 5 | esp_err_t init_ca_store(); 6 | -------------------------------------------------------------------------------- /main/include/wifi_defaults.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "esp_wifi.h" 6 | #include "esp_wifi_types.h" 7 | #include "esp_wpa2.h" 8 | 9 | // Camp WiFi settings. 10 | #define WIFI_MCH2022_SSID "MCH2022" 11 | #define WIFI_MCH2022_USER "badge" 12 | #define WIFI_MCH2022_IDENT "badge" 13 | #define WIFI_MCH2022_PASSWORD "badge" 14 | #define WIFI_MCH2022_AUTH WIFI_AUTH_WPA2_ENTERPRISE 15 | #define WIFI_MCH2022_PHASE2 ESP_EAP_TTLS_PHASE2_PAP 16 | 17 | bool wifi_set_defaults(); 18 | bool wifi_check_configured(); 19 | -------------------------------------------------------------------------------- /main/include/wifi_ota.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void ota_update(bool nightly); 6 | -------------------------------------------------------------------------------- /main/include/wifi_test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "freertos/FreeRTOS.h" 4 | #include "freertos/queue.h" 5 | 6 | void wifi_connection_test(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/menus/dev.c: -------------------------------------------------------------------------------- 1 | #include "dev.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "adc_test.h" 14 | #include "appfs.h" 15 | #include "button_test.h" 16 | #include "file_browser.h" 17 | #include "fpga_download.h" 18 | #include "fpga_test.h" 19 | #include "hardware.h" 20 | #include "ir.h" 21 | #include "menu.h" 22 | #include "pax_codecs.h" 23 | #include "pax_gfx.h" 24 | #include "rp2040.h" 25 | #include "sao.h" 26 | #include "settings.h" 27 | 28 | extern const uint8_t dev_png_start[] asm("_binary_dev_png_start"); 29 | extern const uint8_t dev_png_end[] asm("_binary_dev_png_end"); 30 | 31 | typedef enum action { 32 | ACTION_NONE, 33 | ACTION_BACK, 34 | ACTION_FPGA_TEST, 35 | ACTION_FILE_BROWSER, 36 | ACTION_FILE_BROWSER_INT, 37 | ACTION_BUTTON_TEST, 38 | ACTION_ADC_TEST, 39 | ACTION_SAO, 40 | ACTION_IR, 41 | ACTION_IR_RENZE 42 | } menu_dev_action_t; 43 | 44 | static void render_help(pax_buf_t* pax_buffer) { 45 | const pax_font_t* font = pax_font_saira_regular; 46 | pax_background(pax_buffer, 0xFFFFFF); 47 | pax_noclip(pax_buffer); 48 | pax_draw_text(pax_buffer, 0xFF491d88, font, 18, 5, 240 - 18, "🅰 accept 🅱 back"); 49 | } 50 | 51 | void menu_dev(xQueueHandle button_queue) { 52 | pax_buf_t* pax_buffer = get_pax_buffer(); 53 | menu_t* menu = menu_alloc("Tools", 34, 18); 54 | 55 | menu->fgColor = 0xFF000000; 56 | menu->bgColor = 0xFFFFFFFF; 57 | menu->bgTextColor = 0xFF000000; 58 | menu->selectedItemColor = 0xFFfec859; 59 | menu->borderColor = 0xFFfa448c; 60 | menu->titleColor = 0xFFfec859; 61 | menu->titleBgColor = 0xFFfa448c; 62 | menu->scrollbarBgColor = 0xFFCCCCCC; 63 | menu->scrollbarFgColor = 0xFF555555; 64 | 65 | pax_buf_t icon_dev; 66 | pax_decode_png_buf(&icon_dev, (void*) dev_png_start, dev_png_end - dev_png_start, PAX_BUF_32_8888ARGB, 0); 67 | 68 | menu_set_icon(menu, &icon_dev); 69 | 70 | menu_insert_item(menu, "Infrared remote (deco lights)", NULL, (void*) ACTION_IR, -1); 71 | menu_insert_item(menu, "Infrared remote (badge tent)", NULL, (void*) ACTION_IR_RENZE, -1); 72 | menu_insert_item(menu, "File browser (SD card)", NULL, (void*) ACTION_FILE_BROWSER, -1); 73 | menu_insert_item(menu, "File browser (internal)", NULL, (void*) ACTION_FILE_BROWSER_INT, -1); 74 | menu_insert_item(menu, "Button test", NULL, (void*) ACTION_BUTTON_TEST, -1); 75 | menu_insert_item(menu, "Analog inputs", NULL, (void*) ACTION_ADC_TEST, -1); 76 | menu_insert_item(menu, "SAO EEPROM tool", NULL, (void*) ACTION_SAO, -1); 77 | menu_insert_item(menu, "FPGA selftest", NULL, (void*) ACTION_FPGA_TEST, -1); 78 | 79 | bool render = true; 80 | menu_dev_action_t action = ACTION_NONE; 81 | 82 | render_help(pax_buffer); 83 | 84 | while (1) { 85 | rp2040_input_message_t buttonMessage = {0}; 86 | if (xQueueReceive(button_queue, &buttonMessage, 16 / portTICK_PERIOD_MS) == pdTRUE) { 87 | uint8_t pin = buttonMessage.input; 88 | bool value = buttonMessage.state; 89 | switch (pin) { 90 | case RP2040_INPUT_JOYSTICK_DOWN: 91 | if (value) { 92 | menu_navigate_next(menu); 93 | render = true; 94 | } 95 | break; 96 | case RP2040_INPUT_JOYSTICK_UP: 97 | if (value) { 98 | menu_navigate_previous(menu); 99 | render = true; 100 | } 101 | break; 102 | case RP2040_INPUT_BUTTON_HOME: 103 | case RP2040_INPUT_BUTTON_BACK: 104 | if (value) { 105 | action = ACTION_BACK; 106 | } 107 | break; 108 | case RP2040_INPUT_BUTTON_ACCEPT: 109 | case RP2040_INPUT_JOYSTICK_PRESS: 110 | case RP2040_INPUT_BUTTON_SELECT: 111 | case RP2040_INPUT_BUTTON_START: 112 | if (value) { 113 | action = (menu_dev_action_t) menu_get_callback_args(menu, menu_get_position(menu)); 114 | } 115 | break; 116 | default: 117 | break; 118 | } 119 | } 120 | 121 | if (render) { 122 | menu_render(pax_buffer, menu, 0, 0, 320, 220); 123 | display_flush(); 124 | render = false; 125 | } 126 | 127 | if (action != ACTION_NONE) { 128 | if (action == ACTION_FPGA_TEST) { 129 | fpga_test(button_queue); 130 | } else if (action == ACTION_FILE_BROWSER) { 131 | file_browser(button_queue, "/sd"); 132 | } else if (action == ACTION_FILE_BROWSER_INT) { 133 | file_browser(button_queue, "/internal"); 134 | } else if (action == ACTION_BUTTON_TEST) { 135 | test_buttons(button_queue); 136 | } else if (action == ACTION_ADC_TEST) { 137 | test_adc(button_queue); 138 | } else if (action == ACTION_SAO) { 139 | menu_sao(button_queue); 140 | } else if (action == ACTION_IR) { 141 | menu_ir(button_queue, false); 142 | } else if (action == ACTION_IR_RENZE) { 143 | menu_ir(button_queue, true); 144 | } else if (action == ACTION_BACK) { 145 | break; 146 | } 147 | action = ACTION_NONE; 148 | render = true; 149 | render_help(pax_buffer); 150 | } 151 | } 152 | 153 | menu_free(menu); 154 | 155 | pax_buf_destroy(&icon_dev); 156 | } 157 | -------------------------------------------------------------------------------- /main/menus/dev.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void menu_dev(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/menus/hatchery.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void menu_hatchery(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/menus/ir.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void menu_ir(xQueueHandle button_queue, bool alternative); 8 | -------------------------------------------------------------------------------- /main/menus/launcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void menu_launcher(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/menus/sao.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void menu_sao(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/menus/settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void menu_settings(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/menus/start.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void menu_start(xQueueHandle button_queue, const char* version); 7 | -------------------------------------------------------------------------------- /main/menus/wifi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void menu_wifi(xQueueHandle button_queue); 7 | -------------------------------------------------------------------------------- /main/metadata.c: -------------------------------------------------------------------------------- 1 | #include "metadata.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "menu.h" 17 | #include "pax_codecs.h" 18 | #include "pax_gfx.h" 19 | #include "system_wrapper.h" 20 | 21 | static const char* TAG = "Metadata"; 22 | 23 | void parse_metadata(const char* path, char** device, char** type, char** category, char** slug, char** name, char** description, char** author, int* version, 24 | char** license) { 25 | FILE* fd = fopen(path, "r"); 26 | if (fd == NULL) { 27 | ESP_LOGW(TAG, "Failed to open metadata file %s", path); 28 | return; 29 | } 30 | char* json_data = (char*) load_file_to_ram(fd); 31 | fclose(fd); 32 | if (json_data == NULL) return; 33 | cJSON* root = cJSON_Parse(json_data); 34 | if (root == NULL) { 35 | free(json_data); 36 | return; 37 | } 38 | if (device) { 39 | cJSON* device_obj = cJSON_GetObjectItem(root, "device"); 40 | if (device_obj && (device_obj->valuestring != NULL)) { 41 | *device = strdup(device_obj->valuestring); 42 | } 43 | } 44 | if (type) { 45 | cJSON* type_obj = cJSON_GetObjectItem(root, "type"); 46 | if (type_obj && (type_obj->valuestring != NULL)) { 47 | *type = strdup(type_obj->valuestring); 48 | } 49 | } 50 | if (category) { 51 | cJSON* category_obj = cJSON_GetObjectItem(root, "category"); 52 | if (category_obj && (category_obj->valuestring != NULL)) { 53 | *category = strdup(category_obj->valuestring); 54 | } 55 | } 56 | if (slug) { 57 | cJSON* slug_obj = cJSON_GetObjectItem(root, "slug"); 58 | if (slug_obj && (slug_obj->valuestring != NULL)) { 59 | *slug = strdup(slug_obj->valuestring); 60 | } 61 | } 62 | if (name) { 63 | cJSON* name_obj = cJSON_GetObjectItem(root, "name"); 64 | if (name_obj && (name_obj->valuestring != NULL)) { 65 | *name = strdup(name_obj->valuestring); 66 | } 67 | } 68 | if (description) { 69 | cJSON* description_obj = cJSON_GetObjectItem(root, "description"); 70 | if (description_obj && (description_obj->valuestring != NULL)) { 71 | *description = strdup(description_obj->valuestring); 72 | } 73 | } 74 | if (author) { 75 | cJSON* author_obj = cJSON_GetObjectItem(root, "author"); 76 | if (author_obj && (author_obj->valuestring != NULL)) { 77 | *author = strdup(author_obj->valuestring); 78 | } 79 | } 80 | if (version) { 81 | cJSON* version_obj = cJSON_GetObjectItem(root, "version"); 82 | if (version_obj) { 83 | *version = version_obj->valueint; 84 | } 85 | } 86 | if (license) { 87 | cJSON* license_obj = cJSON_GetObjectItem(root, "license"); 88 | if (license_obj && (license_obj->valuestring != NULL)) { 89 | *license = strdup(license_obj->valuestring); 90 | } 91 | } 92 | cJSON_Delete(root); 93 | free(json_data); 94 | } 95 | 96 | void free_launcher_app(launcher_app_t* app) { 97 | if (app->path) { 98 | free(app->path); 99 | app->path = NULL; 100 | } 101 | if (app->type) { 102 | free(app->type); 103 | app->type = NULL; 104 | } 105 | if (app->slug) { 106 | free(app->slug); 107 | app->slug = NULL; 108 | } 109 | if (app->title) { 110 | free(app->title); 111 | app->title = NULL; 112 | } 113 | if (app->description) { 114 | free(app->description); 115 | app->description = NULL; 116 | } 117 | if (app->category) { 118 | free(app->category); 119 | app->category = NULL; 120 | } 121 | if (app->author) { 122 | free(app->author); 123 | app->author = NULL; 124 | } 125 | if (app->license) { 126 | free(app->license); 127 | app->license = NULL; 128 | } 129 | if (app->icon) { 130 | pax_buf_destroy(app->icon); 131 | free(app->icon); 132 | app->icon = NULL; 133 | } 134 | free(app); 135 | } 136 | 137 | static appfs_handle_t find_appfs_handle_for_slug(const char* search_slug) { 138 | appfs_handle_t appfs_fd = appfsNextEntry(APPFS_INVALID_FD); 139 | while (appfs_fd != APPFS_INVALID_FD) { 140 | const char* slug = NULL; 141 | appfsEntryInfoExt(appfs_fd, &slug, NULL, NULL, NULL); 142 | if ((strlen(search_slug) == strlen(slug)) && (strcmp(search_slug, slug) == 0)) { 143 | return appfs_fd; 144 | } 145 | appfs_fd = appfsNextEntry(appfs_fd); 146 | } 147 | 148 | return APPFS_INVALID_FD; 149 | } 150 | 151 | void populate_menu_entry_from_path(menu_t* menu, const char* path, const char* type, const char* slug, void* default_icon_data, size_t default_icon_size) { 152 | char metadata_file_path[128]; 153 | snprintf(metadata_file_path, sizeof(metadata_file_path), "%s/%s/metadata.json", path, slug); 154 | char icon_file_path[128]; 155 | snprintf(icon_file_path, sizeof(icon_file_path), "%s/%s/icon.png", path, slug); 156 | char app_path[128]; 157 | snprintf(app_path, sizeof(app_path), "%s/%s", path, slug); 158 | 159 | launcher_app_t* app = malloc(sizeof(launcher_app_t)); 160 | if (app == NULL) { 161 | ESP_LOGE(TAG, "Malloc for app entry failed"); 162 | return; 163 | } 164 | memset(app, 0, sizeof(launcher_app_t)); 165 | app->appfs_fd = (strcmp(type, "esp32") == 0) ? find_appfs_handle_for_slug(slug) : APPFS_INVALID_FD; 166 | app->path = strdup(app_path); 167 | app->type = strdup(type); 168 | app->slug = strdup(slug); 169 | parse_metadata(metadata_file_path, NULL, NULL, &app->category, NULL, &app->title, &app->description, &app->author, &app->version, &app->license); 170 | app->icon = NULL; 171 | 172 | FILE* icon_fd = fopen(icon_file_path, "rb"); 173 | if (icon_fd != NULL) { 174 | size_t icon_size = get_file_size(icon_fd); 175 | uint8_t* icon_data = load_file_to_ram(icon_fd); 176 | if (icon_data != NULL) { 177 | app->icon = malloc(sizeof(pax_buf_t)); 178 | if (app->icon != NULL) { 179 | if (!pax_decode_png_buf(app->icon, (void*) icon_data, icon_size, PAX_BUF_32_8888ARGB, 0)) { 180 | free(app->icon); 181 | app->icon = NULL; 182 | } 183 | } 184 | free(icon_data); 185 | } 186 | fclose(icon_fd); 187 | } 188 | 189 | if ((app->icon == NULL) && (default_icon_data != NULL)) { 190 | app->icon = malloc(sizeof(pax_buf_t)); 191 | if (app->icon != NULL) { 192 | if (!pax_decode_png_buf(app->icon, default_icon_data, default_icon_size, PAX_BUF_32_8888ARGB, 0)) { 193 | free(app->icon); 194 | app->icon = NULL; 195 | } 196 | } 197 | } 198 | 199 | menu_insert_item_icon(menu, (app->title != NULL) ? app->title : app->slug, NULL, (void*) app, -1, app->icon); 200 | } 201 | 202 | bool populate_menu_from_path(menu_t* menu, const char* path, const char* arg_type, void* default_icon_data, 203 | size_t default_icon_size) { // Path is here the folder containing the apps, for example /internal/apps 204 | char path_with_type[256]; 205 | path_with_type[sizeof(path_with_type) - 1] = '\0'; 206 | snprintf(path_with_type, sizeof(path_with_type), "%s/%s", path, arg_type); 207 | DIR* dir = opendir(path_with_type); 208 | if (dir == NULL) { 209 | ESP_LOGW(TAG, "Directory not found: %s", path_with_type); 210 | return false; 211 | } 212 | struct dirent* ent; 213 | while ((ent = readdir(dir)) != NULL) { 214 | if (ent->d_type == DT_REG) continue; // Skip files, only parse directories 215 | populate_menu_entry_from_path(menu, path_with_type, arg_type, ent->d_name, default_icon_data, default_icon_size); 216 | } 217 | closedir(dir); 218 | return true; 219 | } 220 | 221 | bool for_entity_in_path(const char* path, bool directories, path_callback_t callback, void* user) { 222 | DIR* dir = opendir(path); 223 | if (dir == NULL) { 224 | ESP_LOGW(TAG, "Directory not found: %s", path); 225 | return false; 226 | } 227 | struct dirent* ent; 228 | while ((ent = readdir(dir)) != NULL) { 229 | if ((directories) && (ent->d_type == DT_REG)) continue; // Skip files, only parse directories 230 | if ((!directories) && (ent->d_type != DT_REG)) continue; // Skip directories, only parse files 231 | callback(path, ent->d_name, user); 232 | } 233 | closedir(dir); 234 | return true; 235 | } 236 | -------------------------------------------------------------------------------- /main/rtc_memory.c: -------------------------------------------------------------------------------- 1 | #include "rtc_memory.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define RTC_MEM_INT_SIZE 64 12 | #define RTC_MEM_STR_SIZE 512 13 | 14 | static int *const rtc_mem_int = (int *const) (RTC_SLOW_MEM + CONFIG_ESP32_ULP_COPROC_RESERVE_MEM); 15 | static uint16_t *const rtc_mem_int_crc = (uint16_t *const) (rtc_mem_int + (sizeof(int) * RTC_MEM_INT_SIZE)); 16 | static char *const rtc_mem_str = (char *const) (rtc_mem_int_crc + sizeof(uint16_t)); 17 | static uint16_t *const rtc_mem_str_crc = (uint16_t *const) (rtc_mem_str + (RTC_MEM_STR_SIZE * sizeof(char))); 18 | 19 | esp_err_t rtc_memory_int_write(int pos, int val) { 20 | if (pos >= RTC_MEM_INT_SIZE) return ESP_FAIL; 21 | rtc_mem_int[pos] = val; 22 | *rtc_mem_int_crc = crc16_le(0, (uint8_t const *) rtc_mem_int, RTC_MEM_INT_SIZE * sizeof(int)); 23 | return ESP_OK; 24 | } 25 | 26 | esp_err_t rtc_memory_int_read(int pos, int *val) { 27 | if (pos >= RTC_MEM_INT_SIZE) return ESP_FAIL; 28 | if (*rtc_mem_int_crc != crc16_le(0, (uint8_t const *) rtc_mem_int, RTC_MEM_INT_SIZE * sizeof(int))) return ESP_FAIL; 29 | *val = rtc_mem_int[pos]; 30 | return ESP_OK; 31 | } 32 | 33 | esp_err_t rtc_memory_string_write(const char *str) { 34 | if (strlen(str) >= RTC_MEM_STR_SIZE) return ESP_FAIL; 35 | memset(rtc_mem_str, 0, RTC_MEM_STR_SIZE * sizeof(char)); 36 | strcpy(rtc_mem_str, str); 37 | *rtc_mem_str_crc = crc16_le(0, (uint8_t const *) rtc_mem_str, RTC_MEM_STR_SIZE); 38 | // printf("RTC memory @ %p written, CRC %04X\n", rtc_mem_str, *rtc_mem_str_crc); 39 | return ESP_OK; 40 | } 41 | 42 | esp_err_t rtc_memory_string_read(const char **str) { 43 | // printf("RTC memory @ %p read\n", rtc_mem_str); 44 | uint16_t crc = crc16_le(0, (uint8_t const *) rtc_mem_str, RTC_MEM_STR_SIZE); 45 | if (*rtc_mem_str_crc != crc) { 46 | // printf("RTC memory @ %p invalid %04X != %04x\n", rtc_mem_str, *rtc_mem_str_crc, crc); 47 | return ESP_FAIL; 48 | } 49 | // printf("RTC memory @ %p valid: %s\n", rtc_mem_str, rtc_mem_str); 50 | *str = rtc_mem_str; 51 | return ESP_OK; 52 | } 53 | 54 | esp_err_t rtc_memory_clear() { 55 | memset(rtc_mem_int, 0, RTC_MEM_INT_SIZE * sizeof(int)); 56 | memset(rtc_mem_str, 0, RTC_MEM_STR_SIZE * sizeof(char)); 57 | *rtc_mem_int_crc = 0; 58 | *rtc_mem_str_crc = 0; 59 | return ESP_OK; 60 | } 61 | -------------------------------------------------------------------------------- /main/settings.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "hardware.h" 10 | 11 | esp_err_t nvs_init() { 12 | const esp_partition_t* nvs_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); 13 | if (nvs_partition == NULL) return ESP_FAIL; 14 | esp_err_t res = nvs_flash_init(); 15 | if (res != ESP_OK) { 16 | res = esp_partition_erase_range(nvs_partition, 0, nvs_partition->size); 17 | if (res != ESP_OK) return res; 18 | res = nvs_flash_init(); 19 | if (res != ESP_OK) return res; 20 | } 21 | return ESP_OK; 22 | } 23 | 24 | esp_err_t nvs_get_str_fixed(const char* nvs_namespace, const char* key, char* target, size_t target_size, size_t* size) { 25 | nvs_handle_t handle; 26 | esp_err_t res; 27 | 28 | res = nvs_open(nvs_namespace, NVS_READWRITE, &handle); 29 | if (res != ESP_OK) return res; 30 | 31 | size_t required_size; 32 | res = nvs_get_str(handle, key, NULL, &required_size); 33 | if (res != ESP_OK) { 34 | nvs_close(handle); 35 | return res; 36 | } 37 | 38 | if (required_size > target_size) { 39 | nvs_close(handle); 40 | return ESP_FAIL; 41 | } 42 | 43 | res = nvs_get_str(handle, key, target, &required_size); 44 | 45 | if (size != NULL) *size = required_size; 46 | 47 | nvs_close(handle); 48 | 49 | return res; 50 | } 51 | 52 | uint8_t nvs_get_u8_default(const char* nvs_namespace, const char* key, uint8_t default_value) { 53 | nvs_handle_t handle; 54 | esp_err_t res; 55 | 56 | res = nvs_open(nvs_namespace, NVS_READWRITE, &handle); 57 | if (res != ESP_OK) return default_value; 58 | 59 | uint8_t target; 60 | res = nvs_get_u8(handle, key, &target); 61 | if (res != ESP_OK) { 62 | nvs_close(handle); 63 | return default_value; 64 | } 65 | 66 | nvs_close(handle); 67 | return target; 68 | } 69 | 70 | esp_err_t nvs_set_u8_fixed(const char* nvs_namespace, const char* key, uint8_t value) { 71 | nvs_handle_t handle; 72 | esp_err_t res; 73 | 74 | res = nvs_open(nvs_namespace, NVS_READWRITE, &handle); 75 | if (res != ESP_OK) return res; 76 | 77 | res = nvs_set_u8(handle, key, value); 78 | nvs_close(handle); 79 | 80 | return res; 81 | } 82 | -------------------------------------------------------------------------------- /main/system_wrapper.c: -------------------------------------------------------------------------------- 1 | #include "system_wrapper.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "hardware.h" 12 | #include "rp2040.h" 13 | 14 | void restart() { 15 | vTaskDelay(1000 / portTICK_PERIOD_MS); 16 | fflush(stdout); 17 | esp_restart(); 18 | } 19 | 20 | bool wait_for_button() { 21 | RP2040* rp2040 = get_rp2040(); 22 | if (rp2040 == NULL) return false; 23 | while (1) { 24 | rp2040_input_message_t buttonMessage = {0}; 25 | if (xQueueReceive(rp2040->queue, &buttonMessage, portMAX_DELAY) == pdTRUE) { 26 | if (buttonMessage.state) { 27 | switch (buttonMessage.input) { 28 | case RP2040_INPUT_BUTTON_BACK: 29 | case RP2040_INPUT_BUTTON_HOME: 30 | return false; 31 | case RP2040_INPUT_BUTTON_ACCEPT: 32 | return true; 33 | default: 34 | break; 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | uint8_t* load_file_to_ram(FILE* fd) { 42 | fseek(fd, 0, SEEK_END); 43 | size_t fsize = ftell(fd); 44 | fseek(fd, 0, SEEK_SET); 45 | uint8_t* file = malloc(fsize); 46 | if (file == NULL) return NULL; 47 | fread(file, fsize, 1, fd); 48 | return file; 49 | } 50 | 51 | size_t get_file_size(FILE* fd) { 52 | fseek(fd, 0, SEEK_END); 53 | size_t fsize = ftell(fd); 54 | fseek(fd, 0, SEEK_SET); 55 | return fsize; 56 | } 57 | 58 | bool remove_recursive(const char* path) { 59 | size_t path_len; 60 | char* full_path; 61 | DIR* dir; 62 | struct stat stat_path, stat_entry; 63 | struct dirent* entry; 64 | 65 | // stat for the path 66 | stat(path, &stat_path); 67 | 68 | // if path does not exists or is not dir - exit with status -1 69 | if (S_ISDIR(stat_path.st_mode) == 0) { 70 | // printf("%s: %s\n", "Is not directory", path); 71 | if (unlink(path) == 0) { 72 | // printf("Removed a file: %s\n", full_path); 73 | return true; 74 | } else { 75 | // printf("Can`t remove a file: %s\n", full_path); 76 | return false; 77 | } 78 | } 79 | 80 | // if not possible to read the directory for this user 81 | if ((dir = opendir(path)) == NULL) { 82 | // printf("%s: %s\n", "Can`t open directory", path); 83 | return false; 84 | } 85 | 86 | bool failed = false; 87 | 88 | // the length of the path 89 | path_len = strlen(path); 90 | 91 | // iteration through entries in the directory 92 | while ((entry = readdir(dir)) != NULL) { 93 | // skip entries "." and ".." 94 | if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { 95 | continue; 96 | } 97 | 98 | // determinate a full path of an entry 99 | full_path = calloc(path_len + strlen(entry->d_name) + 2, sizeof(char)); 100 | strcpy(full_path, path); 101 | strcat(full_path, "/"); 102 | strcat(full_path, entry->d_name); 103 | 104 | // stat for the entry 105 | stat(full_path, &stat_entry); 106 | 107 | // recursively remove a nested directory 108 | if (S_ISDIR(stat_entry.st_mode) != 0) { 109 | remove_recursive(full_path); 110 | continue; 111 | } 112 | 113 | // remove a file object 114 | if (unlink(full_path) == 0) { 115 | // printf("Removed a file: %s\n", full_path); 116 | } else { 117 | // printf("Can`t remove a file: %s\n", full_path); 118 | failed = true; 119 | } 120 | free(full_path); 121 | } 122 | 123 | // remove the devastated directory and close the object of it 124 | if (rmdir(path) == 0) { 125 | // printf("Removed a directory: %s\n", path); 126 | } else { 127 | // printf("Can`t remove a directory: %s\n", path); 128 | failed = true; 129 | } 130 | closedir(dir); 131 | return !failed; 132 | } 133 | -------------------------------------------------------------------------------- /main/terminal.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "driver/uart.h" 12 | #include "esp32/rom/crc.h" 13 | #include "graphics_wrapper.h" 14 | #include "hardware.h" 15 | #include "pax_gfx.h" 16 | #include "system_wrapper.h" 17 | 18 | #define LOG_LINES 24 19 | 20 | typedef struct terminal_line { 21 | char* data; 22 | bool is_dynamically_allocated; 23 | } terminal_line_t; 24 | 25 | static QueueHandle_t log_queue = NULL; 26 | 27 | void terminal_printf(char* fmt, ...) { 28 | terminal_line_t line; 29 | line.data = malloc(256); 30 | line.is_dynamically_allocated = true; 31 | if (line.data == NULL) return; 32 | line.data[255] = '\0'; 33 | va_list va; 34 | va_start(va, fmt); 35 | vsnprintf(line.data, 255, fmt, va); 36 | va_end(va); 37 | 38 | xQueueSend(log_queue, &line, portMAX_DELAY); 39 | } 40 | 41 | void terminal_log(char* buffer) { 42 | terminal_line_t line; 43 | line.data = buffer; 44 | line.is_dynamically_allocated = false; 45 | xQueueSend(log_queue, &line, portMAX_DELAY); 46 | } 47 | 48 | void terminal_log_wrapped(char* buffer) { 49 | char* newbuf = calloc(1, strlen(buffer) + 1); 50 | strcpy(newbuf, buffer); 51 | terminal_line_t line; 52 | line.data = newbuf; 53 | line.is_dynamically_allocated = true; 54 | xQueueSend(log_queue, &line, portMAX_DELAY); 55 | } 56 | 57 | static void log_event_task(void* pvParameters) { 58 | const pax_font_t* font = pax_font_sky_mono; 59 | terminal_line_t lines[LOG_LINES] = {0}; 60 | int offset = 0; 61 | pax_buf_t* pax_buffer = get_pax_buffer(); 62 | xQueueHandle queue = (QueueHandle_t) pvParameters; 63 | pax_noclip(pax_buffer); 64 | pax_background(pax_buffer, 0x0000FF); // Blue screen 65 | display_flush(); 66 | for (;;) { 67 | if (lines[offset].data != NULL && lines[offset].is_dynamically_allocated) { 68 | free(lines[offset].data); 69 | } 70 | if (xQueueReceive(queue, &lines[offset], portMAX_DELAY) != pdTRUE) { 71 | break; 72 | } 73 | offset = (offset + 1) % LOG_LINES; 74 | pax_background(pax_buffer, 0x000000); 75 | int position = 0; 76 | for (int index = 0; index < LOG_LINES; index++) { 77 | terminal_line_t* line = &lines[(offset + index) % LOG_LINES]; 78 | if (line->data != NULL) { 79 | pax_draw_text(pax_buffer, 0xFFFFFFFF, font, 9, 0, 10 * position, line->data); 80 | position++; 81 | } 82 | } 83 | display_flush(); 84 | } 85 | for (int index = 0; index < LOG_LINES; index++) { 86 | if (lines[index].data != NULL && lines[index].is_dynamically_allocated) { 87 | free(lines[index].data); 88 | } 89 | } 90 | pax_background(pax_buffer, 0xFF0000); // Red screen 91 | display_flush(); 92 | vTaskDelete(NULL); 93 | } 94 | 95 | void terminal_start() { 96 | log_queue = xQueueCreate(8, sizeof(terminal_line_t)); 97 | xTaskCreate(log_event_task, "log_event_task", 2048, (void*) log_queue, 12, NULL); 98 | } 99 | -------------------------------------------------------------------------------- /main/test_common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "hardware.h" 10 | #include "ice40.h" 11 | #include "pax_gfx.h" 12 | #include "rp2040.h" 13 | 14 | typedef bool (*test_fn)(uint32_t *rc); 15 | 16 | bool test_wait_for_response(uint32_t *rc) { 17 | printf("Waiting for button press...\r\n"); 18 | RP2040 *rp2040 = get_rp2040(); 19 | rp2040_input_message_t button_message = {0}; 20 | if (rc != NULL) *rc = 0; 21 | while (1) { 22 | if (xQueueReceive(rp2040->queue, &button_message, portMAX_DELAY) == pdTRUE) { 23 | if (button_message.state) { 24 | switch (button_message.input) { 25 | case RP2040_INPUT_BUTTON_HOME: 26 | case RP2040_INPUT_BUTTON_MENU: 27 | case RP2040_INPUT_BUTTON_BACK: 28 | return false; 29 | case RP2040_INPUT_BUTTON_ACCEPT: 30 | if (rc != NULL) *rc = 1; 31 | return true; 32 | default: 33 | break; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | bool run_test(const pax_font_t *font, int line, const char *test_name, test_fn fn) { 41 | pax_buf_t *pax_buffer = get_pax_buffer(); 42 | bool test_result; 43 | uint32_t rc; 44 | 45 | printf("Starting test %s...\r\n", test_name); 46 | 47 | /* Test name */ 48 | pax_draw_text(pax_buffer, 0xffffffff, font, 18, 0, 20 * line, test_name); 49 | display_flush(); 50 | 51 | /* Run the test */ 52 | test_result = fn(&rc); 53 | 54 | /* Display result */ 55 | if (!test_result) { 56 | /* Error */ 57 | char buf[10]; 58 | snprintf(buf, sizeof(buf), "%08x", rc); 59 | pax_draw_text(pax_buffer, 0xffff0000, font, 18, 200, 20 * line, buf); 60 | } else { 61 | /* OK ! */ 62 | pax_draw_text(pax_buffer, 0xff00ff00, font, 18, 200, 20 * line, " OK"); 63 | } 64 | 65 | display_flush(); 66 | 67 | printf(" Test %s result: %s\r\n", test_name, test_result ? "OK" : "FAIL"); 68 | 69 | /* Pass through the 'OK' status */ 70 | return test_result; 71 | } 72 | -------------------------------------------------------------------------------- /main/wifi_cert.c: -------------------------------------------------------------------------------- 1 | #include "wifi_cert.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "esp_log.h" 7 | #include "esp_tls.h" 8 | 9 | extern const uint8_t custom_ota_cert_pem_start[] asm("_binary_custom_ota_cert_pem_start"); 10 | extern const uint8_t custom_ota_cert_cert_pem_end[] asm("_binary_custom_ota_cert_pem_end"); 11 | 12 | extern const uint8_t isrgrootx1_pem_start[] asm("_binary_isrgrootx1_pem_start"); 13 | extern const uint8_t isrgrootx1_pem_end[] asm("_binary_isrgrootx1_pem_end"); 14 | 15 | esp_err_t init_ca_store() { 16 | // This function is called from main and initializes a custom certificate storage. 17 | // Custom certificates can be added to this storage as required. 18 | esp_err_t res = esp_tls_init_global_ca_store(); 19 | if (res != ESP_OK) { 20 | return res; 21 | } 22 | 23 | res = esp_tls_set_global_ca_store(custom_ota_cert_pem_start, custom_ota_cert_cert_pem_end - custom_ota_cert_pem_start); // Self-signed fallback certificate 24 | if (res != ESP_OK) { 25 | return res; 26 | } 27 | 28 | res = esp_tls_set_global_ca_store(isrgrootx1_pem_start, isrgrootx1_pem_end - isrgrootx1_pem_start); // Let's encrypt root CA 29 | if (res != ESP_OK) { 30 | return res; 31 | } 32 | return res; 33 | } 34 | -------------------------------------------------------------------------------- /main/wifi_defaults.c: -------------------------------------------------------------------------------- 1 | #include "wifi_defaults.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static const char *TAG = "wifi_defaults"; 16 | 17 | bool wifi_set_defaults() { 18 | nvs_handle_t handle; 19 | esp_err_t res = nvs_open("system", NVS_READWRITE, &handle); 20 | if (res != ESP_OK) { 21 | ESP_LOGE(TAG, "Can't set WiFi to default: %s", esp_err_to_name(res)); 22 | return false; 23 | } 24 | nvs_set_u8(handle, "wifi.authmode", WIFI_MCH2022_AUTH); 25 | nvs_set_u8(handle, "wifi.phase2", WIFI_MCH2022_PHASE2); 26 | nvs_set_str(handle, "wifi.ssid", WIFI_MCH2022_SSID); 27 | nvs_set_str(handle, "wifi.username", WIFI_MCH2022_USER); 28 | nvs_set_str(handle, "wifi.anon_ident", WIFI_MCH2022_IDENT); 29 | nvs_set_str(handle, "wifi.password", WIFI_MCH2022_PASSWORD); 30 | 31 | res = nvs_commit(handle); 32 | if (res != ESP_OK) { 33 | ESP_LOGE(TAG, "Can't set WiFi to default: %s", esp_err_to_name(res)); 34 | nvs_close(handle); 35 | return false; 36 | } 37 | nvs_close(handle); 38 | return true; 39 | } 40 | 41 | bool wifi_check_configured() { 42 | nvs_handle_t handle; 43 | esp_err_t res = nvs_open("system", NVS_READWRITE, &handle); 44 | if (res != ESP_OK) { 45 | nvs_close(handle); 46 | return false; 47 | } 48 | 49 | size_t len; 50 | res = nvs_get_str(handle, "wifi.ssid", NULL, &len); 51 | if ((res != ESP_OK) || (len < 1)) { 52 | nvs_close(handle); 53 | return false; 54 | } 55 | 56 | nvs_close(handle); 57 | return true; 58 | } 59 | -------------------------------------------------------------------------------- /main/wifi_test.c: -------------------------------------------------------------------------------- 1 | #include "wifi_test.h" 2 | 3 | #include 4 | 5 | #include "bootscreen.h" 6 | #include "esp_event.h" 7 | #include "esp_http_client.h" 8 | #include "esp_https_ota.h" 9 | #include "esp_log.h" 10 | #include "esp_ota_ops.h" 11 | #include "esp_system.h" 12 | #include "esp_timer.h" 13 | #include "esp_wifi.h" 14 | #include "freertos/FreeRTOS.h" 15 | #include "freertos/queue.h" 16 | #include "freertos/task.h" 17 | #include "hardware.h" 18 | #include "nvs.h" 19 | #include "nvs_flash.h" 20 | #include "string.h" 21 | #include "system_wrapper.h" 22 | #include "wifi.h" 23 | #include "wifi_connect.h" 24 | #include "wifi_connection.h" 25 | 26 | static const char* wifi_auth_names[] = { 27 | "None", "WEP", "WPA1", "WPA2", "WPA1/2", "WPA2 Ent", "WPA3", "WPA2/3", "WAPI", 28 | }; 29 | 30 | static const char* wifi_phase2_names[] = { 31 | "EAP", "MSCHAPv2", "MSCHAP", "PAP", "CHAP", 32 | }; 33 | 34 | static void display_test_state(const char* text, const char* ssid, const char* password, wifi_auth_mode_t authmode, esp_eap_ttls_phase2_types phase2, 35 | const char* username, const char* anon_ident, esp_netif_ip_info_t* ip_info, bool buttons) { 36 | pax_buf_t* pax_buffer = get_pax_buffer(); 37 | const pax_font_t* font = pax_font_saira_regular; 38 | char buffer[512]; 39 | pax_noclip(pax_buffer); 40 | pax_background(pax_buffer, 0xFFFFFF); 41 | if (authmode == WIFI_AUTH_WPA2_ENTERPRISE) { 42 | snprintf(buffer, sizeof(buffer), 43 | "SSID: %s\nSecurity: WPA2 Ent + %s\nIdentity: %s\nAnonymous identity: %s\nPassword: %s\nIP address: " IPSTR "\nNetmask: " IPSTR 44 | "\nGateway: " IPSTR "\n", 45 | ssid, wifi_phase2_names[phase2], username, anon_ident, password, IP2STR(&ip_info->ip), IP2STR(&ip_info->netmask), IP2STR(&ip_info->gw)); 46 | } else { 47 | snprintf(buffer, sizeof(buffer), "SSID: %s\nSecurity: %s\nPassword: %s\nIP address: " IPSTR "\nNetmask: " IPSTR "\nGateway: " IPSTR "\n", ssid, 48 | wifi_auth_names[authmode], password, IP2STR(&ip_info->ip), IP2STR(&ip_info->netmask), IP2STR(&ip_info->gw)); 49 | } 50 | pax_draw_text(pax_buffer, 0xFF000000, font, 18, 5, 5, buffer); 51 | pax_draw_text(pax_buffer, 0xFF000000, font, 18, 5, 240 - 3 * 18, text); 52 | if (buttons) pax_draw_text(pax_buffer, 0xFF000000, font, 18, 5, 240 - 18, "🅰 test 🅱 back"); 53 | display_flush(); 54 | } 55 | 56 | void wifi_connection_test(xQueueHandle button_queue) { 57 | nvs_handle_t handle; 58 | 59 | nvs_open("system", NVS_READWRITE, &handle); 60 | char ssid[33] = ""; 61 | char password[65] = ""; 62 | char username[129] = ""; 63 | char anon_ident[129] = ""; 64 | wifi_auth_mode_t authmode = 0; 65 | esp_eap_ttls_phase2_types phase2 = 0; 66 | size_t requiredSize = 0; 67 | 68 | esp_err_t res = nvs_get_str(handle, "wifi.ssid", NULL, &requiredSize); 69 | if ((res == ESP_OK) && (requiredSize < sizeof(ssid))) { 70 | res = nvs_get_str(handle, "wifi.ssid", ssid, &requiredSize); 71 | } 72 | 73 | res = nvs_get_str(handle, "wifi.password", NULL, &requiredSize); 74 | if ((res == ESP_OK) && (requiredSize < sizeof(password))) { 75 | res = nvs_get_str(handle, "wifi.password", password, &requiredSize); 76 | } 77 | 78 | uint8_t dummy = 0; 79 | res = nvs_get_u8(handle, "wifi.authmode", &dummy); 80 | authmode = dummy; 81 | 82 | if (authmode == WIFI_AUTH_WPA2_ENTERPRISE) { 83 | res = nvs_get_str(handle, "wifi.username", NULL, &requiredSize); 84 | if ((res == ESP_OK) && (requiredSize < sizeof(username))) { 85 | res = nvs_get_str(handle, "wifi.username", username, &requiredSize); 86 | } 87 | res = nvs_get_str(handle, "wifi.anon_ident", NULL, &requiredSize); 88 | if ((res == ESP_OK) && (requiredSize < sizeof(anon_ident))) { 89 | res = nvs_get_str(handle, "wifi.anon_ident", anon_ident, &requiredSize); 90 | } 91 | 92 | dummy = 0; 93 | res = nvs_get_u8(handle, "wifi.phase2", &dummy); 94 | phase2 = dummy; 95 | } 96 | 97 | nvs_close(handle); 98 | 99 | bool quit = false; 100 | char test_result[128] = {0}; 101 | while (!quit) { 102 | esp_netif_ip_info_t* ip_info = wifi_get_ip_info(); 103 | wifi_disconnect_and_disable(); 104 | display_test_state(test_result, ssid, password, authmode, phase2, username, anon_ident, ip_info, true); 105 | quit = !wait_for_button(); 106 | if (quit) break; 107 | display_test_state("Connecting...", ssid, password, authmode, phase2, username, anon_ident, ip_info, false); 108 | 109 | if (!wifi_connect_to_stored()) { 110 | sprintf(test_result, "Failed to connect to network!"); 111 | continue; 112 | } 113 | 114 | display_test_state("Testing...", ssid, password, authmode, phase2, username, anon_ident, ip_info, false); 115 | 116 | esp_wifi_set_ps(WIFI_PS_NONE); // Disable any WiFi power save mode 117 | 118 | esp_http_client_config_t config = {.url = "https://mch2022.ota.bodge.team/test.bin", .use_global_ca_store = true, .keep_alive_enable = true}; 119 | 120 | esp_http_client_handle_t client = esp_http_client_init(&config); 121 | int64_t time_start = esp_timer_get_time(); 122 | esp_err_t err = esp_http_client_perform(client); 123 | int64_t time_end = esp_timer_get_time(); 124 | 125 | if (err != ESP_OK) { 126 | snprintf(test_result, sizeof(test_result), "Failed: %s", esp_err_to_name(err)); 127 | continue; 128 | } 129 | 130 | int status_code = esp_http_client_get_status_code(client); 131 | 132 | if (status_code != 200) { 133 | snprintf(test_result, sizeof(test_result), "Failed: received status code %u", status_code); 134 | continue; 135 | } 136 | 137 | float content_length = ((esp_http_client_get_content_length(client) * 8) / 1024.0) / 1024.0; // megabit 138 | 139 | if (content_length < 1) { 140 | snprintf(test_result, sizeof(test_result), "Failed: received no data"); 141 | } 142 | 143 | float time_diff = (time_end - time_start) / 1000000.0; // seconds 144 | 145 | float speed = content_length / time_diff; // Mbps 146 | 147 | printf("Downloaded %.2f megabit in %.2f seconds: %.2f Mbps\n", content_length, time_diff, speed); 148 | 149 | snprintf(test_result, sizeof(test_result), "Success! Speed: %.2f Mbps", speed); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /partitions.csv: -------------------------------------------------------------------------------- 1 | # ESP-IDF Partition Table 2 | # Name, Type, SubType, Offset, Size, Flags 3 | nvs, data, nvs, 0x9000, 16K, 4 | otadata, data, ota, 0xD000, 8K, 5 | phy_init, data, phy, 0xF000, 4K, 6 | ota_0, 0, ota_0, 0x10000, 1600K, 7 | ota_1, 0, ota_1, 0x1A0000, 1600K, 8 | appfs, 0x43, 3, 0x330000, 8000K, 9 | locfd, data, fat, 0xB00000, 5120K, 10 | -------------------------------------------------------------------------------- /partitions.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/partitions.ods -------------------------------------------------------------------------------- /resources/boot.snd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/boot.snd -------------------------------------------------------------------------------- /resources/custom_ota_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFJTCCAw2gAwIBAgIUZa4hCLGTxUca/K+mZkpPV84jSrEwDQYJKoZIhvcNAQEL 3 | BQAwITEfMB0GA1UEAwwWbWNoMjAyMi5vdGEuYm9kZ2UudGVhbTAgFw0yMjA2Mjkw 4 | MDAzMzJaGA8yMTIyMDYwNTAwMDMzMlowITEfMB0GA1UEAwwWbWNoMjAyMi5vdGEu 5 | Ym9kZ2UudGVhbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFVikCY 6 | Y3R4L7NehXVJaXgqtJlM0R0P41BKh8tld71iyNl49+xpk/8K1caBFF2trwRuhGzG 7 | LOdhX7crqoaV8wLCV3WwHQtJkanuuUXUkByUoYHXFlQGeDgF23bW9kqD2dApCGJb 8 | 3Tu1wEHseSLjCvQ4oZTTSk+TyFw5IfN1oGh8qY4uXdhBtpT2a654HeMIMzx7W2SZ 9 | UmaBHB2JDyPA2wsARVIZsHAdc1w+d6VLmM5o354uiJJ0PCDoWQuyQLGrCE3Ry2LS 10 | 39XWBjtDmCfAp6V2/ZTv9DJ47X1P3S84LaQRl+MBHJMH0mmX2E2Pv6tkD/2KA2Vr 11 | iKghI3tC7jgmYGiJLkzJPPE9XwWorakEjQHU3/uuUw9Q7lX0/NoLhddtLvhsSU4T 12 | pCBkKA9XaOGusCNiLuiJ+gQM5kQOwM9ZcCJUn4OA3ZcCkEe8ox752zCl5ELqdGNh 13 | /8Xqzz6dbq0hu13LDgILrs0BDzb+zyP7VBa58gAivTZO5nllTT+e1s9e201myeVY 14 | Y+0Q8OSXJ5DMH2UP6CI6lPguZubLXtkJ4JhxXpKJZZbF0LJhAE213SKbzgttzRov 15 | oq6cRu2igtHRxaPU02OJ2JoN7IgNlgTaXIyHupch93URl1HRaXvzMCc/J/LN0r3u 16 | C8VFDpS94wBUBLnsj9qbZiUZadsjb75wYSGhAgMBAAGjUzBRMB0GA1UdDgQWBBT3 17 | r2fy/+yGTFfAzZPYON6fbKWc+jAfBgNVHSMEGDAWgBT3r2fy/+yGTFfAzZPYON6f 18 | bKWc+jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQDk9d0cc7fX 19 | vtn20H5t3puJW2ecM5rumCHtMsM+mlVIByY3dtvbrSt/TKQiRblnuQIIoyAoGUDD 20 | WoqPZuUgtfBMpT40oD2Q4xwOqhVeKF/qo6nK4r/eS8YszG/JMmyiGPiYoD0DqbyC 21 | hxcFs8vIxr1sYTS4zFTp3EjKap5jA197qdmBzv/Z3ueWDyttHR2KU7oSVFIt13DA 22 | XoF2scb6AatPoLQqmlbSnEeErsjPXmziBAS07S2zN8evdeu4VKHQ4PlfgwPsUpPm 23 | jVhcwUB48MIqsXy5gFOMWp95jW1GGEq2Zq3cesWcpVhkfLZ0u5lYldY5apRk14y+ 24 | jYD871AFcqz+qSuAk5s2gPEwMoFohG65hazYtyLVtPnSNuhtLYRbG9SSBv82kYmd 25 | KsFzXJMWXSnIP8LeI+rMIFL2TUuxGrVcmsVmb8KWRWL8MZx6kiNFDXZ/ZAuaY9h6 26 | SsmASMg/L69d4lJY8bkDyf4E/MRa0OA03MjVs7S2CucLC8O1Jm6SVxpBHKo4TVgG 27 | Ci4UQUT2P8/JHrxa/AuzV7+uDaEd5MVrNPC3DkVm5IEr4dri1jJ7VnsvE7G9a3Kw 28 | 1Y7KLpGu7NT8Uen/z3BiK31gcOJM+/E7SSj+d6WvxNcgBVxWSdrpfEq3ozT2TDvG 29 | mHbbE6QeFk7bCFvuC2eaPJak1vQ76baZng== 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /resources/fpga_selftest.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/fpga_selftest.bin -------------------------------------------------------------------------------- /resources/icons/apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/apps.png -------------------------------------------------------------------------------- /resources/icons/bitstream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/bitstream.png -------------------------------------------------------------------------------- /resources/icons/dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/dev.png -------------------------------------------------------------------------------- /resources/icons/hatchery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/hatchery.png -------------------------------------------------------------------------------- /resources/icons/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/home.png -------------------------------------------------------------------------------- /resources/icons/hourglass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/hourglass.png -------------------------------------------------------------------------------- /resources/icons/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/python.png -------------------------------------------------------------------------------- /resources/icons/sao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/sao.png -------------------------------------------------------------------------------- /resources/icons/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/settings.png -------------------------------------------------------------------------------- /resources/icons/tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/tag.png -------------------------------------------------------------------------------- /resources/icons/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/icons/update.png -------------------------------------------------------------------------------- /resources/isrgrootx1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw 3 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh 4 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 5 | WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu 6 | ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY 7 | MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc 8 | h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ 9 | 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U 10 | A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW 11 | T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH 12 | B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC 13 | B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv 14 | KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn 15 | OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn 16 | jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw 17 | qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI 18 | rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV 19 | HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq 20 | hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL 21 | ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ 22 | 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK 23 | NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 24 | ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur 25 | TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC 26 | jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc 27 | oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq 28 | 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA 29 | mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d 30 | emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /resources/mch2022_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/mch2022_logo.png -------------------------------------------------------------------------------- /resources/rp2040_firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badgeteam/mch2022-firmware-esp32/881166b72ceb99593c9da841a58ce4dbb49eec36/resources/rp2040_firmware.bin --------------------------------------------------------------------------------